Raspberry Pi Picoを始めよう! 週末工作でステップアップ
ラズパイPicoシリーズで作るUSB接続テンキーパッド——PIOプログラムにトライしよう

前回取り上げた4×4キーパッド(以下、キーパッド)を予告どおり、PCに接続するUSBキーパッドに仕立ててみます。テーマは、標準C SDKにおけるProgrammable IO(PIO)の使い方と、USB HIDキーボードの実装方法です。どちらも軽くはないテーマですが、取り立てて難しいわけでもないので気軽に取り組んでみてください。今回はPIOプログラムの扱いを説明し、次回にUSB HIDキーボードの実装方法を解説します。
なお、前回取り上げたキーパッドの知識やPIOコードの理解、そして使用している用語を前提にした記事になります。あらかじめ前回の記事に目を通してから今回の記事に取り組んでください。
【前回の記事はこちら】
まずはUSBキーパッドを使ってみよう
どのようなものを作るのかを最初に把握しておくと理解しやすいので、実際に製作するUSBキーパッドを動かしてみることにしましょう。必要なのは次の機材です。
- Raspberry Pi Pico 2またはPico 1
- Arduino向けのキーパッド(たとえばこのような製品)
- SSD1306採用I2C接続の128×32ドットOLEDモジュール(たとえばこのような製品)
- 小信号用シリコンダイオード1N4148
- 4×集合抵抗10kΩ(または10kΩ×4本)
- ブレッドボード
- ジャンパー類(適量)
前回の機材に追加したのは、SSD1306コントローラーを採用したI2C接続のモノクロOLEDモジュールです。キーパッドの機能だけだと面白くないので、ステータス表示用にOLEDモジュールを追加しました。SSD1306コントローラーを使用したI2C接続の製品で、パネル解像度が128×32ドットなら何でも構いません。筆者は0.91インチの超小型ディスプレイを使用しました。OLEDモジュールに表示する内容は後で触れることにしましょう。
また、流用した表示ライブラリの都合上、デフォルトのI2C(GP4およびGP5)を使用したいので、ColumnポートをGP10~GP13に変更します。接続図は、図1のようになります。


PicoのBOOTSELボタンを押しながらUSBケーブルでPCに接続し、先にダウンロードした拡張子.uf2ファイルをPicoのUSBストレージにドラッグ&ドロップします。Picoが再起動し、新たなUSB HID入力デバイスが認識されるはずです※。そしてキーパッドのボタンを押すと文字が入力されるでしょう。デフォルトでは16個のボタンに10キーパッド風のキーが割り当てられています。
また、OLEDディスプレイには1行目にCAPS LOCK、NUM LOCK、SCROLL LOCKが表示されます。他のキーボードでいずれかのロックをすると1行目に表示されるでしょう。OLEDディスプレイの最下行にはUSBキーパッドからPCに送信された6バイトのキーレポート配列が16進数で表示されます。これは一種のデバッグ用です。キーレポート配列については後で説明します(図3)。

USB接続テンキーパッドとして正常に機能することを確かめてください。ボタンを押しっぱなしにすればリピート入力もされるはずです。また、2キー以上の同時押しも可能です。ただし、前回で軽く触れた通り、このキーパッドでは3キー以上を同時に押すとゴーストが現れる組み合わせが存在します。
※ まれにUSBケーブルを抜いて再接続しないとUSB HID入力デバイスが認識されないことがあるようです。うまくいかないときは、いったんケーブルを抜いて差し替えてださい。
ソースコードをビルドする
ソースコードから自力でバイナリを作成できるようにしておけば、キーパッドのキー割り当てを自在にカスタマイズして好きに改造できますから、ぜひともビルドできるようにしておきましょう。
ソースからビルドするためにはRaspberry Pi公式拡張機能をインストールしたVisual Studio Code(以下VSCode)が必要です。まだインストールしていない方は、リンク先の記事を参考にしてVSCode+拡張機能の環境を整備してください。
LinuxやGitをインストールしているWindows環境であれば、Gitを使ってソースを入手できます。Picoのプロジェクトディレクトリで次のコマンドを実行すれば良いでしょう。pico_usb_keypadフォルダ以下にソースが展開されます。
git clone https://future.quake4.jp/gogs/yoneda/pico_usb_keypad.git
Gitがない環境なら、リポジトリのダウンロードボタン(図4)を使ってZIP形式の圧縮ファイルpico_usb_keypad-master.zipとして最新のソースをダウンロードできます。

