先日、「M5StickS3」を用いて「スリープ中にデバイス内の大部分への電源供給を遮断」する方法、および「スリープ中にGROVEポートから外付けデバイスへの電源供給を遮断」する方法を確認しました(記事は こちら)。
目的は、以下のようなIoTデバイスをつくりたいというものです。
- 1日に数回だけデータを測定し、そのデータをGoogleスプレッドシートに送信する。
- GROVEポートに「CAT-Mモジュール」を接続し、モバイル通信でデータ送信する。
- データ送信しない期間はデバイスをスリープさせる。
- スリープ期間中は「デバイス内の大部分」および「CAT-Mモジュール」への電源供給を遮断することで消費電力を削減し、乾電池でできるだけ長期間連続稼働させる。
「M5StickS3」で低電力動作させる方法が確認できたので、いよいよ上記のIoTデバイスをつくることにします。
まず、「CAT-Mモジュール」でデータ送信する方法ですが、以前動作確認した方法をそのまま使います(記事は こちら)。
#define TINY_GSM_MODEM_SIM7080
#include <TinyGsmClient.h>
const char apn[] = "XXXXXXXX";
const char lte_user[] = "XXXXXXXX";
const char lte_pass[] = "XXXXXXXX";
const int rx_pin = 10; // GROVE 4th pin of M5StickS3
const int tx_pin = 9; // GROVE 3rd pin of M5StickS3
TinyGsm modem(Serial1);
TinyGsmClientSecure client(modem);
boolean connectCatM() {
delay(500);
Serial1.begin(115200, SERIAL_8N1, rx_pin, tx_pin);
delay(3000);
M5.Display.println("Wait...");
// モデム初期化
M5.Display.println("Initializing modem...");
modem.init();
String modemInfo = modem.getModemInfo();
M5.Display.println("Modem Info: " + modemInfo);
// 接続
M5.Display.println("Connecting to "+ String(apn));
while (!modem.gprsConnect(apn, lte_user, lte_pass)) {
M5.Display.println("NG.");
delay(10000);
M5.Display.println("retry");
}
M5.Display.println("OK.");
// 接続まち
M5.Display.print("Waiting for network...");
if (!modem.waitForNetwork()) {
M5.Display.println(" fail");
delay(10000);
return false;
}
M5.Display.println(" success");
bool res = modem.isGprsConnected();
M5.Display.printf("GPRS status: %s\n", res ? "connected" : "not connected");
return true;
}
次に、データを「Googleスプレッドシート」に送信する方法についてですが、以前調査した こちら の方法をそのまま採用したところ、データ送信処理中に「M5StickS3」がシャットダウンしてしまい、リブートを繰り返すという問題が生じました。
ChatGPTに聞いたところ、ここで使っている「HTTPClient」ライブラリの、データ送信処理中の消費電力が大きいのが原因ではないか?とのコメントでした。
データ送信中の消費電力が大きいため、同じく消費電力が大きい「CAT-Mモジュール」をつないだ状態では、データ送信時に電源電圧の瞬間的なドロップが大きくなり、それによって「M5StickS3」がリブートしてしまう、というものです。
この問題を回避するため、データ送信に「HTTPClient」ライブラリを使わず、これまでもよく使ってきたデータ送信用スケッチに準じて、スケッチを以下のように修正しました。
const char* host = "script.google.com";
const char* deployid = "XXXXXXXX";
uint16_t chipid = (uint16_t)((ESP.getEfuseMac())>>32);
boolean addRowToGoogleSheets(float val0, float val1, float val2) {
M5.Display.print("connecting to ");
M5.Display.println(host);
if (!client.connect(host, 443)) {
M5.Display.println("connection failed");
return false;
}
String url = "/macros/s/" + String(deployid) + "/exec?chipid=ESP" + String(chipid) + "&val0=" + String(val0) + "&val1=" + String(val1) + "&val2=" + String(val2);
M5.Display.print("Requesting URL: ");
M5.Display.println(url);
String sendData = "GET " + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n";
client.print(sendData);
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
M5.Display.println(">>> Client Timeout !");
client.stop();
return false;
}
}
while(client.available()){
String line = client.readStringUntil('\r');
M5.Display.print(line);
}
M5.Display.println("closing connection");
client.stop();
return true;
}
あとは、先日調査した、「M5StickS3」で「スリープ中にデバイス内の大部分、およびGROVEポートへの電源供給を遮断」する方法(記事は こちら)を使って、「起動後、3つのランダムな数値を取得したら速やかに60秒のスリープに移行する」というスケッチをつくります。
#include <M5Unified.h>
#include <M5PM1.h>
M5PM1 pm1;
unsigned long interval = 60; // unit:sec
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);
M5.Power.setExtOutput(true);
M5.Power.setBatteryCharge(false);
M5.Display.setRotation(1);
M5.Display.clear();
M5.Display.setCursor(0, 0);
// Get Data
float val0 = random(0, 1000) / 10.0;
float val1 = random(0, 1000) / 10.0;
float val2 = random(0, 1000) / 10.0;
M5.Display.printf("[DATA] %.1f %.1f %.1f\n", val0, val1, val2);
M5.Power.setExtOutput(false);
pm1.timerSet(interval, M5PM1_TIM_ACTION_POWERON);
pm1.shutdown();
}
void loop() {}
これら3つのスケッチを組み合わせて完成させます。以下のとおりです。
#define TINY_GSM_MODEM_SIM7080
#include <M5Unified.h>
#include <M5PM1.h>
#include <TinyGsmClient.h>
const char apn[] = "XXXXXXXX";
const char lte_user[] = "XXXXXXXX";
const char lte_pass[] = "XXXXXXXX";
const int rx_pin = 10; // GROVE 4th pin of M5StickS3
const int tx_pin = 9; // GROVE 3rd pin of M5StickS3
unsigned long interval = 60; // unit:sec
const char* host = "script.google.com";
const char* deployid = "XXXXXXXX";
uint16_t chipid = (uint16_t)((ESP.getEfuseMac())>>32);
M5PM1 pm1;
TinyGsm modem(Serial1);
TinyGsmClientSecure client(modem);
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);
M5.Power.setExtOutput(true);
M5.Power.setBatteryCharge(false);
M5.Display.setRotation(1);
M5.Display.clear();
M5.Display.setCursor(0, 0);
// Connect to CAT-M
connectCatM();
M5.Display.clear();
M5.Display.setCursor(0, 0);
// Get Data
float val0 = random(0, 1000) / 10.0;
float val1 = random(0, 1000) / 10.0;
float val2 = random(0, 1000) / 10.0;
M5.Display.printf("[DATA] %.1f %.1f %.1f\n", val0, val1, val2);
// Send Data
M5.Display.println("Send Data");
addRowToGoogleSheets(val0, val1, val2);
M5.Display.println("Send request is finished.");
M5.Power.setExtOutput(false);
pm1.timerSet(interval, M5PM1_TIM_ACTION_POWERON);
pm1.shutdown();
}
void loop() {}
boolean connectCatM() {
delay(500);
Serial1.begin(115200, SERIAL_8N1, rx_pin, tx_pin);
delay(3000);
M5.Display.println("Wait...");
// モデム初期化
M5.Display.println("Initializing modem...");
modem.init();
String modemInfo = modem.getModemInfo();
M5.Display.println("Modem Info: " + modemInfo);
// 接続
M5.Display.println("Connecting to "+ String(apn));
while (!modem.gprsConnect(apn, lte_user, lte_pass)) {
M5.Display.println("NG.");
delay(10000);
M5.Display.println("retry");
}
M5.Display.println("OK.");
// 接続まち
M5.Display.print("Waiting for network...");
if (!modem.waitForNetwork()) {
M5.Display.println(" fail");
delay(10000);
return false;
}
M5.Display.println(" success");
bool res = modem.isGprsConnected();
M5.Display.printf("GPRS status: %s\n", res ? "connected" : "not connected");
return true;
}
boolean addRowToGoogleSheets(float val0, float val1, float val2) {
M5.Display.print("connecting to ");
M5.Display.println(host);
if (!client.connect(host, 443)) {
M5.Display.println("connection failed");
return false;
}
String url = "/macros/s/" + String(deployid) + "/exec?chipid=ESP" + String(chipid) + "&val0=" + String(val0) + "&val1=" + String(val1) + "&val2=" + String(val2);
M5.Display.print("Requesting URL: ");
M5.Display.println(url);
String sendData = "GET " + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n";
client.print(sendData);
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
M5.Display.println(">>> Client Timeout !");
client.stop();
return false;
}
}
while(client.available()){
String line = client.readStringUntil('\r');
M5.Display.print(line);
}
M5.Display.println("closing connection");
client.stop();
return true;
}
次に、ハードウェアの接続を行います。
「M5StickS3」のGROVEポートに「M5Stamp CAT-Mモジュール」を接続します。以下のようにつなぎます。
| M5StickS3 | CAT-Mモジュール |
|---|---|
| GND | GND |
| EXT5V | 5V |
| G9 | RX |
| G10 | TX |
単三型Ni-MH電池4本を、「M5StickS3」のHATに接続します。「5VIN」と「GND」につなぎます。


