CANopen 後編 ―― 仮想サーボを回す(3/5)
- canopen
- python
- サーボ
- 産業用通信
- 組み込み
前編では CANopen の配管、NMT・SDO・PDO・SYNC を一通り通しました。今回はその配管に中身を流します。
サーボモータを回します。といっても実物は使いません。PCの中に、規格どおりに振る舞うサーボを1台作ります。
この記事で作るもの
仮想サーボを規格の手順どおりに起動し、位置指令を与えて追従させ、そのカーブをグラフにします。
起動直後 Statusword = 0x0250 -> Switch on disabled
Controlword = 0x06 (Shutdown) Statusword = 0x0231 -> Ready to switch on
Controlword = 0x07 (Switch on) Statusword = 0x0233 -> Switched on
Controlword = 0x0F (Enable operation) Statusword = 0x0237 -> Operation enabled
SDO write 0x6060 = 8 -> 0x6061 (実際のモード) = 8 (csp)
t=0.101s target= 10000 actual= 6229 status=0x0237
t=0.302s target= 10000 actual= 9854 status=0x0237
t=0.591s target= 10000 actual= 9999 status=0x0237
0x0F を書いた行で通電し、そこから位置が目標に寄っていきます。
使うもの(すべてオープンソース・費用ゼロ・ハード不要)
| ライセンス | ||
|---|---|---|
| python-can | 4.6.1 | LGPL-3.0-only |
| matplotlib | 3.10.7 | PSF-based |
./.venv/bin/pip install "python-can[multicast]==4.6.1" matplotlib==3.10.7
コードは3本に分かれています。実行するのは3つ目です。
| ファイル | 中身 |
|---|---|
02_canopen/cia402.py |
Controlword / Statusword の定義と状態機械 |
02_canopen/servo.py |
仮想サーボ(SDO サーバ・PDO・SYNC 追従) |
02_canopen/run_servo_demo.py |
マスタ側。これを実行します |
./.venv/bin/python 02_canopen/run_servo_demo.py
グラフの見た目はリポジトリ直下の figstyle.py に切り出してあります。
① CiA 402 とは — サーボの共通語
前編で「0x6000 番台は規格で決まっている」と書きました。それを決めているのが CiA 402 で、サーボに必要な操作に番号を振って標準化したものです。

