PIC32で省電力モードを使用する

Use idle/sleep mode on PIC32
 
例えばテレビリモコンのように、電池はつなぎっぱなしにするけれど、ボタンを押したときくらいしかCPUを動かす必要のない機器の作成では、普段は省電力モードにしておくことでバッテリーをより長く保つことができます。

PIC32では主な省電力モードに「CPUは止めるが周辺モジュールは維持する」アイドルモードと「CPUと、I/Oピンの入出力とWDT/RTCC/ADC/UART以外のモジュールを止める」スリープモードがあります(PIC32MZなどの上位モデルにはDMAモジュールを動かすときだけスリープからアイドルモードに移行するドリームモードなんてのもある)。もちろん、アイドルよりもスリープの方が低電力ですが、それだけ制限もあるので、使いたい機能は何であるかを念頭に置いて選択しましょう。

待機モードに移行するにはアセンブラ命令で「asm volatile ("wait");」を実行するだけです。この命令が実行されると、割り込み処理(割り込み優先度がCPU優先度より低いと何も起きないが、CPU優先度の初期状態は0なので特別気にする必要はない)によって復帰するまでこの命令にとどまり、復帰後はプログラムにおける次の行から再開されます。

特別な指定がなければ、待機命令でアイドルモードに移行します。以下のサンプルコードでは、初期設定の後にアイドル状態になり、タクトスイッチを押して割り込み処理を発生させると、先にタイマー1を有効にした上で、プログラムに復帰します。
idle.c
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#pragma config FNOSC = PRIPLL // 発信源 = 主外部発振器
#pragma config POSCMOD = XT // 主発振の方法 = 外部高精度発振器を使う
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2
#pragma config FPBDIV = DIV_2 //周辺モジュールクロック倍数 : x1/2

int count = 0, toggle = 0;
void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1(void) 
{
    IFS0bits.T1IF = 0; // 割り込みフラグをリセット

    count++;
    if(count >= 10){
        toggle = !toggle;
        LATBbits.LATB0 = toggle; // RB0のスイッチを切り替える      
        count = 0;
    }
}

void InitTimer1()
{
    T1CONbits.ON = 0;    // 設定のため、タイマー1を無効にする
    TMR1 = 0;            // タイマー1をリセット
     
    // 100ms周期で割り込み <- 500ns(1 / 20MHz) * 256 * 7812(0x1E84) = 100ms
    T1CONbits.TCKPS = 3; // プリスケーラ : x1/256
    PR1 = 0x1E84;        // カウンター
        
    IPC1bits.T1IS = 3;   // 副優先度(0-3)
    IPC1bits.T1IP = 4;   // 割り込み優先度(0-7)
    IFS0bits.T1IF = 0;   // 割り込みフラグをリセット
    IEC0bits.T1IE = 1;   // タイマー1の割り込みを許可
}

void __ISR(_CHANGE_NOTICE_VECTOR, IPL4AUTO) OnChanged(void) 
{
    IFS1bits.CNBIF = 0;
    T1CONbits.ON = 1;    // タイマー1有効
}

void InitB1Input()
{
    TRISBbits.TRISB1 = 1; // B1を入力にする
    CNCONBbits.ON = 1;    // ポートBの状態変化検出モジュールを使用
    CNENBbits.CNIEB1 = 1; // B1の検出を有効
    CNPUBbits.CNPUB1 = 1; // プルアップ抵抗を使用   
    
    IPC8bits.CNIP = 4;    // 割り込み優先度(0-7)
    IPC8bits.CNIS = 3;    // 副優先度(0-3)
    IFS1bits.CNBIF = 0;   // 割り込みフラグをリセット
    IEC1bits.CNBIE = 1;   // 状態変化Bの割り込みを許可
    
    PORTBbits.RB1 = 1;  // B1の初期状態を設定
}

int main(void)
{
    ANSELB = 0x00;          // ポートBをすべてデジタルモードにする  
    TRISB = 0;              // ポートBをすべて出力にする
    PORTB = 0;
    
    InitB1Input();
    InitTimer1();
        
    INTCONbits.MVEC = 1;            // マルチベクタ割り込みを有効にする      
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効  

    asm volatile ("wait");   // wait命令でアイドルモードに移行
        
    while(1) ;
}
先ほど述べたように、スリープモードでもI/O入力は受け付けるので、今度はスリープモードで動かしてみましょう。スリープモードにするには「OSCCONbits.SLPEN = 1;」と前置きしてからwait命令を実行するだけです。

スリープモードに移行する際に、あらかじめクロック発振源を内蔵発振器にすると、より低い電力の維持と素早い復帰を見込めます。ただし、これは誤動作の元になるため、通常はクロックの切り替えはできません。プログラム実行中でも任意に変更できるようにするには、「#pragma config FCKSM = CSECMD」で切り替えの許可を指示し、プログラム内で「OSCCONbits.CLKLOCK = 0」でロックを解除してから「OSCCONbits.NOSC」の値を変更します。
sleep.c
#include <p32xxxx.h>
#include <sys/attribs.h>

#pragma config FCKSM = CSECMD // クロックの切り替えを許可する

int main(void)
{
    ANSELB = 0x00;          // ポートBをすべてデジタルモードにする  
    TRISB = 0;              // ポートBをすべて出力にする
    PORTB = 0;
    
    InitB1Input();
    InitTimer1();
        
    INTCONbits.MVEC = 1;            // マルチベクタ割り込みを有効にする      
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効  
    
    OSCCONbits.CLKLOCK = 0;  // クロック切り替えのロックを解除
    OSCCONbits.NOSC = 0b101; //低電力内蔵発振器(約31KHzで動作)に切り替える
    OSCCONbits.SLPEN = 1;    // スリープモードを使用
    asm volatile ("wait");   // wait命令でスリープモードに移行
    
    OSCCONbits.NOSC = 0b011; // XTPLLモードに戻す
    OSCCONbits.CLKLOCK = 1;  // クロック切り替えのロックを有効
        
    while(1) ;
}
このサンプルコードは抜粋ですので、idle.cのコードに追加してください。

2018/10/02