e-Paperを利用したカレンダーの製作(STM32)　実験編 その2 低消費電力のテスト

# 概要
前回に引き続き、e-Paperを利用したカレンダー製作にあたり、STM32を利用して、低消費電力を実現するための実験を行いました。

# 基礎情報

STM32のスリープモードについて調べてみました。STの公式ドキュメントでもいいのですが、比較的わかりやすくまとめているこちらのサイトなどを参照しました。

https://mischianti.org/stm32-power-saving-sleep-deep-sleep-shutdown-and-power-consumption-5/
https://www.embedic.com/technology/details/stm32f103c8t6-mcu-power-consumption-evaluation-guide-2023?srsltid=AfmBOoqDd9l4BAglg_iVm-Vpm24EgipSyni_bu2_o5GJ_Bi5Sv1CJ66p

簡単に理解した限りでは、以下の3つのモードがあるようです。
資料によって呼び方が微妙に違うようで混乱しますが、以下3つのモードが存在するようです。

1. 「Sleep」  CPUのクロックは停止、周辺機器は動作している状態
2. 「DeepSleep」 または 「Stop」   CPUも周辺機器もOFF状態
3. 「Shutdown」 または 「Standby」  1.8Vドメインも停止する状態のことで、復帰するときはリセットと同様の処理になる。

NO3が最も低消費電力なのでしょうが、復帰する際にすべてを初期化するところからはじまるので、復帰にはかなり時間がかかりそうです。NO2ぐらいでなんとかしたいものですね。


# 関連ライブラリ
本当はSTM32Cubeなどで開発するほうが、いろいろと自由度は高いのでしょうが、Arduinoでどこまでできるか調べてみます。電子ペーパーの描画ライブラリがArduino用なので、なんとかArduinoで進めたいです。

低消費電力をSTM32 Arduinoで実現するためのライブラリですが、こちらにあります。

https://github.com/stm32duino/STM32LowPower

この中に以下の3つのメソッドが用意されています。上のNO1～NO3に相当するようです。

* void sleep(uint32_t ms): enter in sleep mode param ms (optional): number of milliseconds before to exit the mode. The RTC is used in alarm mode to wakeup the chip in ms milliseconds.
* void deepSleep(uint32_t ms): enter in deepSleep mode param ms (optional): number of milliseconds before to exit the mode. The RTC is used in alarm mode to wakeup the chip in ms milliseconds.
* void shutdown(uint32_t ms): enter in shutdown mode param ms (optional): number of milliseconds before to exit the mode. The RTC is used in alarm mode to wakeup the board in ms milliseconds.

# Bluepillの限界とNucleroの利用

当初Bluepillを利用して低消費電力を追求していたのですが、deepsleepに入っても400μAぐらい消費している状態で、原因がよくわかりません。
STのフォーラムなどの回答でも、Bluepillはいろいろは種類があって、品質も一定していないようなので、この際ST純正のNucleroの中の低消費電力向けのLシリーズを利用してみることにしました。

STM32L073RZ 
https://www.st.com/ja/evaluation-tools/nucleo-l073rz.html

ここからは、消費電力を抑えるための手法を調査した結果をまとめて3つ紹介します。


# 消費電力を抑える方法その1 - 低消費電力向けのSTM32を利用する。

やはりNucleroのLシリーズは素晴らしかったです。
以下のような単純にdeepsleepするだけのテストをしてみると、Bluepillでは400μAほどでしたが、Nuclero Lシリーズでは、数μAになっています。

