PICの割り込み処理 ( MCC編 )

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どちらも)に割り込み処理を実行する。

mplabx_interrupt001

割り込みを有効にすると、MCCのInterrupt Moduleのリスト内のPin Moduleが連動して有効になる。

mplabx_interrupt002

タイマーを追加して、割り込みを有効にする。

mplabx_interrupt003

MCCのInterrupt Moduleのリストを見ると、やはり連動してTMR0が追加されてEnableになっている。

mplabx_interrupt004

この状態で再度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

ポイントとしては、

  1. interrupt_managerが追加
  2. tmr0が追加
  3. 既存コードにも割り込み処理が追加

されている。

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()がコールされることが確認できる。

mplabx_interrupt005

割り込みのピンが増えると、IOCAF4_SetInterruptHandler()に相当する関数が増えるはずである。