これから作るロボットは、ADコンバータからなんらかの値を得て、その値によってモーターを動かすことをします。モーターを動かすにはPWM制御という方法を使いますので、タイマーの値を見ながらこれらの仕事をしなければいけません。一人一人勝手なことをする使用人全てを一気に監督するのはかなりきつい仕事で、これを打開するには賢い使用人を用いて、全責任を負わせ、100%の監督をしないことです。一回使用人に仕事を言いつければ、その使用人はその仕事を勝手にやり続けてくれるようにします。プログラミングも同じようなものです。
ここでは、ADコンバーターから値を得て、決まった変数に代入するだけの仕事をやらせます。ですから、監督するmainプログラムの方では、ADコンバータのことは忘れて、その変数の値を見るだけでよくなります。
割り込み処理ルーチン(上の図ではBの処理とかCの処理)は void interrupt Intr(void) { 割り込み処理の中身、BかCを判別する。 : : } のように宣言します。ここではtimerからの割り込みを行ってみます。以下のプログラムをコピーして、貼り付けて使用してください。
「IntLED」プログラム |
---|
// ファイル名 IntLED.c #include <htc.h> __CONFIG(PWRTEN&HS&WDTDIS&UNPROTECT&MCLRDIS&BORDIS&IESODIS&FCMDIS); #define _XTAL_FREQ 20000000 #define PERIOD 218 /* 20MHz/4 = 5MHz TMR0 * prescale*counter/5MHZ = (256-PERIOD) * 256*255/5MHZ = (256-218) * 256*255/5MHZ = 0.4961秒 */ volatile static unsigned char counter=0; void interrupt Intr(void) { // (256-PERIOD)*256/5MHz = 1.9456ms // 約2ms毎に割り込む // 割り込みがtimer0の場合 counter++; TMR0 = PERIOD; T0IF = 0; // 割り込みフラグクリア return; } main() { PORTA = 0; // PORTAを0にする TRISA = 0; // PORTAを出力に設定する ANSEL = 0; // アナログ入力をOFF // Timer0 の設定 OPTION = 0xd7; // FOSC/4 プリスケーラ1/256 // 20MHz/4/256 = 19.531kHz T0IE=1; // Timer0 の割り込みON T0IF=0; // 割り込みフラグクリア GIE=1; // CPUへの割り込みON while(1) { // 0.5秒経ったら反転させる if(counter == 0) { counter++; // ポートが高速on/offするのを防止 PORTA = ~PORTA; } } } |
void interrupt Intr(void)
|
割り込みが起きた場合にこの関数に飛んできます。 この記述はコンパイラの種類によって異なります。 |
OPTION = 0xd7;
|
RABPU=1, INTEDG=1, T0SE=0, PSA=0, PS=111(1/256) タイマーに送られるクロックは20/4/256=19.531kHz と設定しています。他の設定はこの場合関係ありません。下図参照。 |
T0IE=1; T0IF=0; GIE=1; |
Timer0 の割り込みON 割り込みフラグクリア CPUへの割り込みON 下図参照。 |
counter++; TMR0 = PERIOD; T0IF = 0; |
約2msの割合でカウントするカウンター 2ms毎に割り込ませるための設定値(218) 次に割り込ませるために必要 |
以下のプログラムをコピーして、貼り付けてください。
「A/Dコンバーターと割り込み処理」プログラム |
---|
// ファイル名 IntADCdSLED.c // CdSに手をかざすとそれに対応するLEDが点灯する // これをタイマー0割り込みを用いて実現する #include <htc.h> __CONFIG(PWRTEN&HS&WDTDIS&UNPROTECT&MCLRDIS&BORDIS&IESODIS&FCMDIS); #define true 1 #define false 0 #define _XTAL_FREQ 20000000 #define ANS4 0x10 #define ANS5 0x20 #define ANS6 0x40 #define AN4 0x11 #define AN5 0x15 #define AN6 0x19 #define PERIOD 218 /* 20MHz/4 = 5MHz TMR0 * prescale*counter/5MHZ = (256-PERIOD) * 256*256/5MHZ = (256-218) * 256*256/5MHZ = 0.4981秒 */ typedef unsigned char byte; volatile static byte counter=0; byte LeftCdS, CenterCdS, RightCdS; byte ADch=0; byte ConvEndFlag; byte ADC[3]={AN4,AN5,AN6}; void interrupt Intr(void) { // (256-PERIOD)*256/5MHz = 1.9456ms // 約2ms毎に割り込む if(T0IF) { // 割り込みがtimer0からか? counter++; TMR0 = PERIOD; T0IF = 0; // 割り込みフラグクリア return; } if(ADIF) { // 割り込みがADコンバーターからか? if(ADch==0) LeftCdS = ADRESH; else if(ADch==1) CenterCdS = ADRESH; else if(ADch==2) RightCdS = ADRESH; ConvEndFlag=true; // データの更新あり ADIF = 0; // 割り込みフラグクリア } } main() { unsigned int threshold=32; // 周囲の明るさによって値を調整する // 明るいときは大きくする PORTA = 0; // PORTAを0にする TRISA = 0; // PORTAを出力に設定する ANSEL = ANS4 | ANS5 | ANS6; // アナログ入力をAN4-6をON ADCON0 = AN4; // ADFM=0(左詰) ADon OPTION = 0xd3; // FOSC/4 プリスケーラ1/16 PORTC = 0; // PORTCを0にする TRISC = 0x0f; // PORTC下4bit入力、上4bit出力 // Timer0 の設定 OPTION = 0xd7; // FOSC/4 プリスケーラ1/256 T0IE=1; // Timer0 の割り込みON T0IF=0; // 割り込みフラグクリア // ADコンバーターの設定 ADIE = 1; // ADコンバータの割り込み許可 ADIF = 0; // 割り込みフラグクリア PEIE = 1; // 周辺装置の割り込み許可 GIE = 1; // CPUへの割り込み許可 GODONE = 1; // AD変換スタート // 永久ループ while(true) { if(ConvEndFlag) { // AD変換が終わったらすぐに次のチャンネルの変換を行う ConvEndFlag = false; if(++ADch==3) ADch=0; ADCON0 = ADC[ADch]; // ADチャンネル変更 GODONE = 1; // AD変換スタート } PORTA=0; if(RightCdS<threshold) RA2=1; // 赤 if(CenterCdS<threshold) RA1=1; // 緑 if(LeftCdS<threshold) RA0=1; // 青 } } |
unsigned int threshold=160;
| 暗くなったかどうかを判断する数値。これより小さくなったら暗くなったことになるが、絶対値を用いているので、環境の明るさによって左右される。 |
ADIE = 1; ADIF = 0; PEIE = 1; GIE = 1; GODONE = 1; |
ADコンバータの割り込み許可 割り込みフラグクリア 周辺装置の割り込み許可 CPUへの割り込み許可 AD変換スタート |
if(ADch==0) LeftCdS = ADRESH; else if(ADch==1) CenterCdS = ADRESH; else if(ADch==2) RightCdS = ADRESH; ConvEndFlag=true; ADIF = 0; |
ADchはmainプログラムの方で変換終了毎に0,1,2,0,1,2と繰り返すようになっています。 変換値は10bit精度で左詰です。ここでは細かい精度は必要としないので上位8bitのADRESHのみを利用します。プログラムコードの縮小及び高速化も関係あります。 ADchの値に応じて、LeftCdS,CenterCdS,RightCdSというグローバル変数に代入されます 1回の変換終了をmainに知らせるためにConvEndFlagをtrueにします。 次のAD変換割り込みをさせるためにADIFを0にします。 |
以下のプログラムをコピーして、貼り付けてください。
「A/Dコンバーターと割り込み処理2」プログラム |
---|
// ファイル名 IntADCdSLED2.c // CdSに手をかざすとそれに対応するLEDが点灯する // これをタイマー0割り込みを用いて実現する // 関数にして機能分担化させる // 自動閾値設定 #include <htc.h> __CONFIG(PWRTEN&HS&WDTDIS&UNPROTECT&MCLRDIS&BORDIS&IESODIS&FCMDIS); // マクロ宣言 #define true 1 #define false 0 #define _XTAL_FREQ 20000000 #define ANS4 0x10 #define ANS5 0x20 #define ANS6 0x40 #define AN4 0x11 #define AN5 0x15 #define AN6 0x19 #define PERIOD 218 /* 20MHz/4 = 5MHz TMR0 * prescale*counter/5MHZ = (256-PERIOD) * 256*256/5MHZ = (256-218) * 256*256/5MHZ = 0.4981秒 */ typedef unsigned char byte; // プロトタイプ宣言 void initialize(); byte checkCdS(); void setThreshold(byte th); // グローバル変数の宣言 volatile static byte counter=0; byte LeftCdS, CenterCdS, RightCdS; byte ADch=0; byte ConvEndFlag; byte ADC[3]={AN4,AN5,AN6}; byte threshold[4]; // 割り込み処理ルーチン void interrupt Intr(void) { // 約2ms毎に割り込む if(T0IF) { // 割り込みがtimer0からか? counter++; TMR0 = PERIOD; T0IF = 0; // 割り込みフラグクリア return; } if(ADIF) { // 割り込みがADコンバーターからか? if(ADch==0) LeftCdS = ADRESH; else if(ADch==1) CenterCdS = ADRESH; else if(ADch==2) RightCdS = ADRESH; ConvEndFlag=true; // データの更新あり ADIF = 0; // 割り込みフラグクリア } } main() { initialize(); // 各種初期設定 // 永久ループ while(true) { checkCdS(); // CdSの状態をチェックする } } void initialize() { PORTA = 0; // PORTAを0にする TRISA = 0; // PORTAを出力に設定する ANSEL = ANS4 | ANS5 | ANS6; // アナログ入力をAN4-6をON ADCON0 = AN4; // ADFM=1(右詰) ADon PORTC = 0; // PORTCを0にする TRISC = 0x0f; // PORTC下4bit入力、上4bit出力 // Timer0 の設定 OPTION = 0xd7; // FOSC/4 プリスケーラ1/256 T0IE=1; // Timer0 の割り込みON T0IF=0; // 割り込みフラグクリア setThreshold(65); // 平均の明るさの65%の位置に閾値を設定する // ADコンバーターの設定 ADIE = 1; // ADコンバータの割り込み許可 ADIF = 0; // 割り込みフラグクリア PEIE = 1; // 周辺装置の割り込み許可 GIE = 1; // CPUへの割り込み許可 GODONE = 1; // AD変換スタート } byte checkCdS() { byte status; if(ConvEndFlag) { // AD変換が終わったらすぐに次のチャンネルの変換を行う ConvEndFlag = false; if(++ADch==3) ADch=0; ADCON0 = ADC[ADch]; // ADチャンネル変更 GODONE = 1; // AD変換スタート } PORTA=0; if(RightCdS<threshold[2]) RA2=1; // 赤 if(CenterCdS<threshold[1]) RA1=1; // 緑 if(LeftCdS<threshold[0]) RA0=1; // 青 status = RightCdS<threshold[2]; status += (CenterCdS<threshold[1])?2:0; status += (LeftCdS<threshold[0])?4:0; return status; } void setThreshold(byte th) // 平均の指定%を閾値にする { byte i, j, x, g; int average[3]={0,0,0}; g = GIE; // 割り込み状態の保存 GIE=0; // 割り込みは使わない // 明るいときの値 明るいと大きい値 ADC[0]=AN4; ADC[1]=AN5; ADC[2]=AN6; for(j=0; j<40; j++) { // 平均の明るさを求める for(i=0; i<3; i++) { ADCON0 = ADC[i]; GODONE = 1; while(GODONE); x = ADRESH; average[i] = (average[i]*9+x)/10; } } for(i=0; i<3; i++) threshold[i]=average[i]*th/100; GIE = g; // CPUへの割り込み状態を戻す } |
void setThreshold(byte th) // 平均の指定%を閾値にするの中の計算について。
average[i] = (average[i]*9+x)/10;
|
これは、
average[i] = average[i]*0.9+0.1*x;を整数型で行っています。 完全な平均を計算しているのではなく、過去の最近の値を早い変動分を取り除いて平均らしい値を求めています。パソコンで平均を計算するのならば、データをメモリに入れて後で計算するか、データを全部加算して、その後データ個数で割れば平均は求まりますが、PICのようにメモリに限界がある場合、メモリは多く使えない、全部加算すると変数がオーバーフロー起こすことがあり得ます。従って、それらしい平均を求めるにはこの方法が有効です。 |