タミヤ楽しい工作シリーズをDonkeyCar化する 

> AI RCカー Advent Calendar 2019 参加記事です。

![タミヤ工作シリーズを使ってDonkeycarを作ってみよう](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/a42cc8e8-78b1-ed74-530b-1a7c0fe1340f.jpeg)


[タミヤの工作シリーズ「ブルドーザ」](https://www.tamiya.com/japan/products/70104/index.html) をベースにAI自律走行カーDonkey Car をつくってみました。実際に走行会にて周回コースを次回学習モデルを使って走行させてみました。

今回使用したソースコードや作り方は、[GitHub](https://github.com/coolerking/caterpillar) 側に詳しく書いていますので、この記事では概要と改変ポイントなどを紹介します。

* [GitHub:楽しい工作シリーズ「ブルドーザ」のDonkeycar化](https://github.com/coolerking/caterpillar)

## 1 Donkey Car とは

![Donkey Car](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/b38f3b7e-2e21-cfca-a3a3-dbaaca4270c4.jpeg)


[Donkeycar](http://www.donkeycar.com) は、Raspberry Pi/Jetson Nano や市販のRCカーなど比較的入手しやすい部品を使って、自律走行を実現するためのMIT準拠のオープンソースおよび3Dプリンタ用設計図(Creative Commons)などを含むドキュメントのセットです。

* [Donkeycar公式サイト](http://www.donkeycar.com)
* [Donkeycar公式ドキュメント](http://docs.donkeycar.com/)
* [GitHub:Donkeycarソースコード](https://github.com/autorope/donkeycar)

## 2. Donkey Car をはじめるには

![香港のショップで購入した標準DonkeyCar](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/d86944ff-570a-b6a5-f271-bbc56ea5e905.jpeg)


Donkey Carを始めるには、[ドキュメント](http://docs.donkeycar.com/)に従って、各自で部品を調達し、 Donkey Car を組み立て、ソフトウェアを導入・設定を行う必要があります。DIYが得意ではない方や、プログラミングやソフトウェアに詳しくない方でも参加できるように、コミュニティでは標準 Donkey Car キットを販売しているので、こちらをまず購入することをおすすめします。

私も最初のDonkey Carは香港の [Robocar Store](https://www.robocarstore.com/) で購入して組み立てました。その際の組み立て方は [こちら](https://www.slideshare.net/HoriTasuku/donkey-car) を参照してください。

* [SlideShare:写真で見るDonkeyCarの組み立て](https://www.slideshare.net/HoriTasuku/donkey-car)

> 香港の標準DonkeyCarのバッテリはタミヤのミニコネクタを使用しています。この形状のバッテリは国内だと入手が難しいかもしれません..

海外のサイトでの購入はちょっと..という方は、国内で販売されている会社が何社かありますので、検索してみてください。

## 3. Donkey Carの自律走行

![自律走行中の標準DonkeyCar](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/d8d9b51c-4dec-6466-90c9-e599f5ad10b6.jpeg)

Donkey Car における自律走行は、 **周回コースをできるだけ早く走行する** ことを目的としています。このため、指定された場所で人やモノを乗せ、移動するといった、複雑な自律走行はできません。Donkey Carをコース上にのせて自動運転モードにすると、周回コースをずっと走り続けます。

Donkey Carのデフォルト自律走行モデルでは"教師あり機械学習"を採用しています。

> 詳細は説明しませんが、Donkey シミュレータを使った強化学習モデルを扱うことも可能です。
> <img width="468" alt="Donkey Simulator" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/75c8d724-bbd9-f2fc-fb89-ac3124f6767c.png">

入力データは前方に搭載されたカメラの画像(120x160x3,nd.array型)、出力データはRCカーのステアリング・スロットルへの出力値(float×2)を使用します。ただしDonkey Carには [いくつか別のモデル](https://fight-tsk.blogspot.com/2019/09/donkeycar310.html) も用意されており、入力データや出力データが異なるものも存在します

<img width="650" alt="linear.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/4b3d6893-aa5d-54f1-2cff-cec82accd41b.png">

> Donkey Car 3.1 にて用意されているモデル群は [こちら](https://fight-tsk.blogspot.com/2019/09/donkeycar310.html) を参照してください。

<img width="611" alt="Donkey Carの学習から自律走行までの流れ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/1443aba6-5e8a-f86a-1e6e-83ad4dfe0183.png">


デフォルトのモデルを使用する場合、"教師あり"ですので、まず学習データを収集しなくてはなりません。Donkey Carの構築が完了したら、 

1. リモコンを使って正しい運転走行を記録（手動運転による学習データ作成）
2. 取得した記録を元にトレーニング処理をPC上で実行（機械学習モデルのトレーニング）
3. 学習済みモデルに従って自動運転（学習済みモデルの推論結果で自動運転）

をおこないます。

学習に必要なデータ量ですが、公式ドキュメントには周回コース10周程度とあります。簡単なコースであれば、それほど必要ありませんが、コース周囲に人や動くものなどがある場合は、もう少し周回が必要になります。まずは10周以上をめどに走行させて、工夫してみてください。

## 4. Donkey Car のしくみ

### 4.1 RCカー

Donkey Car は比較的入手しやすい部品で、自律走行を実現するという思想のもとで設計されています。このため、車体のベースに RCカーを使用しています。
市販のRCカーには様々な種類がありますが、タミヤの1/10スケールなど一般的な **ホビー用のRCカー** を使用します。ホビー用RCカーの仕組みは、以下の図のような構成となっています。

<img width="468" alt="RCカーのしくみ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/cef970dc-71e6-4885-51fa-0ca97dac4c33.png">


**プロポ** とは操作用のコントローラのことで、車体に搭載する **受信機** とペアで販売されています（灰色の部分）。車体のみを購入した場合は、受信機を車体に組み付ける必要があります。

RCカーのステアリングは **サーボモータ** で動かしており、ステアリング用サーボからの3本ケーブル(電源、GND、PWM)を受信機にとりつけます。

強力な **スロットル用モータ** は、RCカーバッテリから給電を行いますが、直接接続されるのではなく **スピードコントローラ(ESC)** と呼ばれるマッチ箱より大きな箱に接続されます。スピードコントローラは、受信機から受けた指示に従って、調節された電気量をスロットル用モータへ送ります。なお、スピードコントローラはモータの種類によって違うので注意してください。

### 4.2 Donkey Car上の結線

当然自律走行にはRCカー用のプロポは不要です(ホビー用のRCカーショップでは、プロポを別売りしているので、RCカー本体のみを入手できます)。プロポに変わるものを代わりにつなぎます。

以下の図は、RCカーの構成図です。
受信機から見ると、２つのサーボモータを操作しているのとかわりません。

<img width="459" alt="rc_car_02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/771655d9-30a3-bd7c-0dc8-52fed64aa43f.png">


Donkey Car は、この受信機とプロポに当たる部分を、サーボコントローラで代替している構成になっています。

<img width="502" alt="Donkey Carのしくみ2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/a57eda7d-36da-4a62-8ff3-dbca5684bdef.png">

サーボコントローラは、Raspberry PiなどGPIOを搭載したマイクロコンピュータでサーボモータを操作できるようにした基板です。標準 Donkey Car では PCA9685 という16ch、つまり最大16個のサーボモータを操作が可能な基板を採用しています。

サーボモータは、GND(-)、Vcc(3.3V)、PWMの3つのケーブルで接続します。PWMとは、デジタル出力ピン(0か1)を短い時間で交互に出力して、擬似的なアナログ出力を実現する方法です。

しかし、Raspberry PiのGPIOにはPWMをハードウェアレベルで実装しているピンは4本、同時に複数の波形をだしたい場合は2本しかありません。

このような場合、Raspberry Piでは シリアル、I2C、SPIといった一定のピン数で複数のデバイスを操作する方法が用意されています。

![PCA9685](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/9f484b93-4c9a-28b2-7d63-a614d2c37be1.jpeg)


標準DonkeyCarで2台以上のサーボモータを操作する場合、PCA9685(上図の青い基板)というモータドライバを使用しています。このPCA9685は I2C形式で Raspberry Piへ接続するため、複数台のサーボモータを少ないピン数(GND,Vcc含め4本)ですみます。

> Raspberry Pi の弱点は、PWMピンが少ないことのほかに、 **アナログピンがない** ことがあげられます。アナログピンは、文字通り0と1の間の電圧を入出力できるピンのことです。とくにアナログ入力はセンサの接続などに用いられるため、ほかのたくさんのセンサ類を追加する場合には、 アナデジ変換器(ADC)やアナログピンを持つマイクロコンピュータとシリアルやI2C通信でつなぐなどの方法を取る必要があります。

### 4.3 ソフトウェア

Donkey Car はPythonで記述されています。Pythonがわからない人でも、標準Donkey Carを動かす場合は、知識は殆どなくてもかまいません。

しかし、今回のようなサーボモータドライバをDCモータドライバへ変更するなどの改変を行う場合は、プログラムの追加や書き換えが必要になります。

Donkey Car 上のマイクロコンピュータへ Donkey Car ソフトウェアをドキュメント通りにインストールした場合、 `manage.py` というモジュールが起動のエントリポイントとなります。

```bash
# Donkeycarをジョイスティックを使って手動運転するコマンド例
python manage.py drive --js
```


第一引数に`drive`を渡すと、手動・自動運転処理が実行されます。`train`を渡すと、トレーニング処理が実行されます。

> `drive`を指定した場合の手動・自動の切替はプロポにあたるゲームパッドやスマートフォンの操作で行います。

今回は機械学習モデルの入力データ、出力データの変更は行わないので、`drive`を指定した場合のみ、すなわち `manage.py` 内の関数 `drive()` 内が改変箇所になります。

#### 4.3.1 Vehicle フレームワーク

Donkey Car はPythonパッケージ donkeycar と、このパッケージを使って実際に（手動・自動）運転、トレーニングを行う実行プログラムで構成されています。

これらのプログラムは `donkeycar.vehicle` 上の `Vehicle` クラスをつかったフレームワーク（ここでは vehicle フレームワークと呼称）ベースで実装されています。

```python
:
import donkey as dk
:
# Veicleオブジェクトの生成
V = dk.vehicle.Veichle()
:
# パーツの追加
core_fighter = anaheim.core.Fighter()
V.add(core_fighter, outputs=[
    'amuro/left/arm', 'amuro/right/arm',
    'amuro/left/leg', 'amuro/right/leg'],
    threaded=True)
a_part = anaheim.rx_78.APart()
V.add(a_part,
    inputs=['amuro/left/arm', 'amuro/right_arm'])
b_part = anaheim.rx_78.BPart()
V.add(b_part,
    inputs=['amuro/left/leg', 'amuro/right/leg'])
:
# Veichleループの実行(1秒間20回、100,000回迄)
V.start(start_hz=20,
    max_loop_count=100000)

```

使い方は簡単で、`Vehicle` オブジェクトを生成しパーツとよばれるクラスを追加(`add`)してから、最期にループを`start`させると、指定した引数に従ってパーツクラスが`add`された順番に実行されます。

パーツクラス間の値のやりとりは、パーツを`add`する際に指定した`inputs`および`outputs`で指定したキーに格納されている辞書`V.mem`の値を使います。

> `start`する前に初期値を設定したい場合は、例えば `V.mem['amuro/left/arm'] = 0.0` と書くことで指定できます。

#### 4.3.2 パーツクラス

Veicleが`start`すると、`add`された順番で各クラスの`run`メソッド(`threaded=True`の場合は`run_threaded`メソッド)が呼び出されます。

`run` メソッドの引数は、`add`された際の`inputs`に指定された値が格納されます。`run` メソッドの戻り値は、`outputs`に指定されたキーの値としてフレームワーク側`V.mem`に格納されます。

Veicleフレームワークは指定された周期(`rate_hz`)でループが実行されますが、基本シングルスレッドで処理するため、`run`メソッドの処理時間が長くなると周期をまもることができません。そのため１回の処理が長くなりそうなパーツはマルチスレッドパーツとして実装します。

マルチスレッドパーツは、`run`メソッドの代わりに`run_threaded`メソッドを実装します。マルチスレッドパーツが生成されると、別のスレッドが生成され定期的に引数なしの`update`メソッドが実行されます。Veicleフレームワーク側のメインスレッドはループの周回ごとに`run_threaded`を呼び出します。このため、一般的な使い方はセンサなどの読み取りパーツに使用されます。`update`メソッドは、新規スレッド内で実行されるメソッドです。普通は`update`メソッド内で無限ループをつくり、そのなかでセンサから読み取った値をクラス内インスタンス変数へ格納し`time.sleep`メソッドを実行させます。そして`run_threaded`メソッドでは、`update`メソッドが更新したインスタンス変数の値をVeicleフレームワーク側へ出力するといった実装を行います。

```python
# センサの値を読み取るthreadedパーツの例
class ThreadedSamplePart:
    def __init__(self):
        # センサの準備
        self.sensor = HogehogeSensor()
        # センサ値を格納するインスタンス変数を初期化
        self.value = 0
    def update(self):
        while True:
            # センサからデータを読み取りインスタンス変数へ格納
            self.value = self.sensor.read()
            time.sleep(0.1)
    def run_threaded(self):
        # インスタンス変数に格納されている最新値を返却
        return self.value
    def shutdown(self):
        # センサの終了処理などを呼び出す
        self.sensor.close()
```

Vehicleフレームワークに搭載し、実行するには以下の例のようにパーツを追加します。

```python
# Vehicleオブジェクト生成
V = donkey.vehicle.Veicle()
:
# 戻り値を辞書V.memにキー'value'で格納
V.add(ThreadedSamplePart(),
    outputs=['value'],
    threaded=True)
:
# Vehicleループ開始(1秒間20回、全10000回)
V.start(rate_hz=20, max_loop_count=10000)
```

> センサベンダ側がマルチスレッドを使ったデータ読み取りモジュールを提供している場合は、通常のシングルスレッドパーツクラスで記述します。`theaded=True`でも動作しますが、デバッグが大変なので余計なスレッドはなるべく増やさないようにしたほうがよいでしょう。ただ..デフォルトのループ周回時間である1/20秒以内に完了できないような重い処理であれば、マルチスレッドパーツクラス化したほうが良いでしょう。

#### 4.3.3 GPIO 操作

Python から Raspberry Pi の GPIO を操作する方法はいくつかあります。

| **パッケージ名** | **説明** |
|:--|:--|
| [RPi.GPIO](https://sourceforge.net/projects/raspberry-gpio-python/) |昔から使用されているGPIO操作ライブラリ。１つ１つのGPIOピン操作を記述していく。ハードをよく知っている人なら直感的にわかるコードの書き方となり、未だ多くの人が利用している。|
| [WireingPi](http://wiringpi.com/) |一番事例が多くQitta上でも多数のサンプルコードが存在する。RPi.GPIOより後発で、それゆえ使いやすい。|
| [pigpio](http://abyz.me.uk/rpi/pigpio/) |この表の中では一番新しい。pigpiodデーモンが起動していることが前提となるが、デーモン間通信によりリモート操作も可能。ソフトウェアPWMの精度が高い。設計がソフト屋よりな気がする。|
| [python-periphery](https://github.com/vsergeev/python-periphery) |GPIOキャラクタデバイスを操作するライブラリ。[Coral Dev Board公式ドキュメント](https://coral.withgoogle.com/docs/dev-board/datasheet/#io-header-pinout) にはこのライブラリをつかった例で記述されている。|

donkeycarパッケージで使用しているのは RPi.GPIO パッケージです。モータドライバは、[Adafruit社の提供するライブラリ](https://github.com/adafruit/Adafruit_Python_PCA9685) を使用しています。このドライバもソースをすべておいきってはいませんが RPi.GPIO パッケージを使用しています。

#### 4.3.4 donkey コマンド

donkeycarパッケージをインストールすると、コマンドライン支援ツール`donkey`が使用可能になります。このコマンドで手動運転や自動運転するわけではありません。あくまで支援のためのツールで、Donkey Carの手動運転や自動運転を行うプログラム(DonkeyCarアプリケーション)はこの支援ツールを使って生成します。

Donkey Car アプリケーション生成以外にもいくつか機能があります。以下の表は、第1引数に指定する値の一覧です。

|**第１引数**|**説明**|
|:--|:--|
|`createcar`|Donkey Car アプリケーションを生成する。|
|`findcar`|同一セグメント上にてalive状態のDonkeyCarが存在する場合、IPアドレスを返却する。mac/Unix系OSのみ実行可能。|
|`calibrate`|Donkey Carを走行させる前に、ステアリングやスロットルの調整を行うために使用する。|
|`tubclean`|Web UIを使って不要なTubデータを削除する。が、削除後のTubデータは連番が飛び飛びになってしまう。|
|`makemovie`|Tubデータ上のイメージをつなげて動画にする。salientモードで実行すると動画の上にモデルが反応している箇所をオーバレイしてくれるが、処理に時間がかかる。|
|`checktub`|Tubデータの妥当性をチェックし、機械学習コーパスとして使えるかどうか確認する。|
|`tubhist`|Tubデータからヒストグラム作成し表示する。統計情報確認用。|
|`tubplot`|Tubデータからスロットル・アングル値を時系列のグラフとして表示する。|
|`consync`|Donkey CarからホストPCへ継続的にファイルをコピーする。Tubデータコピーに使用可能だが、rsyncを使うので設定が必要となる。|
|`contrain`|トレーニング処理の1epochごとに新しいデータがないか継続的に調べ存在する場合はそれらも含め次のepochに使う。ホスト側のトレーニング処理でより良いloss値となったモデルができたらそれを差し替えることもできるらしい。|
|`createjs`|サポートしていないジョイスティックを使いたい場合、ベースとなるPythonコードを生成させるウィザード。|
|`cnnactivation`|画像１枚に対してどの箇所でモデルが発火しているかを可視化する。`makemovie`は第1層目のConvのみ可視化するのに対し、このコマンドでは全部のConv層を可視化する。|

#### 4.3.5 Donkey Car アプリケーション

`donkey createcar`コマンドを実行することで生成されるPython プログラム群をDonkey Car アプリケーションとよびます。このアプリケーションが、(手動・自動)運転やトレーニング処理を行います。

パッケージ化されず、どうしていちいち生成している理由は、Donkey Car固有の設定を行う必要があります。

たとえば、Donkey Carのステアリングやスロットルは、使用するRCカーによって特性が異なります。この特性ごとの"調整"を行い、定義をDonkey Carアプリケーションへ反映させるためにDonkey Carアプリケーションを修正することで対応しています。

Donkey Carアプリケーションには以下の構成要素があります。

|**ファイル名**|**説明**|
|:--|:--|
|`config.py`|Donkey Carへ渡しているデフォルトのパラメータ群が定義されたファイル。基本、このファイルは編集しない。|
|`myconfig.py`|各自のDonkey Car固有の設定を行うためのファイル。生成時点では`config.py`の構成がすべてコメントアウトされた状態となっており、ここに指定した値が `config.py` の値をオーバライドされる。donkeyコマンドもこの設定を使用するものがある。|
|`manage.py`|Donkey CarアプリケーションのEntry Point。このファイルを第1引数に`drive`を指定して実行すると（自動・手動）運転を、`train`を指定して実行するとトレーニング処理を開始する。Donkey Carにセンサやサポート外のゲームパッドなどを使用する場合は、このファイルを修正する。|
|`train.py`|トレーニング処理が実装されている。`manage.py`が`train`モードで実行されると、このファイルへ処理が移る。独自モデルを改変し入力層、出力層を変更する場合などがなければ、特に変更する必要はない。|

初期パラメータ変更は `myconfig.py` を、独自Donkey Car仕様変更があれば `manage.py` を、独自モデル利用は `train.py` をそれぞれ編集することとなります。

> 先程説明したVehicleフレームワークは、`manage.py` 上に記述されています。トレーニング処理では使用していません。

#### 4.3.6 Tubデータ

Tubデータとは、Donkey Car の機械学習モデルのコーパスに相当するファイル群です。

Tubデータが格納されるディレクトリを指定せずに手動運転を開始した場合、Donkey Carアプリケーションディレクトリのサブディレクトリ `data/tub_N_YY-MM-DD/` が作成され、以下の表にあるようなファイルが作成されます。

|**ファイル名形式**|**説明**|
|:--|:--|
|`NNN_cam-image_array_.jpg`|前方カメラ撮影画像ファイル。120x160のJPG形式。|
|`record_NNN.json`|運転者の操作を数値化したJSON形式のファイル。デフォルトでは、スロットル値、アングル値、運転モード、対応するイメージファイル名、記録時刻が記録される。|
|`meta.json`|Tubデータを構成する要素の定義が格納されているJSON形式のファイル、各要素とそのタイプが記述されている。|

`NNN` は1から始まる連番となっており、手動運転を開始すると、1/20秒ごとにイメージファイルとrecordファイルが作成されていきます。総件数を知りたい場合は、ファイル名でソートすることでわかります。

> MPU6050などのIMUセンサを搭載した場合(imuモデル)や、behaviorモデルなどを使用する場合、recordファイルの構成要素が増えます。独自センサ搭載などでrecordファイルへ要素を追加することが可能ですが、トレーニング処理時間が増えます。

## 5 タミヤ楽しい工作シリーズ「ブルドーザ」への改変

Donkey Car のどの部分を改変すればいいかを考える前に、まず「ブルドーザ」の構成を確認します。

### 5.1 タミヤ楽しい工作シリーズ「ブルドーザ」

タミヤの楽しい工作シリーズは、自作でなにかを作る場合のギアやプーリー、ユニバーサルプレート、タイヤ、キャタピラといった部品売りのものとは別に、DCモータやゴムを動力としたブルドーザやショベルカー、フォークリフト、船、車、ロープウェイなどのキット品があります。

今回は、そのキット品のなかでリモコンで操作可能な「ブルドーザ」を選択しました。

<a target="_blank"  href="https://www.amazon.co.jp/gp/product/B002DR3H4Y/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B002DR3H4Y&linkCode=as2&tag=hara2dev0d-22&linkId=a91e0633318f19ab79e263994b7eb20d"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=JP&ASIN=B002DR3H4Y&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=hara2dev0d-22" ></a><img src="//ir-jp.amazon-adsystem.com/e/ir?t=hara2dev0d-22&l=am2&o=9&a=B002DR3H4Y" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />

右左折が前・後進かのうであることと、Raspberry Piやバッテリが取り付けやすそうな構造であることと、なによりRCカーより値段が安いことが、選択した理由です。

RCカーをこのブルドーザに改変する場合、いくつかの問題点があります。

### 5.2 DCモータ

ブルドーザに使用しているDCモータは、ミニ四駆でおなじみの [FA-130RA モータ](https://product.mabuchi-motor.co.jp/detail.html?id=9) (のバルク品)です。

これが2基搭載していますが、RCカーとの大きな違いは、左右別々の駆動であることです。Donkey Carの入力層はアングル値、スロットル値ですが、ブルドーザは左モータ入力値、右モータ入力値となり、場合によってはモデルを変更しなくてはなりません。

モデルの変更を行うと、修正が大きそうなので、入力層の構成はそのまま変更せず、DCモータへの出力直前にアングル値、スロットル値を左モータ入力値、右モータ入力値に変換しています。

* [GitHub:actuator.py](https://github.com/coolerking/caterpillar/blob/master/parts/actuator.py)

![DCモータノイズ対策](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/7127dad9-b21e-d269-f320-82c0d28634c4.jpeg)

なお、DCをモータはノイズ発生源となるので、[0.1μFのコンデンサをはんだ付け](https://monoist.atmarkit.co.jp/mn/articles/1602/19/news010.html) しています。

### 5.3 モータドライバ

<a target="_blank"  href="https://www.amazon.co.jp/gp/product/B01D1D0CX2/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B01D1D0CX2&linkCode=as2&tag=hara2dev0d-22&linkId=8caf1c08543debdfe836d342d5ebd294"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=JP&ASIN=B01D1D0CX2&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=hara2dev0d-22" ></a><img src="//ir-jp.amazon-adsystem.com/e/ir?t=hara2dev0d-22&l=am2&o=9&a=B01D1D0CX2" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />

Donkey Car標準のモータドライバはPCA9685ですが、今回はDCモータなのでこの基板は使用できません。

そこで、秋月電子でDCモータ2基を操作可能なモータドライバを探してきました。「[TB6612使用Dual DCモータドライブキット](http://akizukidenshi.com/catalog/g/gK-11219/)」です。

<img width="604" alt="秋月電子のTB6612" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/77c53395-4780-4a11-bb00-d49d538849f5.png">

* [TB6612使用Dual DCモータドライブキット](http://akizukidenshi.com/catalog/g/gK-11219/)

キット品なので、はんだ付けが必要ですがピンコネクタとターミナルブロックになっているので、配線がつないだりはずしたり自由になることと、１枚で2基のDCモータを個別に操作できるところがポイントです。

ただし、この基板はI2C接続ではなく、DCモータ1基あたり操作に3本のGPIOを使用します。うち1本はPWMピンである必要があります。

Raspberry Piには同時操作可能なハードウェアPWMピンは2本しか無いので、間に合うのですが、今回は技術的欲求もあって `pigpio` パッケージをつかったソフトウェアPWMを試してみました。先程も書きましたが `pigpio` パッケージのソフトウェアPWM(通常のGPIOピンをPWMピンとして使用する)がどれくらいの性能なのか知りたかったのです。

このため [`pigpio`パッケージをつかったラッパクラス](https://github.com/coolerking/caterpillar/blob/master/parts/pigpio_wrapper.py) を別途作成しました。

上記以外にもTB6612自体への電源供給のため、Vccピン(5V)とGNDピン、そしてモータドライバの有効・無効を管理するSTBYピンの3本を使用するので、合計9本のピンをRaspberry Piとジャンパケーブルを使って結線しています。

> STBYピンは、基板上のJP1をはんだ付けすることで、省略可能です。

### 5.4 バッテリ

Raspberry Pi は [USB給電](https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%8B%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB%E3%83%BB%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%BB%E3%83%90%E3%82%B9) ですので、5V電源が必要になることは明らかです。

1つのバッテリでRaspberry PiとDCモータ2基に給電する場合は、降圧回路が必要になり、電気回路への難易度が上がります。そこで、今回は別々にバッテリを用意しました。


<a target="_blank"  href="https://www.amazon.co.jp/gp/product/B01HUX8YDY/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B01HUX8YDY&linkCode=as2&tag=hara2dev0d-22&linkId=58d57273684bf4367ca39525726a5a94"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=JP&ASIN=B01HUX8YDY&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=hara2dev0d-22" ></a><img src="//ir-jp.amazon-adsystem.com/e/ir?t=hara2dev0d-22&l=am2&o=9&a=B01HUX8YDY" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />

まず、DCモータ2基のバッテリですが、これは3Vの単3電池で対応しました。電池ボックスは、本体への搭載のしやすさを考えて真四角のものを選んで使用しています。

そしてRaspberry Pi側のバッテリです。先程書いたとおりUSB給電ですので、手っ取り早いのは携帯などのモバイルバッテリの流用です。

Raspberry Piは、十分な電力が得られない場合はつないでも起動しません。起動しても、syslogにバッテリーエラーが頻発して勝手にshatdownしてしまうこともあるそうです。

そこでいくつか購入して試行錯誤した結果、[マクセルのモバイルバッテリMPC-C2600](https://www.yodobashi.com/product/100000001002808783/) を選択しました。起動するモバイルバッテリの中で最軽量で、かつ薄い板のような形状なので、本体に取り付けやすかったためです。

* [ヨドバシ.com:マクセル Maxell MPC-C2600BK [モバイル充電バッテリー 2.600mAh ブラック]](https://www.yodobashi.com/product/100000001002808783/)

### 5.5 カメラ取付台

先程のバッテリは、ヨドバシ.comで探してきたものですが、その際によさそうなものを見つけました。クリップ型カメラスタンドです。

![デザインはダサいが便利なカメラスタンド](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/1d6a9542-4090-0b0f-c4ee-8d2ea07a3827.jpeg)

一般のカメラを取り付けるためのものらしく、このクリップがなかなかに強力なので即採用しました。Raspberry Pi用カメラケースに一般のカメラ用のナットがついていたのもちょうどよかったです。

ただこのカメラケース、Wide Angle Piカメラはつけることができないため、標準カメラに変えています。

### 5.6 ジョイスティック

Donkey Car を手動運転する際、Web UIかジョイスティックのどちらかが選択できます。ただしこのWeb UIを使っている人はあまりいません。運転操作が難しいためです。

そこでジョイスティックを使用することになります。標準でサポートされているジョイスティックの種類は `myconfig.py` の `CONTROLLER_TYPE` のコメントにも書かれていますが、PS3/PS4コントローラ、XBoxコントローラ、Nimbus(AppleTVなど)コントローラ、WiiUコントローラ、Logicool F710コントローラなどがあります。

> [RC3ChanJoystick](https://github.com/autorope/donkeycar/blob/5f67f59004efae8d7d608c2430b6ca7955d217c0/donkeycar/parts/controller.py#L1278) というクラスも存在するのですが、これがどのジョイスティックなのかがわかりませんでした..

Raspberry PiにはBluetoothが搭載されているので、これを使ってPS3/PS4コントローラを..と考える方が多いと思いますが、Raspberry Pi Stretchでbluetoothを有効にするとデーモンがPermissionエラーをだしていたり、OSが不安定になってしまってイメージの再作成になったりと、なかなかうまく行かないことが多く、私は使用している人をみたことがありません。

<a target="_blank"  href="https://www.amazon.co.jp/gp/product/B00475S13W/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B00475S13W&linkCode=as2&tag=hara2dev0d-22&linkId=d0b8971a87777b10c8ae797ae8f4703c"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=JP&ASIN=B00475S13W&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=hara2dev0d-22" ></a><img src="//ir-jp.amazon-adsystem.com/e/ir?t=hara2dev0d-22&l=am2&o=9&a=B00475S13W" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />

国内のDonkey Carの多くはLogicool F710 ワイヤレスジョイスティックを使用しています。ドングルをUSBポートに刺してしまえばあとは`CONTROLL_TYPE`値を`f710`にすれば操作するのでとても簡単です。

ただMaker Fairなどの大きな走行会でDonkey Carを動かそうとすると、F710が混戦して運転操作ができなくなるという問題が発生しました。

<a target="_blank"  href="https://www.amazon.co.jp/gp/product/B01NCIYC3F/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B01NCIYC3F&linkCode=as2&tag=hara2dev0d-22&linkId=731b55eaafaaf48715fc2215e3476789"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=JP&ASIN=B01NCIYC3F&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=hara2dev0d-22" ></a><img src="//ir-jp.amazon-adsystem.com/e/ir?t=hara2dev0d-22&l=am2&o=9&a=B01NCIYC3F" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />

そこで、自分はマイナーなジョイスティックである ELECOM製 JC-U3912T というマイナーなゲームパッドを使用しました。

独自のゲームパッドを使用する場合はコントローラパーツクラスを自作しなくてはなりませんが、先程紹介した`donkey createjs`コマンドを使うとこのクラスを対話形式で作成することができます。

* [GitHub:controller.py](https://github.com/coolerking/caterpillar/blob/master/parts/controller.py)

### 5.7 `manage.py`

ブルドーザをDonkey Car化するために作成したパーツは以下のとおりです。

* TB6612 を動かすためのpigpioラッパパーツおよびモータドライバパーツ
* JC-U3912Tを動かすためのパーツ

これを`manage.py`に組み込めば完成です。そこで`manage.py`がデフォルト状態でどのようなパーツが組み込まれているのかを理解する必要があります。

以下の表は、デフォルト状態で実行した際にVeicleループで処理されるパーツクラスリストです。

|パーツクラス|`inputs`|`outputs`|`threaded`|用途|
|:--|:--|:--|:-:|:--|
|`PiCamera`|N/A|`['cam/image_array']`|`False`|Piカメラ撮影画像を取得|
|`LocalWebController`|`['cam/image_array']`|`['user/angle', 'user/throttle', 'user/mode', 'recording']`|`True`|Web画面から入力された値を取得|
|`ThrottleFilter`|`['user/throttle']`|`['user/throttle']`|`False`|後進する際一旦スロットル値をゼロにする|
|`PilotCondition`|`['user/mode']`|`['run_pilot']`|`False`|運転モード3値(文字列)をBoolean化|
|`RecordTracker`|`["tub/num_records"]`|`['records/alert']`|`False`|一定件数ごとにアラート表示|
|`ImgPreProcess`|`['cam/image_array']`|`['cam/normalized/cropped']`|`False`|`run_condition`が真値の場合のみ画像を指定値だけ上下左右を削除してリシェイプ|
|`FileWatcher`|N/A|`['modelfile/modified']`|`False`|指定ファイルの更新有無判定|
|`FileWatcher`|N/A|`outputs=['modelfile/dirty']`|`False`|`ai_running`が真の場合のみ指定ファイルの更新有無判定|
|`DelayedTrigger`|`['modelfile/dirty']`|`['modelfile/reload']`|`False`|`ai_running`が真の場合指定件数ごとに真値を返却|
|`TriggeredCallback`|`["modelfile/reload"],`|N/A|`False`|`ai_running`が真の場合指定メソッドを実行|
|`KerasLinear`|`'cam/normalized/cropped']`|`['pilot/angle', 'pilot/throttle']`|`False`|`ai_running`が真の場合機械学習モデルの推論を実行|
|`DriveMode`|`['user/mode', 'user/angle', 'user/throttle', 'pilot/angle', 'pilot/throttle']`|`['angle', 'throttle']`|`False`|運転モードに合わせて最終出力値を決定|
|`AiLaunch`|`['user/mode', 'throttle']`|`['throttle']`|`False`|機械学習モデル初期起動時から安定するまで一定スロットルを維持|
|`AiRunCondition`|`['user/mode']`|`['ai_running']`|`False`|自動運転中かどうかを判別|
|`AiRecordingCondition`|`['user/mode', 'recording']`|`['recording']`|`False`|自動運転中の場合常に記録モードを有効化|
|`PWMSteering`|`['angle']`|N/A|`False`|入力値をステアリングサーボへ伝達|
|`PWMThrottle`|`['throttle']`|N/A|`False`|入力値をスロットルモータ(ESC)へ伝達|
|`TubWriter`|`['cam/image_array','user/angle', 'user/throttle', 'user/mode']`|`["tub/num_records"]`|`False`|`ai_recording`が真値の場合、Tubデータを１件書き込み現在件数を取得|

上記のうちPCA9685を直接操作しているパーツクラスは、`PWMSteering`と`PWMThottle`の2つ。先の入力値を両輪駆動モータ値に変換する機能とこれらの変わりのTB6612へ出力するクラスを作成、追加しなくてはなりません。

モータドライバの違いは`myconfig.py`の`DRIVE_TRAIN_TYPE`値で分岐しているので、TB6612用の分岐を増やして追加しました。

```python
    :
    # DC2モータ pigpio制御、TB6612 ジャンパ設定無し
    elif cfg.DRIVE_TRAIN_TYPE == "DC_TWO_WHEEL_PIGPIO":
        try:
            import pigpio
        except:
            raise
        # pigpio 制御開始
        pgio = pigpio.pi()

        from parts import PIGPIO_OUT, PIGPIO_PWM, CaterpillerMotorDriver

        # TB6612 STBY ピン初期化
        stby = PIGPIO_OUT(pin=cfg.TB6612_STBY_GPIO, pgio=pgio, debug=use_debug)
        stby.run(1)

        # ジョイスティック出力値をDCモータ入力値に変換
        driver = CaterpillerMotorDriver(
            left_balance=cfg.LEFT_PWM_BALANCE, 
            right_balance=cfg.RIGHT_PWM_BALANCE,
            debug=use_debug)
        V.add(driver, 
                inputs=['throttle', 'angle'],
                outputs=['left_motor_vref', 'left_motor_in1', 'left_motor_in2',
                'right_motor_vref', 'right_motor_in1', 'right_motor_in2'])

        # 左モータ制御
        left_in1 = PIGPIO_OUT(pin=cfg.LEFT_MOTOR_IN1_GPIO, pgio=pgio, debug=use_debug)
        left_in2 = PIGPIO_OUT(pin=cfg.LEFT_MOTOR_IN2_GPIO, pgio=pgio, debug=use_debug)
        left_vref = PIGPIO_PWM(pin=cfg.LEFT_MOTOR_PWM_GPIO, pgio=pgio, freq=cfg.PWM_FREQ, range=cfg.PWM_RANGE, debug=use_debug)
        V.add(left_in1, inputs=['left_motor_in1'])
        V.add(left_in2, inputs=['left_motor_in2'])
        V.add(left_vref, inputs=['left_motor_vref'])  

        # 右モータ制御
        right_in1 = PIGPIO_OUT(pin=cfg.RIGHT_MOTOR_IN1_GPIO, pgio=pgio, debug=use_debug)
        right_in2 = PIGPIO_OUT(pin=cfg.RIGHT_MOTOR_IN2_GPIO, pgio=pgio, debug=use_debug)
        right_vref = PIGPIO_PWM(pin=cfg.RIGHT_MOTOR_PWM_GPIO, pgio=pgio, freq=cfg.PWM_FREQ, range=cfg.PWM_RANGE, debug=use_debug)
        V.add(right_in1, inputs=['right_motor_in1'])
        V.add(right_in2, inputs=['right_motor_in2'])
        V.add(right_vref, inputs=['right_motor_vref'])
```

デフォルト状態ではジョイスティックではなくWeb UIによる操作となっており、これは `LocalWebController` が行っています。これをジョイスティックに変更する必要がありますが、ジョイスティックパーツクラスは`manage.py`では直接生成していません。ファクトリ関数 `get_js_controller()` を使ってインスタンス化しています。

このため、今回は`get_js_controller()`をラップした関数を作成して、`manage.py`上の該当する関数と差し替えています。

```python
        :
        # 独自のファクトリ関数に変更
        #from donkeycar.parts.controller import get_js_controller
        from parts import get_js_controller
        
        ctr = get_js_controller(cfg)
```

なお、[公開中のGitHubリポジトリ](https://github.com/coolerking/caterpillar/) には、ブルドーザ用の`manage.py`を`manage_cat.py`という名前で保存しています。

### 5.8 `myconfig.py`

`manage_cat.py`に追加したいくつかのパーツ用に以下の変数を追加・変更しました。

|**変数名**|**値**|**説明**|
|:--|:--|:--|
|`DRIVE_TRAIN_TYPE`|`DC_TWO_WHEEL_PIGPIO`|`pigpio`パッケージをつかったDCモータ2基をTB6612で操作するパーツ群を組み込む|
|`LEFT_PWM_BALANCE`|`1.0`|左右のバランスを調整するため左輪DCモータ出力に乗算される指標(0.0～1.0)|
|`RIGHT_PWM_BALANCE`|`1.0`|左右のバランスを調整するため右輪DCモータ出力に乗算される指標(0.0～1.0)|
|`PWM_RANGE`|`255`|PWM出力ピンのレンジ値|
|`PWM_FREQ`|`50`|PWMのタイムスライス(Hz)|
|`LEFT_MOTOR_PWM_GPIO`|`13`|右輪駆動モータの電圧レベルを操作するPWM出力ピンのGPIO番号|
|`LEFT_MOTOR_IN1_GPIO`|`26`|左輪駆動モータの正逆転を操作する出力ピンのGPIO番号(2ピンで操作する)|
|`LEFT_MOTOR_IN2_GPIO`|`19`|左輪駆動モータの正逆転を操作する出力ピンのGPIO番号(2ピンで操作する)|
|`RIGHT_MOTOR_PWM_GPIO`|`16`|右輪駆動モータの電圧レベルを操作するPWM出力ピンのGPIO番号|
|`RIGHT_MOTOR_IN1_GPIO`|`21`|右輪駆動モータの正逆転を操作する出力ピンのGPIO番号(2ピンで操作する)|
|`RIGHT_MOTOR_IN2_GPIO`|`20`|右輪駆動モータの正逆転を操作する出力ピンのGPIO番号(2ピンで操作する)|
|`TB6612_STBY_GPIO`|`4`|TB6612を有効化・無効化を操作するスタンバイピンのGPIO番号|
|`USE_JOYSTICK_AS_DEFAULT`|`True`|デフォルトでジョイスティックを使用する|
|`AUTO_RECORD_ON_THROTTLE`|`True`|手動運転中スロットル値が0.0ではない場合、自動的にTubデータを記録する|
|`CONTROLLER_TYPE`|`JCU3912T`|コントローラとしてELECOM製JC-U3912Tを使用する|               
|`JOYSTICK_DEADZONE`|`0.1`|ジョイスティックからのスロットルアナログ入力値の最小値、これより小さい値はスロットルゼロとする|

## 6 問題点

実際に製作して、なんどか試走してみましたが、いくつか問題点が見つかりました。

### 6.1 履帯ゴム伸びる問題

ブルドーザ(というより楽しい工作シリーズで履帯を使うものすべて)の履帯を巻くとわかると思いますが、ややキツめになっています。このため走行せず長時間巻いたままにすると伸びてしまい、いざ走行すると履帯がポロポロ外れやすくなります。

このため、こまめに取り外しておく必要があります。

### 6.2 まっすぐ進まない問題

毎回履帯を巻き直していることもあるのですが、左右のモータ出力比を調整が毎回異なります。そして厄介なことに低速時は真っ直ぐ進むのに、高速になればなるほどどちらかによってしまいます。

このため、毎回のTubデータがその場のみ有効な学習データとなってしまい、再利用ができなくなってしまいます。

### 6.3 グリスつけると動きが変わる問題

グリスをつけるとなめらかに走行できるようになり、ドライバビリティが向上します。しかし、まっすぐ進まない問題同様、過去のTubデータの再利用ができません。

### 6.4 走行会では走るシケインになる問題

![最初に参加した走行会](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/b00fed6e-2077-ec56-64cc-3edb058d4c54.jpeg)

私の参加した走行会では、自分以外すべての自動走行車はRCカーベースなので、そもそもレースになりません..

## 7. さいごに

Donkey Car を始めようとされている方には、まず標準 Donkey Car のキット品で始めることをおすすめします。標準Donkey Carであれば、RCカーの知識やRaspberry Pi、Pythonの知識は不要です。

![最初に作った標準Donkey Car](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/51bfe2f6-dd22-675f-5550-63cc7888c4ab.jpeg)


ただ、RCカーの知識のある方は、ブラシレスやエンジン(!)カーでやってみたくなるかもしれませんし、PythonやTensorflowの知識のある方は Tensorflow LiteやTensorflow Extentionを使ってみようかと思うかもしれません。IoTに詳しい人ならROSやMQTTブローカ、IoTゲートウェイによるEdge化などの拡張を考えるかもしれません。できるかはわかりませんがArduinoやM5、Corel Dev Board、Pi4Bに換装してみたいと思うかもしれません。あるいはLiDARや超音波センサ、IMUセンサなどを搭載させようと考えるかもしれません。

![2台めに作ったWARRIOR RCカーベースのDonkey Car](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/5977ec6e-f0bb-775d-1e90-fba652014b93.jpeg)


そして、できあがった自分独自の車を自慢させたくなるかもしれません。もしそうなっちゃった人は、ぜひ他の車も走っている走行会に参加して走らせてみましょう。

![最初に参加した走行会の全Donkey Car](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213774/76806c63-4090-65c2-58d6-018996825e08.jpeg)


検索してみるとわかると思いますが、日本でも1～2ヶ月に1回位の頻度でどこかしらで走行会が開催されています。

そして、もしかしたらどこかで私と会うかもしれませんね。


p.s.

結束バンド、最強。


<a target="_blank"  href="https://www.amazon.co.jp/gp/product/B004OCQH8O/ref=as_li_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=B004OCQH8O&linkCode=as2&tag=hara2dev0b-22&linkId=08a3ac456768d8908c462602107a36fb"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=JP&ASIN=B004OCQH8O&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=hara2dev0b-22" ></a><img src="//ir-jp.amazon-adsystem.com/e/ir?t=hara2dev0b-22&l=am2&o=9&a=B004OCQH8O" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />