計数と割り込み

  1. 計数と割り込み

     ここでは、割り込み機能を利用したスイッチの押した回数を計数する回路を紹介します。計数値の表示には先に紹介した、7素子動的表示回路を利用します。

  2. 回路設計

    1. スイッチ入力とチャタリング

       スイッチは一番簡単な「入力装置」です。ただし、この入力装置は、計算機から見ると少し困る代物です。というのは、「スイッチを1回オン・オフすると1回信号が変化する」、のなら良いのですが、実は1回の切り替えで信号は何度も変化してしまうのです。長い目でみれば一回の変化ですが、短い(マイクロ秒)で見ると、最終的な変化の前に機械的な細かい振動が見えてしまうのです。この現象をチャタリングといいます。

    2. チャタリング防止回路

       チャタリングを回路で防止するには、積分やラッチ回路が利用できます。積分回路は、次のような回路です。スイッチ回路の後の、抵抗とコンデンサが「積分回路」になります。コンデンサは少量の電気を蓄えることができます。

      スイッチがオフのとき、コンデンサは電源から充電されます。スイッチをオンにするとコンデンサは蓄えた電気(電荷)を放電します。従って、OUTの信号は、スイッチを押してもコンデンサ放電が完了するまで、0Vになりません。この間にスイッチの細かいチャタリング(オン・オフ)が起こっても、コンデンサの放電時間に影響を与えるだけで、OUT の信号には影響を与えません(と期待されます)。放電時間の目安は、抵抗の値*コンデンサの容量 で定まり、抵抗が10K、コンデンサが10μF(マイクロファラッドでコンデンサの容量の単位です)のとき、
       10*103*10*10-6=100mS=0.1秒
      です。ただし、時にはとんでもない大規模なチャタリングが発生することがあり、絶対安全な回路ではありません。ここでは、回路によらずプログラミングでチャタリングに対処します。

  3. 割り込み

    1. 割り込みとは

       割り込みとは、プログラム実行中に、外部のハードウエアの信号であらかじめ指定したプログラム(関数)を割り込んで実行する機能です。割り込み関数の実行が完了すると、割り込まれた位置から実行を再開します。
       マウスやキーボードの入力処理、ネットワークやプリンタの印字など、多くの入出力装置は割り込みを利用して、効率的な処理を行うことができます。
      割り込み機能は、実用的な計算機にはすべて組み込まれていますが。しかしながら、一般のプログラムで割り込みを処理することはありません(できません)。割り込みはすべて OS(Windowsなどのシステム) が処理をし、一般のプログラムが勝手に割り込みを制御することは禁止されていますWindowsの場合、新しいハードウエアを導入したとき、その装置の割り込み処理は、「ドライバ」ソフトが実行します。
       C言語のscanf()やウインドウズプログラミングで処理するイベント処理はこの割り込み機能を利用しています。

    2. PICの割り込み処理

       PICでは、この割り込み処理を直接C言語でプログラムできます。PICにも多くの割り込み信号がありますが、ここでは、ポートBのPB0による割り込みを利用します。この割り込みは次の関数を呼び出すことで、RB0端子の信号変化で割り込み処理を実行できます。

       enable_interrupts(INT_EXT);
       enable_interrupts(GLOBAL);

      enable_interrupts(INT_EXT); は、RB0端子の外部割込みを許可する意味です。enable_interrupts( GLOBAL); は割り込み機能そのものを利用することを許可します。この関数呼び出しを行うと、RB0端子の信号が変化による割り込み処理が起こり、以下のように指定された関数(割り込み処理プログラム)を実行します。

      #int_ext
      void ext_isr(){
       //割り込み処理
      }

      先頭の #int_ext が、RB0 端子による割り込み処理関数が始まることを指示します。ext_isr() がRB0による割り込み処理関数の名前です。これで、RB0の端子に接続した信号が変化すると、現在実行中のプログラムを中断して、ext_isr() 関数を実行します。この関数が終了すると、先に中断したプログラムが再開されます。

    3. 割り込みとスタック

       関数呼び出しには、戻り番地を記録するためにハードウエアによるスタック処理が必要です。このスタックは8段までしかありませんから、割り込みを含めた関数の多重呼び出し(関数の中で関数を呼ぶ)は8段に制限されます。

  4. 回路設計

    1. スイッチによる割り込み回路

       ポートBのB0端子の割り込みを行うには、押しボタンスイッチをB0端子に接続します。チャタリング除去はプログラムで行いますから、スイッチに付加する抵抗・コンデンサは不要です。ただし、プログラムではチャタリングを防止できない場合、抵抗・コンデンサを接続してみてください。

    2. 回路設計

      7素子の各素子をRB1,RB2,..,RB7に接続します。RA0,1,2 でトランジスタを利用し、表示する7素子の桁を切替えます。計数ボタンには押しボタンスイッチを利用します。押しボタンスイッチはRB0に接続し、割り込み処理を行います。

  5. プログラム

    1. プログラムの流れ

       main() 関数では、変数を初期化し、割り込みを許可します。繰り返しの中では、st[] の値を表示するだけでst[]の値は変更していません。割り込み処理プログラムで、val の値を増し、その値を st[]  に十進変換しています。

    2. 割り込み処理とチャタリング回避

       関数:ext_isr() で割り込み処理を行います。この関数は、B0端子に接続した信号の1から0への変化で呼び出されます。この、関数の冒頭で、ct が0でないと処理をしないで戻ります。ct は割り込み処理を実行すると 20 に設定し、main() でループを繰り返すたびに1減らします。
       したがって、一度割り込みを行うと、20回のループを行うまで、次の割り込み処理をしません。これで、連続するチャタリング信号を無視しています。
       ただし、チャタリングは押し下げと押し上げ時の双方で発生します。一度割り込み信号(ボタンスイッチ)が0になったら、0でなくなるまでチャタリング信号を無視する必要があります。main() の中で、

       if (input(PIN_B0) == 0 )  ct=20;

      はこの処理を行います。

    3. #use fast_io(b)

       割り込みにからんで、もう一つ注意が必要です。標準処理では、

       output_b( segment_data[st[2]]);

      を行うときに、output_b()関数は、Bポートのすべての端子に出力に設定します。次に input(PIN_B0) を行うとき、自動的にB0端子のみ入力に戻しますが、このとき、割り込みが発生する可能性があります。これは、input(PIN_B0) でB0端子が1になり、output_b() ですべての端子を一度出力に設定するとき0になるためです。
      そこで、

       #use fast_io(b)
      を指定します。これはコンパイラに対して、Bポートに対しては自動的に(余分な)入出力の設定をしないよう通知します。この場合、main() の先頭で行っている、

      set_tris_b(0b00000001);

      が有効になり、第0ビットは常に入力に設定されます。
       set_tris_a(0b11111000); は不要です。output_bit(PIN_A2,1); を行うとき、A2端子は自動的に出力に設定されるからです。Aポートにも、

       #use fast_io(a)
      を設定しset_tris_a(0b11111000); を指定すれば、Aポートへの余分な入出力の切り替え処理を抑えることができます。

    4. ソース

      #include <16f648a.h>
      
      #fuses INTRC_IO,NOWDT,NOLVP,NOMCLR//内部クロック、WDT,LVPなし
      #use delay(CLOCK=4000000) //クロック4MHz
      #use fast_io(b)
      
      
      //b7-b1:7素子接続
      //A2,A1,A0:3,2,1桁点灯
      //ct:チャタリング回避時間
      //st:表示10進数 
      
      signed int ct;
      long val;
      int digit;
      
      int segment_data[]={0x7E,0x0C,0xB6,0x9E,0xCC,0xDA,0xFA,0xE,0xFE,0xCE};
      char st[3]={0,0,0};
      
      #int_ext
      void ext_isr(){
         if(ct == 0){//数字の更新
              val++;
           st[2]=val/100;
           st[1]=val/10-st[2]*10;
           st[0]=val%10;    
           if(val==1000) val=0;
           ct=20;//チャタリング回避
         }        
      }
      
      void main(){
      
        set_tris_b(0b00000001);//Bポート下位4bit出力
        set_tris_a(0b11111000);
        digit=0;
        ct=1;//チャタリング期間
        val=0;//表示する値
        
        enable_interrupts(INT_EXT);
        enable_interrupts(GLOBAL);
        
        while(1){
               
         if(digit== 2){//第3桁の表示
                 output_b( segment_data[st[2]]);
              output_bit(PIN_A2,1);//3桁表示開始
              delay_ms (5);        //表示期間(ミリ秒)
              output_bit(PIN_A2,0);//表示を消す
              delay_us(100);
            }
      
          if(digit== 1){
                 output_b( segment_data[st[1]]);
              output_bit(PIN_A1,1);
              delay_ms (5);
              output_bit(PIN_A1,0);
              delay_us(100);
            }
      
          if(digit == 0){
                 output_b( segment_data[st[0]]);
              output_bit(PIN_A0,1);
              delay_ms (5);
              output_bit(PIN_A0,0);
              delay_us(100);
            }
      
          digit ++;//表示桁を変更
          if(digit ==3 ) digit=0;
          
          if(input(PIN_B0)  == 0 ) {
                  ct=20;//チャタリング回避
              }
          
          if(ct>0) ct--;
        }
      
      }