PIC18F26K22でLEDちかちか タイマ割り込み編

 2013-08-22
LEDちかちかは前回やったので、今度はタイマ割り込みを使ってみます。

 
タイマーってなんじゃらほい?って人に簡単に説明をしておくと、・・・簡単には難しいですね。
CPUとは独立して動作するモジュールで、初期設定などはCPUで行います。
「俺はこの後別の用事があるんで、この後100ms経ったら、俺を呼んでくれ」ていうのがタイマーです。
まぁ他にも色々、外部イベントカウンタだったりパルス幅測定だったりPWMだったり、いくつか使い方があるのですが、今回の「インターバルタイマー」の用途に限れば、上記のイメージでだいたいあってる。

この「CPUと独立して動作する」というのが非常に重要で、この間にCPUは別の仕事をすることができるため、このタイマーのようなモジュール(俗に周辺モジュール、ペリフェラルと言ったりします)が沢山あれば、組み合わせ次第でいろいろとできるわけです。

前回のLEDちかちかでは、CPUで「待ち」を行っていました。下記のコードの部分です。
int wait_ms(unsigned int wait){
unsigned int cnt=0;
unsigned int i =0;
for(cnt=0; cnt <= wait ; cnt++){
for(i=0; i <= WAIT_MS; i++){ ; }
}
}

forが二つ重なっていて、実際にはなにもしてないループです。こんなコードでもCPUは律儀に処理をするので、このようなループを沢山書いておくことでCPUを「空回し」させて「待ち」を作っています。
これが一番簡単な「待ち」なので、前回はこれを使いました。
しかしこれには大きなデメリットがあり、その筆頭が「待ちの間はCPUがまったく仕事をしない」ということです。処理が他に何もなければ問題はないのですが、いろいろと機能を詰め込んでゆくといずれ限界が来てしまうと言うわけです。また他にも、「割り込み処理」が入ると待ちの時間が変動してしまうというデメリットもあります。割り込みって何?って人は、「待ちループ中に突然CPUが別の仕事をしに行ってしまい、その後何事もなくループに戻ってくる」と考えて下さい。

これらのデメリットを解決出来るのが「タイマ割り込み」です。インターバルタイマとも言います。
最初にCPUで「500ms毎に俺に声を掛けてくれ」と設定しておいてタイマーを走らせると、500ms毎にCPUに割り込みが入ってCPUは500ms経ったことを知ることが出来るって寸法ですよ。

今回は1Hzの点滅を作りたいので、500ms毎にLEDの点灯・消灯を繰り返す事になります。
500msのタイマー割り込みを設定してやればいいのです! PIC18F26K22にはタイマーが8bitのものが3個、16bitのものが4個搭載されています。が、ユーザマニュアルをよく読んでみると、「8bitのタイマーを二つ使って16bitのタイマーとして使います」なんて書いてある物もあるので実際に使うときはマニュアルを読みましょう。
pic18f2x,4xk22

十分マニュアルとにらめっこして適当なタイマーを選びます。今回はタイマー6、TMR6を使うことにします。今回は特に理由はないです。なんとなくです。
pic18f26k22_tmr6.jpg
とりあえずまずはユーザーマニュアルのブロック図を眺めます。
左の FOSC/4 から、CPUクロック(FOSC)を4分周したクロックが入力されることがわかります。
またその FOSC/4 はPrescalerを通ってからTMR6へ入力されることがわかります。
そしてTMR6の出力は TMR6Output と Postscaler へ行く事が分かります。今回はTMR6Outputは使わないので無視します。
Prescaler, Postscalerは1:1から1:16まで選択可能で、それぞれT6CKPSとT6OUTPSというレジスタで制御されているっぽいということが分かります。
詳細はマニュアルの各章を読んで確認が必要なので、この図から「なんとなく」が分かれば十分です。
Postscalerは「1:1 to 1:16」と書いてあり、その中間が記載されていません。要確認、ですね。まぁ、T6OUTPSが4bitあるようなので、選択肢はそれなりに少なくないっぽい。というのが予想出来ます。

