LPWAは、低消費電力だと言いながら、そこそこいいお値段のする電流計を手に入れないと消費電力量を計ることができないと思われているかと思います。しかし、数千円レベルの電流電圧計モジュールとM5Stackなどにより簡単に測れる方法があります。
ここでは、M5Stack Basicと電流電圧計モジュール(INA226PRC)を使って、Sigfox無線モジュール(Innovation Farm社のIFS-M01)の消費電力を計測する方法をご紹介します。
IFS-M01ベースの開発キットは、Innovation Farmさんから7月にリリース予定となっていますが、少し先んじて触ってみました。
#用意するもの
機器名称 | 写真 |
---|---|
M5Stack Basic | |
INA226PRC - I2Cディジタル電流・電圧・電力計モジュール | |
ICクリップとリード線(既に結線済みのものをお奨めします) | |
Sigfoxモジュール(Inosensor ES Devkit) |
##Inosensor ES Devkit
Inosensor ES Devkitは、ST社のサブGHz帯トランシーバS2-LPベースのSigfoxモジュールIFS-M01の開発キットです。
ボタンと接点x2を搭載したモデルですので、とりあえずSigfoxを試してみたいという方はは乾電池だけ用意してもらえればボタンや接点入力をトリガにSigfoxメッセージを送信できます。また、エンジニア向けに、STのSDKでも開発できるようになっています。
#電力計モジュール[INA226PRC]
消費電流値の測定は、ストロベリーリナックスのINA226PRCというI2Cディジタル電流・電圧・電力計モジュールを使用します。
INA226PRCの仕様は下記の通りです。
#INA226PRCとSigfoxモジュール、M5Stackとの配線
今回は、Sigfoxモジュールの消費電流を計測したいので、3本のICクリップとショートカット用のリード線を使用し、下図のように配線します。
INA226モジュールの配線は、グランド電位が共通となるハイサイド接続と電源の電位が共通となるローサイド接続があるようですが、推奨されているハイサイド接続で組みました。
ちなみに、端子側は下の写真のようにしています。
完成した配線は下の写真のようになります。(既にM5Stack上に電流値グラフが表示されてしまっていますが、気にしないでください)
#電流・電圧測定プログラム
M5StackとINA226RPCとは、I2Cで通信し電流値、電圧値を計測していくことになりますので、Wireライブラリ(Wire.hをインクルード)を使えば可能です。しかも、INA226から電流電圧値をミリ秒単位でサンプリングし、画面表示するプログラムは、Takehiko ShimojimaさんがGitHubにアップされているので、これを流用させていただくことにしました。
**こちら**です。
私は、SDカードを持っていなかったので、SDカードの書き込み部分をコメントアウトし、Serialで結果を送信するように変えています。サンプルスケッチは下記の通りになりますので、先ほどのGitHubリポジトリから、その他必要なファイルはダウンロードしお使いください。
#include <M5Stack.h>
#include <Wire.h>
#include "INA226PRC.h"
#include "menu.h"
INA226PRC ina226prc;
void beep(int freq, int duration, uint8_t volume);
#define TIMER0 0
hw_timer_t * samplingTimer = NULL;
#define NSAMPLES 3000 // 4ms x 3000 = 12秒
short ampbuf[NSAMPLES];
short voltbuf[NSAMPLES];
int sampling = 4; // サンプリング間隔(ミリ秒)
int startthreshold = 3; // 記録を開始する電流値(ミリA)
Menu menu;
volatile int t0flag;
void IRAM_ATTR onTimer0() {
t0flag = 1;
}
int selectitem(int *candi, int items, int val, char *tail) {
int focused;
for (int i = 0; i < items; i++) {
if (candi[i] == val) {
focused = i;
}
}
M5.Lcd.fillScreen(BLACK);
menu.setMenu("up", "OK", "down");
bool first = true;
while (true) {
bool modified = false;
M5.update();
if (M5.BtnA.wasPressed()) {
focused--;
modified = true;
}
if (M5.BtnC.wasPressed()) {
focused++;
modified = true;
}
if (M5.BtnB.wasPressed()) {
M5.Lcd.fillScreen(BLACK);
return candi[focused];
}
if (first || modified) {
first = false;
beep(1000, 100, 2);
for (int i = 0; i < items; i++) {
M5.Lcd.setCursor(100, 40 + i * 20);
int16_t textcolor = ((focused % items) == i) ? BLACK : WHITE;
int16_t backcolor = ((focused % items) == i) ? WHITE : BLACK;
M5.Lcd.setTextColor(textcolor, backcolor);
M5.Lcd.printf(" %d ", candi[i]);
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.print(tail);
}
}
}
}
void config() {
int focused = 2;
const int nItems = 3;
int thresholds[] = {2, 3, 5, 10, 20};
int samplings[] = {2, 4, 10, 20, 50};
bool first = true;
while (true) {
bool modified = false;
M5.update();
if (M5.BtnA.wasPressed()) {
focused--; // upボタン
modified = true;
}
if (M5.BtnC.wasPressed()) {
focused++; // downボタン
modified = true;
}
if (M5.BtnB.wasPressed()) { // changeボタン
modified = true;
switch (focused % nItems) {
case 0:
startthreshold = selectitem(thresholds, sizeof(thresholds) / sizeof(int), startthreshold, "mA");
break;
case 1:
sampling = selectitem(samplings, sizeof(samplings) / sizeof(int), sampling, "ms");
break;
case 2:
default:
return;
}
}
if (first || modified) { // loop中で文字を書くとスピーカーからノイズが出るようだ
first = false;
beep(1000, 100, 2);
menu.setMenu("up", "GO", "down");
M5.Lcd.setCursor(20, 40);
M5.Lcd.print("Start threshold: ");
if ((focused % nItems) == 0) M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.printf(" %d ", startthreshold);
if ((focused % nItems) == 0) M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.print("mA");
M5.Lcd.setCursor(20, 100);
M5.Lcd.print("Sampling period: ");
if ((focused % nItems) == 1) M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.printf(" %d ", sampling);
if ((focused % nItems) == 1) M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.print("ms");
M5.Lcd.setCursor(20, 160);
if ((focused % nItems) == 2) M5.Lcd.setTextColor(BLACK, WHITE);
M5.Lcd.print(" DONE ");
if ((focused % nItems) == 2) M5.Lcd.setTextColor(WHITE, BLACK);
}
}
}
#define X0 10
#define Y0 220
void drawData(short maxamp) {
M5.Lcd.fillRect(0, 0, 320, 220, BLACK);
maxamp = ((maxamp / 100) + 1) * 100;
for (int i = 0; i < 299; i++) {
int y0 = map(ampbuf[i * 10], 0, maxamp, Y0, 0);
int y1 = map(ampbuf[(i + 1) * 10], 0, maxamp, Y0, 0);
M5.Lcd.drawLine(i + X0, y0, i + 1 + X0, y1, WHITE);
Serial.printf("%d, %d, %d, %d\r\n", ampbuf[i * 10], ampbuf[(i + 1) * 10], y0, y1);
}
M5.Lcd.drawLine(X0, Y0, 310, Y0, WHITE);
M5.Lcd.drawLine(X0, 0, X0, Y0, WHITE);
}
void setup() {
Serial.begin(115200);
M5.begin();
Wire.begin();
ina226prc.begin();
Serial.print("Manufacture ID: ");
Serial.println(ina226prc.readId(), HEX);
M5.Lcd.setTextSize(2);
config();
M5.Lcd.fillScreen(BLACK);
beep(1000, 100, 2);
menu.setMenu("start", "", "");
M5.Lcd.setCursor(20, 100);
M5.Lcd.print("Press A button");
M5.Lcd.setCursor(40, 120);
M5.Lcd.print("to start sampling");
while (true) {
M5.update();
if (M5.BtnA.wasPressed()) break;
}
M5.Lcd.fillScreen(BLACK);
beep(2000, 100, 2);
samplingTimer = timerBegin(TIMER0, 80, true); // 1マイクロ秒のタイマーを初期設定する
timerAttachInterrupt(samplingTimer, &onTimer0, true); // 割り込み処理関数を設定する
timerAlarmWrite(samplingTimer, sampling * 1000, true); // samplingミリ秒のタイマー値を設定する
timerAlarmEnable(samplingTimer); // タイマーを起動する
bool started = false;
int indx = 0;
short maxamp = 0;
M5.Lcd.fillRect(50, 100, 200, 10, BLACK);
while (true) {
t0flag = 0;
while (t0flag == 0) { // タイマー割り込みを待つ
delay(0);
}
short amp = ina226prc.readCurrentReg();
short volt = ina226prc.readVoltageReg();
if (!started) {
// 電流値がしきい値(startthreshold)未満だったら、測定を始めない
if (amp * 0.1 > -(float)startthreshold && amp * 0.1 < (float)startthreshold) {
continue;
}
started = true; // 電流値がしきい値を超えたら測定開始
}
ampbuf[indx] = amp; // 電流値をメモリーに記録する
voltbuf[indx] = volt; // 電圧値をメモリーに記録する
maxamp = max(amp, maxamp);
M5.Lcd.setCursor(100, 100);
M5.Lcd.print(indx * 100 / NSAMPLES); M5.Lcd.print(" %");
if (++indx >= NSAMPLES) { // データー数がサンプル数を超えたら、周期処理を終わる
break;
}
}
timerAlarmDisable(samplingTimer); // タイマーを停止する
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(20, 100);
beep(2000, 400, 2);
Serial.println("time, current(mA), volt(mV)");
for (int i = 0; i < NSAMPLES; i++) {
Serial.printf("%d, %.2f, %.2f\r\n", sampling * i, ampbuf[i] * 0.1, voltbuf[i] * 1.25);
}
menu.setMenu("view", "", "");
M5.Lcd.setCursor(40, 160);
M5.Lcd.print("Press A button");
M5.Lcd.setCursor(40, 180);
M5.Lcd.print("to view data");
while (true) {
M5.update();
if (M5.BtnA.wasPressed()) {
drawData(maxamp);
}
}
}
void loop()
{
}
#消費電力を計算する
Sigfoxでデータ送信した時の消費電流の時間変化の結果が下図のようにでました。
上の消費電流のグラフを見ると、3回のメッセージが送信されていることが見れます。メッセージ送信時の消費電流は約20mA(あれ、公称値より若干低めになったかな?)、Wi-Fiのデータ送信時消費電流が100~200mAと言われていることからもLPWAが低消費電力であるということが結果からもわかると思います。
さらに、消費電力と電池について考察してみます。
通信1回(3メッセージ)あたりの消費電流量は、1メッセージの送信時間を2.08[s]とすると
20[mA] x 2.08[s] ÷ 3600[s/h] = 0.011556[mAh]
仮に、1時間に1回通信すると仮定すると、
0.11556[mAh] x 24[h/day] ÷ 1[h] = 0.277333[mAh/day]
が1日あたりの消費電流になります。
何もセンサーを載せない(例えばボタン通知だけの)デバイスを想定した場合、待機電流を1μAとすると
1[μA] x 3600[s/h] ÷ 3600[s/h] × 24[h/day] = 0.024[mAh/day]
となり、1日あたりの消費電流計は0.277333 + 0.024 ≒ **0.3[mAh/day]**となります。
もし、2年間バッテリー運用したい場合、0.3[mAh/day] x 365[day] * 2[years] ≒ 220[mAh]の容量を持ったバッテリーを準備する必要があります。
ただ、ここからは電池屋さんの専門分野。使用環境や自己放電特性、セーフティーマージン等を加味し最適な電池を選択することになります。
いかがでしょうか?
改めて、LPWA(Sigfox)が低消費電力であるということを感じ取ってもらえたと同時に、この組み合わせで、お手元にお持ちの端末やモジュールの消費電力を簡単に測れるようになると思います。
是非、お試しください。