技術情報

M5StackでSigfoxモジュール(IFS-M01)の消費電力を測定する

2020.08.05

LPWAは、低消費電力だと言いながら、そこそこいいお値段のする電流計を手に入れないと消費電力量を測ることができないと思われているかと思います。しかし、数千円レベルの電流電圧計モジュールとM5Stackなどにより簡単に測れる方法があります。
ここでは、M5Stack Basicと電流電圧計モジュール(INA226PRC)を使って、Sigfox無線モジュール(Innovation Farm社のIFS-M01)の消費電力を計測する方法をご紹介します。

IFS-M01ベースの開発キットは、8月4日にInnovation Farm社からリリースされました。
詳細はこちら

用意するもの

身の回りにないかもしれませんが、下記を用意してください。

機器名称写真
M5Stack Basic img-20200805-technical-01.png
INA226PRC - I2Cディジタル電流・電圧・電力計モジュール img-20200805-technical-02.png
ICクリップとリード線(既に結線済みのものをお奨めします) img-20200805-technical-03.png
Sigfoxモジュール(Inosensor ES Devkit) img-20200805-technical-04.png

Inosensor ES Devkit

Inosensor ES Devkitは、ST社のサブGHz帯トランシーバS2-LPベースのSigfoxモジュールIFS-M01の開発キットです。
ボタンと接点x2を搭載したモデルですので、とりあえずSigfoxを試してみたいという方は乾電池だけ用意してもらえればボタンや接点入力をトリガにSigfoxメッセージを送信できます。また、エンジニア向けに、STのSDKでも開発できるようになっています。

電力計モジュール[INA226PRC]

消費電流値の測定は、ストロベリーリナックスのINA226PRCというI2Cデジタル電流・電圧・電力計モジュールを使用します。
INA226PRCの仕様は下記の通りです。

項目仕様
電源電圧 3~5V
インターフェース I2C
測定電圧範囲 0~36V
測定電流範囲 -3.2768~+3.2767A
分解能 電流:0.1mA, 電圧:1.25mV

81Wまでの測定が可能という事で、組み込みセンサ系には適しているのではないかと思います。(待機電流までは無理ですが。。。)
下の写真のようにモジュール基板に加え、端子台x2、2種類のコネクタが付属していますので、ご自身の環境に合わせたコネクタを選択し、はんだ付けしてください。
また、I2Cのアドレスを設定するために、下右図のように、アドレスを0b1000000xとする場合は、Gのところを、はんだでグランドを落としておく必要があるようです。

img-20200805-technical-05.png

INA226PRCとSigfoxモジュール、M5Stackとの配線

今回は、Sigfoxモジュールの消費電流を計測したいので、3本のICクリップとショートカット用のリード線を使用し、下図のように配線します。

img-20200805-technical-06.png

INA226モジュールの配線は、グランド電位が共通となるハイサイド接続と電源の電位が共通となるローサイド接続があるようですが、推奨されているハイサイド接続で組みました。
ちなみに、端子側は下の写真のようにしています。

img-20200805-technical-07.png

完成した配線は下の写真のようになります。(既にM5Stack上に電流値グラフが表示されてしまっていますが、気にしないでください)

img-20200805-technical-08.jpeg

電流・電圧測定プログラム

M5StackとINA226RPCとは、I2Cで通信し電流値、電圧値を計測していくことになりますので、Wireライブラリ(Wire.hをインクルード)を使えば可能です。しかも、INA226から電流電圧値をミリ秒単位でサンプリングし、画面表示するプログラムは、Takehiko ShimojimaさんがGitHubにアップされているので、これを流用させていただくことにしました。
こちらです。
私は、SDカードを持っていなかったので、SDカードの書き込み部分をコメントアウトし、Serialで結果を送信するように変えています。サンプルスケッチは下記の通りになりますので、先ほどのGitHubリポジトリから、その他必要なファイルはダウンロードしお使いください。

INA226PRC.ino.c
#include  
#include  
#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でデータ送信した時の消費電流の時間変化の結果が下図のようにでました。

img-20200805-technical-09.png

上の消費電流のグラフを見ると、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)が低消費電力であるということを感じ取ってもらえたと同時に、この組み合わせで、お手元にお持ちの端末やモジュールの消費電力を簡単に測れるようになると思います。
是非、お試しください。

著者情報

研究部責任者 日比学

京セラコミュニケーションシステム株式会社(KCCS)の経営企画部門で新規事業・研究開発に従事。
Sigfoxネットワークの更なる普及にむけて、IoT、LPWA(Sigfox)関連の情報を発信しています。

【Twitter】https://twitter.com/ghibi