HumansXドキュメントセンター 公式サイトへ ↗

2. GPIO の応用 - LED 制御と割り込み

GPIO Python ライブラリのインポート

開発ボードには GPIO Python ライブラリ Hobot.GPIO がプリインストールされています。以下のコマンドで GPIO ライブラリをインポートできます。

sunrise@ubuntu:~$ sudo python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08)
Type "help", "copyright", "credits" or "license" for more information.
>>> import Hobot.GPIO as GPIO
>>> GPIO.VERSION
'0.0.2'
>>> GPIO.model
'RDK_X5'

ピン番号方式の設定

開発ボードのピン番号方式には 4 つのモードがあります。

  • BOARD:物理ピン番号で、開発ボードのシルク印刷の番号と一対一で対応します。
  • BCM:Broadcom SoC で定められた GPIO 命名規則に基づきます。
  • CVM:数字の代わりに文字列を使用し、CVM / CVB コネクタの信号名に対応します。
  • SOC:チップ内部の GPIO ピン番号に対応します。

本書では BOARD 番号方式の使用を推奨します。番号方式の設定方法は以下のとおりです。 注意:番号方式は一度しか設定できません。再設定したい場合は、GPIO.cleanup() を実行してから設定し直してください。

GPIO.setmode(GPIO.BOARD)
# or
GPIO.setmode(GPIO.BCM)
# or
GPIO.setmode(GPIO.CVM)
# or
GPIO.setmode(GPIO.SOC)

番号方式の確認:

GPIO.getmode()

プログラムは BOARD, BCM, CVM, SOC or None のいずれかの結果を出力します。

警告メッセージ

以下の場合にコードを実行すると警告ログが出力されますが、正常な機能には影響しません。

  • 使用しようとしている GPIO が、他のアプリケーションですでに使用されている場合
  • モードとチャネルを設定する前に、GPIO.cleanup を呼び出してピンをクリーンアップしようとした場合

警告メッセージを非表示にするには、以下のコマンドを使用します。

GPIO.setwarnings(False)

ピンの設定

GPIO ピンは使用前に、以下のように設定する必要があります。

入力に設定する方法:

GPIO.setup(channel, GPIO.IN)

出力に設定する方法:

GPIO.setup(channel, GPIO.OUT)

出力チャネルに初期値を指定することもできます。例:

GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH)

また、複数の出力チャネルを同時に設定することもできます。例:

# set gpio(18,12,13) to output
channels = [18, 12, 13]
GPIO.setup(channels, GPIO.OUT)

入力操作

チャネルの値を読み取るには、以下を使用します。

GPIO.input(channel)

戻り値は 0 または 1 です。0 は GPIO.LOW、1 は GPIO.HIGH を表します。

出力操作

チャネルの出力値を設定するには、以下を使用します。

GPIO.output(channel, state)

state には GPIO.LOW または GPIO.HIGH を指定できます。

ピン占有のクリーンアップ

プログラム終了前に、チャネルのクリーンアップを行うことを推奨します。以下を使用してください。

GPIO.cleanup()

特定のチャネルのみをクリーンアップする場合は、以下を使用します。

# 清除单个通道
GPIO.cleanup(channel)
# 清除一组通道
GPIO.cleanup((channel1, channel2))
GPIO.cleanup([channel1, channel2])

ピン状態の確認

この機能で、対応する GPIO チャネルの機能を確認できます。

GPIO.gpio_function(channel)

この関数は IN または OUT を返します。

エッジ検出と割り込み

エッジとは、電気信号の ローからハイ(立ち上がりエッジ)または ハイからロー(立ち下がりエッジ)への変化です。この変化はイベントの発生とみなすことができ、CPU の割り込み信号のトリガーとして利用できます。

情報

RDK Ultra プラットフォームでは、40pin のうち特定のピンのみを割り込みピンとして使用できます。BOARD 番号方式での番号は 13、16、18、22、27、28、32、33、37 です。

ピン定義については ピン設定と定義 をご参照ください。

GPIO ライブラリには、入力イベントを検出する方法が 3 つ用意されています。