CiA 402 が動かすのは、この実物です。サーボモータとドライブ(CANopen ノード)、回転する負荷が軸でつながっています。Controlword で命じ、Statusword で状態を受け取り、Target position で位置を指令します。
| 番号 | 名前 | 役割 |
|---|---|---|
| 0x6040 | Controlword | サーボに「こうしろ」と命じる(16bit) |
| 0x6041 | Statusword | サーボが「今こうです」と答える(16bit) |
| 0x6060 | Modes of operation | 動作モードの指定 |
| 0x6064 | Position actual value | 現在位置 |
| 0x607A | Target position | 目標位置 |
| 0x60FF | Target velocity | 目標速度 |
| 0x6071 | Target torque | 目標トルク |
利点は、メーカーが違っても同じ番号に同じものが入っていることです。各社が公開しているサーボの取扱説明書を見ると、通電までの手順は共通しています。
安川のサーボでも、Beckhoff のサーボでも、0x6040 に 0x06、0x07、0x0F と順に書く形です。だからモータを別メーカーに載せ替えても、マスタ側のコードはほぼそのまま動きます。
② 状態機械と動作モード
いきなり回らない
CiA 402 のサーボは、電源を入れてもすぐには動きません。決められた順番で状態を進めないと通電しません。安全のためです。
本編では、動かすために最低限必要な3ステップだけ扱います。
stateDiagram-v2
[*] --> SOD
SOD : Switch on disabled
RTSO : Ready to switch on
SW : Switched on
OE : Operation enabled
SOD --> RTSO : Controlword = 0x06
RTSO --> SW : Controlword = 0x07
SW --> OE : Controlword = 0x0F
note right of OE : ここで初めて通電
Operation enabled に入って初めて、モータに電流が流れます。
完全な状態遷移図(Quick Stop / Fault 系を含む)
実際には Quick stop active、Fault reaction active、Fault、Not ready to switch on を含む8状態あります。本文では扱いませんが、実機では必要になります。
stateDiagram-v2
[*] --> NRSO
NRSO : Not ready to switch on
SOD : Switch on disabled
RTSO : Ready to switch on
SW : Switched on
OE : Operation enabled
QSA : Quick stop active
FRA : Fault reaction active
F : Fault
NRSO --> SOD : 自動
SOD --> RTSO : 0x06 Shutdown
RTSO --> SW : 0x07 Switch on
SW --> OE : 0x0F Enable operation
OE --> SW : 0x07 Disable operation
OE --> RTSO : 0x06 Shutdown
RTSO --> SOD : 0x00 Disable voltage
SW --> SOD : 0x00 Disable voltage
OE --> SOD : 0x00 Disable voltage
OE --> QSA : Quick stop (bit2 = 0)
QSA --> SOD : 自動(停止完了)
QSA --> OE : 0x0F(許可時のみ)
OE --> FRA : Fault
FRA --> F : 自動
F --> SOD : 0x80 Fault reset
note right of F : Fault はどの状態からでも入りうる
Controlword のビット
bit 0 : Switch on
bit 1 : Enable voltage
bit 2 : Quick stop ← アクティブ Low(0 で急停止が発動)
bit 3 : Enable operation
bit 7 : Fault reset ← 立ち上がりエッジで作用
先ほどの3つの値を分解すると:
| 値 | bit3 | bit2 | bit1 | bit0 | コマンド名 |
|---|---|---|---|---|---|
| 0x06 | 0 | 1 | 1 | 0 | Shutdown |
| 0x07 | 0 | 1 | 1 | 1 | Switch on |
| 0x0F | 1 | 1 | 1 | 1 | Enable operation |
bit2(Quick stop)はアクティブ Low で、私は最初ここを読み違えました。1 が「急停止していない」を表すので、0x06 にも 0x07 にも bit2 が立っています。
同じ 0x07 が、2つの意味を持つ
CiA 402 を読んでいて面白かったのがここです。0x07 は "Switch on" であり、同時に "Disable operation" でもあります。
- Ready to switch on で 0x07 を書くと Switched on に進む(投入)
- Operation enabled で 0x07 を書くと Switched on に戻る(停止)
同じ16ビットのパターンが、受け取った側の状態によって正反対の意味になります。第1回の Modbus では「番地に何が入っているかが線の上に書かれていない」という話でしたが、CiA 402 では意味そのものが文脈の中にあり、プロトコルを読むときはその文脈も一緒に追うことになります。
Statusword の読み方
サーボは 0x6041 で状態を返します。
bit 0 : Ready to switch on
bit 1 : Switched on
bit 2 : Operation enabled
bit 3 : Fault
bit 4 : Voltage enabled
bit 5 : Quick stop ← アクティブ Low
bit 6 : Switch on disabled
bit 10: Target reached
状態の判定表:
| bit6 | bit5 | bit3 | bit2 | bit1 | bit0 | 状態 |
|---|---|---|---|---|---|---|
| 1 | x | 0 | 0 | 0 | 0 | Switch on disabled |
| 0 | 1 | 0 | 0 | 0 | 1 | Ready to switch on |
| 0 | 1 | 0 | 0 | 1 | 1 | Switched on |
| 0 | 1 | 0 | 1 | 1 | 1 | Operation enabled |
| 0 | 0 | 0 | 1 | 1 | 1 | Quick stop active |
| 0 | x | 1 | 0 | 0 | 0 | Fault |
この表が見ているのは下位7ビットだけです。実例の 0x0250 や 0x0237 にある上位バイト 0x02 は bit9(Remote。この実装では常に 1)で、bit4(Voltage enabled)も常に 1 なので、実際に返る Statusword は表の値に 0x200 と 0x10 が乗ります。
動作モード — 軌道を誰が作るか
0x6060 に書く番号でモードを選びます。
| 値 | 略号 | 名前 |
|---|---|---|
| 1 | pp | Profile position |
| 3 | pv | Profile velocity |
| 6 | hm | Homing |
| 8 | csp | Cyclic synchronous position |
| 9 | csv | Cyclic synchronous velocity |
| 10 | cst | Cyclic synchronous torque |
「csp は位置制御、csv は速度制御」という説明を見かけますが、それだけだと pp / pv との違いが残ります。私が分けやすいと感じたのは「軌道を誰が作るか」という軸のほうでした。
| 軌道生成 | マスタがやること | |
|---|---|---|
| pp / pv | サーボの中 | 「そこまで、この速度で行け」と1回言う |
| csp / csv / cst | マスタ側 | 毎周期、次の1点を送り続ける |
csp は、マスタが「1ms 後はここ、その次はここ」と点を打ち続け、サーボはひたすら追従します。周期が確実に来ることが前提です。
③ 手を動かす
構成は前編と同じ、1プロセスの中にマスタ役(メイン)とサーボ役(スレッド)を同居させる形です。マスタは NMT で Operational にし、SDO で 0x06、0x07、0x0F を順に書いて、モードを csp にします。以降は RPDO で目標位置を送り、TPDO で現在位置を受け取ります。
状態機械の実装
規格の状態遷移は、ほぼそのまま辞書1つで書けます。
TRANSITIONS = {
(State.SWITCH_ON_DISABLED, Command.SHUTDOWN): State.READY_TO_SWITCH_ON,
(State.READY_TO_SWITCH_ON, Command.SWITCH_ON): State.SWITCHED_ON,
(State.SWITCHED_ON, Command.ENABLE_OPERATION): State.OPERATION_ENABLED,
(State.OPERATION_ENABLED, Command.SWITCH_ON): State.SWITCHED_ON, # ← 同じコマンド
# …(Quick stop / Fault 系は付録)
}
(READY_TO_SWITCH_ON, SWITCH_ON) の行と (OPERATION_ENABLED, SWITCH_ON) の行を見比べると、同じ Command.SWITCH_ON(= 0x07)が、キーの左側の状態によって行き先を変えているのが分かります。これが先ほどの「同じビット列が正反対の意味を持つ」の実装です。
上に載せたのは抜粋で、実装には Ready to switch on から直接 Operation enabled へ飛ぶ近道や Quick stop / Fault 系を含めて13エントリあります。
Statusword の判定は、②の表を (マスク, 一致値, 状態) の並びに移すだけです(cia402.py の STATE_PATTERNS)。マスクが 0x6F と 0x4F の2種類あるところも、そのままコードに出ます。
実行結果(出力から抜粋)
まず状態機械を進めます。
起動直後 Statusword = 0x0250 -> Switch on disabled
Controlword = 0x06 (Shutdown) Statusword = 0x0231 -> Ready to switch on
Controlword = 0x07 (Switch on) Statusword = 0x0233 -> Switched on
Controlword = 0x0F (Enable operation) Statusword = 0x0237 -> Operation enabled
SDO write 0x6060 = 8 -> 0x6061 (実際のモード) = 8 (csp)
Statusword が 0x0250 から 0x0231、0x0233、0x0237 と変わっていくのが、規格の状態機械そのものです。続いて 10ms 周期で目標位置を送ります。
t=0.001s target= 0 actual= 0 status=0x0237
t=0.101s target= 10000 actual= 6229 status=0x0237
t=0.302s target= 10000 actual= 9854 status=0x0237
t=0.591s target= 10000 actual= 9999 status=0x0237
取りこぼした SYNC 応答: 0 / 60
最後に、同じ 0x07 を Operation enabled の状態で書いてみます。
0x0006 : Switch on disabled -> Ready to switch on
0x0007 : Ready to switch on -> Switched on ← 投入
0x000F : Switched on -> Operation enabled
0x0007 : Operation enabled -> Switched on ← 停止(同じ 0x0007)
追従カーブ

