2020年6月14日日曜日

PIC16F1829でウォッチドックタイマーとスリープ機能を利用して低消費電力なLチカをさせてみた


背景

PIC16F1829とは、Microchip社が販売している集積回路の1つです。
低消費電力を売りにしています。

このマイコンにはスリープ機能があり、ウォッチドックタイマーという内部のタイマーと組み合わせることで、低消費電力モードと起動を繰り返せます。
以前から気になっていて、調べて試したら期待通りに動かせたので、備忘録を兼ねてプログラムの書き方を共有します。

使ったもの


回路

書き込み装置の違いはあるものの、Lチカしたときの回路を利用しました。


また、電源としてPICKit4から3.3Vを供給してマイコンを動かしました。

1秒間隔で10ミリ秒LEDを光らせる

プログラムはこちらです。
#define _XTAL_FREQ 500000
#include <xc.h>
#pragma config FOSC=INTOSC, WDTE=ON, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF
#pragma config WRT=OFF, PLLEN=OFF, STVREN=OFF, LVP=OFF

void main(void) {
// REGISTER 10-1: WATCHDOG TIMER CONTROL REGISTER
// 01010 // 1 sec
WDTCONbits.WDTPS = 0b01010;

TRISB7 = 0;
LATB7 = 0;

while(1) {
LATB7 = 1;
__delay_ms(10);
LATB7 = 0;
SLEEP();
}
}

要所を解説します。

WDTEをONにすることで、ウォッチドックタイマーが起動時もスリープ時も有効になります。
スリープ時だけ有効になるモードがあれば良いのですが、無さそうなので常時オンにします。
#pragma config WDTE=ON

WRDTCONのWDTPSを01010にすることで、ウォッチドックタイマーの実行間隔を1秒に設定します。
    WDTCONbits.WDTPS = 0b01010;

設定値と間隔の関係はPIC16F1829のデータシートの「REGISTER 10-1」に示されており、1ミリ秒から256秒の2の乗数秒の間隔を設定できます。


LEDを接続したピンの電位を高くしてLEDを光らせ、10ミリ秒後に電位を低くしてスリープモードに移ります。
    while(1) {
LATB7 = 1;
__delay_ms(10);
LATB7 = 0;
SLEEP();
}

上記のプログラムをマイコンに書き込むと、1秒ごとにLEDが光る装置ができます。


起動時はウォッチドッグタイマーを止め、スリープからの復帰だけに利用する

先ほどのプログラムは起動時もスリープ時もウォッチドッグタイマーを動かしていましたが、動作を制御出来るモートにすることで必要な時だけウォッチドッグタイマーを動かせます。

スリープ中だけウォッチドッグタイマーを有効にするプログラムはこちらです。
#define _XTAL_FREQ 500000
#include <xc.h>
#pragma config FOSC=INTOSC, WDTE=01, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF
#pragma config WRT=OFF, PLLEN=OFF, STVREN=OFF, LVP=OFF

void main(void) {
WDTCONbits.SWDTEN = 0;
// 01010 // 1 sec
WDTCONbits.WDTPS = 0b01010;

TRISB7 = 0;
LATB7 = 0;

while(1) {
LATB7 = 1;
__delay_ms(10);
LATB7 = 0;
WDTCONbits.SWDTEN = 1;
SLEEP();
WDTCONbits.SWDTEN = 0;
}
}

常時ウォッチドッグタイマーを利用していたプログラムとの差分を解説します。

WDTEを01とすることで、WDTCONのSWDTENが1の時にウォッチドッグタイマーが動き、0の時は止まる設定になります。
他の設定を知りたい場合はデータシートの「10.2 WDT Operating Modes」を参照してください。
#pragma config WDTE=01

SWDTENでウォッチドッグタイマーを止めたり動かしたり出来るようになったため、sleep呼び出し直前に1にし、復帰後に0にすることで、WDTPSで設定した時間だけsleepさせれます。
    while(1) {
        // ..
WDTCONbits.SWDTEN = 1;
SLEEP();
WDTCONbits.SWDTEN = 0;
}

カウンターはWDTCONbits.SWDTENを0にするとリセットされます。
必要な時だけウォッチドッグタイマーを動かすので、不意のリセットに遭遇する可能性が減って良さそうです。

ウォッチドックタイマーのふるまい

いくつかプログラムを書き換えて試したところウォッチドックタイマーは下記の動作をするようでした。
  • 時間になる前にSLEEPが呼ばれた場合、時間が来た時はSLEEPの後からプログラムが始まる
  • SLEEPが呼ばれる前に時間が来た場合、プログラムがリセットされてmainの先頭からプログラムが始まる
  • 「CLRWDT();」でタイマーをリセットできる
  • WDTCONbits.SWDTENを0にすると、タイマーがリセットされる

