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

0 件のコメント:

コメントを投稿