2023年12月20日水曜日

LT-R6からIC-R6への書き込み方法を公開します

これまで多くにの方々にLT-R6をお使いいただきましたが、特に大きな問題もなく使っていただいているものと思います。

一方、作者はすでに古希を迎えており、(とくに健康を害している訳ではありませんが)今後とも利用者のお問い合わせに回答を続けられる自信がありません。そこでLT-R6からIC-R6への書き込みについて、以下の通り方法を公開する事としますので自由にご利用ください。

なおLT-R6についてのメールでのお問い合わせはこれまで通り受けますので、遠慮なくお問い合わせ下さい。

 【アップロードの方法】

LT-R6の起動スクリプト(LT-R6.bat)の中の起動オプションに「-OMG」を加えてください。例えば、

set LTR6_OPT=-JP -A -UF -OMG

といった感じです。
これでアップロードが出来るようになります。
 あくまで自己責任でお願いします。

なお、プログラムのダウンロードはこちらから。

作者

 

2023年11月22日水曜日

カーナビのハードディスク(HDD)をソリッドステートディスク(SSD)に換装

 壊れるとユニット交換で高額修理の対象となる純正カーナビのHDDをSSDに換装する事が流行っているらしい。一般に自家用車の使用時間はPCに比べると短いだろうが、温度変化や振動などを加味するとPCに比べて使用環境は過酷だ。元々機械的な機構を持つHDDはいずれ壊れる消耗品であり換装(或いは交換)は当然の流れである。これまで問題にならなかったことが不思議なくらいである。

換装で気になるのはコピーしたHDDをナビが認識し機能させられるか、という事である。HDDには製造段階で1台ごとに固有の番号を割り振ってあり、それをソフト的にチェックする事で交換した事が分かる。WindowsはHDDを交換した事を密かにチェックしているらしいがカーナビはどうなんだろうか?先人の実績を見ると特に問題は起きていないようではあるが・・・。

もう1つ気になる点は、コピーの方法である。一般的なHDDのコピーにはUSB-SATA変換ケーブルとTrue Image等のWindows用のバックアップツールを使うが、カーナビ用HDDでは記憶領域の管理方法や実際に使っている領域が異なっている可能性があるため、Windowsで利用している領域のみをコピーするようなツールを使うと失敗する可能性が高い。またHDDはエラーがある領域を検知して別の代替領域を割り当てる機能があるが、これに対応していないとコピーの途中でエラーで止まってしまい正常にコピーできない可能性がある。先人の実例では、エラーを回避しながら全領域をコピーするツールを使ってこの問題を回避しているようである。

わが愛車も無償メンテ期間を過ぎたのでSSDに換装する事にした。

何しろ本件は失敗したら高額修理になるので用心の上にも用心して取り組む必要があり、奮発して、といっても1諭吉もしないが、先人と同じSDDと地獄コピーマシン(クローンマシン)を用意した。これを使えば、コピーの方向が矢印で示されているので操作を誤ってオリジナルのHDDに上書きして壊す心配もない。

YouTubeを参考に車から取り出したHDDは東芝製のMQ01AAD-Cシリーズの200GBの物だった。用意した256GBのSSDに収まる容量だ。


東芝のHPによると「MQ01AAD-Cシリーズはディスク回転数4200rpmの2.5型HDDで、自動車や、工業用途に適している。高度仕様、温度範囲、耐振動性に優れており厳しい環境での利用可能です」とありヘビーデューティのHDDらしい。HDDにはAdvanced Format とラベルに書いてあるが、これは従来から使われてきた512Bのブロックサイズでなく通常4096Bのブロックサイズを使い512Bのブロックサイズをエミュレーションすることで10%程度の容量拡大が見込めるやり方らしい。これは今回の換装には関係無さそう。 

ちなみにHDDの取付ネジはにはロックタイト(緩み止め)処理されており、振動対策は万全だった。

このHDDをコピー器でSSDにデッドコピーして車に取り付ければ終わりである。スタートから1時間半程度経過してコピーは終わった。

これを車に戻して終り。エンジンキーをONにすると何事も無かったように立ち上がった。SSDに換装したことで動きがキビキビになったかと言えば、少し速くなったように感じたが極端な変化はない。しかしこれで、バックアップ用のHDDが確保できたことを含め、高額修理になる確率がかなり減った事で十分元は取ったと考えている。 

もし換装をやりたいと思う人がいたら、車のパネルの分解方法と必要な工具は予めよく調べておく必要がある。特に分解は下手をするとパネルに傷を付けたり固定用の爪を折ったりするからやっかいである。工具が足りないと分解の途中で元に戻したり、或いは無理な作業をやることで損傷を与えたり作業を途中で投げ出す事になる。

次の目標は、内蔵バッテリーがヘタってSDカードが壊れがちなドラレコの新調である。


2023年11月7日火曜日

頑張る日本の女子

様々な分野での 日本の女子の活躍が気になっている。といっても私が知っているのはYouTubeで流れてくるほんの一部の人たちの動画だろうが。

ハラミちゃんは都庁展望室でコソコソとピアノを弾いていてフォロワー数が2千人くらいの時から注目していて、凄い才能の子がいると周囲に話したが誰も興味を示さなかった。だがいつの間にかTVに出るようになりフォロワーも100万人を超え、もはや有名人だ。 

juna serita は独特の存在感のあるジャズ系べーシスト。最初聞いたとき日本人とは思わなかったが、芹田珠奈が本名で石川県出身、紛れもない日本人だ。

Famiは既にフォロワーが60万を超えているベーシストであり、私は完全に出遅れた。Famiが17歳の時投稿した動画 alien alien は女子高生が弾いているとはとても思えない。重い弦を様々なテクニックを駆使して目にもとまらぬ速度で自由に引きこなすのは男でも容易ではないだろう。さらに彼女が国際的に活躍する女子ヘビメタバンドLOVEBITESに加わったおかげでヘビメタにも興味が湧いたし、才能溢れるLOVEBITESのメンバーも知る事が出来た。
ベーシストといえばぴにょも忘れてはならない。揺れ具合も凄いが現役の看護師でコロナの間は本業のため動画をお休みしていたようだが今は復活している。

 野田樹潤(juju)は欧州のフォーミュラーレース界で活躍する18歳だ。ユーロフォーミュラ(F3相当)で優勝した事もあり、最近参加したF2で上位に入賞した事もある。そして2023年のZinox F2000クラスの年間チャンピオンを獲得した。

永山陽梨は国内の二輪のレースで活躍する中学生。これからも陰ながら応援していきたい。

思えば大学生の頃、矢野顕子の Japanese Girl を聞いてショックを受けたが当時彼女は18歳だったと思う。 KateBushが嵐が丘(Wuthering Hights)を作ったのも18歳だったと聞く。18歳が女子の才能の開花時期なのかな。

 

 

 

2023年10月22日日曜日

出入り検知器をPCB化する(その1)


前回の出入り検知器
で PIC16F5325では信号線数=足の数が足りなくなり、またメモリー不足となったので新しくPICを選びなおした。今回使うのは1つ上位のPIC18F27Q43である。このPICには2個のSPIインターフェンスと5個のUARTが搭載されており、さらにプログラムメモリーが128KB搭載(16F5325は14KBだった)されており今回の目的にぴったりである。さらに秋月で多くのPICが欠品になっている中で在庫があり価格も330円とお手頃である。一方難点としては足の数が28本あり基板上で2倍の面積を占めるためプロトタイプ用基板に乗らず製作がやや面倒である。

そこで今回は最初に専用の基板を起こす事とし、それと並行してソフトを開発することとした。

 受信部は次の2枚で構成される。赤外センサーを搭載する1枚目は更に2分割し、センサーを20cm離れて配置する。この基板にはVカットという手で割るための溝を入れてもらいたかったが、そうするためにはパネル化といい基板をm×n枚並べて面積を増やす操作をする必要があるが、今回はその数も必要無いし勉強不足もあり断念した。

2枚目が受診部の本体である。

 

送信部は特に変わっていない。前出の回路をそのままパターン化した。

 

これら3種の基板を各5枚ずつ発注し(発注数の最小要件が5である)、最も安い配送料も合わせて全部で11ドルでお釣りがくる(一部ディスカウントを含む)。 

基板の発注と並行してソフトを開発していたが、重大な問題に直面した。PIC18F27Q43の各信号とポートの間のマッピング(PPS)に、例えば次のように制約があるのである。例えばSPIのデータとクロックはポートAにマッピングできない。

入力

出力

基板は、全ての機能が任意のピンにマッピングできる事を前提に設計したのでこれには参った。配線のカットとジャンパー接続が必須になる!!!PIC18F27Q43のマニュアルは英文で千ページほどあるので細かい所は読み切れていない。

何処を切って何処にジャンパーを繋ぐかを知るために、実際に使う機能と足の関係を調べると、何と、ソフトの設計変更だけでジャンパー無しで済むことが分かった。 例えばUART3⇒UART4、UART5⇒UART3の変更で済む。これはUART毎に使えるポートが違うからである。