ココで計算しておきます。今回は1Hzの点滅を作りたいのでかなり遅い周期です。
CPUは内蔵OSC16MHzをPLLで4逓倍した64MHzで動作しています。つまりFOSC=64MHzです。
Prescalerに入力されるのはFOSCが4分周された16MHzです。Prescalerは1:16に設定して1MHzまで落とします。
この1MHzの周期でTMR6のレジスタがカウントアップして行きます。TMR6レジスタは8bitなので255まで数えた時点で「一杯になりました」の信号をPostscalerに出力します。これは1MHzを255分周するので3922KHzです。
だいぶ落ちてきました。最後にPostscalerです。ここも1:16にします。結果、245.125Hzの割り込みが発生します。
これ以上は落とせませんので、これ以降はソフトウェアで対応することになります。
割り込みが245.125Hzで入るので、割り込みの回数が245回目で1秒経ったということです。

今回は500ms毎にLEDの状態を変化させるので、245の半分を数えます。
(0.5秒消灯、0.5秒点灯 = 1Hzの点滅、ですね)




という長々とした説明をコードにしたのが下記。

#include

#pragma config FCMEN = OFF // not use Fail-Safe Clock Monitor
#pragma config IESO = OFF // not use Internal-External swithover mode
#pragma config PRICLKEN=ON // Primary Clock is always enabled
#pragma config PLLCFG= ON // PLL is ON
#pragma config FOSC = INTIO67 // INTOSCIO ( use INTOSC )
#pragma config BORV = 250 // Vbor set 2.5V
#pragma config PWRTEN= ON // USE Power-up Timer
#pragma config WDTPS = 32768 // WDT postscaler 1:32768
#pragma config WDTEN = OFF // WDT hardware disabled
#pragma config MCLRE = INTMCLR // use RE3 input
#pragma config P2BMX = PORTC0 // CCP2MUX is on RC0(wtf?)
#pragma config T3CMX = PORTC0 // T3CKI is on RC0(wtf?)
#pragma config HFOFST= OFF // wait for HFINTOSC is stable
#pragma config CCP3MX= PORTC6 // CCP3 in/out is multiplexed with RC6(wtf?)
#pragma config PBADEN= OFF // ANSELB resets to 0(PORTB set as GPIO on reset)
#pragma config DEBUG = OFF // USE RB6,7 as GPIO
#pragma config LVP = ON // Single-Supply ICSP enabled
#pragma config STVREN= ON // Stack Full/Underflow Reset enable
#pragma config CP3 = OFF // NOT USE CodeProtection
#pragma config CP2 = OFF // NOT USE CodeProtection
#pragma config CP1 = OFF // NOT USE CodeProtection
#pragma config CP0 = OFF // NOT USE CodeProtection
#pragma config CPD = OFF // NOT USE DataProtection
#pragma config CPB = OFF // NOT USE BootblockProtection
#pragma config WRT3 = OFF // NOT USE WriteProtection
#pragma config WRT2 = OFF // NOT USE WriteProtection
#pragma config WRT1 = OFF // NOT USE WriteProtection
#pragma config WRT0 = OFF // NOT USE WriteProtection
#pragma config WRTD = OFF // NOT USE WriteDataProtection
#pragma config WRTB = OFF // NOT USE WriteBootblockProtection
#pragma config WRTC = OFF // NOT USE WriteConfigurationregisterProtection

#define WAIT_MS 500
#define TMR6INT 245/2

int sysinit();
int tmrinit();
int tmr6int();
int wait_ms(unsigned int);

unsigned int TMR6INTcnt=0;
unsigned char cnt=0;

void interrupt interhandler(){
if(PIR5bits.TMR6IF==1){
PIR5bits.TMR6IF=0; //flush Interrupt Flag
tmr6int(); //TMR6 Interrupt work
}
}

/*
*
*/
int main() {
sysinit();
tmrinit();

while(1){;}

return (EXIT_SUCCESS);
}

int tmr6int(){
TMR6INTcnt++;
if(TMR6INTcnt>=TMR6INT){
cnt=~(cnt);
PORTA=cnt;
TMR6INTcnt=0;
}
return( EXIT_SUCCESS);
}

int tmrinit(){
//TIMER6(interval interrupt)
// FOSC[64MHz]-->FOSC/4[16MHz]-->Prescaler(1:16)[1MHz]-->
// TMR6=PR6(0xFF)[3.92KHz]-->Postscaler(1:16)[245Hz]-->TMR6Interrupt
T6CONbits.TMR6ON=0; // TMR6 off
T6CONbits.T6OUTPS=0b1111; // 1:16 Postscaler
T6CONbits.T6CKPS=0b11; // 1:16 Prescaler
PR6 =0xFF; // TMR6 period
TMR6=0x00; // flush TMR6
INTCONbits.GIE=1; // Enable Global Interrupt
INTCONbits.PEIE=1; // Enable Peripheral Interrupt
PIE5bits.TMR6IE=1; // Enable TMR6=PR6 match Interrupt
PIR5bits.TMR6IF=0; // flush flag of TMR6 Interrupt

T6CONbits.TMR6ON=1; // TMR6 on

return( EXIT_SUCCESS);
}

