前回Picoでパソピア700のUSBキーボードアダプタを作ってみたが、安定しない。
Core1とPIOのFIFOバッファやりとりに速度のばらつきがありそうなので、そこに依存しないPIOの使い方を考えてみる。
まずは回路図の見直し
今回は8bitのデータ保持用に、74LS574を追加。
配線してみると、こんな感じRaspberry Pi Picoには、133MHzで動作する2つのコアと、それとは独立に動作するプログラマブルI/O(PIO)を8つ持っている。
今回は1つのコア(Core0)と8つのPIO(PIO0 SM0-3とPIO1 SM0-3)を使って、以下の処理を試してみる
(1) Core0でUSBキーボードで押されたキーの情報を、8つのPIOの各FIFOバッファに格納
(2) PIOでパソピアからの7bitスキャン信号(GPIO10-16)をCLK立ち上がり時に読み込み、FIFOに格納されているビット情報から1bitを決めて出力。8つのPIOがそれぞれのGPIOに出力して、8bitデータをGPIO2-9に出力
(5) 前回保存しておいた7bitスキャン信号は74LS574に保存してあるので、キー入力に変化があった場合、押されたキーの情報を、8つのPIOの各FIFOバッファに格納し、8つのPIOを再呼び出しすることでGPIO2-9の出力データを更新する
PIOの都合もあって、FIFOに渡す32bitのデータ配列は以下の並びに
Bit | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
KeyBlock | A&B&C | B&C | A&C | C | A&B | B | A | None | |||||||||||||||||||||||||
KeyScanLine | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 | KeyOff(0), KeyOn(1) |
パソピア700から届く7bitのスキャン信号は、3bitのKeyBlockと4bitのKeyScanLine信号。
7bit全ての組み合わせを保存するのに128bitデータ領域が必要だが、FIFO 1つで対応したかったので32bitにキー情報を収める。
KeyBlockは3bit全ての組み合わせを持ち、KeyScanLineはそれぞれのビット毎にKeyOn/Offデータを保持する。どれか1つでもKeyOn(1)があれば、そのGPIOはLow出力する。
例えば以下表Exampleのように、7bitのスキャン信号(0b0110101)が届いた場合、
Pico GPIO | GP16 | GP15 | GP14 | GP13 | GP12 | GP11 | GP10 | |
---|---|---|---|---|---|---|---|---|
DIN13pin | 9pin | 8pin | 7pin | 6pin | 5pin | 4pin | 3pin | |
Pasopia700 | KeyBlock_C | KeyBlock_B | KeyBlock_A | KeyScanLIne_3 | KeyScanLIne_2 | KeyScanLIne_1 | KeyScanLIne_0 | |
Example | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0b0110101 |
KeyBlockはAとBが有効なので、FIFOの15~12bitのデータをチェックする
KeyScanLineは0と2が有効なので、FIFOの15bitと13bitの値をチェックし、どちらか1つでも1があれば、GPIOのピンにLow(0)をセットする
パソピア700とKeyScanLineの並びが逆だったり、出力信号のLow/Highが逆だったりするが、PIOの制約があったのでFIFOはこの並び順にした。
出来上がったPIOは以下
.program usbkey.side_set 1 optusbkey_start:wait 0 gpio 18 [7] ; GP18(=Din11pin=CLK)が0になるまで待つwait 1 gpio 18 [1] ; GP18(=Din11pin=CLK)が1になるまで待つ// バッファ(74LC574)がある場合、Pinデータを読み込む前に1clock必要in NULL, 32 ; ISRを初期化 →(ISR) |0|...|0|; SideSetピンを1にセットして、TX FIFO(32bitデータ)をOSRに読み出し; noblockオプション: FIFOが空の場合、Scratch XからOSRにコピーpull noblock side 1mov X, OSR ; 次回FIFOが空だった場合に備えてOSR値をScratch Xにコピー; Pins(GP10-GP16)の7bitデータをInput Shift Register (ISR)にセットin pins, 7 ; ISRを7bitシフトした後、pinデータ読み込みmov OSR, ISR ; ISRをOSRにコピー(OSRのShiftCounterもゼロリセット)mov ISR, X ; XレジスタをISRにコピー(ISRのShiftCounterもゼロリセット)out Y, 1 ; OSRの左1bitデータをYレジスタにセットjmp !Y shift_keyblock_c_end ; Yレジスタ=0(KeyBlock_C=0)ならジャンプin NULL, 16 ; 16bitシフトshift_keyblock_c_end:out Y, 1 ; OSRの左1bitデータをYレジスタにセットjmp !Y shift_keyblock_b_end ; Yレジスタ=0(KeyBlock_B=0)ならジャンプin NULL, 8 ; 8bitシフトshift_keyblock_b_end:out Y, 1 ; OSRの左1bitデータをYレジスタにセットjmp !Y shift_keyblock_a_end ; Yレジスタ=0(KeyBlock_A=0)ならジャンプin NULL, 4 ; 4bitシフトshift_keyblock_a_end:in ISR, 4in NULL, 28 ; ISRの左28bitをゼロ埋めkeyscanline_loop:mov Y, OSR ; YレジスタにOSRの内容をコピーjmp !Y keyscanline_end ; Y(OSR)=0(KSL=全てLow)の場合はループ終了out Y, 1 ; OSRの左1bitデータをYレジスタに左シフトjmp !Y keyscanline_Low ; Y=0(KSLx=0)の場合は次のビット判定にJumpkeyscanline_High:in ISR, 1 ; ISRのSETビット列を1ビットループjmp keyscanline_loopkeyscanline_Low:in NULL, 1 ; ISRのSETビット列を1ビット破棄jmp keyscanline_loopkeyscanline_end:in NULL, 28 ; 左4bitの値のみ有効にするmov Y, ISR ; YレジスタにISRの内容をコピーjmp !Y usbkey_start ; Y=0(SETx全て0)なら、指定Pin=Highのまま戻るset_pin_low:nop side 0 ; Y!=0(SETxいずれか1)なら、指定Pin=LowにしてStartに戻る% c-sdk {static inline void usbkey_program_init(PIO pio, uint sm, uint offset, uint pin) {pio_gpio_init(pio, pin);pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);pio_gpio_init(pio, 10);pio_gpio_init(pio, 11);pio_gpio_init(pio, 12);pio_gpio_init(pio, 13);pio_gpio_init(pio, 14);pio_gpio_init(pio, 15);pio_gpio_init(pio, 16);pio_sm_set_consecutive_pindirs(pio, // [pio] The PIO instancesm, // [sm] State machine index (0..3) to use10, // [pin_base] the first pin to set a direction for7, // [pin_count] the count of consecutive pins to set the direction forfalse); // [is_out] the direction to set; true = out, false = inpio_sm_config c = usbkey_program_get_default_config(offset);sm_config_set_sideset_pins(&c, pin);sm_config_set_in_pins(&c,10); // [in_base] 0-31 First pin to set as inputsm_config_set_in_shift(&c, // INは右シフトtrue, // [shift_right] true to shift ISR to right, false to shift ISR to leftfalse, // [autopush] whether autopush is enabled32); // [push_threshold]sm_config_set_out_shift (&c, // OUTは左シフトfalse,// [shift_right] true to shift OSR to right, false to shift OSR to leftfalse, // [autopull] whether autopull is enabled32); // [pull_threshold]pio_sm_init(pio, // [pio] The PIO instance; either pio0 or pio1sm, // [sm] State machine index (0..3)offset, // [initial_pc] the initial program memory offset to run from&c); // [config] the configuration to apply (or NULL to apply defaults)pio_sm_set_enabled(pio, // [pio] The PIO instance; either pio0 or pio1sm, // [sm] State machine index (0..3)true); // [enabled] true to enable the state machine; false to disable}%}
PIOの命令数はギリギリ31に収めた。マニュアルには32が上限とあり、確かに32命令でもビルドが通るが、31命令以下にしないとPico実行時にAssertで止まってしまう。
PIOの設定は、C言語側で行う
void init_pio(void) {uint offset0 = pio_add_program(pio0, &usbkey_program);usbkey_program_init(pio0, 0, offset0, 2); // GP2usbkey_program_init(pio0, 1, offset0, 3); // GP3usbkey_program_init(pio0, 2, offset0, 4); // GP4usbkey_program_init(pio0, 3, offset0, 5); // GP5uint offset1 = pio_add_program(pio1, &usbkey_program);usbkey_program_init(pio1, 0, offset1, 6); // GP6usbkey_program_init(pio1, 1, offset1, 7); // GP7usbkey_program_init(pio1, 2, offset1, 8); // GP8usbkey_program_init(pio1, 3, offset1, 9); // GP9pio0->txf[0] = 0;pio0->txf[1] = 0;pio0->txf[2] = 0;pio0->txf[3] = 0;pio1->txf[0] = 0;pio1->txf[1] = 0;pio1->txf[2] = 0;pio1->txf[3] = 0;}
FIFOに登録するキーデータは、定義しておく
typedef struct {uint32_t nKeyReturnValue;uint8_t keyReturnPin;uint8_t modifier;} PASOPIA700_KEYMAP;static const PASOPIA700_KEYMAP keyMap[256] = {{0x00000000, 0x08, 0x00}, // 00:No Event -> Nop{0x00000000, 0x08, 0x00}, // 01:Overrun Error -> Nop{0x00000000, 0x08, 0x00}, // 02:POST Fail -> Nop{0x00000000, 0x08, 0x00}, // 03:ErrorUndefined -> Nop{0x22220000, 0x00, 0x00}, // 04:a A -> A チ{0x11110000, 0x03, 0x00}, // 05:b B -> B コ{0x11110000, 0x02, 0x00}, // 06:c C -> C ソ…{0x00000000, 0x08, 0x00} // FF:RESERVED -> Nop};
キーが押されたタイミングで、新しいキーデータを各PIOのFIFOに登録しCLK信号をラッチ
static inline void process_kbd_report(hid_keyboard_report_t const *p_new_report) {const PASOPIA700_KEYMAP* pKey;uint32_t keyReturn[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};uint8_t modifier = p_new_report->modifier;// Key1pKey = keyMap + p_new_report->keycode[0];keyReturn[pKey->keyReturnPin] |= pKey->nKeyReturnValue;modifier |= pKey->modifier;…// Key6pKey = keyMap + p_new_report->keycode[5];keyReturn[pKey->keyReturnPin] |= pKey->nKeyReturnValue;modifier |= pKey->modifier;// Modifierif (0 < (modifier & 0b00100010)) {keyReturn[1] |= 0x80808080; // modifier[bit1]=Shift -> KS0[bit1]=SHIFT}…// 各PIOのFIFOにセットするpio0->txf[0] = keyReturn[0]; // GP2pio0->txf[1] = keyReturn[1]; // GP3pio0->txf[2] = keyReturn[2]; // GP4pio0->txf[3] = keyReturn[3]; // GP5pio1->txf[0] = keyReturn[4]; // GP6pio1->txf[1] = keyReturn[5]; // GP7pio1->txf[2] = keyReturn[6]; // GP8pio1->txf[3] = keyReturn[7]; // GP9// GP20を一旦LOWにして、GP18(CLK)に出力するgpio_put(20, 0);gpio_put(20, 1);
パソピア700から届くCLK(Pin11)とPicoのGP20のどちらかがLowなら、GP18にLowが入るようにAND回路を組んでおく。
実際にPasopia700に接続して、ロジアナで計測してみる
DIR=High(パソピア700から7bitスキャン信号が届く)になった直後、KeyScanLine信号がふらついてる。但しCLK立ち上がり時は安定しているので大丈夫そう。
DIR=Low(Picoから8bit信号線を送る)の後に少しふらついている。
CLK信号の立ち上がりにPIOが動作するようにしているので、これは良くない。。
上の回路図に書いたが安全のため、8bit信号線とCLK、DIR、全てにプルアップ、プルダウンをつけると安定した。
上のキャプチャは、A0~A7がKeyScanLine、B0がCLK、B1がDIR
ふらつきが無くなって、全てのキーが安定して入力されるようになった。
多分こんな変態的なPIOの使い方はあまりないと思うが、なんかやりきった感はある。
満足...
0 件のコメント:
コメントを投稿