発注から1週間ほどで基板が届いたので、組み立てた。CADでは部品の密度をできるだけ上げたつもりでも、実際に基板を組み立てるとスカスカである。そして幾つかの問題を見付けた。1つはスイッチのフットプリントがインチサイズではなくmmサイズになっていて、挿入に少し無理があった。もう1点は回路図⇒基板の間はCADでチェックされているので問題はなかったが、大元の回路図が間違っていた。具体的には可変抵抗のピン番号が逆順であった。つまり可変抵抗は3つの端子が左利回りに1,2,3の筈であるが、私が使った製品は逆順であり(同じモデルで正順の製品もある)、右に回すほど音が小さくなる。あと、音声合成ICの通信モードの2つの選択ピンの番号が逆であった。いずれも基板のカットとジャンパーで修正した。

これらの誤りは次の発注ではで修正する。

(続く)

2023年10月21日土曜日

エレクトーンのFDDの修理を試みる

 子供が使っていたYAMAHAのエレクトーンを再び使おうとしたら3.5"FDD(その道ではMDRと言うらしい)の読み込みがエラーとなり読めない、長い間放っておいたので予想された状態であるが。そこで昔持っていたはずのクリーニングディスクを探したが行方不明。仕方なくネット情報を元に下の7本のネジを取ってキーボードを上に開け、

中にあって厳重にアセテート粘着テープで防振処理されているFDDを取出してヘッドを掃除、可動部にグリスを塗ってみた。

これを元に戻し、数枚のFDDの一部は読めたが、多くはエラーを起こし読めなかった。FDドライブが悪いのかフロッピーディスクの磁気が劣化しているのか。

ネット検索を続けると、2012年頃にはFDDを交換した話や、これに対応した楽器の修理屋さんの情報があるが、その時点でFDD自体は製造中止になっていたようだ。それから10年以上も経っており、今更FDDが入手できるのだろうか。

このエレクトーンに内蔵されていたFDDはラベルに型番がALPSのDF354H121Fで03-15とあるから製造は2003年の15週のようだ。そこでオークションでALPSの型番がDF354で始まるFDDを調べてみると思ったより多くが出品されている。しかし同じ型番は無い。多くはSONYのPCに内蔵されていたもののようだが動作確認されたものは殆どない。その中で型番がDF354Nで始まる未使用新品が手頃な値段で即決で出品されていたので思わずポチった。同じ34ピンのIDEインターフェイスで、初期のWindowsには当たり前のように使われていたデバイスなので互換性に問題は無いだろう。

しかし型番の違いが気になる。そのまま使えるのだろうか。ネットで検索しても有用な情報が出てこないが、DF354Hが2モード、DF354Nは3モードである事が分かった。

更にDF354Hは内部のジャンパーを変更することで3モードに変更できる事もネット情報にあった。だったらDF354Nもジャンパーの変更で2モードに出来るかもしれないがここら辺りの情報が全く無い。

そしてDF354Nが届いた。2010年製のようだが未使用新品だから劣化は殆どないと思われる。ベゼルやイジェクトボタンが白色だが、これは古い方から移植すれば何とかなる。ちなみに内部の回路は比較的コンパクトにまとまっており、2つのLSIを中心に構成されている。大きい方の正方形のICがメインコントローラと思われるが旧型と同じものだった。小さい方のICはスピンドルモータのコントローラと思われるが、これは別物になっていた。


ジャンパーは次の様に設定されていて異なっているのはMS2だけである。MSはモードセレクトの略だろう。DSはデバイスセレクト、FGはフレームグラウンド、DNSはdensity(記録密度)かな。INはよく分からないが挿入検知かもしれない。
 これならDF354NからMS2のジャンパーを外せば上手くいく可能性が高い・・・やってみた。

そしてこれを組み立て、エレクトーンに仮付けしてみた。一応読めるFDもあったが旧型と結果は同じであった。つまりFDDに問題はなくフロッピーディスクの磁気が弱っているのであろう、と言うのが結論である。

残念な結果となったが新しいFDDは予備用として保管しておく事にした。


2023年9月17日日曜日

第二種電気工事士に挑戦(その4)結果

 技能試験は欠陥が1つもない事が合格の条件なのだが、上手く出来たつもりでも検査官がどう判断するか分からない。合格通知を受け取るまでは何とも言えない。

そして1か月ほど経って結果が届いた(都合があって家を留守にしていたため受験番号が分からずWEB公表には対応できなかった)。

今回の技能試験の合格率は73%だったそうだ。

免許申請は更に1か月ほど遅れたが、申請書に5300円の収入証紙と顔写真を添えて近くにある県の電気工事工業組合の支部へ持参した。即日発行を期待したが免許は郵送になるとの事。なお申請書と免許状交付申請の説明書は技能試験時に受験者全員に配られていた。

実際に免許が送られてくるまでには1か月半ほどかかるらしい。

そして10月半ばにライセンスを受け取った。

メデタシ、メデタシ
 

第二種電気工事士に挑戦(その3)技能試験

 技能試験は13課題があらかじめ公表され、その中から1課題が出題され、その回路を40分以内に完成させる必要がある。試験内容に比べて時間が多くないので無駄な作業は極力省き効率的に進めたい。

まず、YouTubeでHOZANから提供されている13課題の実演(といっても昨年の問題。今年も内容はそう変わらないはず)を見た。元々弱電の配線には慣れているので ポイントだけ習得すれば何とかなるはずである。そこで分かったのはワイヤーストリッパーが無いとかなり時間をロスることである。HOZANは、カット+シース剥き+被覆剥きを簡単に処理できる電気工事士試験に特化したワイヤーストリッパーを販売している。ペンチ+電工ナイフだと分単位でかかってしまうVVFケーブルの末端処理がこのワイヤーストリッパーを使うと10秒もかからず処理できる。これは時間短縮のために必須だ。さらに重要課題の「の」の字曲げにも使える。
その他リングスリーブの圧着ペンチも圧着したリングスリーブに決まったマークを残す必要があるので必須の工具である。

そうこう調べていると受験向けに工具セットを販売している事が分かり、DK-28というセットを早速購入した。すでに持っている工具もあるのでセットである必要ないが割安なうえに携行に便利な収納袋も付いており便利そうだ。試験が終わればオークションで転売すればよい。そのほかDK-200という合格マルチツールと合格クリップも併せて買ったが、これも試験が終われば不要だ。

工具を買ってみて1つ分かった事は、ワイヤーストリッパーは右利き用という事だ。私は左利きなので使い勝手が少し違う。表面に書いてあるスケールは左利きでは使い辛い。そのほか被覆を剥くとき刃が傾くと被覆や心線に食い込んで傷つけ気味になるが、これは最悪の場合欠陥と判断される。くれぐれも注意が必要だ。

工具は揃えたが練習材料も必要。近くのホームセンターでVVFケーブルや埋め込み型スイッチ(単極、3路、位置表示灯内蔵)、埋め込み型コンセント、露出型コンセント、ランプレセプタクル、引掛けシーリング、リングスリーブ(小、中)、差し込み型コネクタ(3本用)、埋込連用取付枠を購入した。ジョイントボックスは特に練習する必要も無いだろう。
 特に練習したのは「の」の字曲げである。あまりはみ出してはいけない事は分かるが、判断基準が明確でなく試験官の気分で合否を左右される可能性があるので教科書的な美しい「の」の字を作って欠陥判断を回避したい。試験に出るネジの直径は3.5mmの場合と4mmの場合があるようで、被覆を剥く長さもそれに応じて18mmや20mmと変え、「の」の字がネジの頭から横にはみ出ないよう毎日練習した。

最初は甘く見ていたが、リングスリーブの圧着にも意外と力が必要で、よっぽど力が無いと片手の握力だけでは不十分である。HOZANのビデオの講師のお姉さんが苦労していた理由が納得できた。練習の残骸が次の写真である。

あと、個人的に気を付けなければならないのは線の色である。試験では2色(白・黒)のVVFケーブルを多用する。弱電では接地には黒や暗色系の色を使う事が多いが、強電では接地には白を使い黒はLive(Hot)と使い分ける。うっかり色を逆に使わないよう注意が必要だ。

元々弱電の配線には慣れているので複線図をきちっと描けばあとは淡々と作業できる。複線図を描く練習は各課題ごとに3回はやった。実配線では上に述べた幾つかのポイントや各部品やケーブルを手に馴染ませる事を主に練習し、1つの課題を最後まで完成させるような練習はしなかった(そもそもそれだけの材料は買い揃えてはいない)。被覆を剥く長さ10mm、12mm、18mm、20mmなどは目視で出来るようにした。

 

試験会場は全国的に有名なイベント会場だった。受験者数は約2千人程度。概ね 100名×20グループといったように分けられていた。机上には作業用(机の保護用)にB3程度のボール紙が下敷きとして置いてある。配線材料は23×16×10cm程度の段ボール箱1つに入って配られた。VVFケーブルは巻かれた状態で箱に入っていた。

本試験が始まる前に配線材料の確認があるが、この時点で13課題中のどの課題が出題されているか材料の組み合わせで分かる(まだ問題用紙の課題を見てはいけない)。

