PICマイコンで割り込み処理とスリープモードを組み合わせてみた
最近はPICマイコン(16F1827)と格闘する日々です(*´ω`*)
今日は、割り込み処理とスリープモードを組み合わせたプログラムを書いてみたので公開します。
具体的には、割り込み入力が行われたら(ピンの電圧が立ち上がったら、スリープモードに突入し、もう一度、割り込み入力が行われたらスリープから復帰するようなプログラムです。
私は、このプログラムを書くにあたって、PICマイコンの使い勝手がわからず、結構悩みました。
きっと皆さんの中にも悩んでいる方が居ると思うので、公開します。
なお、PICマイコンは16F1827を想定していますが、他のPICでも似たり寄ったりなので、参考にできるかと思います。
目次
スポンサーリンク
目的
タクトスイッチが押された瞬間に割り込みモードに突入して、PICマイコンがスリープモードに移ります。
スリープモードの状態で、もう一度、タクトスイッチを押すとスリープモードから復帰します。
言ってしまえば、タクトスイッチが電源スイッチそのものになるというわけですね!
こういったプログラムを書きます。
PICマイコンの種類
PICマイコンは16F1827を使用しますが、恐らく、他のPICの種類でも似たようなことができるので、今回書いたプログラムも参考になると思われます。
仕様
PIC16F1827のピンの配置は以下のようになっています。
@16F1827のピンの配置

RB0は、外部信号が送られてくると割り込み処理が行えるようにすることができるので、そういった設定をします。(Interrupt-On-Change)
今回は、立ち上がりを検出するようにプログラムを書きましたので、タクトスイッチはプルダウンで組まれています。
プログラムソース
早速、プログラムソースを貼ります。
// ピンの割り当てについて
// Vdd(不明) - 3.3V
// Vss(不明) - GND
// RB1(入力) - スリープボタン(IOC)
#include <xc.h>
#include <stdbool.h> // bool変数を使うためのヘッダ
// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
// プロトタイプ宣言
bool InitDevice();
// delay_ms(x) のための定義
#define _XTAL_FREQ 8000000
// 割り込み処理
void interrupt ISR(void)
{
// スリープモード(外部信号による割り込み処理)
if(IOCBFbits.IOCBF1 == 1)
{
// チャタリングが終了するまで待機
__delay_ms(20);
// IOC割り込み信号がHIGHであることを確認
if(PORTBbits.RB1 == 1)
{
// ボタンが離されたら先に進む(チャタリング防止)
__delay_ms(20);
while(PORTBbits.RB1);
__delay_ms(20);
/* ↓スリープ前の手続きを書く↓ */
// データの保存やピンの出力オフなどの手続き
/* ↑スリープ前の手続きを書く↑ */
// 割り込みフラッグをFALSE
IOCBFbits.IOCBF1 = 0;
// スリープモードに移る
SLEEP();
NOP();
/* ↓スリープ復帰後の手続きを書く↓ */
// データの呼び出しやピンの入出力など
/* ↑スリープ復帰後の手続きを書く↑ */
// ボタンが離されたら先に進む(チャタリング防止)
__delay_ms(20);
while(PORTBbits.RB1);
__delay_ms(20);
}
}
// 割り込みフラッグをFALSE
IOCBFbits.IOCBF1 = 0;
}
int main(void)
{
// 初期設定を行う
if (!InitDevice())
{
// 失敗したとき
return 0;
}
// 待機
while (1);
return 0;
}
// デバイスの初期設定
bool InitDevice()
{
// クロックを設定
OSCCON = 0b01110010; // クロックを8MHzに設定
// ピンの入出力に設定
TRISA = 0b00000000;
TRISB = 0b00000010;
// すべてのピンをデジタル出力に設定
ANSELA = 0b00000000;
ANSELB = 0b00000000;
// すべてのピンの出力をOFFに変更
PORTA = 0b00000000;
PORTB = 0b00000000;
// 割り込み処理の設定
INTCONbits.GIE = 1; // グローバルな割り込みを有効
INTCONbits.IOCIE = 1; // IOCの割り込みを有効
IOCBPbits.IOCBP1 = 1; // RB1を立ち上がりを検出するIOCピンに設定
return true;
}
/* ↓スリープ前の手続きを書く↓ */
// データの保存やピンの出力オフなどの手続き
/* ↑スリープ前の手続きを書く↑ */
と
/* ↓スリープ復帰後の手続きを書く↓ */
// データの呼び出しやピンの入出力など
/* ↑スリープ復帰後の手続きを書く↑ */
には、それぞれの手続きを書いてください。
スリープモードに突入してもピンの出力などは自動的にオフにならないようです。(データの保存や呼び出しはそのまま引き継がれているかもしません。)
以上がソースコードです。
ソースコードのポイント
ポイントは2つあります。
IOCBFbits.IOCBF1 = 0;(フラグリセット)の位置
PICマイコンの種類によって違うようですが、16F1827では割り込み処理に関する論理回路は以下のようになっています。
@PIC16F1827の割り込み処理に関する論理回路

論理回路にWake-Up (If in Sleep mode) と書いてありますね。つまり、IOCIOFとIOCIEが1であれば、スリープモードから復帰してしまうわけです。
なので、誤って以下のようにフラグをリセットする位置を間違えて書くと、スリープモードから即座に復帰して、スリープモードにできなくなってしまいます。
// スリープモード(外部信号による割り込み処理)
if (IOCBFbits.IOCBF1 == 1)
{
// チャタリングが終了するまで待機
__delay_ms(20);
// IOC割り込み信号がHIGHであることを確認
if (PORTBbits.RB1 == 1)
{
// ボタンが離されたら先に進む(チャタリング防止)
__delay_ms(20);
while (PORTBbits.RB1);
__delay_ms(20);
/* ↓スリープ前の手続きを書く↓ */
// データの保存やピンの出力オフなどの手続き
/* ↑スリープ前の手続きを書く↑ */
// スリープモードに移る
SLEEP();
NOP();
// 割り込みフラッグをFALSE(間違い)
IOCBFbits.IOCBF1 = 0;
/* ↓スリープ復帰後の手続きを書く↓ */
// データの呼び出しやピンの入出力など
/* ↑スリープ復帰後の手続きを書く↑ */
// ボタンが離されたら先に進む(チャタリング防止)
__delay_ms(20);
while (PORTBbits.RB1);
__delay_ms(20);
}
}
上のソースだと、IOCBFbits.IOCBF1 = 0;をSLEEP()の後に呼び出しています。これではすぐにスリープモードから復帰してしまいます。
ボタンを押した瞬間のチャタリング問題の解決
スイッチに関して、PICに限らずマイコンすべてにおいて厄介なのはチャタリング問題です。とても面倒な問題です。(チャタリングについてわからないかたはググってみてください)
ハードウェア側(物理的な回路)では、コンデンサを入れたりシュミットトリガを入れたりして解決することができますが、今回はしていません。今回は、ソフトウェア側(プログラム)で解決しています。
具体的には、チャタリングが終わるのを待つために20ms待機したり、whileを使ってボタンが離されるまで待ったりして解決しています。
41行目~44行目では、チャタリングが終わるのを待ってから、信号がHIGHであれば、先に進むようにプログラムを書いています。
何故、信号がHIGHであるかどうかを判断しているのかというと、チャタリングはスイッチを押した瞬間だけではなく、スイッチを離した瞬間にも起きるからです。
スイッチを離した瞬間にスリープモードに突入してしまっては問題ですよね。そのために、こういった処理をしています。
67行目~70行目は、ボタンが離されるのを確認してから、20ms待機して先に進むようにしています。
これは、復帰のためにスイッチを押してから再びスリープモードに突入するのを回避するためです。
まとめ
マイコンは難しいですね(;´・ω・)
ゲーム開発などではソフトウェア側のことだけを気にすればよかったのですが、マイコンだとハードウェア側のこと(チャタリング)などを考慮しなくてはならないので、とても面倒です(;´・ω・)
ノシ
スポンサーリンク
関連記事