CAN通信でDJIのモーター＋センサー「GM6020」を使う

モーターを制御しつつ回転角度を細かく把握する必要があるプロジェクトがあり、いろいろ試した結果GM6020というモーターを使ったけど、ネット上にほぼ情報がなかったのでメモ。

# GM6020とは？

![small_f01dac29-0580-4af4-96f1-01441ec6ba25@2x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486013/02d9f6c4-e496-5eae-e573-146af2a9002a.jpeg)
画像引用元：[DJI](https://store.dji.com/jp/product/rm-gm6020-brushless-dc-motor)

GM6020はドローンやジンバルで有名なDJIが作っているドライバー搭載ブラシレスDCモーターです。PWMもしくはCANで制御できます。CANを使うと角度、回転速度、トルクの値を取得することができます。1万8千円。高い...。
https://store.dji.com/jp/product/rm-gm6020-brushless-dc-motor


# CAN通信とは？

CANはドイツのBosch社が開発した自動車内の機器同士の通信などに使われている通信プロトコルです。

>Controller Area Network (CAN) は、ビークルバス規格の一種で、ホストコンピュータなしでマイクロコントローラやデバイスが相互に通信できるように設計されている。耐ノイズ性の強化が考慮された堅牢な規格である。メッセージベースのプロトコルであり、元々は、自動車内部の多重化電気配線用に設計されたものだが、機器の制御情報の転送用として普及しており、輸送用機械、工場、工作機械などのロボット分野においても利用されている。自動車においては、速度、エンジンの回転数、ブレーキの状態、故障診断の情報などの転送に使用されている。

[Wikipedia](https://ja.wikipedia.org/wiki/Controller_Area_Network)より引用

# 使ったパーツ

今回はMacからモーターの制御をしたり回転角度を取得したかったのでArduinoのUSB Serial通信経由でモーターとやりとりする構成を組みました。CAN通信のためのシールドはSparkFunのものを使いましたが、ネットで調べると同じチップを使っていてもっと安いものもあるし自作している人もいました。

- [GM6020本体](https://store.dji.com/jp/product/rm-gm6020-brushless-dc-motor)
- [SparkFun CAN-BUS Shield](https://www.sparkfun.com/products/13262)
- [Arduino Uno](https://store.arduino.cc/usa/arduino-uno-rev3)
- DC24V電源

# 配線


![GM6020配線.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486013/a3697a19-bd41-7d9c-645d-0991fc1a4164.png)


### 電源
GM6020に電源供給するため、付属の電源ケーブルを改造してDC24V電源と接続します。

### CAN通信
付属のCAN用ケーブルを改造してCAN-BUSシールドに接続します。黒がCAN-Lowで赤がCAN-Highです。CAN-BUSシールドのCAN H、CAN Lと接続します。
※5V側（CAN/Arduino）のGNDと24V側（モーター）のGNDを接続すべきなのか、切り離しておいた方が良いのか分からず、とりあえず接続しなくてもちゃんと通信できているので接続していません。

### Arduino
CAN-BUSシールドは普通にピンヘッダをハンダ付けして、Arduinoに挿すだけです。

# プログラム

### ライブラリ
[SparkFunの公式のもの](https://github.com/sparkfun/SparkFun_CAN-Bus_Arduino_Library)は通信速度が500kbpsまでしか設定できなかったので、以下のものを使いました。
https://github.com/autowp/arduino-mcp2515

とりあえず、examplesの中のCAN_readをそのまま通信速度をCAN_1000KBPSに変えればモーターからのデータを受信できます。

### GM6020の設定

GM6020のCAN通信の仕様やIDの設定方法などは以下のドキュメントに書いてあります。今回はモーター1台だけを接続するので、このドキュメントにしたがってDIPスイッチでモーターIDを1に設定し、ターミナル抵抗をONにします。正しく設定できていればステータスLEDが1秒間隔で1回ずつ点滅します。

[日本語版ドキュメントPDF](https://rm-static.djicdn.com/tem/17348/RoboMaster%20GM6020%20ブラシレスDCモーター.pdf)
[英語版ドキュメントPDF](https://rm-static.djicdn.com/tem/3724/RoboMaster%20GM6020%20Brushless%20DC%20Motor%20User%20Guide.pdf)

### ArduinoのC++コード
ドキュメントに従ってCAN通信のフレームを送受信することでモーターの回転を制御したり、センサーの情報を読み取ることができます。

```C++
#include <SPI.h>
#include <mcp2515.h>

struct can_frame canMsgReceive;
struct can_frame canMsgSend;

MCP2515 mcp2515(10);

short power = 3000; // -30000 to 30000
float rotation = 0; // 0 - 360
short speed = 0;
short torque = 0;
int8_t temperature = 0;


//--------------------------------------------------
void setup() {
  Serial.begin(115200);
  SPI.begin();
  canMsgSend.can_id  = 0x1ff; // for Motor ID 1 to 4
  canMsgSend.can_dlc = 8;
  canMsgSend.data[0] = 0;
  canMsgSend.data[1] = 0;
  canMsgSend.data[2] = 0;
  canMsgSend.data[3] = 0;
  canMsgSend.data[4] = 0;
  canMsgSend.data[5] = 0;
  canMsgSend.data[6] = 0;
  canMsgSend.data[7] = 0;
  
  mcp2515.reset();
  mcp2515.setBitrate(CAN_1000KBPS);
  mcp2515.setNormalMode();

  Serial.println("------- GM6020 Test ----------");
}


//--------------------------------------------------
void loop() {
  
  // read values via can
  readValues();
  // send values via can
  sendValues();
  
  delay(1);
}

//--------------------------------------------------
void readValues() {
  if (mcp2515.readMessage(&canMsgReceive) == MCP2515::ERROR_OK) {
    
    if (canMsgReceive.can_id == 0x205) { // Motor ID 1
      // rotation
      unsigned short r = (canMsgReceive.data[0] << 8) | (canMsgReceive.data[1] & 0xff);
      rotation = (float)r / 8192 * 360;
  
      // speed
      speed = (canMsgReceive.data[2] << 8) | (canMsgReceive.data[3] & 0xff);
  
      // torque
      torque = (canMsgReceive.data[4] << 8) | (canMsgReceive.data[5] & 0xff);

      // temperature
      temperature = canMsgReceive.data[6];
      
      
      Serial.print("rotation:");
      Serial.print(rotation);
      Serial.print(" ");
      
      Serial.print("speed:");
      Serial.print(speed);
      Serial.print(" ");
      
      Serial.print("torque:");
      Serial.print(torque);
      Serial.print(" ");
      
      Serial.print("temperature:");
      Serial.print(temperature);
      Serial.print(" ");
      
      Serial.println();      
    }
  }
}

//--------------------------------------------------
void sendValues() {
  canMsgSend.data[0] = power >> 8 & 0xff;
  canMsgSend.data[1] = power & 0xff;
  mcp2515.sendMessage(&canMsgSend);
}
```

#### sendValues()
CAN ID:0x1ff（モーターIDが1〜4の場合） でモーターに送信する電圧の値を-30000から30000の値2byteの整数値で送ります。（日本語版のドキュメントだと同じ日付のPDFなのに-25000から25000になってる。翻訳ミス？）

#### readValues()
CAN ID: 0x205（モーターIDが1の場合）で受信したバイナリデータから角度や回転速度、トルク、モーター温度を取得します。角度はunsigned、回転速度とトルクはsingedの2byte整数です。温度はたぶん摂氏。

# あとがき
英語でも中国語でもほとんど情報がないので使ってる人少なそうですが、DJI製品なのでとてもしっかりした作りで、中にケーブル通せるようにドーナツ型になってたり、電圧の値を30000とかにするとめちゃくちゃ高速回転もできるし、角度もかなり精密に取得できるのでとても便利なモーターです。高いけど。