階段状に見えるのは描画の粗さではなく、値が SYNC の瞬間にしか更新されないからです。10ms ごとに1段ずつ上がる形に、「マスタが毎周期1点を送る」という csp の性格がそのまま出ています。
④ 発展: EtherCAT との関係
ここまでのコードは、0x6040 に 0x06 / 0x07 / 0x0F を順に書き、0x6041 のビットで状態を判定し、0x607A に目標位置を流し込むだけです。これは EtherCAT のサーボでもそのまま通用します。EtherCAT には CoE(CANopen over EtherCAT)という仕組みがあり、CiA 402 の意味論をそのまま採用しているからです。
ただし同じなのは意味論だけで、配管は別物です。COB-ID は使わず SyncManager に直接置かれ、NMT は ESM になり、SYNC は Distributed Clocks(100ns 未満)に置き換わり、PDO の8バイト制限も無くなります。
EtherCAT をこのシリーズで動かさないのは、「PCの中では動かせないから」ではありません。KickCAT(CeCILL-C)というオープンソース実装には ESC のソフトウェアエミュレータが入っていて、ハードウェアなしでマスタとスレーブを会話させられます。扱わない理由は、仕様書(ETG.1000)が ETG 会員限定で引用できないことと、C++ と CMake のビルドが要ることです。このシリーズは pip install だけで完結することを条件にしているので、外しました。
なお本物のスレーブに ESC が必要なのは事実です。「フレームが通過している最中に処理する」という核心は専用ハードでしか成立せず、エミュレータで学べるのは手順と構造であってタイミングではありません。前編で仮想CANバスについて書いたのと、まったく同じ話です。
コード
この連載で動かすコードは、5回ぶんまとめて GitHub に置いてあります。今回の 02_canopen/ のほか、Modbus と OPC UA も同じリポジトリです。
https://github.com/logicia32/industrial-networks-lab
次回
CANopen のサーボは動きました。しかしこのサーボは、制御盤の中に閉じています。外から使おうとすると、無いものが3つ出てきます。認可(誰が読んでよく、誰が書いてよいのか)、型と意味(0x6064 は何の値で単位は何か)、発見(このネットワークに何が何台いるのか)です。
EDS ファイルは2番目の答えを一部持っていますが、それはファイルであって、ネットワークの上にはありません。
次回は OPC UA です。この3つを正面から扱った規格です。
今回使ったもののライセンス
python-can 4.6.1(LGPL-3.0-only)/ matplotlib 3.10.7(PSF-based)。いずれもオープンソースです。
仕様書は、CiA 301(通信プロファイル)は無料(要・無料登録)ですが、CiA 402-2 / 402-3 と CiA 306(EDS)は CiA 会員限定です。これに由来する国際規格 IEC 61800-7-201 / -301 は有料(CHF 475 / 430)で、しかも IEC 版は2015年で止まっているため内容は同一ではありません(CiA 402-2 は 2024年に v5.0.0)。そのため、この記事では「由来する」と書いています。
この記事は Zenn に初出したものを加筆・補足したものです ── Zenn の元記事を見る