Donkey Car から PCA9685 を取っ払って、ESC・サーボをラズパイに直結する ～pigpio版～

#はじめに

前回、[Donkey Car から PCA9685 を取っ払って、ESC・サーボをラズパイに直結する](https://qiita.com/rukihena/items/1d1f1127c1fee592062a)という記事を書きました。これはPWM出力に[ServoBlaster](https://github.com/richardghirst/PiBits/tree/master/ServoBlaster)を利用するものでした。

ServoBlasterは```apt```でインストールできず、なんだか**アウェイ感**が否めません（※個人の感想です）

その記事でも書きましたが、**[pigpio](http://abyz.me.uk/rpi/pigpio/index.html)**が使えそうです。こちらは```apt```でインストールできる**ホーム感**がうれしいところです。

ということで、**pigpio** を使ってまいります。

#pigpio インストール

```apt``` でインストールできて**ホーム感**がありますね（大事なことではないけど2回言ってみました）

```
sudo apt install python3-pigpio
sudo systemctl start pigpiod
```

#manage.py 追記

（あとで仕切り直しで上書きしますが、参考までに）

```
    elif cfg.DRIVE_TRAIN_TYPE == "SERVO_ESC_PIGPIO":
        from donkeycar.parts.actuator import PiGPIO_PWM, PWMSteering, PWMThrottle

        steering_controller = PiGPIO_PWM(cfg.STEERING_CHANNEL)
        steering = PWMSteering(controller=steering_controller,
                                        left_pulse=cfg.STEERING_LEFT_PWM*305,
                                        right_pulse=cfg.STEERING_RIGHT_PWM*305)

        throttle_controller = PiGPIO_PWM(cfg.THROTTLE_CHANNEL)
        throttle = PWMThrottle(controller=throttle_controller,
                                        max_pulse=cfg.THROTTLE_FORWARD_PWM*305,
                                        zero_pulse=cfg.THROTTLE_STOPPED_PWM*305,
                                        min_pulse=cfg.THROTTLE_REVERSE_PWM*305)

        V.add(steering, inputs=['angle'])
        V.add(throttle, inputs=['throttle'])

```

```*305``` は何をやっているかというと、PCA9685のときの設定値を流用するための係数です。

ServoBlasterで設定した4マイクロ秒ベースの値を使う場合は```*300```にしましょう。
ラジコンの世界からやってきて、1マイクロ秒ベースで設定したい場合は```*75```にしましょう。

なお、1は```1/75/1000000[s]```です。

#myconfig.py 修正

（あとで仕切り直しで上書きしますが、参考までに）

下記定数3つを修正します。

```
DRIVE_TRAIN_TYPE = "SERVO_ESC_PIGPIO"
STEERING_CHANNEL = 12           #pin number on the Raspberry Pi
THROTTLE_CHANNEL = 13           #pin number on the Raspberry Pi
```

今回は物理ピン番号でなく、GPIO番号のほうで指定します。ややこしや。

12と13を選んだのは、ハードウェアの制約でほぼそれしか選べないからです。
使えないピンを選ぶと ```manager.py```起動時に

```
pigpio.error: 'GPIO has no hardware PWM'
```

というエラーが出て実行出ません。

制約がきつすぎますね。ピンが隣り合っていないと、前回記事の「これをこうしてこうじゃ」の簡単な配線ができなくて微妙です。

#仕切り直し

DonkeyCar の ```PiGPIO_PWM``` クラスのソースを読んでみると、pigpioの[hardware_PWM()](http://abyz.me.uk/rpi/pigpio/python.html#hardware_PWM) を利用しています。これは2chしかないハードウェアPWMをそのまま使っていてモッタイナイです。

一方、pigpioには [set_servo_pulsewidth()](http://abyz.me.uk/rpi/pigpio/python.html#set_servo_pulsewidth) という関数もあるので、こちらを使いたいと思います。


> ServoBlaster は PWMとDMAを組み合わせて、多チャンネルPWMを実現しています。詳しく調べていませんが、 pigpio のset_servo_pulsewidth() も同じ仕組みで動いていると思われます。

#PiGPIO_SERVOクラス作成

クラスの内容は以下の通りとなります。
PWM周波数の設定をしていませんが、50Hzで動いているようです。これは特に変える必要はないでしょう。

```
class PiGPIO_SERVO():

    def __init__(self, pin, pgio=None):
        import pigpio

        self.pin = pin
        self.pgio = pgio or pigpio.pi()
        self.pgio.set_mode(self.pin, pigpio.OUTPUT)

    def __del__(self):
        self.pgio.set_servo_pulsewidth(self.pin, 0)
        self.pgio.stop()

    def set_pulse(self, pulse):
        self.pgio.set_servo_pulsewidth(self.pin, pulse)

    def run(self, pulse):
        self.set_pulse(pulse)
```

本来は ```~/projects/donkeycar/donkeycar/parts/actuator.py``` に追記するものと思いますが、書き換えるファイルが増えるとバージョンアップなどの際に面倒なことになるので ```manager.py``` の適当なところに追記しちゃいましょう。

```__del__``` の中で ```self.pgio.set_servo_pulsewidth(self.pin, 0)``` としているのは、非動作時にPWM出力を止めてサーボをお休みさせるためです。

#manager.py修正


ふたたび ```manager.py``` の編集です。
上の方で追記したところを下記のように書き換えます。
（```PiGPIO_PWM```の利用は無かったことにします）

```
    elif cfg.DRIVE_TRAIN_TYPE == "SERVO_ESC_PIGPIO":
        from donkeycar.parts.actuator import PWMSteering, PWMThrottle

        steering_controller = PiGPIO_SERVO(cfg.STEERING_CHANNEL)
        steering = PWMSteering(controller=steering_controller,
                                        left_pulse=cfg.STEERING_LEFT_PWM*4.069,
                                        right_pulse=cfg.STEERING_RIGHT_PWM*4.069)

        throttle_controller = PiGPIO_SERVO(cfg.THROTTLE_CHANNEL)
        throttle = PWMThrottle(controller=throttle_controller,
                                        max_pulse=cfg.THROTTLE_FORWARD_PWM*4.069,
                                        zero_pulse=cfg.THROTTLE_STOPPED_PWM*4.069,
                                        min_pulse=cfg.THROTTLE_REVERSE_PWM*4.069)

        V.add(steering, inputs=['angle'])
        V.add(throttle, inputs=['throttle'])
```

```*4.069``` は何をやっているかというと、PCA9685のときの設定値を流用するための係数です。


```PiGPIO_SERVO``` クラスの追加をマジメに ```actuator.py``` に行った場合は、 ```import``` に ```PiGPIO_SERVO``` を追加しましょう。

#myconfig.py 修正

下記定数3つを修正します。

```
DRIVE_TRAIN_TYPE = "SERVO_ESC_PIGPIO"
STEERING_CHANNEL = 17           #pin number on the Raspberry Pi
THROTTLE_CHANNEL = 27           #pin number on the Raspberry Pi
```

今回も物理ピン番号でなく、GPIO番号のほうで指定します。
前回の記事と同じピンを使うように設定してみました。

#配線

配線は前回記事と同様です。
（同じにするためにわざわざ ```PiGPIO_SERVO``` クラスを作成しました）

#走行
あとはいつものように走行するだけです！

```
cd ~/mycar
python manage.py drive
```

（実は、「Webコントローラーから制御できてるな」という確認までしただけで、教師走行や自律走行は試しておりません。。。）

#細かい話：PWM設定値

```set_servo_pulsewidth()``` はマイクロ秒で指定します。ラジコンの世界から来た人には分かりやすいです。値の指示はマイクロ秒単位でできますが、実際の精度はデフォルトだと5マイクロ秒らしいです。
精度を1マイクロ秒にもできるようですが、メカ側の精度がそんなにないと思うのでいじらなくてもいいんじゃないかと思います。
専用ハードウェアのPCA9685の場合で約4マイクロ秒精度ですし。

#おまけ：キャリブレーション
```
pigs SERVO 17 1500
```
にて、手動でPWM出力できます。

一応説明しておくと、17がGPIO番号、1500が出力値です。

```socket connect failed``` というエラーが出る場合は、```sudo raspi-config```で、```Remote GPIO```を有効にしてください。（```5 Interfacing Options``` -> ```P8 Remote GPIO``` にあります）

用事が済んだら

```
pigs SERVO 17 0
```

として出力を止めるとサーボ・バッテリーにやさしいと思います。

#おわりに
環境整備ばかりしていて教師走行をしておりませんorz
CONFIG.SYS, AUTOEXEC.BATを延々いじっていた学生時代を思い出しますｗ