試験官の合図で試験がスタート。試験の時最も気を付けたのはVVFケーブルをカットする長さである。支給されるVVFケーブルに余裕がないので、切る長さを間違えると最悪の場合課題を完成できなくなる。複線図を描くときカットする長さを書き込んで、その合計と支給されたVVFケーブルの長さに矛盾がないかチェックした。被覆を剥く長さは多少誤魔化しが効く。あとは淡々と作業するのみである。

スタートから35分くらいかかって課題は完成した。その時点ですでに完成し形を整えている受験者も多かった。

試験官の止めの合図で手を下す。その後は一切課題に触れてはならない。

机の上を片付けて、余った材料やゴミは段ボール箱に戻し、課題を机上に置いたまま退席となる。2千人を順に退席させるため自分の番になるまで30分以上かかった。なお問題用紙や段ボール箱、下敷きのボール紙は持ち帰れない。

あとは結果を待つのみである。 

余談であるが、第一種電気工事士の資格を持つ知人からのアドバイスによると、冬場はケーブルのシースや被覆が硬くなり作業に手間取るので実技試験は上期が有利との事だった。

 (続く)

 

 

 

 

第二種電気工事士に挑戦(その2)学科試験

学科試験は筆記を選んだ。最初は日程に自由度のあるCBTを考えたが(1)CBTは筆記より早い時期に行われるため勉強の時間が取れないのと、(2)過去問をA4サイズに印刷すると小さい字が潰れてみ辛い場合があり、もし本番画面上での屋内配線図の解像度が悪く字が細かいと老眼の私に不利なので、眼鏡で何とかなりそうな紙(筆記)を選んだ。

オークションで昨年用の問題集を安く入手した。どうせすぐ不要になる本なので中古で十分である。届いてみると多少の書き込みがあったが気にしない。自分のペースで過去10年分を解いたら傾向とともに自分に足りない知識が何なのか、強化すべき知識が何なのかが分かった。さらに電気技術者試験センターから2022年の試験問題と解答を入手して解いたが、ほぼ過去問の繰り返しだ。

 
問題を解いているうちに配線器具類の名前と記号、ケーブルの名前と特性、ブレーカの許容電流と設置場所、モータを含んだ回路の許容電流の決め方、漏電ブレーカの省略、工事に必要な知識など強化すべき分野が分かった。それとともに同じような問題が繰り返し登場するので、やっているうちに知らなかった知識が身に付いた。

筆記試験は全部で50問出題されるが、回路理論、配線の許容電流やブレーカ、工具に関する知識、電動機、配線材料、リングスリーブの使い方、施工場所と工事の種類、接地、検査、故障診断、法令関係で30問、残り20問が配線図と実物の写真を見ながらの実務的な問題である。

ここ数年の問題の傾向として複合的な問題が出てきている。従来であれば2つの問題であったものが1つの問題に纏められていて、答えの組み合わせとして正しいものを選べ、といった問題である。例えば2021年度上期午前の第19問がこれに当たる。 


 試験会場は家から2時間近くかかる某大学が指定された。受験者は若い人が多い。会場で試験室案内を見ると700名程度の受験者は18室に分けられている。試験は午前・午後と2回あるから倍の受験生がいるのかな。私は午後の部であった。

問題は50問、試験時間は2時間、マークシート方式である。配られた問題用紙は想像したより大きいB4サイズであり、屋内配線図はくっきりと印刷され老眼の私でも全く問題無かった。

回答は淡々と1時間半ほどで終わり、退室した。試験開始後一定時間を過ぎると退室できる。問題用紙は持ち帰って良いので後の自己採点が容易だ。

試験後、自信のない1問を除いて全問正解だと思ったが、後に公開された回答で答え合わせすると3問間違っていて、自己採点で94点であった。じっくり取り組めば100点だったが筈だが早とちり等で当然正解できた問題をミスり、満点とはならなかった。

なお今回の学科の合格率は60%だったそうだ。

(続く)


 

 

2023年9月16日土曜日

第二種電気工事士に挑戦(その1)

 還暦はとっくに通り過ぎた身ではあるが、思い立って第二種電気工事士に挑戦した。といっても第二種電気工事士についての詳しい知識はなく、ブレーカーが時々落ちるのを防ぐために家の中の配線を整理できれば良い、程度の動機である。幸い電子回路(弱電)は超得意分野である。強電もその延長で何とかなるだろう。

ネットで検索すると、一般財団法人電気技術者試験センターが電気工事士の試験をやっているらしく、同センターのホームページを見ると第二種電気工事士は年に2回の試験があるようだ。試験は学科試験と技能試験がある。さらに過去問と回答が10年分掲載されていた。

とりあえず受験案内をダウンロードした。今年2月頃の事である。

 受験案内によると受験料はネット申し込みで9,300円と少々お高い。前期試験の申し込みは3/20~4/6、学科試験(筆記)が5/28、技能試験が7/22又は7/23、結果のWEB発表は8/17と結構長丁場である。学科試験には今年からPCを使ったCBT方式での試験も導入され(4/24~5/11)こちらも選択可能である。

このスケジュールと過去問を見ながら、受験するかどうか悩んだ。学科試験は2時間で60点取ればよいので何とかなるだろう。一方技能試験は40分勝負で1箇所でも欠陥があるとサドンデスで不合格となるので細心の注意が必要で、甘くみてはけない。1回の受験で合格できる一発合格率は5割程度らしい。もし技能試験に落ちても1回は学科免除で再挑戦できるが再度高い受験料を払う必要がある。

 何人かの知人に相談し、祖父が資格を持っていたこともあり、ボケ防止を兼ねて受験する事とした。

申し込みは 電気技術者試験センターホームページにマイページを登録し、その中で申し込む。受験料はコンビニで支払ったが何故か手数料が上乗せされて9593円になっていた。これで腹は決った。

 (続く)

2023年5月9日火曜日

赤外線で通信する 出入り検知器(まとめ)


 これまで出入り検知器を作ってきて、3作品目で一応納得できるものに仕上がった。

【送信部】

 送信部の回路には特に変更はない。

 【受信部】

受信部にはフォトリレーを追加して発声時以外はオーディオアンプの電源を切るようにした。その結果10mAあった通常時の消費電流を3.0~3.5mAまで低下できた。さらにフォトリレーを追加した結果、発声に伴って外部の回路を制御できるようになった。

 

【その後】

これまでの回路に気圧計とRTC(リアルタイムクロック)を追加した。

しかしそのままではPICの足が足りないので、代償としてLEDを廃止した。追加回路は2段目の基板に組み込んだ。

 気圧計のプログラムを書いているうちにメモリーをほぼ使い切ったのでRTCの処理まで進めなかった。PICをもう少し足の数とメモリー容量の大きいものに変える必要がある。またLEDを廃止した事で内部の状態が分かり辛くなったので、LEDも復活したい所である。


2023年4月29日土曜日

赤外線で通信する 出入り検知器(その3)

 【受信部】 

今回PICとボイスシンセサイザとの間をI2Cインターフェイスで繋いだ。I2Cインターフェイスをデフォルトのピン割り当てで使う場合は素直に使えるが、一方他のピンに割り当てる場合は少しややこしい。PIC16F15325のマニュアル15.3項のNoteによると特定のピン(RC3,RC4)だけがI2Cの入力レベル(スレッショルド)に対応しており、他のピンを使う場合はその設定の追加が必要。I2Cはデータとクロックの2つの信号を使うがPICでは2つの信号とも入力に初期設定するだけでなくスレッショルドの変更が必要であり、さらにPPSでは入力だけでなく(当然であるが)出力としても設定する必要がある。今回は以下のように記述した。

     // 12:RA1 - I2C CLK - Bidirectional
    TRISAbits.TRISA1 = 1;   // set Abit1 to input
    ANSELAbits.ANSA1 = 0;   // set Abit1 to digital
    INLVLAbits.INLVLA1 = 1; // ST level instead of TTL
    WPUAbits.WPUA1   = ON;  // weak pull-up
    RA1PPS          = 0x15; // SCK1/SCL1
    SSP1CLKPPS      = 0x01;
    
    // 13:RA0 - I2C DAT - Bidirectional
    TRISAbits.TRISA0 = 1;   // set Abit0 to intput
    ANSELAbits.ANSA0 = 0;   // set Abit0 to digital
    INLVLAbits.INLVLA0 = 1; // ST level instead of TTL
    WPUAbits.WPUA0   = ON;  // weak pull-up
    RA0PPS          = 0x16; // SD1/SDA1
    SSP1DATPPS      = 0x00;
 