int sysinit(){
//setting CLK
OSCCONbits.IRCF = 0b111; // INTOSC 16MHz
OSCCONbits.SCS = 0b00; // USE PLL
OSCTUNEbits.PLLEN = 0b1; // PLL enable

//setting PORTA
PORTA=0x0; //PORTA init
ANSELA=0x0; //NOT USE ANALOGINPUT
CM1CON0=0x0; //NOT USE CMP1
CM2CON0=0x0; //NOT USE CMP2
VREFCON1=0x0; //NOT USE VREF(setting to Vref=Vdd,Vss)
VREFCON2=0x0; //NOT USE VREF
HLVDCON=0x0A; //set default(HLVDL=0b1010, HLVD=3.65V)
SLRCON=0x1F; //set default(PORTA,B,C slew at a limited rate)
SRCON0=0x0; //NOT USE SR latch
SSP1CON1=0x0; //NOT USE SPI
T0CS=0b0; //NOT USE T0CKI as TMR0 CLK source
TRISA=0x0; //PORTA set all output

//setting PORTB
PORTB=0x0; //PORTB init
ANSELB=0x0; //NOT USE ANALOGINPUT
ECCP2AS&=0b1100;//NOT USE CCP2
CCP2CON=0; //NOT USE CCP2
ECCP3AS&=0b1100;//NOT USE CCP3
CCP3CON=0; //NOT USE CCP3
INTCON=0; //DISABLE INT
INTCON2=0; //DISABLE INT
INTCON3=0; //DISABLE INT
IOCB=0; //DISABLE INT
T1GCON=0; //NOT USE TMR1
T3CON=0; //NOT USE TMR3
T5GCON=0; //NOT USE TMR5
TRISB=0; //PORTB set all output
WPUB=0; //DISABLE pullup

//setting PORTC
PORTC=0x0; //PORTC init
ANSELC=0x0; //NOT USE ANALOGINPUT
ECCP1AS&=0b1100;//NOT USE CCP1
CCP1CON=0; //NOT USE CCP1
CTMUCONH=0; //NOT USE CTMU
RCSTA1=0; //Serial port disabled
T1CON=0; //NOT USE TMR1
T3CON=0; //NOT USE TMR3
T3GCON=0; //NOT USE TMR3
T5CON=0; //NOT USE TMR5
TRISC=0; //PORTB set all output

return(EXIT_SUCCESS);
}

int wait_ms(unsigned int wait){
unsigned int cnt=0;
unsigned int i =0;
for(cnt=0; cnt <= wait ; cnt++){
for(i=0; i <= WAIT_MS; i++){ ; }
}
}

今回追加した tmrinit() という関数でTMR6を設定しています。
main関数内では各種設定をした後 while(1){;} という無限ループに入ってしまうのでもうなにもしません。
TMR6から245Hzで割り込みが入るとコード上は void interrupt interhandler() が245Hzで呼び出されることになります。この関数の中で本当にTMR6からの割り込みなのかどうかを TMR6IF を見て判断しています。
目論見通りTMR6からの割り込みだった場合は、 TMR6IF をクリアした後に tmr6int() を呼び出します。
呼び出された tmr6int() では呼び出された回数を数えて、245の半分(#define TMR6INT 245/2)になったらPORTAを反転させる、という動作をしています。

今回、ちょろっとタイマ割り込みを味見しようとしたら、XCコンパイラのC言語の事例が少なくて地味に苦労したので、とりあえず備忘録としていつもより詳しく書いてます。間違いがあったらコメント欄で指摘していただけると私が喜びます。



動画



オシロスコープで確認したところ、ちゃんと1Hz出ていました。
PICの内蔵オシレータは室温で誤差±3%なので高精度を要求する場合は水晶などを使いましょう。
DSC_2130.jpg



 
コメント












管理者にだけ表示を許可する
トラックバック
トラックバックURL:
http://wbbwbb.blog83.fc2.com/tb.php/130-0a5f3e11
≪ トップページへこのページの先頭へ  ≫