Jetson Nanoの 2 つのハードウェアPWM を Jetson.GPIO Python library から動かしてみた。

# はじめに
<A HREF="https://github.com/NVIDIA/jetson-gpio">Jetson.GPIO Python library</A> に付属している PWM のサンプルスクリプトがやっと動いたのでメモに残しておきます。まずは動画です。
<A HREF="https://youtu.be/2RTTaq59EVg"><img width="50%" alt="スクリーンショット 2020-01-13 14.41.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/f86ca2dc-6388-10e9-8586-a338a3766792.png"></A>

他の 5 つのサンプルスクリプトは書籍「Jetson Nano超入門」で執筆させていただきました。
<A HREF="http://www.sotechsha.co.jp/pc/html/1251.htm
">![1251.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/432bb1c1-0c00-4645-5a77-2128ae369608.jpeg)</A>

## サンプルスクリプト
- simple_input.py  
simple_input.pyはピン番号12(Raspberry Pi GPIO番号18)の入力値を読み取り、その値を画面に表示しま す。
- simple_out.py  
simple_out.pyはピン番号12(Raspberry Pi GPIO番号18)の出力値を2秒ごとに変化させ、HIGHとLOW を交互に出力します。
- button_led.py  
button_led.py はボタンの状態を読み取り、LED を点灯させます。ピン番号 18 と GND に接続されたボタン、ピ ン番号 18 と +3.3V に接続されたプルアップ抵抗、ピン番号 12 に接続された LED と電流制限抵抗が必要です。1 秒ごとにボタンの状態を読み取り、ボタンが押されている間は LED を点灯し続けます。
- button_event.py  
button_event.py は、button_led.py と同様にボタンの状態を読み取り、LED を点灯させます。ピン番号 18 と GND に接続されたボタン、ボタンを +3.3V に接続するプルアップ抵抗、ピン番号 12 に接続された LED と電流制 限抵抗が必要です。button_led.py と機能は同じですが、CPU 使用量を減らすためにピン番号の入力値を継続的 にチェックする代わりにボタンを押すイベントにプログラムを変更しています。
- button_interrupt.py  
button_interrupt.py は、ボタンの状態を読み取り、2 つの LED の点滅を制御します。ピン番号 18 と GND に接 続されたボタン、ピン番号 18 と +3.3V に接続されたプルアップ抵抗、ピン番号 12 に接続された 1 つ目の LED と 電流制限抵抗、ピン番号 13 に接続された 2 つ目の LED と電流制限抵抗が必要です。1 番目の LED が 2 秒ごとに遅 く点滅し、ボタンが押されると 2 番目の LED が 0.5 秒ごとに素早く連続して 5 回点滅します。

## simple_pwm.py
ライブラリの README.md には「PWM チャンネルの使い方の詳細は simple_pwm.py を見ろ。」としか書かれておらず、他のサンプルスクリプトと比較して理解するのに苦労しました。

「Jetson.GPIO ライブラリは、ハードウェア PWM コントローラーが接続されたピンでのみ PWM をサポートします。 RPi.GPIO ライブラリとは異なり、Jetson.GPIO ライブラリはソフトウェアエミュレート PWM を実装していません。Jetson Nano は 2 つのハードウェア PWM チャンネルをサポートしています。システム pinmux は、ハードウェア PWM コントローラーを関連するピンに接続するように構成する必要があります。 pinmux が設定されていない場合、PWM 信号はピンに到達しません。Jetson.GPIO ライブラリは、これを実現するために pinmux 構成を動的に変更しません。 pinmux の構成方法の詳細については、L4T のドキュメントを読んでください。」

pinmux や L4T (=Linux for Tegra) ドキュメントなどハードルが高かったのですが、ようやく理解が追いついて無事に動かすことができました。
結論から先に言うと、正しく pinmux 設定さえできればサンプルスクリプトを動かすことができました。
JetPack 4.3 (r32.3.1) で追加された Jetson-IO tool を使用すると Pinmux の設定が容易になったことが大きかったです。

(参考)
<A HREF="https://qiita.com/kitazaki/items/a445994f1f46a1b15f78">JetPack 4.3 (r32.3.1) で追加された Jetson-IO tool を使用して Pinmux テーブルを設定してみた。</A>
<A HREF="https://qiita.com/kitazaki/items/2c9deb912f11106d1215">Jetson Nano の 2 つのハードウェア PWM を使用してみた。</A>

## サーボモーターを動かすためのスクリプトの修正
まず、付属しているサンプルスクリプトの内容を理解します。

- Jetson Nano の場合は PWM の出力に 33 ピンを使用します。  
- PWM サイクル (周期) は 20 ms (50 Hz) を設定します。  
- Duty 比 (パルス幅を周期で割った比率) は 25% を設定します。  

```python:simple_pwm.py
import RPi.GPIO as GPIO
import time

output_pins = {
    'JETSON_XAVIER': 18,
    'JETSON_NANO': 33,
}
output_pin = output_pins.get(GPIO.model, None)
if output_pin is None:
    raise Exception('PWM not supported on this board')


def main():
    # Pin Setup:
    # Board pin-numbering scheme
    GPIO.setmode(GPIO.BOARD)
    # set pin as an output pin with optional initial state of HIGH
    GPIO.setup(output_pin, GPIO.OUT, initial=GPIO.HIGH)
    p = GPIO.PWM(output_pin, 50)
    p.start(25)

    print("PWM running. Press CTRL+C to exit.")
    try:
        while True:
            time.sleep(1)
    finally:
        p.stop()
        GPIO.cleanup()

if __name__ == '__main__':
    main()
```

2 つのサーボモーター (Geekservo 9g 360°サーボモーター) を使用するため、スクリプトを修正する必要があります。

- ピン設定  
2 個 (32ピン、33ピン) へ変更
- PWM サイクル  
20 ms (50 Hz) →変更なし
- 制御パルス  
0.5 ms 〜 2.4 ms →Duty 比を 2.5% (-90°方向へ回転) 〜 7.25%(0°=静止) 〜 12%(+90°方向へ回転) へ変更

(修正後のスクリプト)

```python:simple_pwm_servo.py
#!/usr/bin/env python

import RPi.GPIO as GPIO
import time

output_pin1 = 32
output_pin2 = 33

def main():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(output_pin1, GPIO.OUT, initial=GPIO.HIGH)
    p1 = GPIO.PWM(output_pin1, 50)
    GPIO.setup(output_pin2, GPIO.OUT, initial=GPIO.HIGH)
    p2 = GPIO.PWM(output_pin2, 50)

    print("PWM running. Press CTRL+C to exit.")
    try:
        while True:
            p1.start(2.5)
            print("p1 start at 2.5%")
            time.sleep(1)
            p2.start(2.5)
            print("p2 start at 2.5%")
            time.sleep(1)
            p1.start(7.25)
            print("p1 start at 7.25%")
            time.sleep(1)
            p2.start(7.25)
            print("p2 start at 7.25%")
            time.sleep(1)
            p1.start(12)
            print("p1 start at 12%")
            time.sleep(1)
            p2.start(12)
            print("p2 start at 12%")
            time.sleep(1)
            p1.start(7.25)
            print("p1 start at 7.25%")
            time.sleep(1)
            p2.start(7.25)
            print("p2 start at 7.25%")
            time.sleep(1)
    finally:
        p1.stop()
        p2.stop()
        GPIO.cleanup()

if __name__ == '__main__':
    main()
```

(実行結果)

```bash
$ python3 simple_pwm_servo.py
PWM running. Press CTRL+C to exit.
p1 start at 2.5%
p2 start at 2.5%
p1 start at 7.25%
p2 start at 7.25%
p1 start at 12%
p2 start at 12%
p1 start at 7.25%
p2 start at 7.25%
...
```