PICで電飾 割り込み処理 その1 割り込みとは
電飾と割り込みの関係
模型電飾では、よく徐々に強くなるエンジンノズルの発光とか、窓明かりがちらちらと明るくなったり暗くなったりとかで、LEDの明るさをコントロールする技術が使われます。
PICで明るさのコントロールをするにはPWM(Pulse Width Moduration)制御と呼ばれる方法が必要になります。
それを実現する鍵となるのがタイマ機能による割り込み処理です。
電飾のためのゴールはPWMですが、まず割り込み処理について、作例をお見せしながら説明したいと思います。
やってみようと言う方はできるだけ同じように組んで見ながら確かめてください。ここでお見せする作例はこれまで作ってきた音声回路の使い回しですが、MP3プレーヤーは使わないという方は、その部分は不要なのでここまでの工作で結構です。
LED交互点滅の作例
今回作ったヤマトでは、平常時は翼端灯が交互に点滅しています。そして波動砲やエンジンのボタンが押されると、それらの点灯に仕事が移ります。
実はこのボタンの受付でも、割り込みが処理が活用できます。そこでLEDの交互点滅をしているところでボタンを押すと別のLEDが光る、という作例を作りながら割り込みについて説明したいと思います。
まず2つのLEDをRB6とRB7に繋ぎます。今まで作ってきたブレッドボード上であれば、18列と19列のaの行にLEDの長い方の足を挿せば、それでRB6と7に繋がります(黄色点線)。短い方の足はブレッドボードのマイナスに挿してください。
また、RA01にも波動砲の明かりとしてLEDを付けます。(赤点線)ブレッドボードの工作はこれだけで終わり、次がプログラムです。
(この作例では抵抗入りのLEDを使っていますのでこうして直結できますが、普通のLEDを使う場合はPICの足とLEDの間に抵抗を挟んで下さい。)
割り込み処理のないプログラム
さて、RB6とRB7が1秒おきに交互点滅して、Gボタン(RB3)が押されたら、RA1のLEDを5秒点灯させる、というプログラムにしたいと思ったとします。(まだPWM制御はしません。)
それを実現するプログラムは以下のようになります。(プログラム全体は最後に載せています。)
while(1)
{
if (Event==0)//平常時
{
RB6=1; RB7=0;//RB6点灯、RB7消灯
__delay_ms(1000);//1秒待つ
RB6=0; RB7=1;//RB6消灯、RB7点灯
__delay_ms(1000);//1秒待つ
}
if (Event==1)//イベント時
{
RA1=1;//RA1を点灯
RB6=0; RB7=0;//翼端灯消灯
__delay_ms(5000);//5秒まつ
RA1=0;//RA1を消灯
Event=0;//イベント終了
}
if(RB3==0 && Event==0)//もしRB3が押されていて、イベントが起きていなかったら
{
Event=1;//イベントフラグ立てる
}
}
青の部分が平常時で、翼端灯を1秒毎に点滅させます。翼端灯の交互点滅の部分は、以前LEDの交互点滅を説明したものとほぼ同じです。
赤の部分がイベント発生時で、波動砲に見立てたRA1を5秒発光させます。
そして緑の部分がボタンの受付です。
平常時、つまりEvent==0のとき、PICはRB6を点灯して1秒待ち、RB7を点灯してまた1秒待ち、それからボタンが押されていないか確認しに行って、またRBを点灯しに行きます。
ということは、「ボタンが押されていないかどうか」をPICは2秒に1回しかチェックしていません。
ボタンを押しても、その瞬間にPICが偶然見てくれるのでなければ、見に来てくれるまで最大で2秒、ボタンを押したまま待たないと反応してくれないわけです。
これはちょっと微妙です。いつだってボタンに反応して欲しいですよね。
割り込み処理とは
こんなふうに、ある作業をしているからって他の仕事が完全にお留守になると困る状況は多々あります。そこでPICに
「他のどんな作業をしていても、●秒ごとに1回、これこれの作業をしなさい」
という指示を出す方法があります。PICが今やっている仕事に強制的に別の仕事を割りこませるわけですので、それを割り込み処理といいます。
メイン関数と割り込み関数
割り込み処理をどうやって実現するかですが、まずその前に下掲の今のプログラムを見てください。
黄色字でvoid main(void)とあって、そのすぐ下の{ と、ずっと下の最後の }の間にPICにやらせたい仕事とそのための設定が書かれています。
void main(void)
{
・・・・
}
これをメイン関数といいます。C言語で書くプログラムでは、メイン関数は必ず一つだけです。
電源が入るとPICは一番肝心なメイン関数を見に行って、そこに書かれた内容にとりかかります。他のところには(言われないかぎり)見向きもしません。
「時々ちょっと他のこともやってよ」
と言いたい場合、割り込み関数というものを作って、その中に仕事内容を書きます。割り込み関数はメイン関数と同じような感じなのですが
void __interrupt() XXX(void)
{
・・・・
}
という形式になっていて、割り込ませたい作業を・・・のところに書きます。XXXは関数名なので好きな名前をつけて頂いて結構です。名前の前についている__interrupt() が、このXXXが割り込み関数であることを示します。
(これはXC8コンパイラのVer.2.00以降の書き方で、それ以前はvoid interrupt XXX(void) と書きました。この記事は2016年のものですので、オリジナルは古い表記で作成しています。一応手直ししていますが、もしかすると古い表記が残っているかもしれません。うまく動かないなあというときは記事で void __interrupt() xxx(void) に手直し漏れしていないか見てみてください。2020年5月追記)
今回の例で言えば、さっきのボタン受付の作業を時々やっといて、と指示したいわけなので、緑字の部分をカット&ペーストして、void interrupt XXX(void){ }の括弧の中に入れるわけです。
void interrupt XXX(void)
{
if(RB3==0 && Event==0)//もしRB3が押されていて、イベントが起きていなかったら
{
Event=1;//イベントフラグ立てる
}
}
これをメイン関数の下に置いておきます。(実は位置はどこでもいいので、上でもかまいません。)
放っておけばPICはメイン関数の仕事しかしないわけですが、指示をちゃんとやってやると何秒かに1回、この割り込み関数の作業をしに行きます。
では、この割り込み関数のところに時々行きなさいよ、とPICに指示する方法をどうするか、というところで次回です。
今回のプログラムは下掲のとおりです。ビルドと書き込みをやって、2秒に1度しかボタンを確認してくれないもどかしさを体感してみてください。割り込み関数が機能するプログラム例は次回載せます。
#include <xc.h>
#define _XTAL_FREQ 8000000
//コンフィグ設定
#pragma config FOSC = INTOSC
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config MCLRE = ON
#pragma config CP = OFF
#pragma config CPD = OFF
#pragma config BOREN = ON
#pragma config CLKOUTEN = OFF
#pragma config IESO = OFF
#pragma config FCMEN = OFF
#pragma config WRT = OFF
#pragma config PLLEN = OFF
#pragma config STVREN = ON
#pragma config BORV = HI
#pragma config LVP = OFF
int Event=0;
//メイン関数ここから
void main(void)
{
//特殊レジスタの設定
OSCCON = 0b01110010 ;//内部クロック8MHz
ANSELA = 0b00000000 ;//RAの足をすべてデジタルI/Oとして使う
ANSELB = 0b00000000 ;//RBの足をすべてデジタルI/Oとして使う
OPTION_REG = 0b00000000;//内部プルアップ抵抗を使う
TRISA = 0b00000000 ;//RAの足はすべて出力に使う
TRISB = 0b00001100 ;//RBの足のうちRB2,RB3を入力に使う
WPUB = 0b00001100 ;//RB2,3に内部プルアップ抵抗をつなぐ
PORTA = 0b00000000 ;//RAの初期値はすべてゼロ
PORTB = 0b00000000 ;//RBの初期値はすべてゼロ
while(1)
{
if (Event==0)//平常時
{
RB6=1; RB7=0;//RB6点灯、RB7消灯
__delay_ms(1000); //1秒待つ
RB6=0; RB7=1;//RB6消灯、RB7点灯
__delay_ms(1000);//1秒待つ
}
if (Event==1)//イベント時
{
RA1=1;//RA1を点灯
RB6=0; RB7=0;//翼端灯消灯
__delay_ms(5000);//5秒まつ
RA1=0;//RA1を消灯
Event=0;//イベント終了
}
if(RB3==0 && Event==0)//もしRB3が押されていて、イベントが起きていなかったら
{
Event=1;//イベントフラグ立てる
}
}
}//メイン関数ここまで