展開するとpico_usb_keypadフォルダが得られるので、Picoのプロジェクトディレクトリにコピーしておきます。
VSCodeを起動して「フォルダを開く」を選択し、ソースコードフォルダpico_usb_keypadを開いてください。Pico 1を使用している人は、左ペインのエクスプローラーでCMakeLists.txtを開き、26行目を次のように変更して保存します。Pico 2を利用する人は変更する必要はありません。
set(PICO_BOARD pico CACHE STRING "Board type")
エクスプローラーでCMakeLists.txtを選択して右クリックし、「すべてのプロジェクトをクリーンして再構成」を選択します(図5)。ツールチェーンを聞いてきたら「pico」を選択してください。これでCMakeLists.txtに基づいた再構成がされます。

その後、CMakeLists.txtを選択し右クリックして「すべてのプロジェクトのビルド」を選択すれば、build/ディレクトリ以下にバイナリファイルusb_keypad1.uf2が作成されます。このファイルをPicoにドラッグ&ドロップすれば機能するはずです。
標準C SDKでPIOを使う
USBキーパッドでは、前回とほぼ同じPIOコードを使ってPIOにキースキャンをさせています。MicroPythonではPIOがよく抽象化されていて、ハードウェアをあまり気にせずにPIOを利用できますが、C SDKではハードウェアに近い制御が可能という違いがあります。その代わりに、少々ややこしいわけですね。
C SDKにおいてPIOを利用するには、CMakeLists.txtのtarget_link_librariesコマンドにhardware_pioの追記が必要です。VSCodeのPico拡張でプロジェクトを新規作成するとき、Features欄の「PIO Interface」にチェックを入れれば、この記述が自動で追加されます。もちろん、CMakeLists.txtをエディターで開いてあとから自分で追加しても構いません。
# Add any user requested libraries target_link_libraries(usb_keypad1 hardware_pio .... )
MicroPythonはPIOハードウェアを見えない形に抽象化していますが、C SDKではPIOハードウェアのインスタンスを通じてPIOコードを扱います。1つのPIOユニットに4基のステートマシンが組み込まれていて、Pico 1ではPIOユニットが2基、Pico 2には3基のPIOユニットが搭載されています。
C SDK上ではPIOのインスタンスが定義済みになっており、Pico 1ではインスタンスpio0、pio1が、Pico 2ではpio0、pio1、pio2が利用可能です。PIOコードを実行するときには、たとえばpio0のステートマシン0という具合に指定する形になります。
PIOコードは拡張子.pioのファイルに記述します。USBキーパッドではkeypad_scanner.pioにPIOコードを記述しています。CMakeLists.txtに次のコマンド行を記述すると、ビルド時にSDKに含まれるPIOアセンブラという一種のプリプロセッサがkeypad_scanner.pio.hというCのヘッダーファイルに変換し、コンパイルとリンクが行われる仕様です。
# Generate PIO header pico_generate_pio_header(usb_keypad1 ${CMAKE_CURRENT_LIST_DIR}/keypad_scanner.pio) 拡張子.pioのフォーマットは次のような形になっています。 .program PIO_code_name // ここにPIOコードを書く % c-sdk { void PIO_code_name_program_init(PIO pio, uint sm, uint offset, ...) { // PIOステートマシンの初期化コードを書く } %}
この例における「PIO_code_name」はPIOプログラムに付ける任意の名前です。USBキーパッドではkeypad_scannerという名前を使っています。.pioファイルの.programディレクティブ以下がPIOアセンブラによってPIOコードに翻訳されます。一方、% c-sdk { %}の大かっこにくくられた部分は、そのままの形でヘッダーファイル.pio.hに埋め込まれます。
% c-sdk { %}の中には、メインプログラムから呼び出すステートマシンの初期化関数PIO_code_name_program_init()を記述するのが一般的です。何を書くかは任意ですが、初期化コードでは後述する使用するピンや入出力方向、in命令やset命令のベースGPIO番号、ステートマシンの動作クロックや自動シフトの設定といった前回のMicroPythonで行ったようなことを書くことになるでしょう。これらについては後述します。
なお、拡張子.pioのファイルはC/C++のファイルに分類されないので、Intellisenseの補完や、言語サーバーによる文法チェックが機能しません。C/C++とは異なる記述が交じるので、C/C++と認識されればされたでエラーだらけになり困るでしょうし、わりと不便です。なので、初期化関数を無理に拡張子.pioのファイルに書く必要はないかもしれません。
拡張子.pioのファイルは前述のようにビルド時にPIOアセンブラによってヘッダーファイル.pio.hに変換されます。このヘッダーファイルには次の要素がPIOアセンブラによって生成されて埋め込まれます。
- アセンブル済みPIOコードのバイナリを含む構造体pio_programの実体PIO_code_name_program
- wrapを設定したpio_sm_configを返すインライン関数PIO_code_name_program_get_default_config()
- % c-sdk { %}の大括弧に括られたCコード(PIO_code_name_program_init())
繰り返しになりますがPIO_code_name部分が、プログラマーが記述したPIOプログラムに与えた名前に変換されることに注意してください。USBキーパッドではそれぞれ構造体の実体keypad_scanner_program、インライン関数keypad_scanner_program_get_default_config()がPIOアセンブラによって生成されてkeypad_scanner.pio.hに埋め込まれます。
メインプログラム側では、ヘッダーファイル.pio.hをインクルードしたうえで、次のような手続きでPIOコードを実行します。
#include "PIO_code_name.pio.h" void main(void) { ....// ここでなにかする // PIOハードウェア0のインスタンス PIO pio = pio0; // アセンブル済みPIOコードをpioにロードしてオフセットを得る uint offset = pio_add_program(pio, &PIO_code_name_program); // ヘッダーファイル.pio.hに書いた初期化関数を呼んで初期化する // 第2引数はステートマシン番号 PIO_code_name_program_init(pio, 0, offset); // ステートマシン起動、第2引数はステートマシン番号 pio_sm_set_enabled(pio, 0, true); .... //ここでなにかする }
基本的な流れはpio_add_program()でPIOにプログラムをセットしてPIO_code_name_program_init()で初期化し、pio_sm_set_enabled()の第3引数をtrueにして呼び出せばPIOコードがステートマシンで実行されるという形です。込み入っているようでいて、整理すればごく単純ですね。
なお、PIO_code_name.pio.hはビルド実行時にbuild/ディレクトリ以下に生成され、それまでは存在しないファイルです。記述した時点ではエラーになるほか、PIO_code_name_program_init()やPIO_code_name_programも未定義です。少々気持ち悪いかもしれませんが、無視しておきましょう。
PIOの初期化を行う関数群
続いて、ここまで初期化関数PIO_code_name_program_init()で行うとしてきた内容をざっくりと説明していくことにします。
PIO_code_name_program_init()の処理の流れは次のようになっています。
void PIO_code_name_program_init(PIO pio, uint sm, uint offset) { // ここでGPIOをPIOに割り当て入出力方向を設定する .... // PIOコードに基づくステートマシン設定pio_sm_configを得る pio_sm_config c = PIO_code_name_program_get_default_config(offset); // ここでset/in/out命令等のポートを設定 .... // 自動シフト、動作クロックなど追加の設定 .... // すべての設定をステートマシンにセットする pio_sm_init(pio, sm, offset, &c); }
set/in/out/jump命令に使うポートの割り当てや、シフト、動作クロックといった設定には、ステートマシンの設定情報であるpio_sm_configが必要になります。前述のように、PIOコードに基づくpio_sm_configは、PIOアセンブラが生成してくれる関数PIO_code_name_program_get_default_config()で得られます。
全ての設定を終えたら、pio_sm_init()を呼び出します。これでステートマシンが設定に応じた状態に切り替わります。
なお、pio_sm_init()はpio_sm_set_config()のエイリアス(別名)です。C SDKでは_init()という名前をいろいろところで使っており、統一性を持たせるためにpio_sm_set_config()という名称の関数を用意しているのではないかと思われます。
PIOにピンを割り当てるpio_gpio_init()
PicoではGPIOやI2Cなど複数の機能(インターフェース)を同じピンで利用できます。これは各ピンに内蔵されている切替器(マルチプレクサー)によって実現されます。たとえば、あるピンをGPIOで利用したいならgpio_init()という関数を呼び出しますが、これはピンのマルチプレクサーをGPIOに切り替える働きを持つ関数というわけです。
ピンをPIOで使いたいのなら当然マルチプレクサーを切り替える必要があり、それを行うのがpio_gpio_init()です。
pio_gpio_init(PIOインタンス, ピン番号);
たとえばpio_gpio_init(pio0, 4);などとすればGP4のマルチプレクサーがPIO0に切り替わるわけですね。
SDKのドキュメントに「出力のために必要なだけだ」というような紛らわしい記述がありますが、PIOの入力に使うときにもpio_gpio_init()を呼び出して切り替える必要があることに注意してください。PIOで使うピンに対しては全てpio_gpio_init()を呼び出さなければいけないということです。
USBキーパッドでは出力をするRowポートに6~9、入力をするColumnポートに10~13を使っているので、keypad_scanner_program_init()で次のように呼び出しています。
// rowBaseには4が、columnBaseには10が格納されている pio_gpio_init(pio, rowBase); pio_gpio_init(pio, rowBase+1); pio_gpio_init(pio, rowBase+2); pio_gpio_init(pio, rowBase+3); pio_gpio_init(pio, columnBase); pio_gpio_init(pio, columnBase+1); pio_gpio_init(pio, columnBase+2); pio_gpio_init(pio, columnBase+3);
ピンの入出力方向を設定する
GPIOで入出力方向の設定が必要なのと同様に、PIOでもピンの入出力方向の設定が必要です。やや込み入った話ですが、PIOの入出力方向を決めるpindirsレジスタは、PIOコードから設定します。たとえば、PIOのset命令を使ってpindirsレジスタに値を書き込める仕組みです。
しかし、PIOのコードには32命令までという制限があるので、pindirsレジスタの設定に命令を消費するのは時に困るでしょう。そこで、C SDKにはPIOのピンの入出力方向を設定するユーティリティ関数群が用意されています。
注意が必要なのは、このユーティリティ関数は実行時にステートマシンを使う点です。そのため、空いているステートマシンがないと関数を呼び出せません。ステートマシンを使い切ることはあまりないと思いますが、一応気に留めておいてください。
入出力方向の設定をする関数は次の2つです※。
- マスクを使って複数のピンの入出力方向を一括して設定するpio_sm_set_pindirs_with_mask()
- 連続したピンの入出力方向を設定するpio_sm_set_consecutive_pindirs()
USBキーパッドでは連続したGPIOを使っているので、pio_sm_set_consecutive_pindirs()を使って設定しています。
pio_sm_set_consecutive_pindirs(PIOインスタンス,ステートマシン番号,ベースGPIO,設定するピン数,入出力方向);
最後の引数はbool値で、trueなら出力、falseなら入力です。USBキーパッドでは、次のように設定しています。
pio_sm_set_consecutive_pindirs(pio, sm, rowBase, 4, true); pio_sm_set_consecutive_pindirs(pio, sm, columnBase, 4, false);
このように連続したポートを切り替えるならpio_sm_set_consecutive_pindirs()のほうがわかりやすいでしょう。
※ 48本のGPIOを持つバリエーションモデルRP2350Bがあり、32以上のピンの設定を行うpio_sm_set_pindirs_with_mask64()という関数が用意されていますが、Picoシリーズに採用されていないので本稿では触れません。Pico2のRP2350AのPIOは、拡張された機能に触れない限り基本的にRP2040と同じように扱えます。
in/set/out命令などのポートを設定する
PIOではin/set/outなどの命令に使用するポートを設定しなければなりません。この設定にはpio_sm_configのポインターが必要になります。
C SDKにはin/set/out命令およびsideset修飾に対し、次の2つの関数群を用意しています。
- sm_config_set_命令名_pin_base(): in/set/out命令およびsidesetのベースGPIOを設定
- sm_config_set_命令名_pin_count(): in/set/out命令およびsidesetのGPIO数
たとえば、sm_config_set_set_pin_base()でset命令のベースGPIO番号を設定し、sm_config_set_set_pin_count()でset命令のポート数を設定するという形です。関数名が長くてややこしいですが、わかりやすいといえばわかりやすいですね。
一方、jmp命令は1つのポートしか使わないので、sm_config_set_jump_pin()で設定します。
USBキーパッドでは次のように設定しています。
pio_sm_config c = keypad_scanner_program_get_default_config(offset); // set命令のポート設定 sm_config_set_set_pin_base(&c, rowBase); sm_config_set_set_pin_count(&c, 4); // in命令のポート設定 sm_config_set_in_pin_base(&c, columnBase); sm_config_set_in_pin_count(&c, 4);
自動シフトの設定
PIOでは入力レジスタisrと出力レジスタosrの自動シフトが可能です。前回の記事で触れた通り、キースキャンを行うPIOコードではisrを左シフトさせて16個のキー分のビットパターンを得ています。
isrの自動シフトを行うsm_config_set_in_shift()は次のような引数を持ちます。
sm_config_set_in_shift(pio_sm_config *, シフト設定, 自動push, 自動pushの閾値);
第2引数のシフト設定はbool値で右シフトならtrue、左シフトならfalseです。閾(しきい)値に達したらFIFOバッファに自動pushする機能もありますが、第3引数をfalseにして自動pushを無効化すれば第4引数は意味を持ちません。
osrの自動シフトにはsm_config_set_out_shift()が用意されています。pushがpullになるだけでsm_config_set_in_shift()と同じと思ってもらっておいて構わないでしょう。
USBキーパッドでは次の1行を設定しているだけです。
sm_config_set_in_shift(&c, false, false, 32);
ステートマシンの動作クロック
ステートマシンの動作クロックのデフォルトはシステムクロックです。つまりPico 2なら133MHz、Pico 1は125MHzと超高速で、USBキーパッドの用途であるキースキャンは速すぎて機能しません。
動作クロックを設定するためにsm_config_set_clkdiv()という関数が用意されています。関数名の通り、システムクロックの分周比を指定して動作クロックを変更します。
Picoシリーズのシステムクロックはclock_get_hz(clk_sys)で得られるので、次のように分周比を設定するのが一般的でしょう。
// freqにはステートマシンに設定したい動作クロックが入っている float clkdiv = (float)clock_get_hz(clk_sys) / freq; sm_config_set_clkdiv(&c, clkdiv);
なお、clock_get_hz()はhardware/clocks.hで定義されていて、このファイルをインクルードしておかないとエラーになります。注意してください。
PIO割り込みの使い方
前回に取り上げた通り、キースキャンをするPIOコードではPIO割り込みを使っています。MicroPythonでは簡単に扱えるPIO割り込みですが、C SDKでは少し踏み込んだ理解が必要になってきます。
PIO割り込みの概要
PIOコード上で割り込みを発生させるirq命令は次のような形で記述します。
irq 0
オペランドに指定している0は、割り込みフラグの番号です。PIOは内部に8個の割り込みフラグを持っていて、上の例では割り込みフラグ0をオンにします。
割り込みフラグはステートマシンの間で同期を取ったり、メインCPUへの割り込みを発生させたりするために利用します。つまり割り込みフラグの用途は、メインCPUへの割り込みだけではないということです。
割り込みフラグのうち、RP2040では0~3がメインCPUへの割り込みに使用できます。一方、RP2350では0~7がメインCPUへの割り込みに使用できるという違いがあります。
メインCPUには、PIOユニットあたり2つの割り込みが接続されています。表1と表2にCPU割り込み番号の抜粋を掲載しておきましょう。
表1:RP2040のPIO割り込み
CPU割り込み番号 | 割り当て |
---|---|
7 | PIO0_IRQ0 |
8 | PIO0_IRQ1 |
9 | PIO1_IRQ0 |
10 | PIO1_IRQ1 |
表2:RP2350のPIO割り込み
CPU割り込み番号 | 割り当て |
---|---|
15 | PIO0_IRQ0 |
16 | PIO0_IRQ1 |
17 | PIO1_IRQ0 |
18 | PIO1_IRQ1 |
19 | PIO2_IRQ0 |
20 | PIO2_IRQ1 |
RP2040は2基のPIOユニットがあり、RP2350は3基のPIOユニットを持つという違いがあるほか、割り当てられている割り込み番号も異なっていますが、RP2350で拡張されたPIOユニットを使用せず、ソースコード上で定義済みのキーワードを使う限り両者に互換性があります。
いずれにしても、4つないし8つの割り込みフラグに対してCPUには2つの割り込みしかつながっていないので、割り込みフラグとCPUの割り込みが1対1で対応しているわけではありません。
CPUの割り込みとPIOの割り込みフラグを対応させるのがpio_set_irq0_source_enabled()とpio_set_irq1_source_enabled()です。前者がCPUの*_IRQ0を、後者がCPUの*_IRQ1の対応付けを行う関数です。たとえば、次のように実行するとCPUのPIO0_IRQ0にPIOの割り込みフラグ0が対応付けられます。
pio_set_irq0_source_enabled(pio0, pins_interrupt0, true);
第1引数がPIOのインスタンス、第2引数がフラグ番号でRP2040ではpins_interrupt0~pins_interrupt3が、RP2350ではpins_interrupt0~pins_interrupt7が定義済みです。第3引数はtrueなら有効化、falseで無効化です。
これらの関数を使って複数の割り込みフラグを1つのCPU割り込みに割り当てることも可能で、割り込み処理内ではpio_interrupt_get()を使ってオンになっている割り込みフラグを調べて、処理を変えるといったこともできます。
たとえば、PIO0の割り込みフラグ0をメインCPU側で受け取るのならば次のようなコードを書くことになります。
// PIO割り込みハンドラ void pio_irq_handler(void) { // 割り込みフラグをオフにする pio_interrupt_clear(pio0, 0); ....ここでなにかする } void main(void) { ... ここでなにかする // CPUのPIO0_IRQ0に割り込みハンドラpio_irq_handlerを割り当てる irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler); // PIO0_IRQ0を有効化 irq_set_enabled(PIO0_IRQ_0, true); // PIO0_IRQ0に割り込みフラグ0を割り当てる pio_set_irq0_source_enabled(pio0, pis_interrupt0, true ); .... }
注意しなければならないのは、割り込みハンドラで必ずpio_interrupt_clear()を使って割り込みフラグをオフにする点です。これを忘れるとフラグがオフにならず、割り込みが発生し続けます。
USBキーパッドの割り込み処理
USBキーパッドでは、keypad_scanner.pio、keypad.c、keypad.hにキースキャンとキーパッドの処理をまとめています。keypad_scanner.pioに記述しているPIOコードは前回とほぼ同じで、キーに変化があったときにキーコードをFIFOバッファにプッシュして割り込みを発生させます。
PIO割り込み内では、keystate_bufferという16要素のリングバッファにFIFOから取得したキーコードを格納しています。リングバッファに読み出す以外は前回のMicroPythonコードと大差ないので、何をしているコードかはおおむね理解できるでしょう。
typedef struct _key_state_t { uint16_t code; int16_t state; } key_t; volatile uint write_p = 0; // バッファ書き込みインデックス volatile uint read_p = 0; // バッファ読み出しインデックス // キーバッファ volatile key_t keystate_buffer[KEYPAD_BUFFER_SIZE]; // PIO割り込みハンドラ void pio_irq_handler(void) { static uint32_t prev_keycode = 0; uint32_t keycode; // IRQクリア pio_interrupt_clear(pio0, 0); while(! pio_sm_is_rx_fifo_empty(pio0, 0)) { keycode = pio_sm_get(pio0, 0); uint32_t changed_bit = keycode ^ prev_keycode; prev_keycode = keycode; for(uint16_t idx = 0; idx < 16; idx++) { if(changed_bit & 1) { keystate_buffer[write_p & 0xF].code = idx; keystate_buffer[write_p & 0xF].state = keycode & 1; gpio_put(LED_PIN, keystate_buffer[write_p & 0xF].state); write_p++; } changed_bit >>= 1; keycode >>= 1; } } }
ちょっとした仕掛けとして、キーが押されたときにオンボードLEDを点灯させるようにしました。押すたびに「光ることで動いているな」と視覚的に確認できるからです。
バッファに読み出したキーコードの情報は、get_key()という関数で読み出せます。前回に説明した通り、キーコードは下位ビットから順にキー番号に対応しています。
// キーバッファからデータを取り出す key_t get_key(void) { key_t retval; uint wp; wp = write_p; if(wp != read_p) { retval.code = keystate_buffer[read_p & 0x0F].code; retval.state = keystate_buffer[read_p & 0xF].state; read_p++; } else { retval.code = 0; retval.state = KEYPAD_INVALID; } return retval; }
メンバstateがKEYPAD_INVALIDのときは読み出すべきデータがないことを意味します。stateがKEYPAD_PUSHならキー押下、KEY_RELEASEならキー押上です。
メインプログラム側からの読み出しが間に合わず、リングバッファの16要素を超えるとキーの取りこぼしが発生することになりますが、USBキーパッドではそのような事態は起きませんし、16キーの余裕があるのでよほどのことがない限り取りこぼすことはないでしょう。
また、キースキャンにCPUから独立したPIOを使っているので何が起きてもキースキャンは正確に動き続けます。この点が、このUSBキーパッドの特徴と言っても良いかもしれません。
というわけで、次回はPicoシリーズを使ってUSB接続HIDキーボードを実装する方法を紹介します。興味がある人は先に関連するソースコードを読んでみると良いでしょう。