Wio LTEの省電力運用についての試行錯誤

Wio LTEの消費電力を抑えるために試行錯誤した結果、
Standbyモードに入れることで10mA未満に抑えることができたので、
そこに至るまでの過程と、Standbyモードの注意点を書いておきます。

# きっかけ
Wio LTEにUSB電圧チェッカーを挟んで、消費電力をチェックしてみたところ、
動作中に最大260mA消費していることが分かりました。
また、センサーをSleepさせ、次のデータ送信のタイミングまで待機させる間にも10mA〜170mA常時消費していました。
USB給電ではなく、バッテリー給電を考えていたため、これではすぐに電池が無くなってしまいます。
よく他のマイコンでは、待機中にSleepモードを利用していたため、
Wio LTEでもSleepモードといった省電力モードがあるか調べることになりました。

# Wio LTEのライブラリで用意されているSleep関数を使ってみる
Wio LTEのWikiページを見たところ、Sleep関数があったので使ってみました。

```c:sleep.ino
#include <WioLTEforArduino.h>

WioLTE Wio;

void setup() {
  Wio.Init();
  SerialUSB.println("sleep");
  Wio.Sleep();
}

void loop() {

}
```

上記のプログラムを実行すると、Sleep関数に入った後は電流が**10mA以下〜50mA**ほどになります。

# プロセッサーのStandbyモードを使う
もっと消費電力を抑えられる方法は無いか探していると、こんな記事を見つけました。
[Wio LTEの本気を引き出す（低消費電力編）](https://technologicaladvance.blog.fc2.com/blog-entry-155.html)
プロセッサー（STM32F405）の低電力モードのStandbyモードを使用すると、1mA以下まで消費電力を抑えることができるそうです。
今までの数字が大きかった分「本当か！？」と疑いつつ、参考にしてやってみることにしました。
結果的には、常時**10mA未満**にまで電流を抑えることができました！
しかし、ここで結構長い時間はまりました……

### 西暦を下2桁しか取って来なくて混乱する
上記サイトのプログラムを実行してみると、シリアルモニタにはこのような文字列が表示されます。
<img width="516" alt="1919年という日付" src="https://qiita-image-store.s3.amazonaws.com/0/256451/36328b0e-b74d-8948-04f7-71d32b14d628.png">
1919年という謎の西暦が出てきました。
これは、STM32F4xxのRTClockライブラリ内のsetTime関数が原因です。
setTime関数は、1900年1月1日0時0分0秒から今日に至るまでに経過したtime_t型の秒数を、
日付の形式である構造体tm型に直してプロセッサーに時刻を設定する関数です。
しかし、RTClock.cppのsetTime関数の中身をよく見てみると……
<img width="696" alt="RTClockcpp.png" src="https://qiita-image-store.s3.amazonaws.com/0/256451/751ad00e-3e37-31dc-ddd6-c7d6cfa96b39.png">
何故かyearを100で割ってしまうため、西暦を下2桁しか保持できません。
現在が2019年なので、2019 - 1900 = 119という数字をyearは持っているのですが、
この関数によって下2桁のみ（＝19）というデータになってしまい、
1900 + 19 = 1919年という奇妙な西暦が生まれてしまうのです。
更に、曜日wdayも、西暦に影響されてズレが生じ、1919年の当時の曜日が算出されてしまいます。

ひたすら過去の時間に復帰しようとするため、RTCのアラーム機能を使って復帰する方法は私は諦めてしまいました。
「こうしたらできたよ！」という方法があれば、後学のために教えていただけると幸いです。

### main関数内に復帰するわけではない
RTClockライブラリに記載してある、setPeriodicWakeup関数で、
待機時間の秒数を指定するようにしました。
先のサイトのStandbyモード部分のコードを抜き出して少し手を加えただけですが、
一応載せておきます。

```c:standby.ino
#include <WioLTEforArduino.h>
#include <RTClock.h>
#include <stdio.h>
#include <time.h>
typedef int IRQn_Type;
#define __NVIC_PRIO_BITS          4
#define __Vendor_SysTickConfig    1
#include <libmaple/libmaple_types.h>
#include <libmaple/usbF4/VCP/core_cm4.h>
#include <libmaple/usbF4/VCP/core_cmInstr.h>
#include <libmaple/pwr.h>
#include <libmaple/bkp.h>

WioLTE Wio;
static RTClock rtc(RTCSEL_LSI);

void setup() {
  // put your setup code here, to run once:
  Wio.Init();
}

void loop() {
  // Standbyモードに入っているか確認
  if (PWR_BASE->CSR & (1UL << PWR_CSR_SBF)) {
    PWR_BASE->CSR |= (1UL << PWR_CR_CSBF);
  }
 
  SerialUSB.println("standby");
  enterStandbyMode(60);　// 60秒後に復帰する
}

void clearWUF(){
  // WakeUp Flagビットをクリア
  PWR_BASE->CR |= (1UL << PWR_CR_CWUF); 
}

// Standbyモードへ移行
void setBitStandbyMode() {
  delay(1000UL);

  // PDDS(Power-Down DeepSleep)ビットをセット
  PWR_BASE->CR |= (1UL << PWR_CR_PDDS);

  // WakeUp Flagビットをクリア
  clearWUF();

  // WakeUp Flagビットがクリアされるまで待つ
  while(PWR_BASE->CSR & (1UL << PWR_CSR_WUF)) {
  }
  
  // Cortex-M4にSLEEPDEEPビットマスクをかける
  SCB->SCR |= (SCB_SCR_SLEEPDEEP_Msk);

  // 割り込み待ち（Standbyモード）
  __WFI();
}

void enterStandbyMode(uint16 sleep_sec){
  rtc.setPeriodicWakeup(sleep_sec); // 待機時間(秒数)をセット

  clearWUF();

  setBitStandbyMode();  // Standbyモードに入る
}
```

しかし、ここでまた問題が発生します。
いつまで経っても、シリアルモニタに復帰後のメッセージが表示されないのです。
しかし、しばらくするとUSB電圧計の電流の値が変化していることに気付きます。
もしや？と思い、setup関数を実行している間にLEDを点けるようにしてみました。
すると、指定した待機時間を経過した後、LEDが光り始めました。

プロセッサーから直接弄った結果、復帰の経路がmain関数内ではなく、
setup関数からやり直す＝再起動をする形になりました。

# まとめ
かなり長時間[データシート](https://static.sparkfun.com/datasheets/Dev/dotNET/DM00031020RM.pdf)と向き合っていましたが、プロセッサーの扱い方を理解する良い機会だったと思います。
新しいデバイスは参考資料も少ないため、既存の資料を大切に読み込むことが大切だと実感しました。