MPLABX IDEのMCCプラグインを使って開発してます。割り込みについての自分メモ。
PICの割り込み処理というのは、一般プログラムのコールバック処理(ボタンを押したとき、データが転送されたときetc)などのある動作が完了したときに自動で関数呼び出しされるアレに似ている。
PICの場合は他のプログラム処理中であっても一時停止して割り込み処理が”横入り”するような感じで実行される。PICに備わっている各種Peripheral機能には大抵割り込み処理が可能になっている。
ちゃんとした記事が以下にあるので、まずはそっちをみる。
https://tool-lab.com/pic-app-18/
MCCを起動して、Generateボタンを押すといくつかファイルが自動で生成されて、コンパイルできる状態になる。なにも設定を変更追加しないで生成されるファイルは以下になる。(一部抜粋)
$ tree -r
.
├── mcc_generated_files
│ ├── pin_manager.h
│ ├── pin_manager.c
│ ├── mcc.h
│ ├── mcc.c
│ ├── device_config.h
│ └── device_config.c
├── main.c
file | ざっくり内容 |
---|---|
pin_manager | ピン制御(High/Low)などを行うマクロが定義 |
mcc | 各種Peripheralを束ねる処理 |
device_config | システムモジュールの設定 |
main | プログラムメインループ |
この状態ではまだ、割り込みの制御は実装されていない状態である。ここで各Peripheralモジュールに用意されている割り込みを有効にしてみる。
入力ピンに変化があったとき(High, Lowどちらも)に割り込み処理を実行する。
割り込みを有効にすると、MCCのInterrupt Module
のリスト内のPin Moduleが連動して有効になる。
タイマーを追加して、割り込みを有効にする。
MCCのInterrupt Module
のリストを見ると、やはり連動してTMR0が追加されてEnableになっている。
この状態で再度Generateを実行して生成されるファイルを見てみると以下の状態になる。
$ tree -r
.
├── mcc_generated_files
│ ├── tmr0.h
│ ├── tmr0.c
│ ├── pin_manager.h
│ ├── pin_manager.c
│ ├── mcc.h
│ ├── mcc.c
│ ├── interrupt_manager.h
│ ├── interrupt_manager.c
│ ├── device_config.h
│ └── device_config.c
├── main.c
ポイントとしては、
- interrupt_managerが追加
- tmr0が追加
- 既存コードにも割り込み処理が追加
されている。
interrupt_manager
ソースとヘッダに記載されている内容をまとめて貼り付け。
#define INTERRUPT_GlobalInterruptEnable() (INTCONbits.GIE = 1)
#define INTERRUPT_GlobalInterruptDisable() (INTCONbits.GIE = 0)
#define INTERRUPT_PeripheralInterruptEnable() (INTCONbits.PEIE = 1)
#define INTERRUPT_PeripheralInterruptDisable() (INTCONbits.PEIE = 0)
void __interrupt() INTERRUPT_InterruptManager (void)
{
// interrupt handler
if(INTCONbits.TMR0IE == 1 && INTCONbits.TMR0IF == 1)
{
TMR0_ISR();
}
else if(INTCONbits.IOCIE == 1 && INTCONbits.IOCIF == 1)
{
PIN_MANAGER_IOC();
}
else
{
//Unhandled Interrupt
}
}
マクロ4本は割り込みの有効無効を切り替えるものになる。main.cの初期処理でINTERRUPT_GlobalInterruptEnable()
とINTERRUPT_PeripheralInterruptEnable()
を有効にしておくと割り込みが機能する。
INTERRUPT_InterruptManager(void)
が、割り込み処理のルート関数になる。何かが発火するとこの関数がコールされるらしい。関数の前に__interrupt()
見慣れない物があるが、これが付与されている関数が割り込み関数になる。
上記の例だと、2.56ms毎(誤差はある)にINTERRUPT_InterruptManager()
がコールされる。TMR0の割り込みフラグがONになっているので、TMR0_ISR()
以下の関数が実行される仕組みになっている。
またStimulusで、RA4ピンをHigh、Lowをトリガーすると、PIN_MANAGER_IOC()
以下の関数が実行されることが、シミュレータでも確認できる。
ちなみにvoid __interrupt() My_Interrupt_Manager(void)
などを複数定義してビルドするとコンパイルエラーになる。複数定義できないらしい。
main.c:77:: error: (1506) multiple interrupt functions (_My_Interrupt_Manager and _INTERRUPT_InterruptManager) defined at interrupt level 1
もしmain.c等でハンドリングしたい場合は、上記をコメントアウトする必要があるが、MCC上で割り込み処理を追加したときに追従する必要があるのでメリットはなさそう。
各Peripheralモジュール用の割り込み関数を設定する関数が用意されるので、そっちで対応したほうが良いと思う。
TMR0の割り込み処理
初期化関数内では、割り込みフラグを有効にして、コールバック関数を設定している。
TMR0_ISR()
はINTERRUPT_InterruptManager()でコールされているTMR0の割り込み関数のルートになる。コールされるたびにTMR0IF
をクリアしてから、カスタム関数が存在していれば追加で関数を実行している。
void TMR0_Initialize(void)
{
:
// Clear Interrupt flag before enabling the interrupt
INTCONbits.TMR0IF = 0;
// Enabling TMR0 interrupt
INTCONbits.TMR0IE = 1;
// Set Default Interrupt Handler
TMR0_SetInterruptHandler(TMR0_DefaultInterruptHandler);
}
void TMR0_ISR(void)
{
// Clear the TMR0 interrupt flag
INTCONbits.TMR0IF = 0;
TMR0 = timer0ReloadVal;
if(TMR0_InterruptHandler)
{
TMR0_InterruptHandler();
}
// add your TMR0 interrupt custom code
}
void TMR0_DefaultInterruptHandler(void){
// add your TMR0 interrupt custom code
// or set custom function using TMR0_SetInterruptHandler()
}
MCC上で設定を変更してGenerateすると、上記の関数も上書きされるので、追加でやりたい処理はココで記述するのはおすすめしない。main.c
などで割り込み関数を定義して設定したほうが良い。
以下は、TMR0がオーバーフローしたときにMy_TMR0_Handler()
がコールされるサンプルになる。TMR0_SetInterruptHandler()
でコールバックを上書きしている。
#include "mcc_generated_files/mcc.h"
void My_TMR0_Handler(void);
void main(void)
{
SYSTEM_Initialize();
TMR0_SetInterruptHandler(My_TMR0_Handler);
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
while (1)
{
// Add your application code
}
}
void My_TMR0_Handler(void){
// timer0 overflow
}
GPIOピンの割り込み処理
pin_manager.c
にGPIOピン関連の割り込み処理が実装されているが、おおむねタイマー0とほぼ似た感じで実装されている。
void PIN_MANAGER_Initialize(void)
{
:
/**
IOCx registers
*/
//interrupt on change for group IOCAF - flag
IOCAFbits.IOCAF4 = 0;
//interrupt on change for group IOCAN - negative
IOCANbits.IOCAN4 = 1;
//interrupt on change for group IOCAP - positive
IOCAPbits.IOCAP4 = 1;
// register default IOC callback functions at runtime; use these methods to register a custom function
IOCAF4_SetInterruptHandler(IOCAF4_DefaultInterruptHandler);
// Enable IOCI interrupt
INTCONbits.IOCIE = 1;
}
void PIN_MANAGER_IOC(void)
{
// interrupt on change for pin IOCAF4
if(IOCAFbits.IOCAF4 == 1)
{
IOCAF4_ISR();
}
}
/**
IOCAF4 Interrupt Service Routine
*/
void IOCAF4_ISR(void) {
// Add custom IOCAF4 code
// Call the interrupt handler for the callback registered at runtime
if(IOCAF4_InterruptHandler)
{
IOCAF4_InterruptHandler();
}
IOCAFbits.IOCAF4 = 0;
}
void IOCAF4_DefaultInterruptHandler(void){
// add your IOCAF4 interrupt custom code
// or set custom function using IOCAF4_SetInterruptHandler()
}
こちらもMCCで設定変更すると上書きされるので、割り込み処理をmain.cで実装してみる。
#include "mcc_generated_files/mcc.h"
void My_TMR0_Handler(void);
void My_RA4_Handler(void);
void main(void)
{
SYSTEM_Initialize();
TMR0_SetInterruptHandler(My_TMR0_Handler);
IOCAF4_SetInterruptHandler(My_RA4_Handler);
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
while (1)
{
// Add your application code
}
}
void My_TMR0_Handler(void){
// any code
uint8_t a = 0;
}
void My_RA4_Handler(void){
// any code
uint8_t a = 1;
}
StimulusでRA4をトグル発火させて、My_RA4_Handler()
がコールされることが確認できる。
割り込みのピンが増えると、IOCAF4_SetInterruptHandler()
に相当する関数が増えるはずである。