```arduino:main.cpp
#include <Arduino.h>
#include "STM32LowPower.h"

void setup() {
  LowPower.begin();
}

void loop() {
  LowPower.deepSleep(10000);
}

```
![deepSleep時の消費電流](https://camo.elchika.com/7ef4876bda83b9999b5fe4f7538d0437a5b32f7e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f61653938616538622d653833302d343333612d386465652d663865343439316261383733/)


しかもCPUだけの消費電力を測定できるジャンパーがあるので、消費電流測定が非常にやりやすいです。こちらの赤丸の中のJP6のところでCPUのみの消費電力の計測ができます。

![電流測定用ジャンパ](https://camo.elchika.com/4336044092a74d77e0ea9b89aae2f8fd38e4d5fc/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f34653763343131632d333838622d346461612d613864612d653561373839383835376662/)


# 消費電力を抑える方法その2 - GPIOのモード変更

SPIを利用してe-Paperに表示するコードと結合させてテストしてみると、SPIの利用を終了しているにも関わらず、消費電流が70μAほどながれていました。

```arduino:main.cpp
#include <Arduino.h>
#include <SPI.h>
#include "STM32LowPower.h"

void setup() {
  LowPower.begin();
  SPI.begin();
}

void loop() {
  SPI.end();
  LowPower.deepSleep(10000);
}

```
![SPIを終了したにも関わらず、消費電流が多めです。](https://camo.elchika.com/d7844e756f3801a1f2af5040d878335c4aa008a1/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f33636131626338342d356138302d346137392d613935312d396164616130653436643865/)


こちらの記事を参考にして、SPIで利用していたIOピンの状態の問題かなと推測、すべてのピンのモードを入力モードに変更したところ、ほぼ元の値に戻りました。
https://www.yza.jp/blog/2009/11/stm32_stop_mode_leak_power/

```arduino:main.cpp
#include <Arduino.h>
#include <SPI.h>
#include "STM32LowPower.h"

void setup() {
  LowPower.begin();
  SPI.begin();
}

void loop() {
  SPI.end();
  // SPI1として利用しているGPIOをINPUTに変更してみる。
  pinMode(PA4,INPUT);
  pinMode(PA5,INPUT);
  pinMode(PA6,INPUT);
  pinMode(PA7,INPUT);
  LowPower.deepSleep(10000);
}

```

![対策後](https://camo.elchika.com/20176c1dc0c67b4d647e0e36f3c7fa65a3278ad5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f66353739323430322d303362662d343037302d393362392d383137633337653162336431/)


# 消費電力を抑える方法その3 - 動作クロックの低減

スリープ時はいいのですが、通常に処理をしている時にはmAオーダーで電流が流れます、これではコイン電池での稼働はちょっと無理がありますね。

```arduino:main.cpp

void setup() {
}

void loop() {
}

```
空ループ実行のみでも、それなりに電流を消費しています。

![空ループ実行時](https://camo.elchika.com/52adcef5f0e548ac10ff7bf0198e102560f51a56/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f63333835353536342d316565342d346435642d393236382d356337363337643262663933/)


クロック周波数を下げることで、消費電力が下がりそうです。こちらの記事を参考にSTM32Cubeでクロック設定を構成してコードを生成させて、Arduinoに取り込んでみました。

https://hamayan.blog.ss-blog.jp/2021-07-29

STM32CubeMXを起動して、Nuclero L073RZでプロジェクトを作成します。

Clock Configurationのところで、既定だとMSIの4194kHzになっています。
![変更前](https://camo.elchika.com/45d370e4027f2192a170f8559cc6d982ddaacaf7/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f38643838376331322d363535642d343730302d613964312d376264363263343036666261/)

これを以下のように1/4して、524.288 kHzにしてみます。
![変更後](https://camo.elchika.com/ab71a2f54a8e0dbd032114dcd2993f73733fcc5b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f63313639323036632d306138382d346132652d613332342d643832623837616361663463/)

その後Generate Codeして、main.cの中のvoid SystemClock_Config(void)を探して、その関数の内容一式をコピーペーストします。
SystemClock_Config関数はWEAK修飾子がついているので、自動で置き換わる仕組みのようで、明示的に呼び出す必要も無いようです。


```arduino:main.cpp
#include <Arduino.h>

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSICalibrationValue = 0;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_3;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
  PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

void setup() {
}

void loop() {
}

```

これで、170μA程度におちました、もちろん動作も遅くなるのですが、カレンダー用途では、1日1回ぐらいしか動作しないので、許容できるのではと思います。この程度であれば、コイン電池でも稼働できそうですね。

![クロックダウン後の消費電流](https://camo.elchika.com/25dfd6ddd06c5da1526e8a03b501ceac8183ad14/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62376366383337612d373938622d343866612d393832652d6534383166313931653836312f35303464356632642d366433632d346466612d383935352d353262356437346339326335/)