概要
統合型ブラシレス DC モーターモーションコントロールキット(Roller485 Lite)というモーターなのにディスプレイが付いている。
興味をひかれたので使ってみる。
コントローラにはATOM Matrixを使う。

環境
- モーター Roller485 Lite
- コントローラ ATOM Matrix
- MQTTブローカー
- Arduino IDE バージョン: 2.3.4

構成図

MQTTブローカー PCの起動でサービスも起動する。
使い方
Pythonからコマンドを送る
MQTT_Matrix_pub_commans_NEW.pyをIDLEで開き、実行する。
MQTTを使ってモーターに送るコマンド(変数commans )を調整する。
commans = (("SPIN", 100, 5), ("POS", 30, 5), ("SPIN", 0, 5))
"SPIN": モーターの回転指示
100: モーターの回転数(RTM)
5: 次のコマンドを送るまでの時間(sec)。通常はこの時間指定した回転数を維持する
"POS" 現在の回転位置をレスポンスで取得する
コマンドプロンプトからコマンドを送る
(1) PC(GMK)にリモートログイン
(2) cd C:\Program Files\mosquitto
トピックをパブリッシュする。
(3) mosquitto_pub -h localhost -t motor/command -m {\"seq\":1,\"cmd\":\"SPIN\",\"rpm\":100,\"sec\":5}
100RPMでモーターを回す。コマンドではsecに意味はない。
(4) mosquitto_pub -h localhost -t motor/command -m {\"seq\":1,\"cmd\":\"POS\",\"rpm\":0,\"sec\":5}
モーターのエンコーダー値をレスポンスで取得する。
トピックをサブスクライブする。
(5) mosquitto_sub -h localhost -t motor/response
MQTT_MX_OTA_SUB_commands_NEW
// プログラム名: MQTT_MX_OTA_SUB_commands_NEW:2025-09-17
// 機能概略:
// – MQTT通信を利用して、Atom Matrixに接続されたモーターを遠隔制御します。
// – 受信トピック: “motor/command”
// – cmdが”SPIN”の場合:
// – “rpm”と”sec”の値に応じてモーターを回転させ、指定された時間、速度を維持します。
// – 速度は滑らかに目標値に遷移します。
// – “sec”経過後、受信メッセージを”motor/response”にエコーバックします。
// – “sec”経過後もモーターは指定された速度で回転し続けます。
// – cmdが”POS”の場合:
// – モーターの動きは変えず、現在の位置情報を取得します。
// – {“seq”: 受信値, “POSITION”: 現在位置}というメッセージを直ちに”motor/response”に送信します。
// – 送信後もモーターの状態は維持され、次のコマンドを待ちます。
// – cmdがない、または”SPIN”, “POS”以外の値の場合:
// – “motor/response”に{“command”: “cmd ERROR”}を送信します。
// – モーターの動きは現在の状態を維持し、次のコマンドを待ちます。
// – Wi-Fi、固定IP、OTAアップデートに対応しています。
#include <unit_rolleri2c.hpp> // M5Stack Unit RollerモジュールをI2C経由で制御するためのライブラリ
#include <WiFi.h> // Wi-Fi接続機能を提供
#include <ArduinoOTA.h> // OTA(Over-the-Air)ファームウェアアップデート用ライブラリ
#include “M5Atom.h” // M5Atom Matrixを制御するためのライブラリ
#include <PubSubClient.h> // MQTTクライアント機能を提供
#include <ArduinoJson.h> // JSONデータの解析用ライブラリ
// ====== ユーザー設定変数 ======
// #includeの直後に目立つように記述する
// MQTTブローカー設定
const char* mqtt_broker = “192.168.0.13”;
const int mqtt_port = 1883;
// MQTTトピック設定
const char* mqtt_topic_sub = “motor/command”;
const char* mqtt_topic_pub = “motor/response”;
// Wi-Fiネットワークの認証情報を設定
const char *ssid = “aterm-2b4139-a”;
const char *password = “3e00cfa4ba409”;
// デバイスに割り当てる固定IPアドレス
const IPAddress ip(192, 168, 0, 125);
const IPAddress gateway(192, 168, 0, 1);
const IPAddress subnet(255, 255, 255, 0);
const IPAddress dns1(192, 168, 0, 1);
// OTAアップデート時のネットワーク上のデバイス名
const char* host_name = “M5Stack-AtomMatrix”;
// メインループの遅延時間 (ms)。この値を調整して、モーターの回転の滑らかさを変更します。
int delay_ms = 8;
// ====== オブジェクトインスタンスの作成 ======
UnitRollerI2C roller;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
StaticJsonDocument<256> doc;
// ====== モーター制御と状態管理 ======
int32_t currentTargetSpeed = 0; // 現在の設定速度 (RPM)
int32_t received_rpm = 0; // 受信したrpmの値
int received_sec = 0; // 受信したsecの値
bool isCommandReceived = false; // 新しいコマンドが受信されたかどうかのフラグ
unsigned long commandReceivedTime = 0; // コマンドを受信した時刻
// **追加: プログラム起動時のエンコーダ初期位置**
int32_t initialPosition = 0;
// プログラムの状態を管理するenum
enum ProgramState {
STATE_WAITING_FOR_COMMAND, // コマンド待機状態
STATE_TRANSITIONING_TO_NEXT_SPEED, // 次の速度へ滑らかに移行中
STATE_RUNNING_AT_TARGET_SPEED, // 目標速度で回転中
};
ProgramState currentState = STATE_WAITING_FOR_COMMAND; // 初期状態はコマンド待機
// ====== LED設定 ======
// LEDマトリックスの色を定義
#define LED_COLOR_WIFI_CONNECTING 0x0000FF // 青色 (Wi-Fi接続中)
#define LED_COLOR_WIFI_CONNECTED 0x00FF00 // 緑色 (Wi-Fi接続成功)
#define LED_COLOR_MQTT_CONNECTING 0xFFFF00 // 黄色 (MQTT接続中)
#define LED_COLOR_ERROR 0xFF0000 // 赤色 (エラー)
#define LED_COLOR_IDLE 0x000000 // 消灯 (待機状態)
// rpm値とsec値に基づいたLED表示を更新する関数
void updateLeds(int rpm, int sec) {
M5.dis.fillpix(LED_COLOR_IDLE); // 全LEDを一旦消灯
// RPM値に応じたLED表示
int rpm_leds_to_light = constrain(abs(rpm) / 10, 0, 15);
uint32_t rpm_color = (rpm >= 0) ? 0x00FF00 : 0x0000FF; // 正なら緑、負なら青
for (int i = 0; i < rpm_leds_to_light; i++) {
M5.dis.drawpix(i, rpm_color); // 1行目左から3行目右へ順に点灯
}
// SEC値に応じたLED表示
int sec_leds_to_light = constrain(sec, 0, 10);
uint32_t sec_color = 0xFFFF00; // 黄色
for (int i = 0; i < sec_leds_to_light; i++) {
M5.dis.drawpix(15 + i, sec_color); // 4行目左から5行目右へ順に点灯
}
}
// I2Cバス上でRollerモジュールを見つけて初期化する関数
void initRoller() {
// Atom MatrixのI2Cピンは固定 (SDA:26, SCL:32)
const auto sda = 26;
const auto scl = 32;
for (auto addr = 1; addr < 127; ++addr) {
if (roller.begin(&Wire, addr, sda, scl)) {
// モジュールが見つかったら初期設定を実行
roller.setRGBMode(roller_rgb_t::ROLLER_RGB_MODE_USER_DEFINED);
roller.setRGB(0);
roller.resetStalledProtect();
roller.setOutput(0);
roller.setMode(ROLLER_MODE_SPEED);
return;
}
}
for (int i = 0; i < 25; i++) {
M5.dis.drawpix(i, LED_COLOR_ERROR);
}
while(true) { delay(1000); }
}
// Wi-Fi接続中のピクセル点滅アニメーション
void connectAnimation() {
int count = 0;
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
M5.dis.drawpix(count % 25, LED_COLOR_WIFI_CONNECTING);
delay(500);
M5.dis.drawpix(count % 25, LED_COLOR_IDLE);
count++;
}
}
// 指定された目標速度へ滑らかに変化させる関数
// @param targetSpeed: 目標速度 (RPM)
// @return:
// true: 遷移が完了した
// false: 遷移が継続中
bool updateSpeedTransition(int32_t targetSpeed) {
const int step_size = 2; // 加速・減速の変化量
if (currentTargetSpeed < targetSpeed) {
currentTargetSpeed += step_size;
if (currentTargetSpeed > targetSpeed) {
currentTargetSpeed = targetSpeed;
}
} else if (currentTargetSpeed > targetSpeed) {
currentTargetSpeed -= step_size;
if (currentTargetSpeed < targetSpeed) {
currentTargetSpeed = targetSpeed;
}
} else {
return true; // 遷移完了
}
roller.setSpeed(currentTargetSpeed * 100);
return false; // 遷移継続中
}
// MQTTメッセージを受信したときに呼び出される関数
void onMqttMessage(char* topic, byte* payload, unsigned int length) {
String receivedPayload = “”;
for (unsigned int i = 0; i < length; i++) {
receivedPayload += (char)payload[i];
}
// JSONペイロードの解析
DeserializationError error = deserializeJson(doc, receivedPayload);
// JSON解析が成功し、かつ”cmd”キーが存在する場合
if (!error && doc.containsKey(“cmd”)) {
String cmd = doc[“cmd”].as<String>();
// — cmdが”SPIN”の場合 —
if (cmd == “SPIN”) {
// 受信ペイロードからrpmとsecを取得
// rpmとsecが欠けていても0として処理を続行
received_rpm = doc.containsKey(“rpm”) ? doc[“rpm”].as<int32_t>() : 0;
received_sec = doc.containsKey(“sec”) ? doc[“sec”].as<int>() : 0;
// モーターを有効化し、状態を速度遷移に設定
roller.setOutput(1);
isCommandReceived = true;
commandReceivedTime = millis();
currentState = STATE_TRANSITIONING_TO_NEXT_SPEED;
// LED表示を更新
updateLeds(received_rpm, received_sec);
}
// — cmdが”POS”の場合 —
else if (cmd == “POS”) {
// **修正: 初期位置からの差分を計算して、相対的な位置を取得**
int32_t current_position = roller.getPosReadback() – initialPosition;
// 応答用のJSONドキュメントを作成
StaticJsonDocument<64> posResponseDoc;
posResponseDoc[“seq”] = doc.containsKey(“seq”) ? doc[“seq”].as<int>() : 0;
posResponseDoc[“POSITION”] = current_position;
String posResponse;
serializeJson(posResponseDoc, posResponse);
// “motor/response”に即座に送信
mqttClient.publish(mqtt_topic_pub, posResponse.c_str());
// **モーターの状態やプログラムの状態は変更しない**
// **既存の動作を維持し、次の受信を待つ**
}
// — それ以外のcmdの場合 —
else {
// 無効なコマンドのためエラー応答を返す
StaticJsonDocument<64> errorDoc;
errorDoc[“command”] = “cmd ERROR”;
String errorResponse;
serializeJson(errorDoc, errorResponse);
mqttClient.publish(mqtt_topic_pub, errorResponse.c_str());
// **モーターの状態やプログラムの状態は変更しない**
}
} else {
// JSON解析失敗時、または”cmd”キーがない場合
// エラー応答を返す
StaticJsonDocument<64> errorDoc;
errorDoc[“command”] = “cmd ERROR”;
String errorResponse;
serializeJson(errorDoc, errorResponse);
mqttClient.publish(mqtt_topic_pub, errorResponse.c_str());
// **モーターの状態やプログラムの状態は変更しない**
}
}
// MQTTブローカーに接続する関数
void connectToMqtt() {
M5.dis.fillpix(LED_COLOR_MQTT_CONNECTING);
while (!mqttClient.connected()) {
String clientId = “M5AtomMatrix-“;
clientId += String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
mqttClient.subscribe(mqtt_topic_sub);
M5.dis.fillpix(LED_COLOR_IDLE);
} else {
M5.dis.fillpix(LED_COLOR_ERROR);
delay(5000);
M5.dis.fillpix(LED_COLOR_MQTT_CONNECTING);
}
}
}
void setup() {
// M5Atomの初期化
M5.begin(true, false, true);
initRoller(); // Unit Rollerモジュールを初期化
// **修正: プログラム起動時にエンコーダの初期値を記録**
initialPosition = roller.getPosReadback();
// Wi-FiとOTAを初期化
if (!WiFi.config(ip, gateway, subnet, dns1)) {
}
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
connectAnimation();
ArduinoOTA.setHostname(host_name);
// OTAイベントハンドラの設定
ArduinoOTA.onStart([]() {
M5.dis.fillpix(0xFFFFFF);
});
ArduinoOTA.onEnd([]() {
M5.dis.fillpix(0x00FF00);
delay(3000);
ESP.restart();
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
int leds_to_light = (progress / (total / 25));
for (int i = 0; i < 25; i++) {
M5.dis.drawpix(i, (i < leds_to_light) ? 0x0000FF : 0x000000);
}
});
ArduinoOTA.onError([](ota_error_t error) {
M5.dis.fillpix(0xFF0000);
delay(100);
M5.dis.fillpix(0x000000);
delay(100);
M5.dis.fillpix(0xFF0000);
});
ArduinoOTA.begin();
// MQTTクライアントの設定
mqttClient.setServer(mqtt_broker, mqtt_port);
mqttClient.setCallback(onMqttMessage);
// MQTT接続の開始
connectToMqtt();
M5.dis.fillpix(LED_COLOR_IDLE);
}
void loop() {
M5.update();
ArduinoOTA.handle();
if (!mqttClient.connected()) {
connectToMqtt();
}
mqttClient.loop();
switch (currentState) {
case STATE_WAITING_FOR_COMMAND:
// モーターの状態は変更しない
break;
case STATE_TRANSITIONING_TO_NEXT_SPEED:
// 目標速度への遷移が完了したら状態を更新
if (updateSpeedTransition(received_rpm)) {
// 遷移が完了したら、STATE_RUNNING_AT_TARGET_SPEEDへ移行
currentState = STATE_RUNNING_AT_TARGET_SPEED;
}
break;
case STATE_RUNNING_AT_TARGET_SPEED:
// 受信したsec秒が経過したら
if (millis() – commandReceivedTime >= (unsigned long)received_sec * 1000) {
if (isCommandReceived) {
// JSONドキュメントをシリアライズしてペイロードを作成
String jsonString;
serializeJson(doc, jsonString);
// publish
mqttClient.publish(mqtt_topic_pub, jsonString.c_str());
isCommandReceived = false;
// **変更: 状態を STATE_WAITING_FOR_COMMAND に戻さない**
}
} else {
// 残り時間を計算し、LEDの点灯数を更新
unsigned long elapsedTime = millis() – commandReceivedTime;
unsigned long total_duration_ms = (unsigned long)received_sec * 1000;
int leds_to_light = (int)((total_duration_ms – elapsedTime) / 1000);
updateLeds(received_rpm, leds_to_light);
}
break;
}
delay(delay_ms);
}
MQTT_Matrix_pub_commans_NEW.py
# プログラム名: MQTT_Matrix_pub_commans_NEW:2025-09-17
# 機能概略:
# 定義されたモーターコマンドのシーケンスを順番にMQTTで送信します。
# 各コマンドは、"cmd"(コマンド名)、"rpm"(回転数)、"sec"(秒数)の3つの要素から構成されます。
# Atom Matrixからの応答を受信するたびに次のコマンドを送信し、すべてのコマンドを送信したら終了します。
# 送信メッセージはシーケンス番号(seq)を含み、ターミナルに送受信メッセージを表示します。
import paho.mqtt.client as mqtt
import json
import time
# 送信するコマンドのリスト (cmd, rpm, sec)
# コマンドはタプル形式で定義され、最初の要素がコマンド文字列、2番目がRPM、3番目が秒数です。
commans = (("SPIN", 60, 5), ("SPIN", 30, 5), ("POS", 0, 5), ("SPIN", -30, 5), ("SPIN", 0, 2))
# commans = (("SPIN", 60, 5), ("SPIN", -60, 5), ("SPIN", 60, 5), ("SPIN", 30, 5), ("SPIN", -30, 5), ("SPIN", 0, 1))
# commans = (("SPIN", 150, 10), ("SPIN", -150, 10), ("SPIN", 150, 10), ("SPIN", -150, 10), ("SPIN", 150, 10), ("SPIN", -150, 10), ("SPIN", 0, 1))
commans = (("SPIN", 100, 5), ("POS", 30, 5), ("SPIN", 0, 5))
commans = (("SPIN", 100, 5), ("SPIN", -150, 5), ("SPIN", 100, 5), ("SPIN", 0, 5), ("SPIN", 100, 5), ("SPIN", -150, 5), ("SPIN", 100, 5), ("SPIN", 0, 5))
# 現在のコマンドインデックス
command_index = 0
# ====== 設定変数 ======
# MQTTブローカーのホストとポート
BROKER_HOST = "192.168.0.13"
BROKER_PORT = 1883
# 送信トピックと受信トピック
TOPIC_COMMAND = "motor/command"
TOPIC_RESPONSE = "motor/response"
# 通信の制御フラグ
# False: 次のメッセージは送信しない
# True: 次のメッセージを送信可能
next_message_ready = True
# メッセージのシーケンス番号
message_sequence = 0
# ====== コールバック関数 ======
def on_connect(client, userdata, flags, rc, properties):
"""ブローカーに接続したときのコールバック関数"""
print(f"Connected to MQTT Broker with result code {rc}")
# 応答を受信するためにトピックを購読する
client.subscribe(TOPIC_RESPONSE)
print(f"Subscribed to topic: {TOPIC_RESPONSE}")
def on_message(client, userdata, msg):
"""メッセージを受信したときのコールバック関数"""
global next_message_ready
print("\n--- Received message ---")
print(f"Topic: {msg.topic}")
try:
# ペイロードを文字列にデコードし、JSONとして解析する
payload_str = msg.payload.decode("utf-8")
payload_json = json.loads(payload_str)
print(f"Payload: {payload_json}")
except json.JSONDecodeError:
print(f"Payload (non-JSON): {payload_str}")
print("----------------------")
# 次のメッセージ送信を許可する
next_message_ready = True
def send_message(client):
"""
メッセージを送信する関数。
commansリストから現在のcmd, rpm, secの値を取得し、MQTTメッセージとして送信します。
"""
global message_sequence
global next_message_ready
global command_index
# 現在のコマンドを取得
cmd_value = commans[command_index][0]
rpm_value = commans[command_index][1]
sec_value = commans[command_index][2]
# シーケンス番号をインクリメント
message_sequence += 1
# 送信するペイロードをJSON形式で作成
payload = {
"seq": message_sequence,
"cmd": cmd_value,
"rpm": rpm_value,
"sec": sec_value
}
# ペイロードをJSON文字列に変換
payload_str = json.dumps(payload)
# MQTTメッセージを送信
client.publish(TOPIC_COMMAND, payload_str)
print("--- Sent message ---")
print(f"Topic: {TOPIC_COMMAND}")
print(f"Payload: {payload_str}")
print("--------------------")
# 次の応答を待つため、送信を一時的に停止
next_message_ready = False
# 次回送信のためにインデックスを更新
command_index += 1
# ====== メイン処理 ======
if __name__ == "__main__":
# MQTTクライアントのインスタンスを作成
# 最新のAPIバージョンを指定して警告を解消
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
# コールバック関数を登録
client.on_connect = on_connect
client.on_message = on_message
# ブローカーに接続
try:
client.connect(BROKER_HOST, BROKER_PORT, 60)
print("Connecting to broker...")
except Exception as e:
print(f"Connection failed: {e}")
exit()
# バックグラウンドでネットワーク通信を開始
client.loop_start()
# 起動後3秒待ってから最初のメッセージを送信
print("Waiting 3 seconds before first publish...")
time.sleep(1)
try:
# メッセージの送受信ループ
# すべてのコマンドを送信するまでループを続ける
while command_index < len(commans):
# 応答が来るまで待機
while not next_message_ready:
time.sleep(0.1)
# 次のメッセージを送信
send_message(client)
# すべてのコマンドを送信した後、最後の応答を待つ
while not next_message_ready:
time.sleep(0.1)
print("\nAll commands sent. Exiting script.")
except KeyboardInterrupt:
print("\nExiting script.")
finally:
# クライアントを停止
client.loop_stop()
client.disconnect()