LoginSignup
0
0

More than 1 year has passed since last update.

AE-TYBLE16 with AndroidIDEを徹底的に省電力化する(10uAでLチカ)

Posted at

目的

先日、AE-TYBLE16をArduinoIDEで使用するための記事を書きました。
今回はAE-TYBLE16(NRF51822)の醍醐味である省電力性能を追求していきます。

今回使用する回路構成

image.png

前回の回路からUARTを削除し、代わりにデバッグ用LEDを追加しています。
回路図右側、GND端子の部分に100オームの抵抗を挟み、その両端の電圧をオシロスコープで測定することで、消費電流を測定します。

iBeacon、Lチカの電力測定

iBeacon

まずはお試しで、BLEPeriheralのiBeaconサンプルを書き込み、オシロスコープの波形を見ました。
100msごとに信号を送信するため、その瞬間に電流が跳ね上がっているのがわかります。
非送信時は500mV=5mA、送信時は1500mV=15mA 程度消費しているようです。
image.png

意外なんですけど、GNDで1500mV消費しているということはマイコン部分で3300mV-1500mVの1800mVしかかかっていないことになるのですが、
問題なく動作しているようです。

Lチカ

void LEDsetup()
{
  pinMode(21, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(5, OUTPUT);
}
// 4bitの数値を出力します
void LEDout(int value)
{
  digitalWrite(21, (value >> 0) & 1);
  digitalWrite(4, (value >> 1) & 1);
  digitalWrite(6, (value >> 2) & 1);
  digitalWrite(5, (value >> 3) & 1);
}

void setup()
{
  LEDsetup();
}
void loop()
{
  LEDout(0b1111);//LED全点灯
  delay(500);
  LEDout(0b0000);//LED全消灯
  delay(500);
}  

このコードを動作したところ、CPUの消費電流は5mA程度でした。
※LEDの消費電流は今回測定しません。

image.png
データシートによると、CPUの消費電流が4.1mAと書いてあるので妥当な値ですね。

本番:電流削減するために悪戦苦闘

スリープを試してみる

CPUがON状態だとそれだけで4mA消費してしまうので、まずはスリープを試してみます。
スリープから復帰する方法は後回しにして、とりあえずスリープ時の電流を極限まで減らしてみます。

①愚直にWaitForEvent命令挿入

void setup()
{
  LEDsetup();
  LEDout(0b1111);//LED全点灯
  delay(500);
  LEDout(0b0000);//LED全消灯
  delay(500);
  __WFE(); //①スリープ命令(wait for event)挿入
}

これで1.4mAまで下がりました。

②デバッグモードを解除する

参考:BLE Nano (nRF51822) でどうしても 1mA 以上電流食うぞというとき

ST-LINKで書き込んだ直後はデバッグモードで動いているようです。
この状態だと追加で電流を消費するので、再起動(STLinkを抜き差し)してリセットをかけます。
これで0.5mAまで下がりました。

③HFCLKを停止する

残りの500uAですが、おそらく32Mhzのcrystal oscillatorによるものだと思います。
スリープ時にはHFCLKを止めて、LFCLK(32kHz)のみにしてしまいましょう。

この部分を削るためには、Arduino部分のソースも書き換える必要があります。

C:\Users\kamelong\AppData\Local\Arduino15\packages\sandeepmistry\hardware\nRF5\0.7.0\cores\nRF5\main.cpp
を書き換えます。

int main( void )
{
  // init();
  // initVariant();
  // delay(1);
  setup();
  for (;;)
  {
    loop();
    // if (serialEventRun) serialEventRun();
  }
  return 0;
}

init()でクロックの指定をしているのでコメントアウトします。
ここをコメントアウトすると、delay関数が使えなくなってしまうので、delayを使う可能性がある場所もコメントアウトしましょう。

setup()関数でもdelayを使っています。
こちらは代わりに自作のでDelay関数を作ってそちらに置き換えます。

void LEDsetup()
{
  pinMode(21, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(5, OUTPUT);
}
// 4bitの数値を出力します
void LEDout(int value)
{
  digitalWrite(21, (value >> 0) & 1);
  digitalWrite(4, (value >> 1) & 1);
  digitalWrite(6, (value >> 2) & 1);
  digitalWrite(5, (value >> 3) & 1);
}

// delay関数が使えなくなるので
//だいたい指定した時間待ってくれる自作のdelay
void myDelay(int ms){
  for(int i=0;i<ms;i++){
    //1300は適当
    for(volatile int j=0;j<1300;j++){

    }
  }

}

void setup()
{
  LEDsetup();
  LEDout(0b1111);//LED全点灯
 myDelay(500);
  LEDout(0b0000);//LED全消灯
  myDelay(500);
  __WFE(); //①スリープ命令(wait for event)挿入
}
void loop()
{
    //loopには入らないはずなので、LEDは再点灯しないはず
  LEDout(0b1111);//LED全点灯
  
}  

これで実行すると、
およそ1mVまで電圧が下がりました!!
10uA相当まで下がっています

ノイズに埋もれている感じだったので、10kΩの抵抗に差し替えて実行してみます。
マイコンが起動するまではGNDピンとGNDをジャンパ線でショートさせ、スリープしたらジャンパ線を外し10kΩに電流を流します。
その結果は、10kΩで20mV つまり2uAまで消費電流が下がっていることがわかりました。

delayで使用するクロックを内部RCクロックに変更する

なぜHFCLKが有効になるのか調べたところ、
Arduinoで使用するLFCLKがクロックがHFCLKから生成されているからでした。
ArduinoIDE側からこの設定は変更することができます。
image.png
設定からRC Oscillatorを選択すると、HFCLKを使わずにArduinoのdelay関数を動かすことができるようになります。
main.cppをもとに戻してから、コンパイルしなおすと、スリープ時に3uA程度の消費電流になりました。
データシートより、I_RC32kは0.8uA消費するので、妥当な値だと思います。

スリープからの復帰方法の模索

スリープから復帰するためには、割り込みイベントを起こす必要があります。

今回はLチカをしたいので、タイマー割り込みを使用します。
しかし、nRF51のRTCのうちRTC0はSoftDeviceで、RTC1はArduinoのdelay関数で使用しており、空いているRTCがありません。

SoftDevice側を犠牲にするわけにはいきませんので、必然的にArduinoのdelay関数で使われるRTC1を使用することになります。
delay関数も取っておきたいので、Arudino側の関数と整合性を取りながら改造することになります(難しい…)。

ArduinoでのRTC1の使われ方

wiring.cのinit関数内を見ると、

  NRF_RTC1->PRESCALER = 0;
  NRF_RTC1->INTENSET = RTC_INTENSET_OVRFLW_Msk;
  NRF_RTC1->EVTENSET = RTC_EVTEN_OVRFLW_Msk;
  NRF_RTC1->TASKS_START = 1;

とあります。この記述と、nRF51_RM_v3.0データシートと照らし合わせると、LFCLKの分周はなし(PRESCALER = 0)、
overflow時のイベント割り込みのみ許可されています(RTC_INTENSET_OVRFLW_Msk,RTC_EVTEN_OVRFLW_Msk)。

LFCLKは32768Hz=2^15Hzで動いており、カウントレジスタは24bitなので、この設定だと2^9秒=512秒ごとにoverflowイベントが走ります。

次に、delay.cを見ると、

static volatile uint32_t overflows = 0;
uint32_t millis( void )
{
  uint64_t ticks = (uint64_t)((uint64_t)overflows << (uint64_t)24) | (uint64_t)(NRF_RTC1->COUNTER);
  return (ticks * 1000) / 32768;
}

とあります。このticksが32768Hzでカウントアップされる数値ですね。
overflowsがこれまでオーバーフローした回数、NRF_RTC1->COUNTERが現在のカウンターの値です。

overflowの検知は

void RTC1_IRQHandler(void)
{
  NRF_RTC1->EVENTS_OVRFLW = 0;
#if __CORTEX_M == 0x04
    volatile uint32_t dummy = NRF_RTC1->EVENTS_OVRFLW;
    (void)dummy;
#endif
  overflows = (overflows + 1) & 0xff;
}

この部分で行っているようです。
このRTC1_IRQHandlerという関数はアセンブラ(gcc_startup_nrf51.S)で割り込み関数に割り当てられています。

100ms毎に割り込みを発生させる

それではこのタイマーをいじって、100msごとに割り込みを発生させていきます。
方針として、タイマーカウンターの比較一致割り込みを使います。
RTC1のCC0レジスタに3276(32768/10)を入れ、カウンターがこの値になると割り込みが発生するよう、
INTENSETレジスタに0x00010000
EVTENSETレジスタにも0x00010000を入力します

wiring.cのコードは

//wiring.c
  NRF_CLOCK->TASKS_LFCLKSTART = 1UL;
  NRF_RTC1->PRESCALER = 0;
  NRF_RTC1->CC[0]=3276;//100ms秒おき
  //  NRF_RTC1->INTENSET = RTC_INTENSET_OVRFLW_Msk;
  NRF_RTC1->INTENSET =RTC_INTENSET_COMPARE0_Msk;
  //  NRF_RTC1->EVTENSET = RTC_EVTEN_OVRFLW_Msk;
  NRF_RTC1->EVTENSET = RTC_EVTEN_COMPARE0_Msk;

このようになります。

次に、delay.cも修正します。
100msごとにoverflowsがカウントアップされるので、

//delay.c
uint32_t millis( void )
{
  uint64_t ticks = (uint64_t)(uint64_t)overflows*3276 + (uint64_t)(NRF_RTC1->COUNTER);

  return (ticks * 1000) / 32768;
}

RTC1_IRQHandlerは自分のソース側で使用したいので、
削除します。代わりにoverflowsをインクリメントする関数を用意します。

//delay.c
// void RTC1_IRQHandler(void)
// {
//   NRF_RTC1->EVENTS_OVRFLW = 0;
// #if __CORTEX_M == 0x04
//     volatile uint32_t dummy = NRF_RTC1->EVENTS_OVRFLW;
//     (void)dummy;
// #endif
//   overflows = (overflows + 1) & 0xff;
// }
void overflow(){
    overflows = (overflows + 1) ;
}

delay.hに忘れずにoverflow()の宣言を追加しておきましょう。

最後に本体のソースコードに、RTC1_IRQHandlerを追加します。

void LEDsetup()
{
  pinMode(21, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(5, OUTPUT);
}
// 4bitの数値を出力します
void LEDout(int value)
{
  digitalWrite(21, (value >> 0) & 1);
  digitalWrite(4, (value >> 1) & 1);
  digitalWrite(6, (value >> 2) & 1);
  digitalWrite(5, (value >> 3) & 1);
}
void setup()
{
  LEDsetup();
}
void loop()
{
    __WFE(); //①スリープ命令(wait for event)挿入
}  
extern "C" {
int k=0;//LEDのカウントアップ
void RTC1_IRQHandler(void)
{
  NRF_RTC1->TASKS_CLEAR = 1;
  NRF_RTC1->EVENTS_COMPARE[0] = 0;
  overflow();
  k++;
  LEDout(k);
}
}

これで完成です。

書き込むと100msごとにLEDの表示がカウントアップしていきます。

消費電流も測定しました。
使用した抵抗は100オームです。

image.png

オシロスコープで測るとこのような波形が100msごとに繰り返しています。
ピーク時で200mV=2mA程度の消費のようです。
横軸は1メモリ100usなので200us程度で消費電流が
落ちていることがわかります。
積分するとおよそ0.75msmA程度でしょうか
スリープ状態だとおよそ3uA程度の消費電流なので
平均すると、3uA*100ms+750uAms=1050uAms 10uA程度の消費電力になりそうです。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0