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)へ続く