Picoシリーズで作るUSB接続テンキーパッド~PIO編
このアーティクルは2025年3月をもって終了したエンジニア向け情報サイトfabcrossに寄稿し掲載された初心者向けの記事をfabcrossの許可を得て再掲しています。
前回で取り上げた4×4キーパッド(以下、単にキーパッド)を予告どおり、PCに接続するUSBキーパッドに仕立ててみます。テーマは、標準C SDKにおけるProgrammable IO(PIO)の使い方と、USB HIDキーボードの実装方法です。どちらも軽くはないテーマですが、取り立てて難しいわけでもないので気軽に取り組んでみてください。今回はPIOプログラムの扱いを説明し、次回にUSB HIDキーボードの実装方法を解説します。
なお、前回に取り上げたキーパッドの知識やPIOコードの理解、そして使用している用語を前提にした記事になります。あらかじめ前回の記事に目を通してから今回の記事に取り組んでください。
まずはUSBキーパッドを使ってみよう
どのようなものを作るのかを最初に把握しておくと理解しやすいので、実際に製作するUSBキーパッドを動かしてみることにしましょう。必要なのは次の機材です。
- Pico 2またはPico 1
- Arduino向けのキーパッド(たとえばこのような製品)
- SSD1306採用
接続の128×32ドットOLEDモジュール(たとえばこのような製品)I^2C - 小信号用シリコンダイオード1N4148
- ブレッドボード
- ジャンパ類(適量)
前回の機材に追加したのは、SSD1306コントローラを採用した
また、流用した表示ライブラリの都合上、デフォルトの
図 1: USBキーパッドの接続図
図 1を参考にブレッドボードやジャンパを使って組み立ててください。もちろん、実用的に使っていきたいならケースを自作して収めるのもいいでしょう。組み立てたら、ミスがないか確かめておきます。
USBキーパッドのソースコードとビルド済みバイナリファイルを筆者のリポジトリに公開しています。リリースページを開き、最新バージョンのバイナルファイルをダウンロードしてください。Pico 2用はusb_keypad1_pico2.uf2、Pico 1用はusb_keypad1_pico1.uf2です(図 2)。
図 2: ダウンロードページ
PicoのBOOTSELボタンを押しながらUSBケーブルでPCに接続し、先にダウンロードした拡張子.uf2ファイルをPicoのUSBストレージにドラッグ&ドロップします。Picoが再起動し、新たなUSB HID入力デバイスが認識されるはずです[1]。キーパッドのボタンを押すと文字が入力されるでしょう。デフォルトでは16個のボタンに10キーパッド風のキーが割り当てられています。
また、OLEDディスプレイには1行目にCAPS LOCK、NUM LOCK、SCROLL LOCKが表示されます。ほかのキーボードでいずれかのロックを行うと1行目に表示が現れるでしょう。OLEDディスプレイの最下行にはUSBキーパッドからPCに送信された6バイトのキーレポート配列が16進数で表示されます。一種のデバッグ用です。キーレポート配列については後で説明します(図 3)。
図 3: インジケータ表示例
USB接続テンキーパッドとして正常に機能することを確かめてください。ボタンを押しっぱなしにすればリピート入力も行われるはずです。また、2キー以上の同時押しも可能です。ただし、前回で軽く触れた通り、このキーパッドでは3キー以上を同時に押すとゴーストが現れる組み合わせが存在します。
ソースコードをビルドする
ソースコードから自力でバイナリを作成できるようにしておけば、キーパッドのキー割り当てを自在にカスタマイズしたり好きに改造できますから、ぜひともビルドできるようにしておきましょう。ソースからビルドするためには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として最新のソースをダウンロードできます。
図 4: ダウンロードボタン
展開すると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に基づいた再構成が行われます。
図 5: すべてのプロジェクトをクリーンして再構成を選択
その後、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_gpio_init()
PIOにピンを割り当てるPicoでは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つです[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()
のほうがわかりやすいでしょう。
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キーボードを実装する方法を紹介します。興味がある人は先に関連するソースコードを読んでみると良いでしょう。
Discussion