wait_for_edge() 関数

この関数は、対応するエッジの変化を検出するまで呼び出し元スレッドをブロックします。呼び出し方は以下のとおりです。

GPIO.wait_for_edge(channel, GPIO.RISING)

第 2 引数には検出するエッジを指定し、指定可能な値は GPIO.RISING、GPIO.FALLING または GPIO.BOTH です。待ち時間を指定したい場合は、タイムアウトを設定できます。

# 超时以毫秒为单位
GPIO.wait_for_edge(channel, GPIO.RISING, timeout=500)

タイムアウト時間内に外部信号が変化した場合、関数は検出したチャネル番号を返します。タイムアウトした場合は None を返します。

event_detected() 関数

この関数は、前回の呼び出し以降にイベントが発生したかどうかを定期的に確認するために使用できます。設定と呼び出しは以下のように行います。

# 在通道GPIO上设置上升沿检测
GPIO.add_event_detect(channel, GPIO.RISING)
if GPIO.event_detected(channel):
    print("Rising edge event detected")

GPIO.RISING、GPIO.FALLING、GPIO.BOTH のイベントを検出できます。

エッジイベント検出時にコールバック関数を実行する

この機能はコールバック関数の登録に使用できます。コールバック関数は独立した処理スレッドで実行されます。使い方は以下のとおりです。

# define callback function
def callback_fn(channel):
    print("Callback called from channel %s" % channel)

# enable rising detection
GPIO.add_event_detect(channel, GPIO.RISING, callback=callback_fn)

必要に応じて、以下の方法で複数のコールバックを追加することもできます。

def callback_one(channel):
    print("First Callback")

def callback_two(channel):
    print("Second Callback")

GPIO.add_event_detect(channel, GPIO.RISING)
GPIO.add_event_callback(channel, callback_one)
GPIO.add_event_callback(channel, callback_two)

すべてのコールバック関数は同じスレッド上で実行されるため、異なるコールバックは同時ではなく順番に実行されます。

複数のイベントを 1 つにまとめてコールバック関数が何度も呼び出されるのを防ぐため、デバウンス時間を設定できます。

# bouncetime unit is ms
GPIO.add_event_detect(channel, GPIO.RISING, callback=callback_fn, bouncetime=200)

割り込みの無効化

エッジ検出が不要になった場合は、以下のように削除できます。

GPIO.remove_event_detect(channel)

テストプログラム

/app/40pin_samples/ ディレクトリに主なテストプログラムが用意されています。

テストプログラム名 説明
simple_out.py 単一ピンの 出力 テスト
simple_input.py 単一ピンの 入力 テスト
button_led.py 1 つのピンをボタン入力、1 つのピンを LED 制御用出力として使用
test_all_pins_input.py 全ピンの 入力テスト コード
test_all_pins.py 全ピンの 出力テスト コード
button_event.py ピンの立ち上がり/立ち下がりエッジイベントを検出
button_interrupt.py 割り込み方式でピンの立ち上がり/立ち下がりエッジイベントを処理
  • GPIO を 出力モード に設定し、1 秒ごとに出力レベルを切り替えます。LED の点滅制御に使用できます。テストコード simple_out.py:
#!/usr/bin/env python3
import sys
import signal
import Hobot.GPIO as GPIO
import time

def signal_handler(signal, frame):
    sys.exit(0)

# 定义使用的GPIO通道为37
output_pin = 37 # BOARD 编码 37

def main():
    # 设置管脚编码模式为硬件编号 BOARD
    GPIO.setmode(GPIO.BOARD)
    # 设置为输出模式,并且初始化为高电平
    GPIO.setup(output_pin, GPIO.OUT, initial=GPIO.HIGH)
    # 记录当前管脚状态
    curr_value = GPIO.HIGH
    print("Starting demo now! Press CTRL+C to exit")
    try:
        # 间隔1秒时间,循环控制LED灯亮灭
        while True:
            time.sleep(1)
            GPIO.output(output_pin, curr_value)
            curr_value ^= GPIO.HIGH
    finally:
        GPIO.cleanup()

