はじめに
この記事はDENSO アドベントカレンダー 2025の4日目の記事です。
SIM7080G搭載 CAT-M/NB-IoT+GNSSユニットを使ってSoracom BeamにUDP通信をやってみる記事です。
エンカレッジポイント
私からのメッセージはただ一つです。SIM7070_SIM7080_SIM7090 Series_AT Command Manualを読んでもわからない?そうですそうです。全くわからないのです。ただ一つづつ読んでいけばいつかは分かる日がきっと来ます。
構成について
はじめSIM7080G搭載 CAT-M/NB-IoT+GNSSユニットはM5 Stack用と書いてあったので、Groveコネクタで繋げば動くものだと思っていました。
ところが全く動かない。
SIMの刺し方
モデム側を見ると山が右下を向いた絵が書いてある。この状態で押し込んでも山を左上にした状態でもSIMを指すとカチッと音がしてきちんとセットされたように感じてしまうのです。
これは記事を探すと見つかりました。
UiflowでCAT-M/NB-IoT+GNSSユニットからSORACOMにデータを投げてみる(2022.08.01修正済)
これが正しいです。
次にM5 Stack Basic v2.7と繋いでみたのですがモデムにシリアルからATコマンドを送っても応答がなかったのでまずはRaspberry piを使ってモジュールの動作確認をすることにしました。
ヒントとなったのはこの記事
SIM7080G搭載 CAT-M/NB-IoT+GNSSユニットが動かない時に確認したい事と、対応SIM(LTE-M)
HTTP通信ではあるものの通信方法を示してくれたのは助かりました。
Raspberry piと接続する。
Groveコネクタを見てみるとピンが刺さりそうだったのでジャンパーケーブルを刺してみます。
するとなんとか刺さったのでこれでRaspberry piと接続するようにします。
通信方式はUARTなので
| Raspberry pi側 | CAT-M/NB-IoT+GNSSユニット側 | 色 |
|---|---|---|
| 2 | 5V | 赤 |
| 6 | GND | 黒 |
| 8 | RX | 黄色 |
| 10 | TX | 白 |
これで配線は完了。
Raspberry pi側の設定
minicomのインストール
sudo apt-get install minicom
シリアル通信の有効化
sudo raspi-config → “Interface Options” → “Serial” で「ログインにシリアルを使用するか」と訊かれたら「いいえ (No)」に設定。
代わりに「シリアルハードウェアを有効にしますか?」には「はい (Yes)」を選択します。
Note
Raspberry Pi OS の設定で「シリアルログイン」を有効にしていると、起動時にそのポート上でログインプロンプトやカーネルメッセージが流れるため、モジュールへのATコマンドと衝突して文字化けやノイズになりました。
再起動後、minicom -D /dev/serial0 -b 115200 で接続。
minicom -D /dev/serial0 -b 115200
これで真っ黒な画面がでてきます。
Soracom Beamの設定
UDP → HTTP/HTTPS エントリポイントを読んで設定します。
バックエンドにはAPI Gateway + Lambdaを配置しておきます。
Lambda関数は適当なもので良いのですが例えば
// initialize SDK and document client
exports.handler = function(event, context) {
console.log(event);
const payload = Buffer.from(event.item.payload, 'base64').toString('ascii');
const obj = JSON.parse(payload);
console.log(obj);
};
こんなのでもOK
ATコマンドを実行してみる
まずATコマンドとは米国ヘイズ社によって開発されたモデム用コマンドで、AT○○○とコマンドを入
力することにより、通信することができます。
このコマンドはモデムごとに異なるのでSIM7070_SIM7080_SIM7090 Series_AT Command Manualを読んでコマンドを理解する必要があります。
とはいえ共通のコマンドもあって
AT と打てばモデムからOKと応答があります。これで接続確認ができます。
ちなみにATコマンドは非同期シリアル通信(UART)を前提として設計されているみたいです。
UARTは、スタートビット、データビット、パリティ、ストップビットなどを含むシリアルデータフローの形式で通信するため、ATコマンドのタイミングや終端の改行などが正しく扱われます。これに対し、I2Cはマスタースレーブ間の同期通信で、全く異なるプロトコルを使うため、ATコマンドのテキスト通信としては適していません。
実際にコマンドを実行してみます。
ATE1
AT+CFUN=0
AT+CGDCONT=1,"IP","soracom.io"
AT+CFUN=1
AT+CGNAPN
AT+CNACT=0,1
AT+CNACT?
AT+CAOPEN=0,0,"UDP","uni.soracom.io",23080
AT+CASEND=0,24
{"a":100,"b":200,"c":30}
AT+CACLOSE=0
これでSoracom Beam側に通信ができるのですが肝心のコマンドの中身を理解してないと何をやっているのかわかりません。
具体的に何をやっているのか
- ATE1
- Echo 設定を ON にするコマンドです。入力した文字が端末にも表示されるようになります。Echo オフ (ATE0) の場合は、打ち込んだ文字が画面に表示されません。
- AT+CFUN=0
- モジュールの機能レベル (Functionality) を「最小 (Minimum)」に切り替えます。一時的に RF 機能などを停止させ、設定変更を行うときなどに使います。
- AT+CGDCONT=1,“IP”,“soracom.io”
- PDP コンテキスト(セッションプロファイル) を設定しています。ここではコンテキスト ID=1 に APN (Access Point Name) として soracom.io を指定して、IP 通信を行うという意味です。つまり「Soracom 回線でデータを送受信できるようにモジュールを設定する」イメージです。
- AT+CFUN=1
- モジュールの機能をフル稼働に戻します。AT+CFUN=0 で最低機能状態にしたのを、通常動作に戻すために使います。これを実行したあと、しばらく待つとモジュールがネットワークへ再登録を開始します。
- AT+CGNAPN
- APN 情報を問い合わせます。うまく設定されている場合は、先ほどの soracom.io の設定が返ってくるはずです。もし設定が正しく反映されていない場合は結果が空になったりします。
- AT+CNACT=0,1
- PDP コンテキストを有効化 (Activate) するコマンドです。0 番のコンテキストをアクティブに設定、つまり実際のデータ通信を開始するよう指示します。
- 成功すると IP アドレスが割り当てられ、モデムがネットワークに出られるようになります。OK のあとに IP アドレス等の情報が表示されます。
- AT+CNACT?
- 有効化した PDP コンテキストの状態を確認するコマンドです。IP アドレスが正しく振られていれば問題ありません。
- AT+CAOPEN=0,0,“UDP”,“uni.soracom.io”,23080
- ここで UDP のソケットを開きます。
- 最初の 0 は PDP コンテキスト番号 (前段の CNACT=0,1 で使った 0 番を指定)。
- 2 つ目の 0 は接続 ID (送信ごとに割り当てる “チャンネル番号” のようなもの)。
- "UDP" はプロトコル。
- "uni.soracom.io" は宛先ホスト名、23080 はポート番号です。
- これにより Soracom Beam の UDP → HTTP エントリポイントに接続する準備ができます。
- AT+CASEND=0,24
- 先ほど AT+CAOPEN した接続 ID=0 の UDP ソケットに対して、これから送るデータのバイト数を指定します。ここでは 24 バイト。
- 次の行に送るデータ本体 (JSON 文字列など) を入力すると送信されます。
- 文字数を間違えると送信が途中で切れたり、次のコマンドを待ち続けたりするので注意が必要です。
- {“a”:100,“b”:200,“c”:30}
- 実際に送るデータ(JSON 形式)。Soracom Beam 側ではこのデータを受け取り、設定次第で HTTP や Lambda のバックエンドなどへ転送してくれます。
- Soracom Beam の UDP → HTTP エントリポイントを経由することで、簡単にサーバー側でデータを受け取れるようになります。
- AT+CACLOSE=0
- 使用が終わったら、最後にソケットをクローズします。0 はソケットの ID を指しており、先ほど CAOPEN した接続を指定しています。
ハマりポイント
UDP接続でエラーになる
AT+CAOPEN=0,1,"UDP","uni.soracom.io",23080
+CAOPEN: 0,1
OK
AT+CASEND=0,24
ERROR
これがはじめ出てきてずっとわかりませんでした。チャンネルやプロファイルのように切り替えられるというのを押さえておく必要があります。
困ったらリセット
これでできます。
AT+CREBOOT
さあいよいよM5 Stack Basicで通信してみよう
M5 Stack Basic V2.7でやってみる
ピン配置は
https://logikara.blog/m5stack-basic/
を参考に
| M5 Stack Basic V2.7側 | CAT-M/NB-IoT+GNSSユニット側 | 色 |
|---|---|---|
| 5Vって書いてあるところ | 5V | 赤 |
| Gって書いてあるところ | GND | 黒 |
| T0(1番ピン) | RX | 黄色 |
| R2(16番ピン) | TX | 白 |
Aruduino IDEのシリアルモニターからATコマンドを実行できるようにするプログラムを作る
#include <M5Stack.h>
HardwareSerial modemSerial(2); // UART2を使用 (GPIO16:RX, GPIO1:TX)
// 表示用のカーソル位置を管理する変数
int cursorY = 0;
const int lineHeight = 20; // 行の高さ (2のテキストサイズで約20ピクセル)
void setup() {
M5.begin();
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.fillScreen(BLACK);
modemSerial.begin(115200, SERIAL_8N1, 16, 1);
Serial.begin(115200);
printToScreen("Ready for AT commands");
Serial.println("Ready for AT commands");
}
// 画面スクロール表示のための関数
void printToScreen(const String &msg) {
if (cursorY > 220) { // 画面の高さ (240px) に近づいたらクリア
M5.Lcd.fillScreen(BLACK);
cursorY = 0;
}
M5.Lcd.setCursor(0, cursorY);
M5.Lcd.println(msg);
cursorY += lineHeight;
}
void loop() {
// シリアルモニターからの入力をモデムに送信
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
modemSerial.println(command);
Serial.print("Sent: ");
Serial.println(command);
printToScreen("> " + command); // コマンドを表示
}
// モデムからの応答をLCDとシリアルモニターに表示
if (modemSerial.available()) {
String response = modemSerial.readStringUntil('\n');
response.trim(); // 改行等の余分な空白文字を除去
Serial.print("Received: ");
Serial.println(response);
printToScreen(response); // 応答を表示
}
delay(50);
}
ちなみにM5 Stack Core S3だと以下
#include <M5Unified.h>
// UART2 を使う (例: GPIO18:RX, GPIO17:TX)
// 必要に応じてピンアサインを変更し、実機配線に合わせてください。
HardwareSerial modemSerial(2);
// 画面へのログ表示用の変数
int cursorY = 0;
constexpr int lineHeight = 20; // 1行あたりの高さ
// 画面へのテキスト出力(スクロール)関数
void printToScreen(const String &msg)
{
// ある程度下まで表示したらクリアして先頭に戻す
if (cursorY > 220) { // M5Stackの液晶は 240×320 ピクセルが多い
M5.Display.fillScreen(BLACK);
cursorY = 0;
}
M5.Display.setCursor(0, cursorY);
M5.Display.println(msg);
cursorY += lineHeight;
}
void setup()
{
// M5Unified 初期化(必要に応じて細かい設定を行う)
auto cfg = M5.config();
M5.begin(cfg);
// ディスプレイ設定
M5.Display.setTextSize(2);
M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);
M5.Display.fillScreen(BLACK);
// シリアル初期化
Serial.begin(115200);
// UART2 でモデムに接続 (例: ボーレート115200, RX=GPIO18, TX=GPIO17)
modemSerial.begin(115200, SERIAL_8N1, 18, 17);
printToScreen("Ready for AT commands");
Serial.println("Ready for AT commands");
}
void loop()
{
// ボタン処理など M5Unified 内部イベント処理
M5.update();
// PC側シリアルモニタ(USB) → モデム への書き込み
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
modemSerial.println(command);
Serial.print("Sent: ");
Serial.println(command);
printToScreen("> " + command);
}
// モデム → PC側シリアルモニタ(USB) & 画面 表示
if (modemSerial.available()) {
String response = modemSerial.readStringUntil('\n');
response.trim();
Serial.print("Received: ");
Serial.println(response);
printToScreen(response);
}
delay(50);
}
ATコマンドを送ると応答あり!ようやくここまで来ました。長かった。

