こんにちは。今回はCH32V003F4P6のスリープ機能について試していきたいと思います。
この記事は憶測や間違いが多いかもしれません。詳細に知りたい方は、リファレンスマニュアルを参照してください
目標
- StandBy(deepSleep)機能を使う
- 電流測定してみる
- 関数化
Standby機能の消費電流

このような値になっています。ランモードの時は、24MHzで最小でも4mA程度らしいので、かなり電力を削減できます。

ちなみに、上のやつが、Sleep(通常のSleep)です。結構違いますね。
SleepとStandbyの違い
Sleepはすべてのペリフェラルに電源供給されます。なので、基本的にSleep中でもペリフェラルは動作しています。また、StandByと比べて、復帰時間が非常に速いです。
Standbyはカーネルはもちろん、HSIやHSEなどのオシレーター関連も停止されます。そのため、復帰時間は長いですが、最も消費電流が少ないのが特徴です。
Standbyの実装
PDDSビットとSLEEPDEEPビットをセットし、WFI/WFE命令を動かすと、Standbyに入れるみたいです。
また終了条件も色々ありますが、今回はGPIOに接続してあるボタンを押した瞬間に復帰するようなプログラムにしてみようと思います。(キッチンタイマーでStandbyを使う予定のため)
まずは回路を組みます。

