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