L チカだけじゃ物足りない! gpio コマンドで Pico の GPIO 制御の深淵にもぐる
pico-jxgLABO は、USB ケーブル一本でマイコンボード RaspberryPi Pico の様々な機能を試すことができる実験プラットフォームです。
この記事では、pico-jxgLABO の gpio
コマンドを使った GPIO の制御方法を紹介します。
たかが On/Off の入出力機能と思って、設定もなんとなくうやむやにしてしまいがちですが、コマンド操作でいろいろ試すことで GPIO の制御をより深く理解することができます。最大電流値やヒステリシス特性の設定も変えてみて、Pico の可能性を探ってみましょう。
pico-jxgLABO の書き込み
Pico ボードへの pico-jxgLABO の書き込みと基本的な使い方はこちら。特別なハードウェアは必要なく、Pico や Pico 2 ボードを USB ケーブルで PC に接続するだけで始められます。
この記事で説明する実験を行うには、バージョン 0.2.0 以降の pico-jxgLABO が Pico ボードに書き込まれている必要があります。ターミナルソフトで about-me
コマンドを実行すると pico-jxgLABO のバージョンを確認できます。
L:/>about-me
Program Information
name: pico-jxgLABO
version: 0.2.0
:
:
gpio
コマンドを使った GPIO の制御
gpio
コマンドを使って GPIO の実験をしてみましょう。ここでは Pico 2 を使いますが、Pico でも同様に実験できます。
現在の GPIO 状態の表示
引数なしで gpio
を実行すると、現在の GPIO の状態が表示されます。
L:/>gpio
GPIO0 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO1 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO2 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO3 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO4 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO5 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO6 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO7 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO8 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO9 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO10 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO11 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO12 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO13 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO14 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO15 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO16 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO17 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO18 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO19 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO20 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO21 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO22 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO23*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO24*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO25*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO26 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO27 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO28 lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO29*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
Pico の GPIO は 0 から 29 までの 30 本があります。上の状態表示において、各フィールドの意味は以下の通りです。
- 二番目のフィールドの
lo
,hi
は各ピンのロジック値を示しています。lo
は Low (0)、hi
は High (1) を意味します -
func
は GPIO に割り当てられているファンクションをあらわします。------
は何のファンクションも割り当てられていない (null function) ことを示します -
dir
はピンの方向を示し、in
は入力、out
は出力を意味します -
pull
はプルアップ/プルダウン抵抗の設定で、down
はプルダウン、up
はプルアップ、none
は両方ともなし (ハイインピーダンス状態) 、both
は両方が有効になっていることを示します -
drive
はドライブ強度で、通常は 4mA が設定されています -
slew
はスルーレート制御で、通常はslow
が設定されています -
hyst
は入力ヒステリシスの設定で、on
は有効、off
は無効を示します
上の状態表示で GPIO 名に *
がついている GPIO23, GPIO24, GPIO25, GPIO29 は外部ピンに接続されておらず、以下のような特殊用途に割り当てられています。
Pico および Pico 2:
- GPIO23 - OP Controls the on-board SMPS Power Save pin[1]
- GPIO24 - IP VBUS sense - high if VBUS is present, else low[2]
- GPIO25 - OP Connected to user LED[3]
- GPIO29 - IP Used in ADC mode (ADC3) to measure VSYS/3
Pico W および Pico 2 W:
- GPIO23 - OP wireless power on signal
- GPIO24 - OP/IP wireless SPI data/IRQ
- GPIO25 - OP wireless SPI CS - when high also enables GPIO29 ADC pin to read VSYS
- GPIO29 - OP/IP wireless SPI CLK/ADC mode (ADC3) to measure VSYS/3
これらのピンは誤った操作をするとボードの動作に影響を与える可能性があるので、gpio
コマンドでは操作ができません。ただし、Pico や Pico 2 の GPIO25 は内蔵 LED につながれており、手軽に使えて便利なので、オプション -B
(または --builtin-led
) をつけることで操作可能にしています。
GPIO ピン番号の指定方法
gpio
コマンドの最初の引数には、GPIO ピン番号を指定します。単一の GPIOピン番号を指定することもできますし、範囲を指定することもできます。例を以下に示します。
コマンド | 説明 |
---|---|
gpio 0 |
GPIO0 の状態を表示します |
gpio 2,3,8,9 |
GPIO2,3,8,9 の状態を表示します |
gpio 2-15 |
GPIO2 から GPIO15 までの状態を表示します |
gpio 8- |
GPIO8 から GPIO29 までの状態を表示します |
単一のピン操作のためのコマンド gpio0
から gpio29
も用意されています。例えば gpio2
は gpio 2
と同じ意味になります。
デジタル入出力について
デジタル出力
GPIO を出力モードに設定してデジタル信号を出力してみます。以下は内蔵 LED が接続された GPIO25 に High レベルのデジタル信号を出力して LED を点灯する例です。いわゆる L チカですね。オプション -B は、GPIO25 の操作を許可するためのものです。
L:/>gpio 25 -B func:sio dir:out put:1
サブコマンド func
で設定している sio
はシングルサイクル IO (Single-cycle IO) の略です。SIO は CPU が 1 サイクルでアクセスできる特殊なブロックで、そのレジスタの一部が GPIO の入出力に割り当てられています。サブコマンド dir
で出力モードに設定し、put
で出力値を指定します。
gpio
コマンドにはリピート処理をするサブコマンド repeat
や、ディレイを実行するサブコマンド sleep
があります。これらを使って以下のように LED を点滅させることができます。
L:/>gpio 25 -B func:sio dir:out repeat:20 {put:1 sleep:100 put:0 sleep:100}
出力値を反転させるサブコマンド toggle
を使うと、さらに簡単に点滅させることができます。
L:/>gpio 25 -B func:sio dir:out repeat:20 {toggle sleep:100}
ロジックアナライザを使って GPIO の信号を観測することもできます。
L:/>la enable -p 25
L:/>gpio 25 -B func:sio dir:out repeat:20 {toggle sleep:100}
L:/>la print
Time [usec] P25
│
:
0.00 └─┐
:
99208.00 ┌─┘
:
199199.52 └─┐
:
299194.56 ┌─┘
:
la enable
で測定対象のピンを指定してキャプチャを開始してから GPIO の操作を行い、最後に la print
でキャプチャしたデータを表示しています。
ところで、ファンクションを SIO 以外に設定したとき、put
で指定した出力値はどのように影響するのでしょうか? GPIO1 に UART RX 機能を割り当てて、以下のように出力値を High に指定してみます。
L:/>gpio 1 func:uart dir:out put:1
GPIO1 lo~ func:UART0 RX dir:out pull:down drive:4mA slew:slow hyst:on
ピンの状態は lo~
となっており、設定値は無視されていることがわかります。レベル値の後の ~
は、設定値と実際の信号レベルが異なることを示しています。
ファンクションが SIO でも、信号方向が入力モードになっている場合はやはり設定値が無視されます。
L:/>gpio 1 func:sio dir:in put:1
GPIO1 hi func:SIO dir:in pull:down drive:4mA slew:slow hyst:on
あれ、おかしい。設定値と信号レベルが一致しているように見えます。実は、これはピン状態がなんらかの理由 (今回の場合は後述する Pico 2 のハードウェアバグが影響) で High レベルになっているだけで、設定値を反映しているわけではないのです。以下のように設定値を 0
にしてみると無視されていることが分かります。
L:/>gpio 1 func:sio dir:in put:0
GPIO1 hi~ func:SIO dir:in pull:down drive:4mA slew:slow hyst:on
結論として、CPU からデジタル出力をする場合は、ファンクションを SIO に設定して出力モードにする必要があるということになります。それ以外のときはデジタル出力の設定値がピン状態に影響を与えることはないので、副作用を心配する必要がないともいえます。
デジタル入力
GPIO を入力モードに設定してデジタル信号を入力してみます。以下は GPIO1 を入力モードに設定し、信号レベルを読み取る例です。GPIO1 ピンにジャンパ線をつなぎ、GND に接続して信号を変化させてみてください。Ctrl-C
で中断できます。
L:/>gpio 1 func:sio dir:in pull:up repeat {get sleep:300}
サブコマンド dir
で入力モードに設定します。サブコマンド pull
でプルアップ抵抗を有効にしているのは、ピンがどこにも接続されていないときに信号を High に固定するためです。get
は GPIO の信号レベルを読み取るサブコマンドです。
Pico は GPIO のファンクションが SIO 以外に設定されていてもピンのデジタル信号レベルを CPU で読み取ることができます。これがデジタル出力のときとふるまいが異なる点です。以下のようにすると UART RX 機能が割り当てられた GPIO1 の信号レベルを読み取ることができます。プログラムが十分に速ければ、通信中の UART のデータをビット単位でモニターすることも可能です。
L:/>gpio 1 func:uart dir:in repeat {get sleep:300}
ファンクション設定について
Pico の GPIO ファンクション設定というと、下のピンレイアウト図がおなじみです。
より詳細な情報はデータシートに記載されています。
Pico - RP2040 Datasheet 2.19.2 Function select
Pico 2 - RP2350 Datasheet 9.4. Function select
各ファンクションには F0 から F31 までの番号が割りふられ、F31 が null function (ファンクション割り当てなし) になります。
pico-jxgLABO の gpio
コマンドは、サブコマンド func
を使ってファンクション設定を行います。指定できるファンクション名は以下のとおりです。
Pico - spi
, uart
, i2c
, pwm
, sio
, pio0
, pio1
, clock
, usb
, xip
, null
Pico 2 - spi
, uart
, uart-aux
, i2c
, pwm
, sio
, pio0
, pio1
, pio2
, clock
, usb
, hstx
, xip-cs1
, coresight-trace
, null
下の例では、利用可能なすべての GPIO を PWM ファンクションに設定しています。
L:/>gpio 0- func:pwm
GPIO0 lo func:PWM0 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO1 lo func:PWM0 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO2 lo func:PWM1 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO3 lo func:PWM1 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO4 lo func:PWM2 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO5 lo func:PWM2 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO6 lo func:PWM3 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO7 lo func:PWM3 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO8 lo func:PWM4 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO9 lo func:PWM4 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO10 lo func:PWM5 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO11 lo func:PWM5 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO12 lo func:PWM6 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO13 lo func:PWM6 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO14 lo func:PWM7 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO15 lo func:PWM7 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO16 lo func:PWM0 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO17 lo func:PWM0 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO18 lo func:PWM1 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO19 lo func:PWM1 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO20 lo func:PWM2 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO21 lo func:PWM2 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO22 lo func:PWM3 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO23*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO24*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO25*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO26 lo func:PWM5 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO27 lo func:PWM5 B dir:in pull:down drive:4mA slew:slow hyst:on
GPIO28 lo func:PWM6 A dir:in pull:down drive:4mA slew:slow hyst:on
GPIO29*lo func:------ dir:in pull:down drive:4mA slew:slow hyst:on
GPIO のファンクション設定は、一度決めてしまったものにこだわりがちですが、時々こうやって利用可能な配置をすべてながめていると、より使い勝手の良い設定が見つかるかもしれません。
プルアップとプルダウンについて
GPIO ピンの信号が不定な状態にならないようにプルアップ抵抗やプルダウン抵抗を設定することができます。これにより、ピンが接続されていないときの信号レベルを明確にすることができます。
gpio
コマンドでは、プルアップ抵抗を有効にするには pull:up
、プルダウン抵抗を有効にするには pull:down
を指定します。pull:none
を指定するとプルアップ/プルダウン抵抗は無効になり、ハイインピーダンス状態になります。
ところで、Pico のプルアップ、プルダウンの抵抗値は 25kΩ 程度だそうです。これは、例えば I2C 通信をしようとしてプルアップを設ける際、推奨されるプルアップ抵抗値は 1kΩ から 10kΩ なので、それに比べるとかなり高めです。I2C の場合、プルアップ抵抗が低い方がノイズの影響をうけづらいので、通信エラーなどのトラブルを避けるには外部にきちんと抵抗を設けるのが得策といえます (とは言いつつ、簡便的に利用してしまいそうですが...)。
ドライブ強度について
GPIO のドライブ強度は、出力信号の電流を制御するための設定です。通常は 4mA が設定されていますが、gpio
コマンドでは drive
サブコマンドを使ってドライブ強度を 2mA, 4mA, 8mA, 12mA に変更することができます。
それでは、実際にどれだけの電流が流れるのか? GPIO ピンに電流計をつなげて (ちょっと危ない!)、以下のコマンドをそれぞれ実行しました。Pico 内部抵抗の影響をなくすため、プルアップとプルダウン抵抗は無効にしています。
吐き出し電流測定用のコマンド:
L:/>gpio 1 func:sio dir:out pull:none drive:2mA put:1
L:/>gpio 1 func:sio dir:out pull:none drive:4mA put:1
L:/>gpio 1 func:sio dir:out pull:none drive:8mA put:1
L:/>gpio 1 func:sio dir:out pull:none drive:12mA put:1
吸い込み電流測定用のコマンド:
L:/>gpio 1 func:sio dir:out pull:none drive:2mA put:0
L:/>gpio 1 func:sio dir:out pull:none drive:4mA put:0
L:/>gpio 1 func:sio dir:out pull:none drive:8mA put:0
L:/>gpio 1 func:sio dir:out pull:none drive:12mA put:0
Pico と Pico 2 でそれぞれ計測した結果は以下の通りです。
吐き出し電流:
drive 設定 |
Pico | Pico 2 |
---|---|---|
2mA | 20.2mA | 20.3mA |
4mA | 30.0mA | 30.0mA |
8mA | 48.0mA | 48.7mA |
12mA | 56.5mA | 57.1mA |
吸い込み電流:
drive 設定 |
Pico | Pico 2 |
---|---|---|
2mA | 25.6mA | 27.6mA |
4mA | 37.5mA | 40.6mA |
8mA | 61.2mA | 67.1mA |
12mA | 72.2mA | 79.0mA |
ドライブ強度の設定値は、実際の電流値とかなり乖離していることがわかります。これは、Pico の GPIO のドライブ強度は、あくまで目安であり、実際の電流値は負荷や回路の特性によって変化するためだと考えられます。
Pico と Pico2 の比較では、吸い込み電流において Pico 2 の方が若干大きく、吐き出し電流ではほとんど違いが見られませんでした。
示されている値が相対的な目安にすぎないとはいえ、設定値を上げたときに制御できる電流値が増えることは確かです。負荷の高いデバイスを接続するときに役立つかもしれません。
Slew レートについて
GPIO のスルーレート制御は、信号の立ち上がりと立ち下がりの速度を制御するための設定です。通常は slow
が設定されていますが、gpio
コマンドでは slew
サブコマンドを使って slow
と fast
の2つの設定を選択できます。
ただ、手元の環境では、スルーレートを fast
に設定しても、実際の信号の変化を観測することはできませんでした。
ヒステリシスについて
GPIO のヒステリシス機能は、ゆっくりした変化の信号やノイズの影響を除くために設けられています。通常は on
が設定されていますが、gpio
コマンドでは hyst
サブコマンドを使って off
に設定することができます。
ヒステリシスを off
に設定すると、ゆっくりと変化する入力信号に対して不安定になり、High/Low がパタパタと切り替わるはずです。以下の回路を使って実験してみました。
時定数は
まずは両方のピンのヒステリシスを有効にし、同じ条件で実験します (Pico 2 を使用しています。Pico の場合は pio2
の部分を pio1
にしてください)。
L:/>gpio 2,3 func:pio2 pull::none
L:/>gpio 2 hyst:on
L:/>gpio 3 hyst:on
L:/>la enable --samplers:4 -p 0,*,2,3
L:/>gpio 0 func:sio dir:out put:0 sleep:1000 put:1 sleep:1000 put:0
L:/>la print --reso:300000
結果は以下のとおりです。
Time [usec] P0 P2 P3
│ │ │
0.00 └─┐ │ │
134383.46 │ └─┐ │
134556.04 │ │ └─┐
│ │ │
│ │ │
999803.38 ┌─┘ │ │
1279721.92 │ │ ┌─┘
1282086.20 │ ┌─┘ │
Time [usec] P0 P2 P3
時定数の影響を受けて、GPIO0 の変化に対して 130msec ほど遅延して GPIO2 と GPIO3 が反応していることがわかります。GPIO2 と GPIO3 がずれて見えますが、タイムスタンプの数値を見るとほぼ同時刻に反応しています。
それでは次に GPIO3 のヒステリシスを無効にして実験します。
L:/>gpio 2,3 func:pio2 pull::none
L:/>gpio 2 hyst:on
L:/>gpio 3 hyst:off
L:/>la enable --samplers:4 -p 0,*,2,3
L:/>gpio 0 func:sio dir:out put:0 sleep:1000 put:1 sleep:1000 put:0
L:/>la print --reso:300000
結果は以下のとおりです。
Time [usec] P0 P2 P3
│ │ │
0.00 └─┐ │ │
124398.68 │ │ └─┐
133327.28 │ └─┐ │
│ │ │
│ │ │
1000026.48 ┌─┘ │ │
1224540.98 │ │ ┌─┘
1275310.10 │ ┌─┘ │
Time [usec] P0 P2 P3
ヒステリシスを無効にした GPIO3 の方が、GPIO2 よりも早く反応していることが分かります。しかし残念ながら (?)、当初予想していた不安定になる現象は観測されませんでした。
C/C++ API との関連
gpio
のサブコマンドに相当する Pico SDK の API は以下の通りです。
サブコマンド | Pico SDK API |
---|---|
func |
gpio_set_function(uint gpio, gpio_function_t fn) |
dir |
gpio_set_dir(uint gpio, bool out) |
put |
gpio_put(uint gpio, bool value) |
get |
bool gpio_get(uint gpio) |
pull |
gpio_set_pulls(uint gpio, bool up, bool down) |
drive |
gpio_set_drive_strength(uint gpio, enum gpio_drive_strength drive) |
slew |
gpio_set_slew_rate(uint gpio, enum gpio_slew_rate slew) |
hyst |
gpio_set_input_hysteresis_enabled(uint gpio, bool enabled) |
gpio
のサブコマンドに相当する pico-jxglib の API は以下の通りです。
サブコマンド | pico-jxglib API |
---|---|
func |
GPIO::set_function(gpio_function_t fn) |
dir |
GPIO::set_dir(bool out) |
put |
GPIO::put(bool value) |
get |
bool GPIO::get() |
pull |
GPIO::set_pulls(bool up, bool down) |
drive |
GPIO::set_drive_strength(enum gpio_drive_strength drive) |
slew |
GPIO::set_slew_rate(enum gpio_slew_rate slew) |
hyst |
GPIO::set_input_hysteresis_enabled(bool enabled) |
まとめ
この記事では、pico-jxgLABO を使って GPIO の制御方法を紹介しました。GPIO の基本的な入出力操作から、ファンクション設定、プルアップ/プルダウン抵抗、ドライブ強度、スルーレート制御、ヒステリシス機能まで、さまざまな設定を試してみました。
これらの設定を理解することで、GPIO の制御をより深く理解し、実際のプロジェクトでの活用に役立てることができるでしょう。
Discussion