【ROS 2】スティックを倒してから車輪が回るまでを実機で追ってみた
実機ロボットで学ぶ Physical AI 101 ── 第1回/全4回
はじめに
AI については多少知っているけれど、ロボットのことはほとんど知らない。そんな人に向けて書いた記事です。
ChatGPT、コンピュータビジョン、AI エージェント、VLM など、AI モデルは日々「できること」が増えています。もう馴染みがある方も多いのではないでしょうか。今の AI は画像を読み、声を聞き、質問に答え、コードを書き、計画まで立てられます。
では、次の場合はどうでしょうか。
AI が画面上で答えるだけでなく、実世界で何かを「やる」必要があったら?
例えば、ロボットに「隣の部屋に行って」と言ったとします。この一文を理解すること自体は、LLM ならそう難しくありません。
問題は、理解した「あと」です。
- メモリの中にある一つの文が、どうやって本物の車輪を回すのか?
- 車輪が回ったあと、ロボットは自分が正しく進めたことをどうやって知るのか?
ここが、ロボティクスは純粋なソフトウェアの AI とはかなり違う、と自分が感じ始めたポイントです。
そこで、VLA やヒューマノイドのような大掛かりなものではなく、もっと単純なところから調べてみることにしました。ROSMASTER M3 Pro のジョイスティックです。
この記事では、スティックを倒した瞬間から4つの車輪が回り、その結果がまた戻ってくるまでの流れを、一段ずつ追っていきます。
この記事でわかること
- ジョイスティックの入力が
/joy→/cmd_vel→ micro-ROS → STM32 → モーターへ届くまでの流れ - Jetson(判断)と STM32(実行)の役割分担
- エンコーダと IMU によるフィードバックが必要な理由
- 「ROS 上でコマンドが見えている」=「モーターに届いている」ではない、という落とし穴
対象読者
- AI やソフトウェアの経験はあるが、ロボットは初めての方
- ROS 2 の用語(ノード、トピック)は聞いたことがあるが、全体像がつかめていない方
逆に、ROS 2 で移動ロボットを動かしたことがある方には、目新しい情報は少ないかもしれません。
前提環境
前提環境は次の通りです。
| 項目 | 内容 |
|---|---|
| ロボット | Yahboom ROSMASTER M3 Pro(メカナムホイール4輪+6軸アーム) |
| メインボード | NVIDIA Jetson Orin <要確認: NANO SUPER / NX SUPER> |
| 下位コントローラ | STM32(micro-ROS クライアント搭載ファームウェア) |
| ROS | ROS 2 Humble |
| 入力デバイス | ゲームパッド型ジョイスティック(/dev/input/js0 として認識) |
| Jetson ⇔ STM32 | シリアル接続 |
構成を図にすると、次のようになります。信号がどの順番で各部品を通るのかも、あわせて動きで示しています。
※本記事は 時点の、自分の実機での構成をもとにしています。
全体像:スティックを倒すと、ロボットも左に動く
文字にすると、大したことのない話に聞こえます。子供のおもちゃの車でもできることです。
でも、ロボットが走る様子を見ながら、ふとこう思いました。
スティックを倒した瞬間から4つの車輪が回るまで、内部では本当は何が起きているのか?
最初は Joystick → Robot くらいの単純な話だろうと思っていました。実際はそうではなく、その間には長い連鎖があります。スティックを倒すと信号が順に伝わり、最後にロボットが横へ動きます。
しかも、これで終わりではありません。車輪が回ったあと、今度はエンコーダや IMU の測定値が、逆方向に Jetson まで戻ってくる必要があります。
つまりロボットは、コマンドを受け取って走るだけではありません。「今、自分は実際にどう動いたのか?」 を知る必要があります。
これが、この記事で一番重要なポイントです。まずは一段ずつ見ていきましょう。
Step 1:ジョイスティックはまだ車輪を制御していない
ジョイスティックは、ロボットに搭載された Jetson Orin に接続されています。
Jetson とは?
ロボットに載っている小さな Linux コンピュータだと考えれば十分です。CPU と GPU を備えていて、カメラ処理、コンピュータビジョン、SLAM、ナビゲーション、AI モデルといった重い処理も動かせます。
ジョイスティックを接続すると、Linux はそれを入力デバイスとして認識します。自分の環境では /dev/input/js0 として見えています。スティックを倒すと、このデバイスの値が変化します。
ただし、この時点ではまだモーターとは何の関係もありません。Jetson 上のプログラムがジョイスティックのデータを読み取り、ROS に渡して初めて先に進みます。
そもそも ROS とは?
ROS の話になると、ノード、トピック、Publish、Subscribe といった用語がたくさん出てきます。ただ、この記事を読むだけなら、全部を覚える必要はありません。
まずは一つの単純な問題から考えてみます。ロボットの中では、たくさんのプログラムが同時に動いています。
- ジョイスティックを読むプログラム
- LiDAR を読むプログラム
- カメラ画像を処理するプログラム
- 自己位置を推定するプログラム
- 経路を探索するプログラム
- ハードウェアへコマンドを送るプログラム
これらは、何らかの方法で互いにデータをやり取りしなければなりません。この問題の大部分を解決してくれるのが ROS 2 です。
ざっくり言えば、ROS 2 はロボット内のプログラム同士がデータをやり取りするための基盤です。
一つひとつのプログラムは ノード(node)、データが流れるチャンネルは トピック(topic) と呼ばれます。例えば、ジョイスティックを読むノードは /joy というトピックにデータを送ります。
動作確認:/joy の中身を見る
実際に中身を見てみます。
ros2 topic echo /joy
この状態でスティックを動かすと、axes の値が変化するのが確認できます。ボタンを押すと、buttons の値が 0 から 1 に変わります。
上の出力は例です。axes と buttons の数や並び順は、使うコントローラによって異なります。
これで「よし、Jetson はジョイスティックを読めている」と確認できました。
とはいえ、ロボットはまだ「どのくらいの速さで走るべきか」を知りません。わかっているのは、スティックがどの位置にあるかだけです。
Step 2:/joy から /cmd_vel へ
次に、このデータを「速度」に翻訳するプログラムが必要になります。ROSMASTER では、/joy_ctrl ノードがこの役割を担っています。
例えば、次のように変換されます。
| スティック操作 | 変換後のコマンド |
|---|---|
| 少し前に倒す | 0.2 m/s で前進 |
| 旋回方向に倒す | 0.4 rad/s で回転 |
変換結果は /cmd_vel というトピックに送られます。cmd_vel は command velocity(速度コマンド) の略で、移動ロボットではとても重要なトピックです。メッセージ型は geometry_msgs/msg/Twist で、主に使うフィールドは次の3つです。
| フィールド | 意味 |
|---|---|
linear.x |
前進/後退 |
linear.y |
左/右(横移動) |
angular.z |
回転 |
ここから、かなり面白いことが見えてきます。/cmd_vel より下の制御部分は、そのコマンドがどこから来たのかを知る必要がないのです。
今日は人間がジョイスティックで、明日は Nav2 が、いつかは AI エージェントが /cmd_vel を送るかもしれません。上の「誰が決めるか」は変わっても、/cmd_vel より下の流れは毎回同じです。
つまり、ジョイスティックは「意思決定者」という役割を担っているだけです。ロボットの残りの部分は、その決定が人間から来たのか AI から来たのかを、必ずしも気にしません。必要なのは 「今、どう動けばいいのか?」 という情報だけです。
ここで、ロボティクスと Physical AI のつながりが少しはっきり見えてくると思います。
Step 3:/cmd_vel はあるのに、車輪はまだ回らない?
理由は、/cmd_vel がまだ Jetson 上に存在するだけのデータだからです。Jetson が linear.x = 0.2 という値を取り出して、直接モーターに電気を流すわけではありません。
Jetson の下流には、もう一つ別のチップがあります。STM32 です。
Jetson が「頭」の仕事を担うなら、STM32 は「手足」に近い部分を担います。例えば、エンコーダの読み取り、モーター制御、速度制御ループの実行などです。
まとめると、次のような分担です。
Jetson は、ロボットが「何をすべきか」を決める。
STM32 は、それをモーターに「どう実行させるか」を担う。
このような役割分担は、ロボティクスではとても一般的です。数秒考え込むこともある LLM に、モーター速度の安定化まで兼任させたい人はいないと思います。
Jetson は、どうやって STM32 にコマンドを送るのか?
ここで登場するのが micro-ROS です。
STM32 は Linux を動かしておらず、Jetson のようにフルの ROS 2 を動かしているわけでもありません。その代わり、STM32 のファームウェアには micro-ROS クライアントが組み込まれています。
Jetson 側では micro_ros_agent を起動し、この Agent が Jetson と STM32 の橋渡しをします。Agent が ROS 2 のメッセージをシリアル用の小さなデータに変換します。STM32 側の micro-ROS がそれを受け取り、モーターを動かします。逆向きに、/odom_raw などの測定値も同じ経路で戻ってきます。
自分の環境での起動コマンド
micro_ros_agent serial \
--dev /dev/myserial \
-b 2000000
注目したいのは、ここでコマンドが Linux のプログラムから、ハードウェアを直接制御するチップへ届いた という点です。「数値が動きに変わる」地点に、かなり近づいてきました。
Step 4:「左へ進む」とき、4つの車輪はどう回るべきか?
ROSMASTER M3 Pro は、4つのメカナムホイールを使っています。車輪の周りに斜めのローラーが付いていて、前進・後退・回転に加えて、横移動もできます。見ているだけでも、かなり面白いです。
ただし、ソフトウェアはもう一つ問題を解く必要があります。例えば /cmd_vel が「左へ横移動する」と言っているとします。でも、前左の車輪も前右の車輪も、「左へ移動する」がどういうことかは知りません。各モーターが理解できるのは、どの方向に、どのくらいの速さで回るか だけです。
そこでシステムは、こう計算しなければなりません。
ロボット全体を左へ動かすには、4つの車輪をそれぞれどう回せばいいのか?
これが、メカナムホイールの逆運動学(inverse kinematics) と呼ばれる問題です。名前を聞くと少し身構えてしまいますが、アイデア自体はずっと単純です。ロボット全体として望む動き(vx, vy, ω)を、4つの車輪それぞれの速度に分解するだけです。
実際に、いくつかの動きで各車輪の速度がどうなるかを見てみます。同じ4つの車輪でも、回す向きと速さの組み合わせを変えるだけで、前進・横移動・回転・斜め移動ができます。
これで終わりかと思いきや、まだです。
モーターに「100 RPM で回れ」と言っても、100 RPM で回るとは限らない
ここは、ソフトウェアと物理世界の違いがはっきり出る部分だと感じています。
コードの中では x = 100 と書けば、x は必ず 100 です。でも、モーターに 100 RPM で回るよう指示しても、実際には 92 RPM でしか回らないかもしれません。
原因はいろいろ考えられます。
- バッテリーが少し弱っている
- ロボットが少し重い
- 床の状態や摩擦が違う
- 片方のモーターが、もう片方より少し力強い
- 車輪が滑っている
そのため、ロボットは「コマンドを送ったから、その通りに動いたはず」と信じ込むことができません。実際に測り直す必要があります。
ROSMASTER の車輪にはエンコーダが付いています。エンコーダを使うと、車輪がどれだけ回ったか、今どのくらいの速さで回っているかがわかります。例えば実際が 92 RPM なら、コントローラーはモーターへの信号を強めます。しばらくすると 99 RPM になり、さっきより目標に近づきます。
この調整によく使われるのが PID 制御 です。誤差(目標と実際の差)を見て信号を少しずつ調整し、実際の速度を目標に近づけていきます。
このループがずっと繰り返されます。そして最終的に、
ソフトウェアのコマンド → 電気信号 → モーターが回る → 車輪が回る → ロボットが走る
という流れになります。ここでようやく、スティックを倒した操作が実世界に届きました。
Step 5:ロボットが走り出したら、それで終わり?
まだです。
例えば、直進するよう命令したとします。車輪はすべて回っていますが、実際にはロボットが少しずつ逸れていくことがあります。先ほど見たような小さな誤差が、少しずつ積み重なるからです。
送ったコマンドだけを見ていたら、Jetson は「直進しろと言ったんだから、直進しているはずだ」と考えるでしょう。でもそれは、そうあってほしいというだけの話です。ロボットは、実際に何が起きているかを知る必要があります。
この構成では、STM32 が次のトピックで運動データを Jetson に送り返しています。
| トピック | 中身 |
|---|---|
/odom_raw |
車輪の回転から推定した移動量 |
/imu/data_raw |
角速度・加速度などの IMU データ |
これで、データの流れは一つの循環になります。
例えば、右側のモーターが 6% だけ速く回っていたとします。/cmd_vel は直進のままでも、ロボットは少しずつ左へ曲がっていきます。そのずれに気づけるのは、返ってきた測定値を見たときだけです。
ロボットはコマンドを出すだけでなく、その結果も確認します。これが、ロボティクスで最も基本的な考え方の一つである フィードバック です。
では、これはもう Physical AI と呼べるのか?
まだだと思います。
この例で一番「賢い」存在は、依然として……自分自身です。環境を見るのも、行き先を決めるのも、スティックを操作するのも自分です。ロボットはそれに従っているだけです。この方式は テレオペレーション(teleoperation)、つまり遠隔操作と呼ばれます。
ただし、とても大事な点があります。
/cmd_vel より下の部分は、将来人間を AI に置き換えても、ほぼそのまま必要とされ続けます。
例えば、ジョイスティックを使わずに「3番会議室を見に行って」と言うだけだとします。完全なシステムでは、次のような処理が必要になるかもしれません。
- AI が発話を理解する
- 3番会議室の場所と、ロボットの現在地を特定する
- 経路を計算する
-
/cmd_velを生成して送る - センサーで観測しながら進み、到着したらタスク完了
1〜3 は、かなり「AI らしい」部分になるかもしれません。でも 4 から先は、今回見てきた仕組みとほとんど変わりません。
画面の中の AI と、ロボットの中の AI はどこが違うのか?
チャットボットは、普通 Input → Model → Output で考えます。出力が出れば、ほぼ終わりです。
ロボティクスでは、AI の出力はむしろ物語の始まりにすぎません。そのあとに、プランニング、ナビゲーション、コントローラー、モーター、機械部分、センサー、そして実世界の環境が待っています。
しかも実世界は、コンピュータの中のデータほど「素直」ではありません。
- カメラが暗すぎるかもしれない
- LiDAR がガラスに惑わされるかもしれない
- データが遅れて届くかもしれない
- 誰かが突然ロボットの前に飛び出すかもしれない
チャットボットなら、答えが気に入らなければ Regenerate を押せば済みます。でもロボットが横にずれて走ってしまったら、部屋ごと元に戻す Ctrl+Z はありません。
だからこそ、Physical AI で一番面白いのは「AI は十分に賢いか?」という問いだけではない、と自分は考えています。
賢い判断を、十分に安定した物理システムへどう伝えるか。そして、実世界での結果が予定通りだったかを、どうやって知るか?
ハマったところ:/cmd_vel は出ているのに、ロボットが動かない
これは、自分が実際に ROSMASTER を触っていてハマった例です。
ROS から直接 /cmd_vel を送ってみました。例えば、次のようなコマンドです。
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
ROS 側から見る限り、すべて問題なさそうでした。トピックもあるし、コマンドも流れている。それなのに、ロボットが動かないことがありました。
層を一つずつたどって、ようやく重要なことに気づきました。
ROS の中でコマンドが見えていても、そのコマンドがモーターまで届いているとは限りません。
モーターが動くには、途中のすべてがつながっている必要があります。
- micro-ROS Agent が起動していて、コマンドを中継できている
- STM32 が正常に接続されている
- ファームウェアが正しい subscriber を作っている
- モーターコントローラーが動作している
- 電源が安定している
どこか一箇所でも切れていると、Jetson 側は「きれいに」見えたままなのに、ロボットはじっと止まっています。
次に同じ状況になったら、自分はまず次の順で確認すると思います。
# 1. /cmd_vel を購読しているノードがいるか(Subscription count が 0 なら下流に届いていない)
ros2 topic info /cmd_vel --verbose
# 2. STM32 側からのトピックが上がってきているか(micro-ROS の接続確認)
ros2 topic list | grep -E "odom_raw|imu"
# 3. フィードバックが実際に更新されているか
ros2 topic hz /odom_raw
例えば micro-ROS Agent が起動していなかった場合は、次のような流れで原因にたどり着けます。
ロボットを初めて扱う人にとって、これはかなり大きな発想の転換だと思います。普通のソフトウェアなら「関数は呼ばれているか?」「API はレスポンスを返しているか?」を確認すれば十分でした。ロボティクスでは、さらにこう問う必要があります。
実世界は、ソフトウェアが指示した通りに動いているか?
まとめ
スティックを倒すという単純な操作から、/joy → /cmd_vel → micro-ROS → STM32 → モーター、そしてエンコーダと IMU から Jetson へ戻る道をたどってきました。
これらの名前を全部覚える必要はありません。押さえておきたいのは、次の4点です。
- 判断と実行は分かれている。 Jetson(ROS 2)が「何をするか」を決め、STM32 が「どう動かすか」を担う
-
/cmd_velが境界線になる。 その上が人間でも Nav2 でも AI でも、下の仕組みはほぼ同じ - 指示した通りには動かない。 だからエンコーダや IMU で測り、PID などで補正し続ける
- ROS 上で見えている ≠ 実世界で動いている。 デバッグは層を一つずつたどる
言い換えると、ロボティクスの基本は 認識 → 判断 → 行動 → 結果の観測 → 次の判断 というループです。SLAM や Nav2、VLA といった言葉も、このループのどこかに入る部品だと考えると整理しやすいと思います。
AI が画面から離れ、実世界へ足を踏み入れ始めるのは、AI の判断がこのループにつながったときだと思います。
移動ロボットが手元にある方は、ぜひ ros2 topic echo /joy と ros2 topic echo /cmd_vel を並べて、スティックを動かしてみてください。数値が「速度」に変わる瞬間が見えて、かなり面白いです。
間違いや、もっと良い説明があれば、コメントでご指摘いただけると助かります。
次回予告:アームにモーターはあるのに、なぜ ROS から動かせないのか?
ROSMASTER M3 Pro には、6軸のロボットアームも付いています。外から見ると、機械フレームもサーボもケーブルも、キャリブレーション機能まで揃っています。
ところが ROS グラフを確認してみると、奇妙なことが起きていました。アームを制御し、状態を読み取るために期待していた STM32 からのトピックが、車輪のようには現れていなかったのです。
機械部分はある。モーターもある。でも「脳」からアームへの制御経路は、思っていたようにはつながっていませんでした。
今回が「完全に機能している一本の経路」の話だったとすれば、次回はその逆です。
一つの環が欠けているとき、どうたどれば、どこで切れているかを見つけられるのか?
ちょっと洒落て言うなら、アームには骨も筋肉もあるが、脳へつながる神経がまだ通っていない、という状態です。
第2回/全4回:ロボットアームには骨はあるが、まだ神経がない
参考
- ROSMASTER M3 Pro(Yahboom 公式製品ページ)
- ROSMASTER M3 Pro チュートリアル(Yahboom)
- YahboomTechnology/ROSMASTER-M3PRO(GitHub)
- ROS 2 Humble ドキュメント:Nodes
- ROS 2 Humble ドキュメント:Topics
- geometry_msgs/msg/Twist の定義(GitHub)
- ros-drivers/joystick_drivers(
/joyを配信する joy パッケージ) - micro-ROS 公式サイト
- micro-ROS/micro-ROS-Agent(GitHub)
- Nav2 ドキュメント
- NVIDIA Jetson Orin
- Mecanum wheel(Wikipedia 英語版)
- PID制御(Wikipedia)