今回は、簡単にこんな回路にしました。
プログラムとしては、
- 500ms間隔でLEDが点滅
- 10秒後にStandbyに移行
- ボタンを押したらStandby解除 (また最初から)
みたいな感じで実装してみようと思います。
プログラム
まず、今回使用するペリフェラルを有効にします。
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; //GPIOC 有効
RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; //GPIOD 有効
RCC->APB1PCENR |= RCC_APB1Periph_PWR; //PWR 有効
RCC->APB2PCENR |= RCC_APB2Periph_AFIO; //AFIO 有効次に、PD0のイベント設定を行います。CH32V003マイコンでは、EXTIというペリフェラル(コントローラー)が用意されており、そこで設定したりします。EXTIでは、GPIOが変化したときの割り込みやイベント生成などができるペリフェラルになっています。
このマイコンは、外部端子からの割り込み/イベント生成はEXTI0~EXTI7まであり、それぞれがポート番号 Px0 ~ Px7 に連動しています。(つまりEXTI0であれば、PA0/PB0/PD0ということ)ただし、どれか一つのピンのみ使用できます。例えば、PA0とPB0のイベントを同時に取得することはできません。(説明が下手ですが、PA0 と PB1 とかであれば、EXTIxが違うので、同時に取得できます)
また、EXTI8はPVDイベント(電源電圧の監視用)
EXTI9はAuto-wakeup イベント(AWU)に割り当てられています。ちなみに、AWUというのは、LSI(内部オシレーター 128kHz)で動作するタイマーみたいなものです。
今回は、PD0でスイッチ検出を行うので、EXTI0でイベント生成を行います。
また、PC1はLEDを接続するので、出力設定をしてあげます。
GPIOC->CFGLR |= (0b11 << 4); //PC1 出力
GPIOC->CFGLR &= ~(0b11 << 6); //PC1 プッシュプル出力
GPIOD->CFGLR &= ~(0b11 << 2); //PD0 一旦0埋め
GPIOD->CFGLR |= (0b10 << 2); //PD0 プルアップ/プルダウン入力
GPIOD->OUTDR |= (1 << 0); //PD0 プルアップ
EXTI->EVENR |= EXTI_EVENR_MR0; //Px0 イベント有効化
EXTI->RTENR |= EXTI_RTENR_TR0; //Rising(立ち上がり)でイベント発生
AFIO->EXTICR |= AFIO_EXTICR1_EXTI0_PD; //Px0をPD0に割り当て (イベント設定)次に、Standbyモードに入るための設定をします。
StandbyモードはPWR_CTLRレジスタのPDDSビットを1に
NVIC_SCTLRレジスタのSLEEPDEEPレジスタを1にする必要があります。
//StandByモード
PWR->CTLR &= ~PWR_CTLR_PDDS;
PWR->CTLR |= PWR_CTLR_PDDS;
//SLEEPDEEP
NVIC->SCTLR |= (1 << 2);よくわかりませんが、CH32Vのstandbyモードではこのようになっているので、一応このような表記にしています。(正直意味があるのかはわかりません)
あとは、__WFE(), __WFI()を実行すると、自動的スリープ/スタンバイモードになります。
(なお、__WFE()はイベント用, WFI()は割り込み用です。今回はイベントで解除するので、__WFE()を使用します)
ちょっと憶測になるところ
Timer割り込みなどを使っていると、Sleepに移行しないことがあります。よく分かりませんが、Standbyモードには700μs(ちょっとうろ覚えです)くらい時間が必要らしく、この間に割り込みが発生しているのかもしれません。
対策としては、Timer割り込みを停止(Timerモジュール自体の停止でも可)をするか、SLEEPONEXITビットをSETする方法がありました。ただ、SLEEPONEXITレジスタをONしたままだと、そもそもタイマー割り込みが起きなかったりするので、よくわかりません。(実はSLEEPONEXITってEXTI以外のイベントや割り込みを禁止する設定なんですかね?よくわかりませんが)
サンプルコード
色々実験した末のコードなので、変な場所にコメントがあったりしますが、気にしないでください。
なお、今回はタイマー割り込みを使っています。
#include "ch32v00x.h"
#include "ch32v00x_pwr.h"
void TIM2_interrupt_init(void); //TIM2を割り込みで使うため
void TIM2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); //interrupt関数
volatile unsigned int cnt0 = 0, cnt1 = 0; //1msごとにインクリメント
int main(void) {
RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; //GPIOC 有効
RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; //GPIOD 有効
RCC->APB1PCENR |= RCC_APB1Periph_PWR; //PWR 有効
RCC->APB2PCENR |= RCC_APB2Periph_AFIO; //AFIO 有効
TIM2_interrupt_init(); //TIM2割り込み設定関数
GPIOC->CFGLR |= (0b11 << 4); //PC1 出力
GPIOC->CFGLR &= ~(0b11 << 6); //PC1 プッシュプル出力
GPIOD->CFGLR &= ~(0b11 << 2); //PD0 一旦0埋め
GPIOD->CFGLR |= (0b10 << 2); //PD0 プルアップ/プルダウン入力
GPIOD->OUTDR |= (1 << 0); //PD0 プルアップ
EXTI->EVENR |= EXTI_EVENR_MR0; //Px0 イベント有効化
EXTI->RTENR |= EXTI_RTENR_TR0; //Rising(立ち上がり)でイベント発生
AFIO->EXTICR |= AFIO_EXTICR1_EXTI0_PD; //Px0をPD0に割り当て (イベント設定)
//PFIC->SCTLR |= (1 << 4); //SEVONPEND SET
//NVIC->SCTLR |= (1 << 1); //SLEEPONEXIT(すべての動作が終了したらlow-power modeへ)
//PFIC->SCTLR |= (1 << 5); //SETEVENT
//StandByモード
PWR->CTLR &= ~PWR_CTLR_PDDS;
PWR->CTLR |= PWR_CTLR_PDDS;
//SLEEPDEEP
NVIC->SCTLR |= (1 << 2);
while(1) {
//5s経ったら
if(cnt1 >= 5000) {
cnt1 = 0;
NVIC->SCTLR |= (1 << 1); //SLEEPONEXIT(すべての動作が終了したらlow-power modeへ)
__WFE();
NVIC->SCTLR &= ~(1 << 1);
}
//500ms経ったら
if(cnt0 >= 500) {
cnt0 = 0;
GPIOC->OUTDR ^= (1 << 1); //PC1を反転
}
}
}
//Timer2 割り込み設定 初期化
void TIM2_interrupt_init(void) {
//TIM2有効 (TIM2EN)
RCC->APB1PCENR |= (1 << 0);
//TIM2 イベント設定 (UG)
TIM2->SWEVGR |= (1 << 0);
//aオートリロードプレロード有効(ARPE)
TIM2->CTLR1 |= (1 << 7);
//TIM2 Update割り込み有効(UIE)
TIM2->DMAINTENR |= (1 << 0);
//TIM2プリスケーラ
//TIM2->PSC = 47; //(PLL使用時 @48MHz)
TIM2->PSC = 23; //(a初期状態 @24MHz)
//TIM2->PSC = 7; //@8MHz
//TIM2 オートリロードレジスタ
//(1kHz周期 1ms)
//TIM2->ATRLR = 1000;
TIM2->ATRLR = 1000;
//TIM2 NVIC 割り込み有効
NVIC_EnableIRQ(TIM2_IRQn);
//TIM2カウント有効(CEN)
TIM2->CTLR1 |= (1 << 0);
}
void TIM2_IRQHandler(void) {
// TIM2 割り込み解除(UIF)
TIM2->INTFR &= ~(1 << 0);
cnt0++; //LED点滅用
cnt1++; //StandBy用
}
実測
ここからは、実際に電流値などを測ります。注意ですが、なぜかデータシートと大きくズレています。
なにか測定にミスがあるのか、よく分かりませんが、そこら辺はご了承ください。