if __name__=='__main__':
    signal.signal(signal.SIGINT, signal_handler)
    main()
  • GPIO を 入力モード に設定し、ビジーポーリングでピンのレベル値を読み取ります。テストコード simple_input.py:
#!/usr/bin/env python3
import sys
import signal
import Hobot.GPIO as GPIO
import time

def signal_handler(signal, frame):
    sys.exit(0)

# 定义使用的GPIO通道为37
input_pin = 37 # BOARD 编码 37

GPIO.setwarnings(False)

def main():
    prev_value = None

    # 设置管脚编码模式为硬件编号 BOARD
    GPIO.setmode(GPIO.BOARD)
    # 设置为输入模式
    GPIO.setup(input_pin, GPIO.IN)

    print("Starting demo now! Press CTRL+C to exit")
    try:
        while True:
            # 读取管脚电平
            value = GPIO.input(input_pin)
            if value != prev_value:
                if value == GPIO.HIGH:
                    value_str = "HIGH"
                else:
                    value_str = "LOW"
                print("Value read from pin {} : {}".format(input_pin, value_str))
                prev_value = value
            time.sleep(1)
    finally:
        GPIO.cleanup()

if __name__=='__main__':
    signal.signal(signal.SIGINT, signal_handler)
    main()
  • GPIO を入力モードに設定し、ピンの立ち上がり/立ち下がりエッジイベントを検出します。テストコード button_event.py では、37 番ピンの立ち下がりエッジを検出し、31 番ピンの出力を制御します。
#!/usr/bin/env python3
import sys
import signal
import Hobot.GPIO as GPIO
import time

def signal_handler(signal, frame):
    sys.exit(0)

# 定义使用的GPIO通道:
# 31号作为输出,可以点亮一个LED
# 37号作为输入,可以接一个按钮
led_pin = 31 # BOARD 编码 31
but_pin = 37 # BOARD 编码 37

# 禁用警告信息
GPIO.setwarnings(False)

def main():
    # 设置管脚编码模式为硬件编号 BOARD
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(led_pin, GPIO.OUT)  # LED pin set as output
    GPIO.setup(but_pin, GPIO.IN)  # button pin set as input

    # Initial state for LEDs:
    GPIO.output(led_pin, GPIO.LOW)

    print("Starting demo now! Press CTRL+C to exit")
    try:
        while True:
            print("Waiting for button event")
            GPIO.wait_for_edge(but_pin, GPIO.FALLING)

            # event received when button pressed
            print("Button Pressed!")
            GPIO.output(led_pin, GPIO.HIGH)
            time.sleep(1)
            GPIO.output(led_pin, GPIO.LOW)
    finally:
        GPIO.cleanup()  # cleanup all GPIOs

if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    main()
  • GPIO を入力モードに設定して GPIO 割り込み機能を有効にし、ピンの立ち上がり/立ち下がりエッジイベントに応答します。テストコード button_interrupt.py では、37 番ピンの立ち下がりエッジを検出し、16 番ピンのハイ/ローレベルを高速に 5 回切り替えます。
#!/usr/bin/env python3
import sys
import signal
import Hobot.GPIO as GPIO
import time

def signal_handler(signal, frame):
    sys.exit(0)

# 定义使用的GPIO通道:
# 15号作为输出,可以点亮一个LED
# 16号作为输出,可以点亮一个LED
# 37号作为输入,可以接一个按钮
led_pin_1 = 15 # BOARD 编码 15
led_pin_2 = 16 # BOARD 编码 16
but_pin = 37   # BOARD 编码 37

# 禁用警告信息
GPIO.setwarnings(False)

# 按下按钮时 LED 2 快速闪烁 5 次
def blink(channel):
    print("Blink LED 2")
    for i in range(5):
        GPIO.output(led_pin_2, GPIO.HIGH)
        time.sleep(0.5)
        GPIO.output(led_pin_2, GPIO.LOW)
        time.sleep(0.5)

