ICL7137 (5)

 ICL7137 のセグメント出力をキャプチャするソフトウェアについて述べます。
 表示の変化の途中でキャプチャすることを避けるには、それがセグメント出力の最後の変化であることを確認してから取り込まなければなりません。
 しかし、現在の変化が「最終」であるかどうかは、その時点では分かりません。 そのため、セグメント出力の変化を確認した時点では取り込まず、一定の時間が経過してから取り込むようにします。
 もし現在の変化が最終でない場合、時間経過を待っている間に再度ピン変化割り込みが掛かることになるので、その場合には待機時間を再設定した後に再度待機状態に入るようにします。
 もっと回路寄りの言葉で言うと、ピン変化割り込みのたびに「リトリガラブル・ワンショット・タイマ」(retriggerable one shot timer) をリトリガして、ワンショット時間が完了した時点でセグメント出力をキャプチャします。
 実際にピン変化割り込みのサービス・ルーチン内でポートをトグルしオシロで観測したところ、表示の変化に対して最大 1.5 ms 程の時間幅で、最大 3 回ほどのピン変化割り込みが発生していました。 そこで、ワンショットの時間幅は数十 ms 程度に設定しています。
 PIC16F のタイマと CCP (Capture/Compare/PWM) モジュールの組み合わせでは、ハード的にはワンショットの動作モードは用意されていないので、ソフトと組み合わせてワンショット動作をさせています。
 ピン変化割り込みである IOC (Interrupt On Change) については、次のようなレジスタが関係しています。

  • INTCON の b3 (IOCIE)、b0 (IOCIF)
  • IOCAF
  • IOCAP
  • IOCAN
  • IOCBF
  • IOCBP
  • IOCBN
  • IOCCF
  • IOCCP
  • IOCCN

 INTCON レジスタ内の IOCIE は、IOC 全体の割り込みイネーブル・フラグで、「1」で割り込み可、「0」で割り込み不可です。
 IOC 全体の割り込み不可の状態でも、各ビットのエッジ検出機能は有効です。
 IOC 全体をまとめてのコントロールなので、一部のビットの割り込みは可とする一方、一部のビットのエッジ検出機能を有効にした上で割り込みを不可とすることはできません。
 IOCIF は IOC 割り込みフラグで、「1」で割り込み発生、「0」で割り込みなしを表します。
 ポート A に対しては、割り込みフラグ・レジスタ IOCAF、立ち上がりエッジ割り込みイネーブルレジスタ IOCAP、立ち下がりエッジ割り込みイネーブルレジスタ IOCAN の 3 本のレジスタが用意されています。 ポートのビット位置と、各レジスタのビット位置は対応しています。
 IOCAP と IOCAN の対応するビットが共に「0」の場合は、エッジ検出機能は作動せず、割り込みも発生しません。
 対応するビットが両者とも「1」の場合は、立ち上がりと、立ち下りとの両方のエッジを検出します。
 IOCAF はエッジ検出フラグ・レジスタで、対応するビットが「1」の場合はエッジが検出された、「0」の場合はエッジが検出されなかったことを示します。 このビットは「sticky」で、一度「1」にセットされると、CPU が「0」を書き込んでクリアしない限り、「1」の状態を保持し続けます。
 同様にポートB に対しては IOCBF / IOCBP / IOCBN、ポート C に対しては IOCCF / IOCCP / IOCCN が用意されています。
 PIC 上のソフトウェアは MCC (MPLAB Code Configurator) を利用して作成しましたが、次のような理由で IOC 関係の関数については「枠組み」だけを利用し、中身は書き換えています。 (MPLAB X IDE v3.65 / XC8 v1.43 / MCC v3.36 使用)

  1. IOC 割り込みのエッジ指定が生成されるコードに反映されない
  2. IOC 割り込みサービス・ルーチンでは、使用するエッジ検出フラグ全部について 1 ビットずつ確認するコードが生成される

 2. については、各ビットごとに個別に判断する必要はなく、まとめて割り込みフラグをクリアしています。
 1. についても、ポートの各ビットごとに設定するコードが生成され、それらをいちいち修正していくのも面倒なので、8 ビット・レジスタ全体を書き換えるようにしています。 たとえば、次のようになります。