このI2Cインターフェイスを使ったボイスシンセサイザとの通信は、一見上手く行ったように見えていたが暫く使っているとI2CのSCK(クロック)がLowになったままハングアップした。電源を入れなおしても同じだ。調べると同じような事例もあり、I2Cはソフト的には結構ややこしくロジックアナライザ無しではデバッグも容易でないようだ。よって、I2Cに代えてSPIインターフェイスを使うように変更した。SPIインターフェイスは構造が簡単で送受を同時に行うのが特徴。ソフト的な扱いも単純だが4本も信号線を使うので(最初は/SSをL固定し、それ以外の3本で通信を試みたが上手く機能しなかった)I2Cの2本と比べてPICの足数から自由度が減る事が残念だ。

 受信部のプログラムは1000行にもなるのでここでこれ以上掲載するのは止めて、そこで使っているロジックについて概要を解説する。

受信部の最初のポイントは、2つのIRセンサーで赤外線信号を定常的に受信出来ている状態は無情報であり信号欠落した事が情報(イベント)となる事である。これは5ms毎の割込みとソフト的なカウンタで処理することができる。

2つ目のポイントは 2つのIRセンサーの前をどう横切った時どう判断してどんなメッセージを流すか、またノイズをどう排除するかである。

このプログラミングには条件を細かく設定して分岐で処理しても良いが、そうするとプログラムがスパゲティ状態になり天才でもない限り行き詰まるか保守できなく事は目に見えている。こういう場合はステートマシンを使うのが早道である。ステートマシンでは「状態」と「イベント」を定義し、イベントの発生に伴う「アクション」と「状態の遷移」を表形式でプログラミングすることでロジックの「穴」を防ぐことができる。ステートマシンは有向グラフや状態遷移表で表す事が出来る。

 今回使うイベントは、
    C0)2つのセンサーが信号を受けている
    C1)センサー1が遮られた
    C2)センサー2が遮られた
    CB)センサー1及びセンサー2が遮られた
    TO)タイマーが設定した時間を経過した
の5つとした。また状態としてはシンプルに、
    IDL) 初期状態
    SYN) 人がいない(2つのセンサーでデータを受信)
    EN1)人がセンサー1の前に入った
    OV1)人が両方のセンサーの前まで進んだ
    EX1)人がセンサー1を通り過ぎた
    EN2)人がセンサー2の前に入った
    OV2)人が両方のセンサーの前まで進んだ
    EX2)人がセンサー2を通り過ぎた

とした。これを使って人一人の基本的な動きをトレースするためのステートマシンは次の状態遷移表になろう。この遷移表で実線で囲まれた各升目には3つの欄があり、それぞれある状態にあるときに、あるイベントを受けたときの メッセージタイマー操作次の状態への遷移を表している。 

このステートマシンはTimer0で5ms毎に実行されるようにしている。

IDLが初期状態で、2つの赤外センサー両方で赤外線を感じる(C0)とSYN状態に移行する。SYN状態が赤外線が遮られていない定常状態である。遷移表で灰色の網掛けは同じ状態に留まることを表している。

人が入店する場合は、
SYN→(C1)→EN1→(C3)→OV1→(C2)→EX1→(C0)→SYN
と移行するはずである。逆に人が退店する場合は、
SYN→(C2)→EN2→(C3)→OV2→(C1)→EX2→(C0)→SYN
と移行するはずである。単純に考えればEX1→(C0)の時に「いらっしゃいませ(WELCOM)」、逆に
EX2→(C0)の時に「ありがとうございました(THANK)」と発声すればよい。

しかし、事はそう単純ではなくこの表には空欄が多い。つまりまだ振る舞いが定義されてない条件がある事が分かる。空欄を全て埋めてステートマシンは完成する。例えばEN1-(C0)は人がセンサー1まで来て引き返した場合である。この時どう反応すべきか。またSYN-(CB)等のようにオレンジで網掛けしている欄は人一人の移動としてはあり得ない状態であり、たぶん二人の人がすれ違った場合、或いはセンサーが赤外リモコン等ノイズを拾った場合か、または送信部がいきなり停止した場合などが考えられる。このような場合にどう振舞えばよいか、このシステムへの要請に応じて欄を埋める必要がある。

これを実際に埋めてみたのが次の遷移表である。INITは電源を入れた時一度だけ通る状態であり、2つの赤外線を最初に感知したときチャイムを鳴らすために設けた。なお、このステートマシンは人が一人の場合は正しく判断できるが二人が絡むと結果は予測できない。

ここでT+2は2秒のタイマーを設定、Toffはタイマーを止める事を表す。赤色で示した欄はこの状態になることは無く何らかの内部エラーが発生した事を表すので動作としてはエラーと叫んでIDL状態に戻す事とした。またオレンジの欄も一人の移動では発生しない状態なので、「今日も良い天気」「大谷がんばれ」「大吉」など幾つか用意したメッセージをランダムに選んで発声するとともにIDLやSYN状態に戻す事とした。なお実際のメッセージは次の様になっている。
    HELLO        こんにちわ
    WELCOM    いらっしゃいませ
    THANK        ありがとうございました
    CHM            チャイム音
    ERR            エラー
    RDM         (ランダムなメッセージ)

このほか、「送信機の電圧が低下しました」、「受信機の電圧が低下しました」なども必要に応じて喋るようにしている。 

このステートマシンをプログラミングし実際に動かしてみたら上手く動いているようだ。センサーの前を移動したとき簡略化した状態遷移は次のようになった。

ID-S1-ET
ID-S2-e0-S2-e0-S2-e0-S2-e3-o2-eT
ID-S2-e0-S2-e0-S2-e0-S2-e0-S1-ET
ID-S1-E0-S2-e0-S1-E0-S1-E0-S2-e0-S1-E0-S2-e0-S1-E3-OT-O1-E0-S1-E3-O2-X0-S2-e3-o1-x0-S1-E3-O2-X0-S2-e3-o1-x0-S1-E3-OT-O1-E0-S1-E3-O2-X0-S2-e3-o1-x0-S1-E3-O2-X3-O2-X0-S2-e3-oT
ID-S2-e3-o1-x0-S1-E3-O2-X0-S3

ここでIDがIDL状態、S1がSYN状態で1番のイベントが発生した事を表す、EOXの各文字はそれぞれEN1,OV1,EX1、eoxの各文字はそれぞれEN2,OV2,EX2、また0123Tの各文字はそれぞれC1,C2,C3,TOのイベントを表す。

このシーケンスを見てセンサーの前で何が起きており、どういうメッセージが発声されているか分かるだろうか。例えば最初の ID-S1-ET は人が入口からセンサー1まで移動し、その状態で2秒経過したので「こんにちわ」と発声した事を表す。

なおステートマシンが5ms毎に動くのに比べ発声には時間がかかるので発声要求はキュー(FIFO)に入れて順次喋るようにしている。

  このようなプログラミングの答えは1つとは限らない。もっと複雑な動作をさせたければセンサーを増やしたり状態の数を増やすのも良いだろう。目的に合ったステートマシンを構築すれば今回のように一人ではなく二人の動作を追跡できるかもしれない。昨今流行のAIに作らせる/DEEP LEARNINGで学習させることも出来るかもしれない。

話は変わるが、最近PICが品薄のようである。 今回使った16F15325は秋月で在庫なし。そのほか多くのPICが在庫なしで入荷予定も立たないという事であった。EUSARTを2個内蔵したPICで手頃なものはこれしかないので困ったものである。


2023年4月26日水曜日

赤外線で通信する 出入り検知器(その2)

【送信部】

  先ず、前回以降送信部のPICを同じペリフェラルを持って安価な16F18313(8ピン)に替えた。ピンの接続変更、及びソフトでのピンマッピング(PPS)の変更以外は何もせずそのまま動かすことができた。

この送信部はPIC16F18313を使って次の様に、秋月の片面ユニバーサル基板(47×36mm)の半分を使ってコンパクトに組み立てる事ができた。この大きさなら何かに組み込むことも容易だ。

送信部は一定間隔でひたすらデータを送信するのみで何も特別な事はしない。

さて、実際のプログラムであるが全体を説明しても冗長なのでキモの部分に絞って説明する。初期設定を終えた後のメインプログラムの主要部分は次の通り。

    INTCONbits.PEIE = ON;     // Enable Peripheral Interrupt
    INTCONbits.GIE  = ON;     // Enable Global Interrupt
    T0CON0          = 0x80;   // Enable Timer0, 8bit, post=1:1
    T0CON1          = 0x90;   // LFINTOSC, Async, Pre=1:1
    TMR0H           = 62;     // tick for every 2ms
    PIR0bits.TMR0IF = OFF;
    PIE0bits.TMR0IE = ON;     // Start Timer0 Interrupt
//main loop    
    while(TRUE){
        SLEEP();              // wake every 2ms and UART intr.
        // Show Battery Warning
        if(low_batt){
            if(--bl_blk_cnt==0){
                r_led_on = !r_led_on;
                R_LED(r_led_on);
                bl_blk_cnt  = (r_led_on) ? BL_BLNK_ON : BL_BLNK_OFF ;
            }
        }else{

            r_led_on = OFF;
            R_LED(
r_led_on);
        }
    }

つまり、Timer0の2ms毎の割込みをONにした後SLEEP()する。SLEEP()は2ms毎及びその他の割込みが起きると目覚めるので、もしその時電圧低下(low_batt)フラグが立っていれば赤色LEDを点滅させる。

 割込みサービスルーチンは次の通り。

