目次
CAN通信はBosch社が作ったノイズに強い自動車の通信プロトコルというのを授業で習いました。また、大学院に進学してから周囲がロボコン部だらけになった結果、RoboMasterのモーター(通称ロボマスモーター)という高級でとても良いモーターを動かすのもCAN通信だというのを知りました。
マイコンでCAN通信をするには、CANコントローラを内蔵するマイコン(ESP32, STM32, Arduino Uno R4等)とCANトランシーバを組み合わせるか、CANコントローラーとCANトランシーバの両方を任意のマイコンと組み合わせて使うことになります。

RP2040はCANコントローラを内蔵していないため、CAN通信をするには上記のようなCAN Busモジュール(CANコントローラ+CANトランシーバ)を使う必要があります。
RP2040はコスパが良いため非常に気に入っているのですが、CANコントローラを内蔵していない点が残念ポイントです。
という話を研究室によく遊びに来るロボコンOBの後輩(時系列的には後のキャチロボ制御班長)としていたのですが、気づいたら彼がRP2040のプログラマブルI/O(PIO)を使ってCANコントローラを代替するArduinoライブラリを作ってしまっていました。
つまり、Raspberry Pi PicoはCANトランシーバのみでCAN通信ができます。
ロボマスモーターがそこら辺に転がっている環境なんて今しかないので、今のうちにRaspberry Pi Picoで回しておきます。
M2006とC610は大学院生活の間でかなり馴染んだので私物で1セット欲しいです。いつでもあの音を聴きたい。
Raspberry Pi Picoを使います。純正です。
RoboMaster M2006を回します。使うESCはRoboMaster C610です。
仕様のページからユーザーガイドの8ページを見るとCANのプロトコルが書かれています。
CAN Busのビットレートが1Mbpsで、フィードバックは1kHzで送られてきます。
CAN Bus1つに送信用IDが2つ、ID1つでモーターを4個まで制御できて、モーターへの指令値は電流です(-10000~10000 mA)(つまり-10A~10A)。
取得できる情報は、モーターごとの絶対角(0~8191:13bitのアブソリュートエンコーダ)、ローター速度(rpm)、トルク電流(mA)になります。
トルク(電流)制御なのでモーターは摩擦や負荷と釣り合うまで加速し続けます。電流をものすごく小さくするか、速度制御か位置制御を入れないと怖いです。
SN65HVD230を使います。3.3V電源で動作するのが良いですね。
あとJLCPCBのPreferred (Extended) Partsになっているため、実質Basic Parts扱いなのが嬉しいです。(Extended Partsだと部品追加1種ごとに3ドルの手数料が掛かる)
1つあたりも安いので、マイコン搭載CAN接続モタドラをPCBAするのにもじゃんじゃん使えます。

今回はAliExpressで買ったモジュールを使いました。
MCP2515+TJA1050のCAN Busモジュールと比べると小さくて良いですね。

120Ωの終端抵抗が実装されています。
10kΩの抵抗はMode select pinをGNDにプルダウンしているものです。これでSlope Control Modeになるらしい。
Slope Control Mode
GND直結で選べるHigh-Speed Modeは出力の立ち上がりに制限を設けず、Slope Control Modeは立ち上がりを制御する。
10kΩだと約15V/μsのスルーレートになる。
急激な立ち上がりだと電波干渉や高調波の影響があるのでそれを低減するためのものらしい。
あって動くのならこのままで良さそう。
Earle Philhower氏によるRaspberry Pi Pico/RP2040用のArduino coreであるArduino-Picoを使用します。
Arduino IDEの設定で追加のボードマネージャーのURLとして
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
を入れると表示されるようになります。
導入方法は下記ドキュメント参照。
Arduino IDEからインストール可能です。

速度(ローターのRPS)のP制御をします。10rpsは減速前のローターです。指で触れるくらいの動き方になります。
#include <RP2040PIO_CAN.h>
const uint8_t CAN_TX_PIN = 0; //連続してなくてもいい
const uint8_t CAN_RX_PIN = 1; //GP1とGP3みたいな組み合わせでも動く
const float P_GAIN = 100.0f; // Pゲイン
const float TARGET_RPS = 10.0f; // 目標回転数
const int16_t CURRENT_LIMIT_MA = 10000; // 電流指令値の制限 (mA) (-10A to +10A)
const uint32_t MOTOR_COMMAND_ID = 0x200; // 指令を送信するCAN ID
float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void setup() {
Serial.begin(115200);
CAN.setTX(CAN_TX_PIN);
CAN.setRX(CAN_RX_PIN);
CAN.begin(CanBitRate::BR_1000k); //1Mbps
}
void loop() {
while (CAN.available()) {
CanMsg rxMsg = CAN.read();
int16_t rotorPositionRaw = (int16_t)(rxMsg.data[0] << 8 | rxMsg.data[1]);
int16_t rpm = (int16_t)(rxMsg.data[2] << 8 | rxMsg.data[3]);
int16_t actualTorqueCurrent = (int16_t)(rxMsg.data[4] << 8 | rxMsg.data[5]);
//data[6] and data[7] are Null
float rotorDegree = fmap(rotorPositionRaw, 0, 8192.0f, 0, 360.0f);
float rps = rpm / 60.0f;
float error = TARGET_RPS - rps;
float targetCurrent = P_GAIN * error;
int16_t commandCurrent = constrain(targetCurrent, -CURRENT_LIMIT_MA, CURRENT_LIMIT_MA);
CanMsg txMsg = {};
txMsg.id = MOTOR_COMMAND_ID;
txMsg.data_length = 8;
txMsg.data[0] = commandCurrent >> 8; // モーターid1 電流値上位バイト8ビット
txMsg.data[1] = commandCurrent; // モーターid1 電流値下位バイト8ビット
// txMsg.data[2] = commandCurrent >> 8; // モーターid2 電流値上位バイト8ビット
// txMsg.data[3] = commandCurrent; // モーターid2 電流値下位バイト8ビット
// txMsg.data[4] = commandCurrent >> 8; // モーターid3 電流値上位バイト8ビット
// txMsg.data[5] = commandCurrent; // モーターid3 電流値下位バイト8ビット
// txMsg.data[6] = commandCurrent >> 8; // モーターid4 電流値上位バイト8ビット
// txMsg.data[7] = commandCurrent; // モーターid4 電流値下位バイト8ビット
CAN.write(txMsg);
Serial.print("ID: 0x");
Serial.print(rxMsg.id, HEX);
Serial.print(", Angle: ");
Serial.print(rotorDegree);
Serial.print(" deg, RPS: ");
Serial.print(rps);
Serial.print(", Command_mA: ");
Serial.print(commandCurrent);
Serial.print(", Actual_mA: ");
Serial.print(actualTorqueCurrent);
Serial.println();
}
delay(1);
}
Raspberry Pi PicoのGP0をCTXに、GP1をCRXに、CANHとCANH、CANLとCANLを繋いで電源を繋ぎます。

CANからの読み込みは4μs、書き込みは20μs程度でした。(オシロで雑に計測)
計算処理は考慮していませんが、CAN Bus2系統でモーター8個でも1kHzで制御できそうです。
可変抵抗で双方向速度制御(P制御)

可変抵抗で位置制御
後に釣り糸に一定のテンションを掛けながら巻き取る装置に使うことになる、重力補償っぽいけどただの電流制御