想定外のリセットが発生しないように、SWDTENを利用してタイマーの動作を制御するか、処理する内容がWDTPSで設定する時間より長くならないように気をつける必要がありそうでした。

正確な電流量は分からないものの、ウォッチドックタイマーを使わないよりは0.2mAくらい消費電力が減っていた

22Ωのセメント抵抗をシャント抵抗として利用し、Analog Discoveryというオシロスコープでその抵抗の電圧を計測して大まかな電流量を計測してみました。
(セメント抵抗を利用しているのは手元に精度高めの数十Ωの抵抗が無かったため、また、analog discoveryの精度ではnAの電圧測定は難しいことから大まかに違いが分かれば良かったからです。)


ウォッチドックタイマーとスリープを利用した場合、LED点灯3mA、スリープ時0.5mA以下という結果になりました。


比較対象としてウォッチドックタイマーとスリープを使わない下記のプログラムを書き込んで計測してみました。
#define _XTAL_FREQ 500000
#include <xc.h>
#pragma config FOSC=INTOSC, WDTE=OFF, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF
#pragma config WRT=OFF, PLLEN=OFF, STVREN=OFF, LVP=OFF

void main(void) {
TRISB7 = 0;
LATB7 = 0;

while(1) {
LATB7 = 1;
__delay_ms(10);
LATB7 = 0;
__delay_ms(1000);
}
}

ウォッチドックタイマーとスリープを使わない場合は、LED点灯時3mA、待機時0.7〜0.2mAという結果になりました。


ウォッチドックタイマーとスリープを組み合わせた方が、0.2mAくらい低消費電力になると分かりました。

まとめ

ウォッチドックタイマーを利用してPIC16F1829で低消費電力なLチカをさせることで、LEDを点灯していないときの電流量を0.2mAほど減らせました。

参考

データシート PIC16(L)F1825/9 (PDF)
PRAGMA will not write configuration bit to Configuration Registers

変更履歴

2020.08.01
WDTPSの設定をWDTCONに対してではなくWDTCONbits.WDTPSに対して行う記述に変更しました。
WDTE=01として、WDTCONbits.SWDTENでウォッチドッグタイマーを動かしたり止めたりするプログラムを追加しました。
参考リンクを追加しました。

4 件のコメント :

Unknown さんのコメント...

SLEEPモード及びウォッチドックを参考にさせて頂きました。
ウォッチドックには、ソフトの設定でON/OFF出来る機能があるので
その機能を使用することで起動時の消費電流をさらに減らすことが出来るかもしれません。
※ウォッチドックで使用する電流は、2.5~25uAほどなので誤差ほどの電流かもしれませんが...

低電流を測定できる機器がないので私は独自の方法で測定してみました。
私の方法でどこまで正しいか分かりませんが、PIC16F16346をSLEEPさせた場合では、消費電流 約18uAと結果が出ました。
結果としては、SLEEPモードで消費電流が大幅に減少することが確認出来ました。
SLEEP時に動作する機能をさらに落とすと1uA近くまで落とせそうです。

Asuki Kono さんのコメント...

情報が役に立ったようで良かったです。

> ウォッチドックには、ソフトの設定でON/OFF出来る機能がある
そうなのですね。
どのように設定できるかまで共有していただけると嬉しいです。

Unknown さんのコメント...

ご存じな情報を提供していたら申し訳ございません。
私は、mikroCのコンパイラを使用しているため、MPLABX + XC8コンパイラでの細かな設定が
分かりませんがWDT機能についての詳細を下記に記述いたします。

WDTは、Softwareにてturn on/off出来る機能がついています。
WDT設定のWDTE<1:0>は、2bit分の設定があります。

WDTEを"0b10"と設定することで、Software control modeとなります。
Software control modeにおいて、SWDTENをON/OFFすることでWDTのON/OFFをコントロールすることが可能となります。
※SWDTEN=0の場合に、WDTがCLEARされるはずなのでWDTのカウント値も気にせずに使用可能だと思います。


WDTE<1:0>の設定
00:無効 mode
10:sleep時無効 mode
11:常時有効 mode
01:software control mode※SWDTEN 1:WDT有効/0:WDT無効



以上

Asuki Kono さんのコメント...

> WDTEを"0b10"と設定することで、Software control modeとなります。
その設定は把握していたのですが、記述方法が分からなかったので飛ばしていました。

調べた所、pragma configでのbitの設定は0bを付けなくて良いことが分かったため、下記の記述で有効化できました。

#pragma config WDTE=01

その状態でWDTCONbits.SWDTENを切り替えることで、1でウォッチドッグタイマーが動き、0で止まるプログラムを書けました。

> SWDTEN=0の場合に、WDTがCLEARされるはずなのでWDTのカウント値も気にせずに使用可能だと思います。
これは把握してませんでした。
試してみると確かのそのように動作しました。
情報ありがとうございます。

記事にこれらの情報を追記しました。
有用な情報の提供ありがとうございます。