#define SEG_MASK (0x1f) // bitmask for segment input
#define D1K_MASK (0x20) // bitmask for 1000's digit
#define POL_MASK (0x20) // bitmask for POL input

void PIN_MANAGER_Initialize( void )
{
 . . . . . <中略> . . . . .
// clear IOC flags
  IOCAF = 0x00;
  IOCBF = 0x00;
  IOCCF = 0x00;
// Enable IOC neg. edge interrupt
  IOCAN = (SEG_MASK | D1K_MASK); // 1000's digit, 100's digit
  IOCBN = (SEG_MASK | POL_MASK); // POL, 10's digit
  IOCCN = SEG_MASK; // 1's digit
// Enable IOC pos. edge interrupt
  IOCAP = (SEG_MASK | D1K_MASK); // 1000's digit, 100's digit
  IOCBP = (SEG_MASK | POL_MASK); // POL, 10's digit
  IOCCP = SEG_MASK; // 1's digit
// Enable IOCI interrupt 
  INTCONbits.IOCIE = 1; 
. . . . . <中略> . . . . .
/}
 IOC の割り込みサービス・ルーチンは次のようになります。
>|c|
void PIN_MANAGER_IOC(void)
{   
// re-trigger oneshot
    TMR1_Retrigger();
// clear IOC flags    
    IOCAF = 0x00;
    IOCBF = 0x00;
    IOCCF = 0x00;
}

 TMR1 と CCP1 で構成されたリトリガラブル・ワンショットをリトリガした後に、有無を言わせず IOC 割り込みフラグのすべてのビットをクリアします。
TMR1_Retrigger() は次のように定義しています。

void TMR1_Retrigger( void )
{
    TMR1_StopTimer();
    TMR1_WriteTimer(0x0000); // clear timer1
    TMR1_StartTimer();
}

 CCP の Compare Count の初期設定は次のようにしています。 (TMR1 のクロックは LFINTOSC (31 kHz) を利用しています。)

#define LFINTOSC_FREQ_Hz (31000uL) // LFINTOSC clock freq.
#define MM_DURATION_ms   (50uL) // oneshot pulse duration

void MM_Initialize( void )
{
    TMR1_StopTimer();
    CCP1_SetCompareCount((LFINTOSC_FREQ_Hz * MM_DURATION_ms) / 1000);
} 

 CCP1 割り込み、つまりワンショット完了時の割り込みのサービス・ルーチンは次のようになります。

uint8_t bcd[5];

void CCP1_CompareISR(void)
{
// Clear the CCP1 interrupt flag
    PIR1bits.CCP1IF = 0;
    TMR1_StopTimer();
// capture segment pattern and convert to BCD
    bcd[0] = seg2bcd[SEG_MASK & ~PORTC];  // 1's
    bcd[1] = seg2bcd[SEG_MASK & ~PORTB];  // 10's
    bcd[2] = seg2bcd[SEG_MASK & ~PORTA];  // 100's
    bcd[3] = (0 != (D1K_MASK  & ~PORTA)); // 1000's
    bcd[4] = (0 != (POL_MASK  & ~PORTB)); // minus sign
//  output to USART Tx 
    EUSART_Write(bcd[4] ? '-' : ' '); // minus sign
    EUSART_Write('0' + bcd[3]); // 1000's
    EUSART_Write('0' + bcd[2]); // 100's
    EUSART_Write('0' + bcd[1]); // 10's
    EUSART_Write('0' + bcd[0]); // 1's
    EUSART_Write('\r');  // CR
    EUSART_Write('\xa'); // LF
}

 ワンショット完了の割り込みをクリア、タイマを停止してから、表示数値のセグメント・パターンをキャプチャし、配列 seg2bcd[] を利用して BCD 値に変換して、ただちに ASCII 文字列として EUSART に出力しています。
 EUSART の Tx 出力だけですませるために、新しい表示値を取り込むたびに (通信相手の状況は関知せず) そのまま USART Tx に「垂れ流し」ています。
 I2C を使って外部のマイコンとインターフェースする場合には、PIC16F 側では「最新」の数値を保管しておき、I2C マスターからの「送信要求」に答えて最新の数値を送り返すといった作りになります。