void __interrupt() isr(){
    if(PIR1bits.TXIF && PIE1bits.TXIE) sendISR();
    if(PIE0bits.TMR0IE && PIR0bits.TMR0IF){
        Y_LED(ON);
        tickISR();    PIR0bits.TMR0IF = 0;
        Y_LED(OFF);
    }
}

もしUARTの送信可割込みであればsendISR()を呼び出す。Timer0割込みであればY-LEDをONにし、tickISR()を呼び出した後Y-LEDをOFFにする。これによって2ms毎の割込みが機能していることが分かる(黄色LEDが薄く点灯する)。

sendISR()は次の様に、送信データがあれば送信、データが無ければ割り込みをOFFとする。

byte        *bufp; 

void sendISR(void){
    byte d  = *bufp++;          // get next data
    if(d == 0){
        PIE1bits.TXIE = OFF;    // Disable Interrupt
        bufp--;                 // guard
    }else{
        TX1REG = (low_batt)? (d | 0x80): d; // send data
    }
}

ここで送信データのビット7は受信部に電圧低下を知らせるフラグとして使っている。

送信の起動は次の様にバッファにポインタをセットし割り込みを可とすることで行う。通常この行を実行した瞬間に割込みが発生する。

void sendUART(byte *buf){
    bufp            = buf;
    PIE1bits.TXIE   = ON;
}

tickISR()(タイマー割込み)ではカウンタを設けて40ms毎にデータを送信、及び30秒ごとにバッテリー電圧のチェックを行っている。

static int      cnt     = (TX_PERIOD/TICK_TIME);
static byte     key     = KEY_MIN;
static byte     dat[3]  = "  ";
static uint     bccnt   = BC_PERIOD/5;   // battery check counter

void tickISR(){
    // Send data, every 40ms
    if(--cnt==0){
        dat[0] = key;
        dat[1] = key ^ KEY_SWAP;       // toggle lower 7 bits
        sendUART(dat);
        key++; if(key>KEY_MAX) key = KEY_MIN;
        cnt = (TX_PERIOD/TICK_TIME);
    }
    // Battery Check, every 30sec
    if(--bccnt == 0){
        low_batt = is_bat_low();
        bccnt = BC_PERIOD;
    }
}

送信するデータはKEY_MINからKEY_MAXまでのデータを繰り返している。

 バッテリー電圧はA/Dコンバータでチェックする。10ビットA/Dコンバータは2つの参照電圧(Vref+、Vref-)間を1024(=2^10)段階でデジタル化してくれる。参照電圧に内蔵の2.048Vと0Vを選ぶと1ステップが2mVとなる。実際には精度が10ビットも必要ないので上位8ビットを使って1ステップ8mVとしている。さらに電源電圧は3.3Kと1.5Kで分割してA/Dコンバータに与えているので、その比率を加味すると1ステップ25.6mVに相当する。バッテリーの電圧チェックは次の通り。

#define BC_LEVEL        104     // Voltage Threshold, 0.0256V step

boolean is_bat_low(){
    ADCON0bits.GO   = 1;        // Start Conversion
    while(ADCON0bits.GO==1){}   // Wait until complete
    bt_data = ADRESH;
    return (bt_data < BC_LEVEL);
}

このA/D変換は数μsで終わるので実行時間に与える影響はほぼ皆無である。
最後に、A/Dコンバータの初期化は次の様に行っている。

void Init_AD(){
    // AD Converter
    ADCON0bits.CHS  = 4;    // CHS=ANA4,  A4 for input
    ADCON0bits.ADON = 1;    // AD ON
    ADCON1      = 0x53;     // Fosc/16(=1us), Vref+ is FVR
    ADACT       = 0x00;     // No auto conversion
    // FVR
    FVRCONbits.FVREN    = 1;    // Enable
    FVRCONbits.ADFVR    = 2;    // ADFVR=2.048V    
}

(その3)へ続く

2023年4月8日土曜日

赤外線で通信する 出入り検知器(その1)

 PICを使った 赤外線の送受信及びタイマーと割込みの扱いが理解出来たので、これを応用して人の移動を検知してアクションを起こす簡易なセンサー(入退出検出器)を作ってみる。

【概要】

 人の出入りを検知するには焦電素子を使った人感センサーを先ず思いつくが、これは指向性や反応速度が緩いうえに焦電センサーは赤外線輻射量の変化を検知するので前を通る車や風にも反応し条件が整わないと上手く反応できない。超音波やマイクロ波を使ったレーダー式は周囲に物を置いた状態では検知しずらい。カメラの画像解析は良さそうだが高価になりそうという事で、光電管式ネズミ捕りと同様な仕組みをこれまで試した赤外線を使って作ってみる事とした。

 基本的なアイデアは次の通り。


 送信部では赤外線をある短い間隔で水平に発信する。受信部では水平に離れた2点で赤外線を受信し、人が赤外線を横切った場合の赤外線の断を検知して人の移動を判断し何らかのアクションを行う。

【検討】

 赤外線の遮蔽を的確に検知するには短いパルス状の信号を連続的に送る事が好ましいがリモコン受光部の特性から100msの周期中に25msの休止も必要。ノイズの影響を排除するためには偽信号をチェックするカラクリも欲しい。一般にUARTの信号にはパリティを付加できるがPICのEUSARTにパリティの機能は無い。但しデータに9ビット目を付加できるのでソフト的にパリティビットを実現することは可能であるが面倒、という事でビット反転させた2つのデータを連続して送る事で到達チェックすることにした。これで最短34ms毎の検出が可能となる(実際には約40msに設定した)。ちなみに本装置の受光部はTV等のリモコンの赤外線にも反応するが、このやり方で誤検知を防ぐことができる。

 人の移動速度はせいぜい時速4Km(秒速1.1m)とし、人の前後の厚みを25cmと仮定すると1つのセンサーの前を人が通過するのに必要な時間は227msとなるので送信部で40ms毎に信号を出すと受光部で最低5回のパルスの欠落が発生し探知が可能である。

 では2つのセンサーの間隔はどれくらい必要だろうか。出来れば1回でも人が両方のセンサーを同時に遮蔽することが望ましいので20cmでトライしてみよう。20cmの間隔を秒速1.1mで進むと1つ目のセンサーを通過した後2つ目のセンサーに到達するのに180msかかり、その間40ms毎のパルスを4回は遮る事になる。これで検知区間の単独の通過とその進行方向を検知できる。

 さて2つのセンサーで人の出入りを監視するロジックであるが、単に出入りのチェックだけやるのであれば簡単であるが、途中で引き返したり、真ん中に長時間留まって多少左右に動いて作業したり、TV等のリモコンの赤外線に干渉されたり、実用に使うには様々な問題がある。それらを含めてどれだけだけ正確に検知できるか、がソフト作りの腕の見せ所である。

 とりあえずこれまでの経験をもとに次のようなハードウェアを作成した。

【送信部】 

 送信部は次の回路とした。回路で新しく追加したのはA/D変換器を使ってバッテリー電圧を監視できるようにしたところ。バッテリー電圧が概ね2.7Vまで下がると電圧低下の警告が出るようにし、また送信信号にもその情報を入れるようにして受信側で警告が出せるようにした。PICを使うとバラで組むより配線をかなり簡略化できるのが嬉しい。

 次の写真が実際に秋月の汎用基板に組み立てたところ。黄色LEDは割込み処理中に点灯し、赤色LEDはバッテリー電圧低下(2.7V位)で点灯させる。LEDごとにシリーズ抵抗が違うが、黄色はデューティ比が低い一方赤色は連続点灯なので、LEDの明るさを見て暗めの適当な明るさに調整した結果である。この回路全体の消費電流は2.5mA。単2電池二本で数ヶ月持つ事を想定してる。電池3本(4.5Vでも問題なく使える)。

 その後PICを同じペリフェラルを持つ16F18313(8ピン)に替えたがピンの接続変更、及びソフトでのピンマッピング(PPS)の変更以外は何もせずそのまま動いた。

【受信部】

 受信部は次の回路とし、新しく音声合成ICとスピーカを鳴らすためのオーディオアンプを追加した。PICと音声合成ICのインターフェイスはUARTも使えるがPICの2組のEUSARTを赤外線入力に使って余裕が無かったためI2Cを使った。音声合成ICのUARTインターフェイスはPCと繋ぎPCから音声データの発声テストを出来るようにしている。JP3は人の入退室方向を反転させるためのジャンパー、JP4はPICのUSART2をデバッグ用インターフェイスとして使う場合にショートする予定だったがこれまでの所出番はなかった。

  この回路の消費電流は定常時で10mA程度であるが音声発声時に大きく増えるためUSBで電源供給するようにした。また電池駆動も考え、電源電圧が約4.5Vまで低下すると警告を出すようにした。

 次の写真が実際にこの回路を秋月の汎用基板に組み立てたところ。28ピンの音声合成ICが大きな面積を占有している。

なお、PICから制御信号を出す事で音声合成ICやオーディオアンプをスタンバイ状態において更に消費電力を減らす事も可能なようなので後で検証したい。

 
(その2)へ続く

 

