前回Picoでパソピア700のUSBキーボードアダプタを作ってみたが、安定しない。
Core1とPIOのFIFOバッファやりとりに速度のばらつきがありそうなので、そこに依存しないPIOの使い方を考えてみる。
まずは回路図の見直し
今回は8bitのデータ保持用に、74LS574を追加。
配線してみると、こんな感じ
PicoとUSBキーボードを接続し、DIN13pinはPasopia700と接続する
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 opt
usbkey_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 1
mov 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, 4
in 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)の場合は次のビット判定にJump
keyscanline_High:
in ISR, 1 ; ISRのSETビット列を1ビットループ
jmp keyscanline_loop
keyscanline_Low:
in NULL, 1 ; ISRのSETビット列を1ビット破棄
jmp keyscanline_loop
keyscanline_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 instance
sm, // [sm] State machine index (0..3) to use
10, // [pin_base] the first pin to set a direction for
7, // [pin_count] the count of consecutive pins to set the direction for
false); // [is_out] the direction to set; true = out, false = in
pio_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 input
sm_config_set_in_shift(&c, // INは右シフト
true, // [shift_right] true to shift ISR to right, false to shift ISR to left
false, // [autopush] whether autopush is enabled
32); // [push_threshold]
sm_config_set_out_shift (&c, // OUTは左シフト
false,// [shift_right] true to shift OSR to right, false to shift OSR to left
false, // [autopull] whether autopull is enabled
32); // [pull_threshold]
pio_sm_init(pio, // [pio] The PIO instance; either pio0 or pio1
sm, // [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 pio1
sm, // [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); // GP2
usbkey_program_init(pio0, 1, offset0, 3); // GP3
usbkey_program_init(pio0, 2, offset0, 4); // GP4
usbkey_program_init(pio0, 3, offset0, 5); // GP5
uint offset1 = pio_add_program(pio1, &usbkey_program);
usbkey_program_init(pio1, 0, offset1, 6); // GP6
usbkey_program_init(pio1, 1, offset1, 7); // GP7
usbkey_program_init(pio1, 2, offset1, 8); // GP8
usbkey_program_init(pio1, 3, offset1, 9); // GP9
pio0->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;
// Key1
pKey = keyMap + p_new_report->keycode[0];
keyReturn[pKey->keyReturnPin] |= pKey->nKeyReturnValue;
modifier |= pKey->modifier;
…
// Key6
pKey = keyMap + p_new_report->keycode[5];
keyReturn[pKey->keyReturnPin] |= pKey->nKeyReturnValue;
modifier |= pKey->modifier;
// Modifier
if (0 < (modifier & 0b00100010)) {
keyReturn[1] |= 0x80808080; // modifier[bit1]=Shift -> KS0[bit1]=SHIFT
}
…
// 各PIOのFIFOにセットする
pio0->txf[0] = keyReturn[0]; // GP2
pio0->txf[1] = keyReturn[1]; // GP3
pio0->txf[2] = keyReturn[2]; // GP4
pio0->txf[3] = keyReturn[3]; // GP5
pio1->txf[0] = keyReturn[4]; // GP6
pio1->txf[1] = keyReturn[5]; // GP7
pio1->txf[2] = keyReturn[6]; // GP8
pio1->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の使い方はあまりないと思うが、なんかやりきった感はある。
満足...