2021年5月28日金曜日

PIOを見直してみる

前回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
KeyScanLine0 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の使い方はあまりないと思うが、なんかやりきった感はある。
満足...

0 件のコメント:

コメントを投稿