2023年3月22日水曜日

赤外線で通信する(その5)眠りながら仕事をさせる

 その3,その4の処理ではメインプログラムの主要部分は、

     while(TRUE){
        if((r1state==RxSTOP)&&(nbuf < NBUF)){
            recvUART1(buf[rbufn],BUFLEN);
        }
        
    //途中省略
   
        if(t2state==TxCMPLT){
            tbufn++; tbufn %= NBUF;
            nbuf--;
            t2state = TxSTOP;
        }
    }

等のようにひたすらぐるぐる走り回って無駄に計算資源を消費する構造になっている。 しかし仕事が無いときはここでSLEEP()を実行してCPUを眠らせ省エネに努めたい。つまりOSのカーネルのアイドルプセスみたいに、

    while(TRUE){
       SLEEP();
    }

というメインプログラムとして、全ての処理を割込みの中で行いたい。 

 そう思ってメインループでいきなりSLEEP()を実行したがSleepモードに入るとPICは止まってしまって動かない。マニュアルを読むと、SleepモードにはSLEEPとIDLEの2種類があることが分かる。SLEEPではCPUのほか周辺装置(ペリフェラル)も止まり、IDLEではCPUは止まるがペリフェラルは動く(いずれの場合も割込みが発生するとSleepは解除される)。つまり何か入出力を行いながらCPUを眠らせるにはIDLEにする必要があるのだ。これを制御するのがCPUDOZEのIDLENビットだ。

 今回はEUSARTを使うのでSLEEP()を実行したときモードをIDLEにする必要があり、そのためには予め、

 CPUDOZEbits.IDLEN   = ON;     //IDLE mode when SLEEP()

としておく必要がある。ここで注意が必要なのはSleepモードに入りCPUが止まるときCPUクロック(Fosc)も止まる事である(実際にはCPUクロックを止める事でCPUを止めているのであろう)。ペリフェラルの中にはFosc或いはそれを四分周したFosc/4を同期をとるために使っているものがあり、その場合たとえSleepモードでIDLEになってもペリフェラルは止まってしまう。これを纏めると次表になる。

 EUSARTもFosc/4を使うペリフェラルの1つであり、これを止めないためにはEUSARTはFoscに依存しない非同期モードで動かす必要がある。

 今回のプログラムは、その4で作ったプログラムの一部を次の様に改修したものになる。先ずメインのループは上で述べた通り、

     while(TRUE){
       SLEEP();
    }

とする。そうするとCPUは割り込みが発生するまで何もせず眠る。割り込みが発生して一旦目覚めてもまたすぐSLEEP()を実行するので再び眠りにつく。つまり初期設定とペリフェラルの起動を終えたら何もしないメインプログラムである。

 次に元々メインループ内でやっていた部分をどう実行するかであるが、今回はタイマーで一定周期で割込みをかけてその処理の中で行う。2400bpsのシリアル通信を使うから1バイト送受するのに4ms必要である。ただし1バイト毎にメインルーチンで処理する必要はないのでとりあえず5ms毎にタイマー割込みをかけて、その割込み処理の中でメインループでやっていた処理を実行することとする(もっと短い時間間隔でも可能)。

PIC16F15325はTimer0~2の3つのタイマーを内蔵しており、各タイマーは各々性格が少し異なっている。ここではシンプルなTimer0を8ビット非同期モードで使って約5ms毎に定期的に割り込みを発生させる


タイマーの入力には31KHz(T=32.26μs)のLFINTOSC発振器を使い、156分周して5ms毎の割込みを発生させる。 ソフト的には次のように記述する。

    T0CON0          = 0x80;     //Enable, 8bit, postscaler=1:1
    T0CON1          = 0x90;     //LFINTOSC, Async, Prescaler=1:1
    TMR0H           = 156;      // tick for every 5ms
    PIR0bits.TMR0IF = OFF;
    PIE0bits.TMR0IE = ON;

割込み処理ルーチンにはタイマー割込みチェックを追加する。タイマーの割込みフラグは自動的にクリヤーされないので処理が終了した後ソフト的にクリヤーする必要がある。

 void __interrupt() isr(){
    if(PIR3bits.RC1IF && PIE3bits.RC1IE) recvISR1();
    if(PIR3bits.TX2IF && PIE3bits.TX2IE) sendISR2();
    if(PIE0bits.TMR0IE && PIR0bits.TMR0IF){
        tickISR();    PIR0bits.TMR0IF = 0;
    }
}

