このブログは、サーバー代など、運営費のため広告やアフェリエイトがあります。
大変申し訳ありませんが、ご理解よろしくお願いします。

このブログは、サーバー代など、運営費のため広告やアフェリエイトがあります。
大変申し訳ありませんが、ご理解よろしくお願いします。

CH32V003のスリープ機能(Standby)を試す (スイッチで復帰)

電子工作
スポンサーリンク

こんにちは。今回はCH32V003F4P6のスリープ機能について試していきたいと思います。

この記事は憶測や間違いが多いかもしれません。詳細に知りたい方は、リファレンスマニュアルを参照してください

目標

  • StandBy(deepSleep)機能を使う
  • 電流測定してみる
  • 関数化

Standby機能の消費電流

CH32V003のStandByモードでの消費電流
CH32V003DS0-EN(V1.4)から引用

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

CH32V003DS0-EN(V1.4)から引用

ちなみに、上のやつが、Sleep(通常のSleep)です。結構違いますね。

SleepとStandbyの違い

Sleepはすべてのペリフェラルに電源供給されます。なので、基本的にSleep中でもペリフェラルは動作しています。また、StandByと比べて、復帰時間が非常に速いです。

Standbyはカーネルはもちろん、HSIやHSEなどのオシレーター関連も停止されます。そのため、復帰時間は長いですが、最も消費電流が少ないのが特徴です。

Standbyの実装

PDDSビットとSLEEPDEEPビットをセットし、WFI/WFE命令を動かすと、Standbyに入れるみたいです。

また終了条件も色々ありますが、今回はGPIOに接続してあるボタンを押した瞬間に復帰するようなプログラムにしてみようと思います。(キッチンタイマーでStandbyを使う予定のため)

まずは回路を組みます。

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用
}

実測

ここからは、実際に電流値などを測ります。注意ですが、なぜかデータシートと大きくズレています。

なにか測定にミスがあるのか、よく分かりませんが、そこら辺はご了承ください。

CH32V003F4P6のStandbyモードを確かめるために、簡単にブレッドボードで作った回路の写真

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

上の写真に電流計(テスター)をつけた様子

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

まずは、通常時の電流を調べます。

LEDが点灯しているときのマイコン消費電流

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

LEDが消灯しているときのマイコン消費電流

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

おそらくSleepモード

これがSleepの場合です。(PDDSとDEEPSLEEPビットを0にしてあります)

まあまあの省電力といったところでしょうか。(これは大体データシート通りです)

SW端子をつけっぱなしだったとき

これStandbyモードですが、1.59mA? なんでこんなに消費電力多いんだろう。

と思っていたのですが、どうやらSW端子にデバッグ装置が刺さっていると消費電力が増えるみたいです。

Standbyモードになっている様子

これがStandbyに移行したときっぽいです。はっきりいうと、こんなに消費電流多いの?と思いました。データシートには20uAとか書いてあったんですけどねぇ。

よくわかりません…。まあSleepよりは消費電力は減っていますが…。

実はWH5000だとそんなに小さい電流まで測定できない?って思ったので、500kΩと5V電源で電流測定してみました。

10uAが測定できた様子

バリバリ測定できていますね…。うーんよくわからん。

結論

多分なんかもっと消費電力を抑える方法があるんでしょうね。(すべてのペリフェラルを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にも書きましたが、ヨルクラ(夜のクラゲは泳げない)ってのが結構面白かったです。オリジナルなのに結構良かったです。よかったらぜひ。

参考になったら、コーヒー1杯奢ってくれるとうれしいです
Buy Me A Coffee
電子工作
スポンサーリンク
シェアする
denshi1996をフォローする
スポンサーリンク

コメント

タイトルとURLをコピーしました