こんな感じです。結構簡単な回路かと思います。ちなみに左側に向かっていっている線は、WCH-LinkEに接続されています。(それぞれ5V GND 信号SW)

少し雑ですが、こんな感じでテスター(電流モード)に接続しています。今回使用したテスターはWH5000です。
まずは、通常時の電流を調べます。

LED点灯時です。結構消費しているんですね。

LED消灯時です。ということで、24MHz駆動だと、だいたい4.3mAくらい消費することがわかりました。

これがSleepの場合です。(PDDSとDEEPSLEEPビットを0にしてあります)
まあまあの省電力といったところでしょうか。(これは大体データシート通りです)

これStandbyモードですが、1.59mA? なんでこんなに消費電力多いんだろう。
と思っていたのですが、どうやらSW端子にデバッグ装置が刺さっていると消費電力が増えるみたいです。

これがStandbyに移行したときっぽいです。はっきりいうと、こんなに消費電流多いの?と思いました。データシートには20uAとか書いてあったんですけどねぇ。
よくわかりません…。まあSleepよりは消費電力は減っていますが…。
実はWH5000だとそんなに小さい電流まで測定できない?って思ったので、500kΩと5V電源で電流測定してみました。

バリバリ測定できていますね…。うーんよくわからん。
結論
多分なんかもっと消費電力を抑える方法があるんでしょうね。(すべてのペリフェラルをOFFにするとか、もっとなにかあるのかもしれません)
正直これ以上はよくわかりませんし、とりあえず、こんなもんって感じで使います。
関数化
簡単に関数化します。
//Standby実行関数
//Standby_initを動作させること
//Standbyはイベントで解除
void Standby(void){
//StandByモード
PWR->CTLR &= ~PWR_CTLR_PDDS;
PWR->CTLR |= PWR_CTLR_PDDS;
//SLEEPDEEP
NVIC->SCTLR |= (1 << 2);
NVIC->SCTLR |= (1 << 1); //SLEEPONEXIT(すべての動作が終了したらlow-power modeへ)
__WFE();
NVIC->SCTLR &= ~(1 << 1);
}簡単にこんな感じでしょうか。それ以外のプログラム(ペリフェラルの有効化など)はStandby_init()なんて関数を作って書けば良いと思います。
//Standby 初期化関数
//Standbyのピン設定などもここに記載
void Standby_init(void){
RCC->APB1PCENR |= RCC_APB1Periph_PWR; //PWR 有効
RCC->APB2PCENR |= RCC_APB2Periph_AFIO; //AFIO 有効
}まあ正直言ってしまうと、結構雑ですね…。
まあこんな感じでいいか。
おわりに
タイマーキットにStandbyを使いたいので、今回はこんな記事を書いてみました。結構読みにくいかな?(文才ないので…)
そういえば、Xにも書きましたが、ヨルクラ(夜のクラゲは泳げない)ってのが結構面白かったです。オリジナルなのに結構良かったです。よかったらぜひ。


コメント