最後にメインループに相当する処理を5ms毎の割込みの中で処理するプログラムを作る(メインループ内から移植する)。

 void tickISR(){
    if((r1state==RxSTOP)&&(nbuf < NBUF)){
        recvUART1(buf[rbufn],BUFLEN);
    }

    if(r1state==RxCMPLT){
        nbuf++;
        rbufn++;  rbufn %= NBUF;    // next buffer
        r1state = RxSTOP;
    }

    if((t2state==TxSTOP) && (nbuf>0)){
        sendUART2(buf[tbufn]);
    }

    if(t2state==TxCMPLT){
        tbufn++; tbufn %= NBUF;
        nbuf--;
        t2state = TxSTOP;
    }

これでプログラムは完全割込みで動き始める。つまりCPUは殆ど眠っておりタイマーの5ms毎、及びEUSARTの割込みが発生したときだけ動くようになる。送信部のPIC16F18326も同じように使えるTimer0を内蔵しており、同じやり方に変更できる。

これを発展させるとOSのカーネルを作る事が出来るが、小さいPICでそこまでやるメリットも無いのでここまでに留めておく。

 

【参考】タイマーについて
 今回タイマーを扱った。PIC16F15325は(
ウオッチドッグタイマーなど特殊なものを除き)3つのタイマーを持っているが、各々性格が少し異なっている。いずれのタイマーも8or16ビットカウンタの前後にプエリスケーラとポストスケーラを持っている。プリスケーラは1/2^n形式の分周を行い、ポストスケーラは1/n形式の分周を行う。

Timer0は最も基本的なタイマーで全てのPICマイコンに実装され、8ビットモード時は設定した周期でフリーランのタイマーとして動作する。16ビットモードの時はタイムアップ毎にカウント値を設定しなおす必要がある。

Timer1は16ビットのタイマー/カウンタでクロックに対するゲート機能を備えておりゲート幅を調整することで複雑な動きが出来るようである。

Timer2は 8ビットのカウンタで、何らかの信号とトリガーとしてワンショット、或いはモノステーブルのタイマーとしても動作できるし、単なるフリーランのタイマーとしても使える

PIC16F18326はもっと多くのタイマーを内蔵しており、Timer0はPIC16F15325のTimer0と同じ、奇数番のタイマーはPIC16F15325のTimer1と、偶数番のタイマーはPIC16F15325のTimer2と似ているが機能が制約されている。例えば偶数番のタイマーはクロックソースがFosc/4に固定されているのでSleep状態では作動しないし、一方奇数番のタイマーはクロックソースやゲート信号の選択肢が4つしかない。

 


2023年3月7日火曜日

赤外線で通信する(その4)ソフトウェア(受信処理)編

受信部は、赤外線で送られてきたデータを赤外受信モジュール(OSRB38C9AA)で電気信号に変換しPIC16F15325のEUSART1で受信する。受信するだけでは何にもならないので受信したデータはEUSART2を使って送信し、これをPCで受けてTeraTermで内容を確認する。

メインプログラムは先ずEUSART1の受信割込みを起動する。EUSART1がデータを1バイト受信し割込みが発生すると受信レジスタから受信データを取出し、受信バッファに格納する。受信バッファが満杯になるか改行コードを受信すると受信を停止し受信完了をメインプログラムに通知する。

メインプログラムは受信状況を監視しており、受信が完了すると受信バッファを送信処理に渡し(これを送信バッファとする)EUSART2の送信割込みを起動する。USART2の送信割込みが発生すると送信バッファからデータを1バイト取出し送信レジスタに書き込む。このとき最後のデータを書き込んだ場合は送信割込みを停止し送信処理完了をメインプログラムに通知する。しかし実際にはこのとき最大2バイトが物理的に未送信で送信完了迄には更に最大8msが必要である事に注意が必要である。

 メインプログラムは3つのバッファを用意し、それらを順に使って処理を行う。つまり最初のバッファの受信が完了すると直ぐ送信に回し、同時に2つ目のバッファで受信処理を起動する。これを全3つのバッファを使って繰り返すことで疑似マルチタスク処理を行う。

送信スピードより受信スピードが速ければいずれバッファが不足し受信の取りこぼしが発生するが、受信赤外線回線は赤外線受信モジュールの特性で休止時間があるため最大効率75%だから今回は送受同速の通信でもデータが溢れることは無い筈である。

次図はPIC16F18326の割り込みロジックで、送信部で使ったPIC16F18326と同じと考えてよい。

受信部では2つのEUSARTを(同時に)使うため複数の要因で割込みが起こるが、割込みが発生した場合、1つのエントリーポイントが入口となるため、割込み処理ではどの要因で割込みが発生した識別してそれぞれ対応する処理を呼び出す必要がある。そのため割込みは次の様にする。

void __interrupt() isr(){    //割込み処理の入り口
    if(PIR1bits.RX1IF && PIE3bits.RC1IE)
                recvISR();  //UART1が受信済で割込み可なら受信処理

    if(PIR1bits.TX2IF && PIE3bits.TX2IE)
                sendISR();  //UART2が送信可
で割込み可なら送信処理
}

今回の様なフロー制御の効かない通信方式の場合、送信より受信を優先して処理するのは通信の基本である。また割込み処理中の割込み(多重割込み)は禁止されるが、送信処理の割込みでも受信データの有無が先にチェックされ、もし受信データが存在すると望まない受信処理が実行される事になるので割込み可かどうかのチェックは必須である(最初はこれが無くて上手く行かなかった)。

割り込み処理では次のような状態(state)を導入する。RxSTOPは待機状態、RxBUSYは受信中、RxCMPLTは受信完了を表す。

 typedef enum{ RxSTOP, RxBUSY, RxCMPLT }    rxstate;
 

作業領域は次の様にする。

rxstate     r1state = RxSTOP;   // RX1 state
char        *r1bufp;            // RX Buffer
int         r1buf_siz;          // remaining RX buffer size

上位プログラムは受信部がRxIDLEにある時受信を起動する事が出来る。この時パラメータとして受信データを格納するバッファの場所とその大きさを指定する。そして状態を受信中(RxBUSY)とし、割込みを許可する。

 void recvUART1(char *buf, int siz){
    r1bufp      = buf;
    r1buf_siz   = siz;
    r1state     = RxBUSY;
    PIE3bits.RC1IE  = ON;
}

データが受信され割込みが起こると次の受信処理が実行される。

 void recvISR1(void){
    byte st = RC1STA;            // read status 1st
    char c  = RC1REG;            // read data
    if(st & 0x02){               // if overrun error
        RC1STAbits.CREN = OFF;     // clear CREN bit once
        RC1STAbits.CREN = ON;
    }
    *r1bufp++ = (st & 0x06) ? '?' : c; //if any error chg char to '?'
    r1buf_siz--;
    if((r1buf_siz<=0)||(c=='\n')){  // completion
        PIE3bits.RC1IE  = OFF;
        *r1bufp = 0;
        r1state = RxCMPLT;
        LED1(OFF);
    }
}
先ず受信状態(RxBUSY)である事を確認した後、受信データと受信ステータスを取り込み、エラーがあれば然るべく回復処理を行い、正常であればバッファにデータを格納する(エラーの場合は'?'を入れる)。もし改行を受信するかバッファが満杯になれば割り込みを停止しバッファに文字列の終端(0)を書き込み、状態を受信完了(RxCMPLT)として上位プログラムに知らせる。

プログラムを頻繁にいじってPICに書き込むが、外から中身が分からないのでスタート時にバージョン等を出力するようにした。次は先に受信部を起動し、次に送信部を起動したときのTeraTermの表示である。


次の写真は赤外通信状態にある送信部(左)、受信部(右)である。受信部では4つのLEDで内部状態を示すようにしてデバッグを行った。

    LED1 - 受信がBUSY状態にある
   
LED2 - 割り込み処理を実施中
   
LED3 - 2つ以上のバッファに受信済データが入っている
   
LED4 - 送信がBUSY状態にある


  参考までに3月7日時点のソースコード(約300行)を以下に示す。

 /*
 * File:   main.c
 * Author: MYcrosLip
 * Created on 2023/02/24
 *
 * Recieve Message on IR using interrupt
 *
 * * Programmed for PIC16F15325
 */
/*
 * PIC16F15325 pin configuration
 * PIN & PORT Allocation
 *  5:RC5 - RX1 in
 *  6:RC4 - Tx1 oout
 *  2:RA5 - Rx2 in
 *  3:RA4 - Tx2 out
 * 10:RC0 - LED1
 *  9:RC1 - LED2
 *  8:RC2 - LED3
 *  7:RC3 - LED4
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>

#define _XTAL_FREQ 32000000

// config word 1
#pragma config RSTOSC   = HFINT32   //** Power-up Default Value for COSC
#pragma config FEXTOSC  = OFF       // FEXTOSC External Oscillator Mode Selection
#pragma config FCMEN    = OFF       // Fail-Safe Clock Monitor Enable
#pragma config CSWEN    = ON        // Clock Switch Enable
#pragma config CLKOUTEN = OFF       // Clock Out Enable
#pragma config PWRTE = OFF  // Power-up Timer Enable
#pragma config WDTE  = OFF  //** Watchdog Timer Enable
#pragma config LVP = OFF    //** Low Voltage Programming
#pragma config CP  = OFF    // Program Memory Code Protection

#define MYNAME          "IR-Rx-Intrpt-Basic"
#define VERSION         " 0.03"
#define DATE            " 2023/03/07"
#define COPYRIGHT1      " Copyright(C)2023"
#define COPYRIGHT2      " by MYcrosLip"
#define NL              "\n"

#define NBLINK          2       // startup blink sign

#define BUFLEN          30      // Buffer Length in byte
#define NBUF            3       // Number of Buffer

#define     ON      1
#define     OFF     0

typedef unsigned char         byte;
typedef unsigned int          uint;
typedef enum{TRUE=1, FALSE=0} boolean;


// Syntax Sugar; Dialect Adaptation Codes
#define RX1PPS          RX1DTPPS
#define RX2PPS          RX2DTPPS

// LED Macros
#define LED4(x)         LATCbits.LATC3 = x
#define LED3(x)         LATCbits.LATC2 = x
#define LED2(x)         LATCbits.LATC1 = x
#define LED1(x)         LATCbits.LATC0 = x

typedef enum{ RxSTOP, RxBUSY, RxCMPLT }    rxstate;
typedef enum{ TxSTOP, TxBUSY, TxCMPLT }    txstate;

// PROTOTYPES
void Init_Clk(void);
void Initialize(void);
void blink(int);
void LEDs(byte);

void __interrupt() isr(void);
void resetUART1(void);
void recvUART1(char*, int);
void recvISR1(void);
void resetUART2(void);
void sendUART2(char*);
void sendISR2(void);

//========================================================
// Interrupt Dispatcher
//========================================================
void __interrupt() isr(){
    LED2(ON);
    if(PIR3bits.RC1IF && PIE3bits.RC1IE) recvISR1();
    if(PIR3bits.TX2IF && PIE3bits.TX2IE) sendISR2();
    LED2(OFF);
}

//========================================================
//      UART1 Handler
//========================================================
rxstate     r1state = RxSTOP;   // RX1 state
char        *r1bufp;            // RX Buffer
int         r1buf_siz;          // remaining RX buffer size
txstate     t1state = TxSTOP;   // TX1 state
char        *t1bufp;            // TX Buffer

void resetUART1(){
    byte    b;
    while(PIR3bits.RC1IF) b = RC1REG;
    RC1STAbits.CREN = OFF;     // clear CREN bit once
    RC1STAbits.CREN = ON;
    r1state = RxSTOP;
}
//========================================================
void recvUART1(char *buf, int siz){
    r1bufp      = buf;
    r1buf_siz   = siz;
    r1state     = RxBUSY;
    LED1(ON);
    PIE3bits.RC1IE  = ON;
}
//========================================================
void recvISR1(void){
    byte st = RC1STA;            // read status 1st
    char c  = RC1REG;            // read data
    if(st & 0x02){               // if overrun error
        RC1STAbits.CREN = OFF;     // clear CREN bit once
        RC1STAbits.CREN = ON;
    }
    *r1bufp++ = (st & 0x06) ? '?' : c ; //if any error chg char to '?'
    r1buf_siz--;
    if((r1buf_siz<=0)||(c=='\n')){  // completion
        PIE3bits.RC1IE  = OFF;
        *r1bufp = 0;
        r1state = RxCMPLT;
        LED1(OFF);
    }
}

//========================================================
//      UART2 Handler
//========================================================
rxstate     r2state = RxSTOP;   // RX2 state
char        *r2bufp;            // Recieve Buffer
int         r2buf_siz;          // remaining buffer size
txstate     t2state = TxSTOP;   // TX2 state
char        *t2bufp;            // Send Buffer

void resetUART2(){
    byte    b;
    while(PIR3bits.RC2IF) b = RC2REG;
    RC2STAbits.CREN = OFF;     // clear CREN bit once
    RC2STAbits.CREN = ON;
    t2state = TxSTOP;
}
//========================================================
// Initiator
void sendUART2(char *bufr){
    t2bufp            = bufr;
    t2state           = TxBUSY;
    LED4(ON);
    PIE3bits.TX2IE   = ON;
}
//========================================================
// Interrupt Service
void sendISR2(void){
    TX2REG = *t2bufp++;
    if(*t2bufp == 0){
        LED4(OFF);      
        PIE3bits.TX2IE = OFF;   // Dsiable Interrupt
        t2state = TxCMPLT;      // Mission Completed
    }
}

//=======================================================
// CONSTANTS
static char id[]   = NL NL MYNAME VERSION DATE COPYRIGHT1 COPYRIGHT2 NL;

// VARIABLES
char    buf[NBUF][BUFLEN+1];
int     rbufn = 0, tbufn = 0;   // recieve buffer & send buffer
int     nbuf = 0;               // number of filled buffers

//========================================================
// MAIN
//
int main() {
    __delay_ms(1000);   // 1 sec delay
    Init_Clk();
#undef  _XTAL_FREQ  
#define _XTAL_FREQ   8000000     //chg Fosc to 8MHz dor __delay_ms()

    Initialize();
    blink(NBLINK);      // Startup Salute
    
    // Reset serial ports
    resetUART1();
    resetUART2();
    
    rbufn = 0; tbufn = 0; nbuf = 0;

    PIE3            = 0;
    INTCONbits.PEIE = ON;
    INTCONbits.GIE  = ON;
    
    sendUART2(id);
    while(t2state!=TxCMPLT){};
    t2state = TxSTOP;
    
//MAIN LOOP
    while(TRUE){
        if((r1state==RxSTOP)&&(nbuf < NBUF)){
            recvUART1(buf[rbufn],BUFLEN);
        }
        
        if(r1state==RxCMPLT){
            nbuf++;
            rbufn++;  rbufn %= NBUF;    // next buffer
            r1state = RxSTOP;
        }
        
        LED3( nbuf>1 );
        
        if((t2state==TxSTOP) && (nbuf>0)){
            sendUART2(buf[tbufn]);
        }
        
        if(t2state==TxCMPLT){
            tbufn++; tbufn %= NBUF;
            nbuf--;
            t2state = TxSTOP;
        }
    }
    return (EXIT_SUCCESS);
}

void Init_Clk(){
// OSC: HFINTOSC=16MHz, Fosc=8MHz
    OSCCON1     = 0x61;     // New ClockSource is HIFNTOSC, Div by 2
    OSCEN       = 0x40;     // HFINTOSC OSC Manual Req Enable
    OSCFRQ      = 0x05;     // HFINTOSC is set to 16MHz
}

void Initialize(void){
// PIN-Periphral Selection
    // 5:RC5 - RX1 in
    TRISCbits.TRISC5 = 1;   // set Cbit5 to intput
    ANSELCbits.ANSC5 = 0;   // set Cbit5 to digital
    RX1PPS          = 0x15; // RC5 EUSART1
    // 6:RC4 - Tx1 oout
    TRISCbits.TRISC4 = 0;   // set Cbit4 to output
    ANSELCbits.ANSC4 = 0;   // set Cbit4 to digital
    RC4PPS          = 0x0F;  // TX1 EUSART

    // 2:RA5 - Rx2 in
    TRISAbits.TRISA5 = 1;   // set Abit5 to intput
    ANSELAbits.ANSA5 = 0;   // set Abit5 to digital
    RX2PPS          = 0x05; // RA5 EUSART2
    // 3:RA4 - Tx2 out
    TRISAbits.TRISA4 = 0;   // set Abit4 to output
    ANSELAbits.ANSA4 = 0;   // set Abit4 to digital
    RA4PPS          = 0x11;  // TX2 UASRT2

    //10:RC0 - LED1
    TRISCbits.TRISC0 = 0;   // set Cbit0 to output
    ANSELCbits.ANSC0 = 0;   // set Cbit0 to digital
    // 9:RC1 - LED2
    TRISCbits.TRISC1 = 0;   // set Cbit1 to output
    ANSELCbits.ANSC1 = 0;   // set Cbit1 to digital
    // 9:RC1 - LED3
    TRISCbits.TRISC2 = 0;   // set Cbit2 to output
    ANSELCbits.ANSC2 = 0;   // set Cbit2 to digital
    // 7:RC3 - LED4
    TRISCbits.TRISC3 = 0;   // set Cbit3 to output
    ANSELCbits.ANSC3 = 0;   // set Cbit3 to digital
    //
//EUART1 Tx : Fosc=8MHz
    TX1STA      = 0x24;     // Async mode, Tx enable, high speed
    RC1STA      = 0x90;     //  
    // Baud Rate Generator
    BAUD1CON    = 0x08;     // BRG16
    SP1BRGH     = 0x03;     // 1200: 1666(682) 2400:832(340) on SYNC=0,BRGH=1,BRG16=1
    SP1BRGL     = 0x40;
    
//EUART2 Tx : Fosc=8MHz
    TX2STA      = 0x24;     // Async mode, Tx enable, high speed
    RC2STA      = 0x90;     //  
    // Baud Rate Generator
    BAUD2CON    = 0x08;     // BRG16
    SP2BRGH     = 0x03;     // 1200: 1666(682) 2400:832(340) on SYNC=0,BRGH=1,BRG16=1
    SP2BRGL     = 0x40;
}

void blink(int n){
    for(int i=n; i>0; i--){
        LEDs(ON);   __delay_ms(300);
        LEDs(OFF);  __delay_ms(300);
    }
    __delay_ms(300);
}

void LEDs(byte f){
    LED1(f);    LED2(f);   LED3(f);    LED4(f);
}


 


 

 

 

 

2023年3月3日金曜日

赤外線で通信する(その3)ソフトウェア(送信処理)編

送信部、受信部の機能が確認出来たところでソフトウェアに挑む。

これまでPICやハードウェアの機能確認のために簡単なソフトを組んだが、次はPICの性能を引き出すべく割り込みで処理を行う。次図はPIC16F18326の割り込みロジック。

最初に作るのは送信部だ。

送信部は外部条件に左右されずひたすらデータを送るだけのタスクなので敢えて割り込みを使わなくても作る事が出来る。つまりEUARTの送信バッファが空になるまで待って次のデータを書き込む、ひたすらこれを繰り返せばよい。それだけである。これを割り込みを使うようにするには(PIC16F18326の初期化部分を除いて)次の3つのプロルラムに分ける。

1)起動処理 - 送信データが発生すれば割り込みを起動する。
2)割り込み処理 -
今回使うPIC16デバイスはベクター割り込みが使えないので、割り込みが発生したら先ずその要因を調べ、それがUARTの送信だった場合は 2.5)へ進む。
2.5)データ送信 - データを1バイト送信バッファに書き込む。

3)完了処理 - 送信すべきデータが無くなれば割り込みを停止させ完了をデータ送信を依頼したプログラムに知らせる。

0)作業用データ 

typedef enum{TRUE=1, FALSE=0} boolean;  //真理値型の定義
char        *bufp;             //送信データポインタ
boolean     cmplt = TRUE;          //完了フラグ

1)起動処理

void sendUART(char *buf){
    bufp            = buf;     // バッファポインタをセット
    cmplt           = FALSE;   // 完了フラグをOFF
    PIE1bits.TXIE   = ON;      // TX割り込みON
}

2)割り込み処理

void __interrupt() isr(){
    if(PIR1bits.TXIF) sendISR(); //UART送信可なら送信処理
}

この割り込み処理ルーチンに入る時にGIEはOFFにされ多重の割り込みは起こらない。また割り込み処理ルーチンからリターンするときGIEは自動的にONにされる。この処理時間を調べたが2.5)3)を含めて15~18μs程度であった。

2.5) データ送信 & 3)完了処理

 void sendISR(){
    TX1REG = *bufp++;           // データ1文字送信
    if(*bufp == 0){             // もし最終文字なら
        PIE1bits.TXIE = OFF;    //
TX割り込みOFF
        cmplt = TRUE;           // 完了フラグをON
    }
}

これを呼び出すメインルーチン側は次の様に記述する。

INTCONbits.PEIE = 1;        // ペリフェラル割り込み可
INTCONbits.GIE  = 1;        // グローバル割り込み可

while(1){
    sendUART("Test Data");      // 送信要求(起動)
    while(!cmplt){};            // 完了待ち
    __delay_ms(33);             // 33ms(=25+8)の休止が必要
}

受信モジュールの規格上、sendUART()に渡す文字列(一度に送信できる量)は18文字以下に収め、そして送信完了後は25ms以上の休止時間を設ける必要がある(直後に次の送信がある場合)。ここで注意が必要なのは、ソフトウェア上で送信が完了しても送信バッファには最大2文字分の未送信データが残っており、それらの送出にはその後約8ms必要なので、足して33msの休止時間の確保が必要な事である。

 以上ページを間延びさせたくなかったので細かい部分は省いて骨格だけを書いた。


プログラムを作っている時気付いたが、何故か次の割り込み処理ルーチンのプロトタイプ宣言がコンパイルエラーとなる。

void interrupt isr(void);

xc8コンパイラはvoidやinterruptに文句を言ってくる。まさか文法がANSIからK&Rに戻った訳でもあるまいに。色々調べると、xc8コンパイラのあるバージョン以降は次の書き方をするよう変更になったようで、これに書き換える事でコンパイルはできるようになった。 

void __interrupt() isr(void);

しかしこれは、これまで見た事も無い訳の分からない文法だ。