RaspberryPi でラジコン受信機PWM信号の読み取りと、サーボへの出力

#はじめに

ラズパイでラジコンしたい！という話の中で、プロポの受信機からのPWM信号をラズパイで読みたい場合と、ラズパイからPWM信号を出してサーボを制御したい場合とあると思います。

ここでは両方解説します。

詳細な解説は無いです。私がベストプラクティスと思う方法を紹介するのが趣旨です。
外付けハードウェアを最小限にすること、環境構築が簡単なことに重点を置いてます。


#プロポの受信機からのPWM信号をラズパイで読む

![pwm-in.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104360/8c471ccc-3318-5ccd-561f-3f414aa5eb76.jpeg)

（図は受信機が省略されてます）

普通にそこら辺のGPIOライブラリを使ってパルス幅の計測をすると、所望の精度が得られず途方に暮れてしまいます。RTOS(リアルタイムOS)でないと無理なのだろうか。多くのLチカ卒業生が壁にぶち当たり（以下略）

調べた中で一番良かったのは、[pigpio](http://abyz.me.uk/rpi/pigpio/) の [callback](http://abyz.me.uk/rpi/pigpio/python.html#callback) を使う方法です。（[pigpio](http://abyz.me.uk/rpi/pigpio/) 信者なので他を調べてないですが。）

GPIOの立下りエッジ・立ち上がりエッジでコールバックしてもらうのですが、その時に起動からの時間が引数で渡されます。この時間の精度が数マイクロ秒です。数マイクロ秒というのはラジコンのPWM信号の精度としては**合格点を与えられるギリギリの精度**です。
コールバックされて非RTOSの遅延が発生した後で時刻を拾うのではなく、割り込み関数内で時刻を取得済みであることがポイントとなります。

#ラズパイからPWM信号を出してサーボを制御する

![pwm-out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104360/6dd1ad81-3f08-4890-4fea-7dba98079b15.jpeg)


PWM駆動ハードウェアPCA9685 を外付けしてやる方法がポピュラーみたいなんですが、ラズパイ単体でもイケます。

[pigpio](http://abyz.me.uk/rpi/pigpio/) の [set_servo_pulsewidth](http://abyz.me.uk/rpi/pigpio/python.html#set_servo_pulsewidth) を使う方法です。

他のライブラリを使ってみた感覚からすると、**謎の精度**でPWM信号を出せて、**サーボがプルプルしません。**

#実装例

プロポの受信機からのPWM信号をラズパイで読み、それをそのままラズパイからPWM信号を出してサーボを制御する実装例を示します。

ラズパイが右から来たものを左へ受け流すだけの、なんら役に立たない機能ですが、改造の元にするには最適だと思います。
回転方向の反転ぐらいはしようかなと思いましたが、余計なものは入れない方針で。

```python:rpipwmtest.py
import pigpio
from time import sleep

def gpiocallback(gpio, level, tick):
    global ch1_rise_tick, ch2_rise_tick
    global ch1_in, ch2_in
    global ch1_out, ch2_out
    if gpio == ch1_in :
        if level==1 :
            ch1_rise_tick = tick
        elif level==0 : 
            pwm_width = tick - ch1_rise_tick
            if 500<pwm_width and pwm_width<2500 :
                pi.set_servo_pulsewidth(ch1_out, pwm_width)
        else :
            pi.set_servo_pulsewidth(ch1_out, 0)

    if gpio == ch2_in :
        if level==1 :
            ch2_rise_tick = tick
        elif level==0 : 
            pwm_width = tick - ch2_rise_tick
            if 500<pwm_width and pwm_width<2500 :
                pi.set_servo_pulsewidth(ch2_out, pwm_width)
        else :
            pi.set_servo_pulsewidth(ch2_out, 0)

ch1_rise_tick = 0
ch2_rise_tick = 0

ch1_in = 23
ch2_in = 24

ch1_out = 17
ch2_out = 27

pi = pigpio.pi()
pi.set_mode(ch1_out, pigpio.OUTPUT)
pi.set_mode(ch2_out, pigpio.OUTPUT)
pi.set_mode(ch1_in, pigpio.INPUT)
pi.set_mode(ch2_in, pigpio.INPUT)

pi.callback(ch1_in, pigpio.EITHER_EDGE, gpiocallback)
pi.callback(ch2_in, pigpio.EITHER_EDGE, gpiocallback)

while True:
    sleep(1)
```

ソースの解説はナシで。

PWM信号を扱うにあたってググった方々に、**[pigpio](http://abyz.me.uk/rpi/pigpio/)の存在に気づいていただきたい**ということが本稿の趣旨です。

環境構築は、最近のraspbianをお使いなら

```
sudo apt install python3-pigpio
sudo systemctl start pigpiod
```
でいけると思います。

（細かいことを言うと、tickの32bitロールオーバーの処理が抜けてます。値域チェックで跳ねられるので、実際の動きとしては約72分ごとに1回だけ入力が無視されることになります。）

#評価

PWM出力の方（set_servo_pulsewidth()）は、かなり安定したパルスが出せます。

しかし、入力のcallbackの方は若干精度が悪く、サーボがジリジリ鳴る原因になっています。ただ、目で見てプルプルするほどではないです。ジリジリ鳴る程度で済んでいます。用途によっては精度が足りないことがあるかもしれませんが、多くの場面で十分実用になるのではないでしょうか。

#配線の注意

ラズパイは3.3V I/Oで、サーボは多くの場合5V I/O です。つまり、レベル変換回路が必要となります。
ぶっちゃけ、ラズパイからの出力は直結でもサーボが読み取ってくれればOKです。
また、入力も直結しても意外と壊れませんｗ
まあ、抵抗分圧回路を挟むなど、各自納得いく配線をしてください。
個人的に入力で好きな方法は、ダイオードを介してローレベル側は引っ張ってもらい、ハイレベル側は内蔵プルアップでHIにする方法です。

また、サーボの電源はラズパイのピンヘッダから取るとノイズが回り込んで誤動作しがちです。サーボの電源は分離しましょう。

#終わりに

RPi.GPIOはLチカ用のおもちゃです。
Wiring Piは[非推奨](http://wiringpi.com/wiringpi-deprecated/)になりました。


**[pigpio](http://abyz.me.uk/rpi/pigpio/)の存在に気づいていただきたい。**
そう願っております。