はじめに
これまでライフゲームのLED表示を皮切りに、GA(遺伝的アルゴリズム)による長寿命パターン探索、Flask+CanvasによるWeb監視、と3本立てでArduino UNO Qの記事を書いてきた。これらの実装では、Python側で計算した結果をArduino側のLEDマトリクスに送る「MCU⇄Linux間のブリッジ」を毎回使っていたが、コードとしては作り込んだロジックの中に埋もれてしまっていた。
本記事では、このブリッジ機構だけを切り出し、値を1つ渡すだけの最小構成で仕組みを確認する。外部モジュールは使わず、UNO Q単体(内蔵LEDマトリクスのみ)で完結する内容である。
本機能がこのArduino UNO Qにて、初めに有用となる機能と個人的には思っている。
動作環境
| 項目 | 内容 |
|---|---|
| ハードウェア | Arduino UNO Q |
| 開発環境(MCU側) | Arduino IDE |
| 実行環境(Linux側) | Arduino App Lab(Python 3) |
| LEDマトリクス | 8×13 |
UNO Qのアーキテクチャ:MCUとLinuxが分かれている理由
Arduino UNO Qは、1枚の基板に性格の異なる2つのプロセッサを搭載している。
- MCU(STM32U585): Arduinoスケッチが動く、リアルタイム制御向けのマイコン
- Linux MPU(Qualcomm QRB2210): Debian Linuxが動く、Pythonなどが動かせるマイクロプロセッサ
LEDマトリクスやピンの直接制御はMCU側の担当だが、複雑な計算やネットワーク処理はLinux側の方が得意である。この2つを協調させるため、UNO QにはRPC(Remote Procedure Call)ベースのBridgeという仕組みが用意されている。
[Python: Linux側スクリプト]
|
| Bridge.call("setLevel", 5)
| (RPC over arduino-router)
v
[arduino-router デーモン]
|
v
[Arduino MCU: sketch.ino]
|
| setLevel(5) が実行される
v
[LEDマトリクス 8×13]
裏側ではarduino-routerというLinux側の常駐サービスが通信を仲介しており、MCU側でBridge.provide()により登録した関数を、Linux側からBridge.call()で呼び出せる、という関係になっている(公式ドキュメント:UNO Q User Manual)。過去記事のライフゲームで使っていたBridge.call("drawFrame", frame_str)も、このBridgeの応用にすぎない。
最小サンプル:数値を1つ送ってLEDマトリクスに反映する
過去記事では104文字の文字列でLEDマトリクス全体のフレームを丸ごと送っていたが、今回はそれよりさらに小さく、整数を1つだけ送る。Linux側から送った数値(0〜13)の分だけ、LEDマトリクスの先頭行にバーを表示するだけの構成である。
Arduino側(sketch.ino)
// RPC通信・LEDマトリクス制御ライブラリのインクルード
#include "Arduino_RouterBridge.h"
#include <Arduino_LED_Matrix.h>
// LEDマトリクスインスタンス
ArduinoLEDMatrix matrix;
// Linux側からRPCで呼び出される関数
// level: 点灯させる列数(0〜13)。先頭行を左からlevel列分だけ点灯するバー表示
void setLevel(int level) {
if (level < 0) level = 0;
if (level > 13) level = 13;
uint8_t frame[104] = {0}; // 8行×13列、まず全消灯
for (int col = 0; col < level; col++) {
frame[col] = 1; // 0行目(先頭行)のcol列を点灯
}
matrix.draw(frame);
}
// 初期化処理
void setup() {
// LEDマトリクスの初期化(0/1の2階調で十分)
matrix.begin();
matrix.setGrayscaleBits(1);
// RPCブリッジの初期化・関数登録
Bridge.begin();
Bridge.provide("setLevel", setLevel);
}
void loop() {}
実装のポイント
Bridge.provide()で関数を登録する
Linuxから呼び出せるようにするには、Bridge.begin()のあとにBridge.provide("名前", 関数)を呼ぶだけでよい。過去記事のLEDマトリクスへのフレーム送信では引数にString型を使う必要があったが(std::vector<int>やstd::stringはうまく扱えなかった)、今回のように単一のint・bool・floatを渡すだけであれば、そのままの型で問題なく動作する。
loop()が空
過去記事と同様、Bridge(内部的にはArduino_RouterBridge)がRPCの受信処理を管理するため、loop()への記述は不要である。
Python側(bridge_demo.py)
from arduino.app_utils import *
import time
# Linux側から「数値を1つ」MCU側へ送るだけの最小サンプル
# setLevel(int level) をRPCで呼び出し、LEDマトリクスのバーを伸縮させる
def loop():
for level in range(14): # 0 -> 13 まで1列ずつバーを伸ばす
Bridge.call("setLevel", level)
time.sleep(0.2)
for level in range(13, -1, -1): # 13 -> 0 まで縮める
Bridge.call("setLevel", level)
time.sleep(0.2)
App.run(loop)
実装のポイント
Bridge.call("関数名", 引数...)
arduino.app_utilsをインポートすると使えるようになるBridgeは、MCU側でBridge.provide()済みの関数をRPC経由で呼び出すためのオブジェクトである。今回はsetLevel(int level)に対応させて、Bridge.call("setLevel", level)と数値を1つ渡すだけでLEDマトリクスのバーが伸び縮みする。
App.run(loop)
過去記事同様、App.run()に渡した関数がArduinoのloop()に相当する形で繰り返し呼ばれる。今回はその中で単純にlevelを増減させながらBridge.callしているだけである。
双方向にしたい場合は
今回はLinux→MCUの一方向(数値を送ってLEDに反映)のみを扱ったが、MCU側の関数に戻り値を持たせれば、逆方向(MCU→Linux)にも対応できる。公式ドキュメントによると、Bridge.call()はRpcCallという非同期のオブジェクトを返し、その.result()を呼ぶことでMCU側関数の戻り値を受け取れる(Arduino_RouterBridge)。たとえばMCU側にボタンの状態を返す関数を用意しておき、Linux側から定期的にBridge.call("readButton").result()のように呼び出す、といった使い方が考えられる。本記事では深追いせず、①の一方向サンプルにとどめる。
まとめ
UNO QのMCU⇄Linuxブリッジを、値を1つ渡すだけの最小構成で確認した。
- MCU側は
Bridge.provide("名前", 関数)で関数を登録するだけでLinuxから呼び出し可能になる - Linux側は
Bridge.call("名前", 引数)で対応する関数をそのまま呼び出せる - 単一の数値(int/bool/float)を渡すだけなら、過去記事で必要だった
String型への変換は不要
このブリッジさえ押さえれば、Linux側でどんな値を計算しようと、MCU側のLEDマトリクスに反映する部分は今回のサンプルと同じ形で使い回せる。次回は、この仕組みを使ってCPU使用率などをLinux側で取得し、LEDマトリクスにバー表示する簡易モニターを作る。
※本記事のサンプルコードはClaude Codeで大枠を作成し、著者が動作確認・調整を行った。
