OdriveでBLDCを動かしてみよう!

# 本記事の目的

Odriveを使用してBLDCのモーター制御を実行します

# 環境
本記事は以下の環境で実験しています.

| 項目 | バージョン |
|:-:|:-:|
| Ubuntu | 18.04 |
| Odrive | 0.5.2 |

**実験環境**

<table>
<tr>
<td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/7cd0d5dd-5dce-5c14-2a2b-e4bd596e59b7.jpeg" width="300"></td>
<td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/81c42806-fb26-bb0b-e96c-204baf9ec1be.jpeg" width="300"></td>
</tr>
</table>

**Hall wireの接続**

| Hall wire | J4 signal |
|:-:|:-:|
| Red | 5V |
| Yellow | A |
| Blue | B |
| Green | Z |
| Black | GND |

![IMG_1839.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/7cfa270b-3a36-e1b5-76b6-d4d6b55206df.jpeg)

**必要機材**
- [Odrive](https://odriverobotics.com/shop/odrive-v36)
- [インホイールモーター](https://ja.aliexpress.com/item/32949771012.html?spm=a2g0s.9042311.0.0.3efc4c4dFsWZqc)
- [安定化電源](https://www.amazon.co.jp/gp/product/B088M3CN5P/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&psc=1)
- [Wire 10AWG](https://www.amazon.co.jp/gp/product/B07CWWGMQQ/ref=ppx_yo_dt_b_asin_title_o06_s00?ie=UTF8&psc=1)
- [アルミフレーム 2020](https://jp.misumi-ec.com/vona2/detail/110302683830/?list=PageCategory)
- [クランプ](https://www.amazon.co.jp/gp/product/B07L6T5SMT/ref=ppx_yo_dt_b_asin_image_o01_s00?ie=UTF8&psc=1)

# Odrivetoolインストール

以下のコマンドを順に打ってodriveをインストールします。

```bash:Odriveインストールコマンド
$ sudo apt update
$ sudo apu upgrade
$ sudo apt install python3 python3-pip 
$ sudo pip3 install --upgrade odrive
$ echo "PATH=$PATH:~/.local/bin/" >> ~/.bashrc 
```

無事にインストールされた場合、**odrivetool**と打てば以下のような表示が出るはずです。

```bash:odrivetool動作確認
$ odrivetool

ODrive control utility v0.5.2
Please connect your ODrive.
Type help() for help.
ash
Connected to ODrive 207B346D5748 as odrv0
```

**Connected to ODrive 207B346D5748 as odrv0**の部分は、odriveに外部電源から電源供給せずにPCに接続しても表示されないので電源供給してから**odrivetool**と打ちましょう。
また207B346D5748はodriveの識別ID、odrv0がodriveのボードを意味します。複数個のodriveを使用する場合、odrv1, odrv2...などと表示されます。

次に以下のコマンドでファームウェアをupdateします。

```bash
$ sudo odrivetool dfu
```

無事成功すると以下のように表示されます。

```bash
ODrive control utility v0.5.2.post0
Waiting for ODrive...
Found ODrive 207B346D5748 (v3.6-24V) with firmware [unknown version] in DFU mode
Checking online for newest firmware... found v0.5.2
Downloading firmware v0.5.2...
The configuration cannot be backed up because the device is already in DFU mode. The configuration may be lost after updating. Do you want to continue anyway? [Y/n] Y
Erasing... done            
Flashing... done            
Verifying... done            
Device firmware update successful.
```

# BLDC速度制御(1輪のみ)

odriveではボードの永続メモリにconfigを保存してから、ターミナルで指令値を送る事でモーターを回転させる事ができます。ここでは片輪のモーターの速度制御のためのconfigを記載します。**しかし、このconfigやキャリブレーションが一筋縄ではいかなかったのでここではその経緯についても補足します。**config内の各種パラメータについてはdocumentを参照してください。

https://docs.odriverobotics.com/odrivetool

まず以下のconfigをodriveに保存します。

```bash:config内容
odrv0.axis0.motor.config.pole_pairs = 15
odrv0.axis0.motor.config.resistance_calib_max_voltage = 4
odrv0.axis0.motor.config.requested_current_range = 25 
odrv0.axis0.motor.config.current_control_bandwidth = 100

odrv0.axis0.motor.config.torque_constant = 8.27 / 16
odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL
odrv0.axis0.encoder.config.cpr = 90
odrv0.axis0.encoder.config.calib_scan_distance = 150

odrv0.axis0.encoder.config.bandwidth = 100
odrv0.axis0.controller.config.pos_gain = 1
odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_limit = 10

odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL

odrv0.config.enable_brake_resistor = True
odrv0.axis0.motor.config.current_lim = 5
odrv0.axis0.motor.config.calibration_current = 5
odrv0.axis0.encoder.config.hall_polarity_calibrated = True 

odrv0.save_configuration()
odrv0.reboot()
```

次にモーターのキャリブレーションを行います。モーターのキャリブレーション時には、モーターからビープ音がなり、モーターに一定の電流値が流れます。ここでビープ音が鳴らなければconfig内のキャリブレーション関係のパラメータ設定に問題がある可能性があります。具体的なエラー内容については**dump_errors(odrv0)**で確認しましょう。

```bash:モーターキャリブレーション
odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
odrv0.axis0.motor
```

次にエンコーダのオフセットキャリブレーションを行います。エンコーダオフセットキャリブレーション時にはゆっくりモーターが正転一回、逆転一回動作します。ここでモーターが回らない場合はconfig設定や配線等に問題がある可能性があります。

```bash:エンコーダオフセットキャリブレーション
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis0.encoder
```

しかし私の場合、このエンコーダオフセットキャリブレーションで正転一回、逆転一回したにも関わらず、以下のようなエラーが発生していました。

```bash
system: no error
axis0
  axis: no error
  motor: no error
  sensorless_estimator: no error
  encoder: Error(s):
    ENCODER_ERROR_ILLEGAL_HALL_STATE
  controller: no error
axis1
  axis: no error
  motor: no error
  sensorless_estimator: no error
  encoder: no error
  controller: no error
```

エラー内容は**ENCODER_ERROR_ILLEGAL_HALL_STATE**の部分です。エラーの原因について調べると以下のようなサイトに行きつきました。

https://discourse.odriverobotics.com/t/encoder-error-error-illegal-hall-state/1047/7?u=madcowswe

結局のところ上記**エラーの原因はエンコーダーのノイズ**によるところらしいです。ノイズが酷い事によりエンコーダーからの信号が全て0の場合に発生するエラーです。このエラーに対処する方法は、各エンコーダー信号ピンとGND間に22nF~47nF程度のコンデンサを入れてノイズ除去することで解決することができます。

ノイズ除去のコンデンサーとしては、**セラミックコンデンサー**もしくは**フィルムコンデンサー**を使用してください。私はamazonで**フィルムコンデンサー**を購入しました。

https://www.amazon.co.jp/gp/product/B08H59TK6F/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&psc=1

以下の画像のように22nFのコンデンサーを半田付けを行います。

![IMG_1823.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/d5c89e9f-f744-9a8d-bcfb-423fd24fb77e.jpeg)

**この状態で再度エンコーダオフセットキャリブレーションを行うとエラーが無くなりました！**

またエンコーダのデバックについては**odrv0.axis0.encoder**の**hall_state**も見ておきましょう。hall_stateが0か7の場合、何か異常があるサインです。**本来は、キャリブレーション中にhall_stateを監視し続けて、0か7になる瞬間があるかどうかで判断するのが良いと思われます。**

```
In [37]: odrv0.axis0.encoder
Out[37]: 
calib_scan_response: 144.0 (float)
config:
  abs_spi_cs_gpio_pin: 1 (uint16)
  bandwidth: 100.0 (float)
  calib_range: 0.019999999552965164 (float)
  calib_scan_distance: 150.0 (float)
  calib_scan_omega: 12.566370964050293 (float)
  cpr: 90 (int32)
  direction: -1 (int32)
  enable_phase_interpolation: True (bool)
  find_idx_on_lockin_only: False (bool)
  hall_polarity: 0 (uint8)
  hall_polarity_calibrated: False (bool)
  ignore_illegal_hall_state: False (bool)
  index_offset: 0.0 (float)
  mode: 1 (uint16)
  phase_offset: -23 (int32)
  phase_offset_float: -0.4535244107246399 (float)
  pre_calibrated: False (bool)
  sincos_gpio_pin_cos: 4 (uint16)
  sincos_gpio_pin_sin: 3 (uint16)
  use_index: False (bool)
  use_index_offset: True (bool)
count_in_cpr: 48 (int32)
delta_pos_cpr_counts: 5.605193857299268e-45 (float)
error: 528 (uint16)
hall_state: 5 (uint8)
index_found: False (bool)
interpolation: 0.5 (float)
is_ready: True (bool)
phase: 0.04866981506347656 (float)
pos_abs: 0 (int32)
pos_circular: 0.534058153629303 (float)
pos_cpr_counts: 48.02492141723633 (float)
pos_estimate: 0.5333594679832458 (float)
pos_estimate_counts: 48.00235366821289 (float)
set_linear_count(obj: object_ref, count: int32)
shadow_count: 48 (int32)
spi_error_rate: 0.0 (float)
vel_estimate: 0.0 (float)
vel_estimate_counts: 0.0 (float)
```

エラーが無くなったので、キャリブレーション内容を永続メモリに保存します。**永続メモリに保存する事で次回以降キャリブレーションをする必要がなくなります。**永続メモリに保存するコマンドは以下の通りです。

- モーターのキャリブレーションの場合  
**odrv0.axis0.motor.config.pre_calibrated = True**
- エンコーダオフセットキャリブレーションの場合  
**odrv0.axis0.encoder.config.pre_calibrated = True**

最後にconfigをsaveしてrebootします。

```bash:キャリブレーションを保存
odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
odrv0.axis0.motor
odrv0.axis0.motor.config.pre_calibrated = True

odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis0.encoder
odrv0.axis0.encoder.config.pre_calibrated = True

odrv0.save_configuration()
odrv0.reboot()
```

次にモータのrequested_stateを**AXIS_STATE_CLOSED_LOOP_CONTROL**にして、指令値を送る事でモータが回転します。今回、control_modeが**CONTROL_MODE_VELOCITY_CONTROL**なので**odrv0.axis0.controller.input_vel**コマンドで指令値を送ります。

```bash:指令値を送信
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis0.controller.input_vel = 2
```

これでモーターを速度制御する事が出来ました！

![one2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/9a1a50c2-9b8a-d940-d569-753b42621be1.gif)

上記一連のコマンドを記載いたします。以下の内容を順にconfig及びターミナルで実行すれば、モーターの速度制御が出来ると思います。

```bash:全体のconfig及びキャリブレーション内容
odrv0.axis0.motor.config.pole_pairs = 15
odrv0.axis0.motor.config.resistance_calib_max_voltage = 4
odrv0.axis0.motor.config.requested_current_range = 25 
odrv0.axis0.motor.config.current_control_bandwidth = 100

odrv0.axis0.motor.config.torque_constant = 8.27 / 16
odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL
odrv0.axis0.encoder.config.cpr = 90
odrv0.axis0.encoder.config.calib_scan_distance = 150

odrv0.axis0.encoder.config.bandwidth = 100
odrv0.axis0.controller.config.pos_gain = 1
odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_limit = 10

odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL

odrv0.config.enable_brake_resistor = True
odrv0.axis0.motor.config.current_lim = 5
odrv0.axis0.motor.config.calibration_current = 5
odrv0.axis0.encoder.config.hall_polarity_calibrated = True 

odrv0.save_configuration()
odrv0.reboot()

odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
odrv0.axis0.motor
odrv0.axis0.motor.config.pre_calibrated = True

odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis0.encoder
odrv0.axis0.encoder.config.pre_calibrated = True

odrv0.save_configuration()
odrv0.reboot()

odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis0.controller.input_vel = 2

odrv0.axis0.requested_state = AXIS_STATE_IDLE
```

# BLDC速度制御(2輪)

1つのodriveボードで制御できるモータの個数は2個までです。ここでは上記のconfigを参考に2個のBLDCを同時に回転させるためのconfigを示します。1個目のモータの軸は**odrv0.axis0**、2個目のモータの軸は**odrv0.axis1**です。1輪のみの速度制御のconfigとの違いは、axis0と同じ内容をaxis1でも同様に設定する事です。

```bash:2輪動作のconfig内容
odrv0.axis0.motor.config.pole_pairs = 15
odrv0.axis0.motor.config.resistance_calib_max_voltage = 4
odrv0.axis0.motor.config.requested_current_range = 25 
odrv0.axis0.motor.config.current_control_bandwidth = 100

odrv0.axis1.motor.config.pole_pairs = 15
odrv0.axis1.motor.config.resistance_calib_max_voltage = 4
odrv0.axis1.motor.config.requested_current_range = 25 
odrv0.axis1.motor.config.current_control_bandwidth = 100

odrv0.axis0.motor.config.torque_constant = 8.27 / 16
odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL
odrv0.axis0.encoder.config.cpr = 90
odrv0.axis0.encoder.config.calib_scan_distance = 150

odrv0.axis1.motor.config.torque_constant = 8.27 / 16
odrv0.axis1.encoder.config.mode = ENCODER_MODE_HALL
odrv0.axis1.encoder.config.cpr = 90
odrv0.axis1.encoder.config.calib_scan_distance = 150

odrv0.axis0.encoder.config.bandwidth = 100
odrv0.axis0.controller.config.pos_gain = 1
odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
odrv0.axis0.controller.config.vel_limit = 10

odrv0.axis1.encoder.config.bandwidth = 100
odrv0.axis1.controller.config.pos_gain = 1
odrv0.axis1.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis1.encoder.config.cpr
odrv0.axis1.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis1.encoder.config.cpr
odrv0.axis1.controller.config.vel_limit = 10

odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
odrv0.axis1.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL

odrv0.config.enable_brake_resistor = True
odrv0.axis0.motor.config.current_lim = 5
odrv0.axis0.motor.config.calibration_current = 5
odrv0.axis0.encoder.config.hall_polarity_calibrated = True 

odrv0.axis1.motor.config.current_lim = 5
odrv0.axis1.motor.config.calibration_current = 5
odrv0.axis1.encoder.config.hall_polarity_calibrated = True 

odrv0.save_configuration()
odrv0.reboot()

odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
odrv0.axis0.motor.config.pre_calibrated = True

odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis0.encoder.config.pre_calibrated = True

odrv0.axis1.requested_state = AXIS_STATE_MOTOR_CALIBRATION
odrv0.axis1.motor.config.pre_calibrated = True

odrv0.axis1.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
odrv0.axis1.encoder.config.pre_calibrated = True

odrv0.save_configuration()
odrv0.reboot()

odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis1.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis0.controller.input_vel = 2
odrv0.axis1.controller.input_vel = 2

odrv0.axis0.requested_state = AXIS_STATE_IDLE
odrv0.axis1.requested_state = AXIS_STATE_IDLE
```

上手く行けば以下のように2輪のモータを同時に速度制御する事が出来ます。

![two2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628764/f516f198-810c-bf81-107e-387a0b142448.gif)

# その他よく使用するコマンド
- 指令値コマンド

```
odrv0.axis0.controller.input_pos = <turn>
odrv0.axis0.controller.input_vel = <turn/s>
odrv0.axis0.controller.input_torque = <torque in Nm>
```

- エンコーダーの値の確認

```
odrv0.axis0.encoder.shadow_count
```

- 外部電源電圧の確認

```bash
odrv0.vbus_voltage
```

- エラー確認

```
dump_errors(odrv0)
```

- 構成をODriveの永続メモリに保存

```
odrv0.save_configuration()
```

- 構成変数を工場出荷時のデフォルトにリセット

```bash
odrv0.erase_configuration()
```

- odriveの再起動

```bash
odrv0.reboot()
```

- デバイスを大文字の16進数で一意に識別する番号

```bash
odrv0.serial_number
```

https://docs.odriverobotics.com/commands

# Odrivetoolアンインストール

以下のコマンドを実行することでOdriveをアンインストールすることが出来ます。

```bash
$ sudo pip3 uninstall odrive
```

無事アンインストール出来れば以下の内容が表示されます。

```bash
Successfully uninstalled odrive-0.5.2.post0
```

# 移行ガイド

v0.5.1からv0.5.2への変更は、以下のポイントです。

### ブレーキ動作
ブレーキ抵抗器を使用する前に、次のように明示的に有効にする必要があります。

```bash
odrv0.config.enable_brake_resistor = True
```
次に、構成を保存して再起動し、設定を有効にします。

https://docs.odriverobotics.com/migration

# まとめ
- odriveを使用してBLDCの単純な速度制御を行いました

# 参考文献

https://docs.odriverobotics.com/hoverboard

https://discourse.odriverobotics.com/t/encoder-error-error-illegal-hall-state/1047/7

https://github.com/LuSeKa/HoverBot

https://discourse.odriverobotics.com/t/multiple-errors-when-during-trap-moves-with-high-accelerations/7512

https://www.youtube.com/watch?v=9UxTPxgvOAA&t=12s