ちなみにデバイスから送ったメッセージはBase64に変換されてSoracomからAWSに送信されてきます。
UDP → HTTP/HTTPS エントリポイント
より
バイナリデータは JSON 形式に変換されて転送されます
バイナリデータとして処理される場合は、以下の JSON 形式に変換されたうえで転送されます。payload の値 (eyJs...) は、デバイスから送信した JSON 形式のデータを Base64 形式でエンコードしたものです。これを、payload 変換と呼びます。
{
"payload": "eyJsYXQiOm51bGwsImxvbiI6bnVsbCwiYmF0IjozLCJycyI6MywidGVtcCI6MTkuOSwiaHVtaSI6NDcuNiwieCI6bnVsbCwieSI6bnVsbCwieiI6bnVsbCwidHlwZSI6MX0="
}
payload 変換されたデータから元のデータを取り出すには、payload の値 (eYJs...) を Base64 形式でデコードしてください。
なんでUDPを使うのか
通信量が削減できるからです。
SORACOM Beamによる通信量の削減効果を金額と電力量の数値で示す
IoTみたいに小さなデータを送るユースケースではTCPヘッダのオーバーヘッドも馬鹿になりません。もちろん信頼性とのトレードオフではあるのですが、Soracom Beamにはレスポンスをデバイス側に返す機能もあるのである程度の信頼性があればいいユースケースにおいてはUDP送信も選択肢に入ってきます。