def main():
    # Pin Setup:
    GPIO.setmode(GPIO.BOARD)  # BOARD pin-numbering scheme
    GPIO.setup([led_pin_1, led_pin_2], GPIO.OUT)  # LED pins set as output
    GPIO.setup(but_pin, GPIO.IN)  # button pin set as input

    # Initial state for LEDs:
    GPIO.output(led_pin_1, GPIO.LOW)
    GPIO.output(led_pin_2, GPIO.LOW)

    # 把blink函数注册为按钮下降沿事件的中断处理函数
    GPIO.add_event_detect(but_pin, GPIO.FALLING, callback=blink, bouncetime=10)
    # 开始测试, Led1 缓慢闪烁
    print("Starting demo now! Press CTRL+C to exit")
    try:
        while True:
            # blink LED 1 slowly
            GPIO.output(led_pin_1, GPIO.HIGH)
            time.sleep(2)
            GPIO.output(led_pin_1, GPIO.LOW)
            time.sleep(2)
    finally:
        GPIO.cleanup()  # cleanup all GPIOs

if __name__ == '__main__':
    signal.signal(signal.SIGINT, signal_handler)
    main()

hb_gpioinfo ツールの紹介

hb_gpioinfo は X5 向けの GPIO 補助ツールで、現在の開発ボードの PinName と PinNum の対応関係を確認できます。

hb_gpioinfo の構成

hb_gpioinfo ツールはドライバーとアプリケーションの 2 つで構成されています。ドライバーは pinmux-gpio.dtsi を解析して pinnode と pinname の情報を debugfs に出力し、hb_gpioinfo アプリケーションがそれを解析してターミナルに表示します。

ドライバーのコードパス:kernel/drivers/gpio/hobot_gpio_debug.c

hb_gpioinfo の使用例

  • PinName:SoC 上のピン名で、回路図上の X5 SoC のピン名と一致します
  • PinNode:デバイスツリー内の PinNode 情報です
  • PinNum:X5 の実際の対応する GPIO 番号です
root@ubuntu:~# hb_gpioinfo
gpiochip0 - 8 lines: @platform/31000000.gpio: @GPIOs 498-505
        [Number]                [Mode]  [Status]  [GpioName]       [PinName]              [PinNode]           [PinNum]
        line  0:        unnamed input                             AON_GPIO0_PIN0        aon_gpio_0              498
        line  1:        unnamed input                             AON_GPIO0_PIN1        aon_gpio_1              499
        line  2:        unnamed input  active-low  GPIO Key Power AON_GPIO0_PIN2        aon_gpio_2              500
        line  3:        unnamed input              interrupt      AON_GPIO0_PIN3        aon_gpio_3              501
        line  4:        unnamed input                             AON_GPIO0_PIN4        aon_gpio_4              502
        line  5:        unnamed input              id             AON_ENV_VDD           aon_gpio_5              503
        line  6:        unnamed input              id             AON_ENV_CNN0          aon_gpio_6              504
        line  7:        unnamed input                             AON_ENV_CNN1          aon_gpio_7              505
gpiochip1 - 31 lines: @platform/35060000.gpio: @GPIOs 466-496
        [Number]                [Mode]  [Status]  [GpioName]       [PinName]              [PinNode]           [PinNum]
        line  0:        unnamed input                             HSIO_ENET_MDC         hsio_gpio0_0            466
        line  1:        unnamed input                             HSIO_ENET_MDIO        hsio_gpio0_1            467
        line  2:        unnamed input                             HSIO_ENET_TXD_0       hsio_gpio0_2            468
        line  3:        unnamed input                             HSIO_ENET_TXD_1       hsio_gpio0_3            469
        line  4:        unnamed input                             HSIO_ENET_TXD_2       hsio_gpio0_4            470
        line  5:        unnamed input                             HSIO_ENET_TXD_3       hsio_gpio0_5            471
        line  6:        unnamed input                             HSIO_ENET_TXEN        hsio_gpio0_6            472
        line  7:        unnamed input                             HSIO_ENET_TX_CLK      hsio_gpio0_7            473
        line  8:        unnamed input                             HSIO_ENET_RX_CLK      hsio_gpio0_8            474
        ....