それでは動かしてみます。
Googleスプレッドシート側は、以前の記事(こちら)のとおりにセットアップしておきます。
電池ボックスのスイッチをオンにすると、スプレッドシートに3つのランダムな数値が書き込まれました。その後も1分毎にデータが追記されていきます。
うまく動いたので、このデバイスを動かしたときの消費電流波形を測定してみます。こちら の方法を使います。
6回測定し、それらの結果を重ね合わせてグラフ化しました。

1回の処理にかかる時間は平均して「13.13秒」、その期間の平均消費電流値は「186.1mA」でした。
また、スリープ中の消費電流値は、この測定方法では「ゼロ」(実際は若干のマイナス値)となりました。スリープ中の消費電力が極めて小さいことが分かります。
スリープ中の消費電力を無視して机上計算すると、2400mAhの単三型Ni-MH電池4本でこのハードウェアを動かした場合、「2400mAh ÷ ( 186.1mA × ( 13.13s / 3600s ) ) = 3536回」のデータ送信ができることになります。
1回の電池交換で、10分毎のデータ送信なら24.5日、1時間毎のデータ送信なら147日も稼働できる計算になります(机上計算どおりになるとは思えませんが)。
非常に低電力で動作し、乾電池等での長期間使用に耐えうる「CAT-M通信をつかったIoTデバイス」をつくることができました。
今後は、実際にこのデバイスを連続稼働させてみて、机上計算どおりの長期間稼働ができることを確認していこうと思います。
なお、私がM5Stack、M5StickCの使い方を習得するのにあたっては、以下の書籍を参考にさせていただきました。
ごく基本的なところから、かなり複雑なスケッチや、ネットワーク接続など、比較的高度なものまで、つまづかずに読み進めていけるような構成になっており、大変わかりやすい本です。

