2018年04月21日

自作キーボード ソフト編(1)

どのようなコードを作るかについて書いてきたが、これも良くわからなくなっている。仕切り直しであらためて書こうと思う。

ソフト編(1)では、キースキャン側の処理について。I2C や V-USB のインターフェイスについては別途。



全体の処理構造

    メインループを持ち、イベントを if 文で検出し、それぞれの処理を行うこととする。i2c のみの対応であれば、他にも方法はあるのだが、こういう構造でないと V-USB に対応できない。

    キーボードスキャンを定期的に実行する必要があるが、この処理にタイマーを使う。が、V-USB を使う場合、割り込みは使わない。タイマー割り込みを禁止して、タイマー割り込み要求フラグをメインループでチェックするのだ。なお、タイマー割り込みが禁止されている場合、ソフトでタイマー割り込み要求フラグを落とさないといけない。

    完全にビジーループである。が、V-USB を使う場合はやむを得ない。

    さて、V-USB を使わない場合、別の方法を使う。I2C デバイスで、ビジーループを使って 消費電力を増やしてしまうのは面白くない。しかし基本構造は変えない。

    どうするかというと、SLEEP 命令を使うのである。メインループで やることがなかったら SLEEP 命令で 停止させる。タイマー割り込みが来れば、SLEEP が解けて ループが回る。

    タイマー割り込み要求フラグは自動的に解除される。割り込み処理の中で 別のフラグを立ててやらなければならない。割り込みで行う処理はそれだけである。別のフラグとしては GPOIR を使いたい。

    この2つの方法を実行時に切り分ける。VBUS の電圧で I2Cモード か USBモードか決めるつもりである。切り分けるといっても、タイマー割り込みを禁止するかどうかだけであり、メインループでは両方のフラグをチェックすれば良い。SLEEP については、タイマー割り込みが禁止されていれば、SLEEP しないという処理で良い。

      普通 SLEEP 命令は、注意深く使わなければならない。イベントが来ているのに SLEEP してしまったりすると、次の割り込みが来なくなってシステムが止まってしまうからである。しかし今回の使い方では気にしなくて良い。

キーボードスキャン

    定期的に キーボードスキャンをするわけだが、その処理について。

    設計したものは、ポートに割り当てた COL に L を出力し、ポートに割り当てた ROW を読み取るようになっている。ただし、861 版だけは、COL と ROW の番号を出力して 特定のポートを読み取る。処理内容が違う。

    861 版以外がどういうポートの割り当てをしているか 整理すると
                                                                          
    COL1 COL2 COL3 COL4 COL5 COL6 COL7 ROW1 ROW2 ROW3 ROW4 ROW5 ROW6 ROW7 ROW8
    Type 2313
    PB6 PB4 PB3 PB2 PB1 PB0 PD6 PD0 PD1 PA1 PA0 PD5 PD4 PD3 PD2
    Type t88a
    PA1 PC0 PC1 PC2 PD3 PA3 PB7 PC3 PD0 PD1 PA2 PD5 PD6 PD7 PB0
    Type t88b
    PB0 PD7 PD6 PD5 PD1 PD0 PC3 PB7 PA3 PA2 PD3 PC2 PC1 PC0 PA1
    Type t88c
    PC0 PC1 PC2 PC3 PD5 PD6 PD7 PD0 PD1 PD3 PB7 PB0 PB1 PB3 PB5
    Type t88d,t88e
    PB5 PB4 PB0 PD7 PD1 PD0 PC3 PD6 PD5 PB7 PD3 PC2 PC1 PC0 PB1

    このように規則性が全くない上にボードによって違うのであった。



ROW の読み取り

    最も下位レベルの処理として、ROW を読み取って 1バイトの bitmap にすることを考えよう。2313 版はメモリが少ないので、コード量を最小にしたい。

    最小のコードはループではない。ROW に規則性がないので、逐次処理を並べるのが最短。AVR なら、16 命令+α で作れる。

    SBIS PINx,n (PINx の bit n が 1 ならスキップ)
    SBR (実際は、ORI)

    というのを ROW のポートについて 8 個 並べる。C で書いてもこのようにコンパイルされる。

COL の出力

    さて、COL の出力はどうするのか? ここはループでないと困る。最短のコードは、関数テーブルを使う。

    SBI (CBI)
    RET

    2命令 4 バイトエントリのテーブルを 7 エントリ作る。これは、SET と CLEAR 2 セット必要である。
    これも 単なるテーブルなので ASM 命令を使えば C で書ける。また、C の switch 文 が 関数テーブルとしてコンパイルされるかも知れない。要調査である。

    C がうまくやってくれないのであれば、1 回のスキャンを 7 バイトの配列に格納するところまで アセンブラ関数にしてしまうのが良いかも知れない。

      まず C で書く。コンパイラでアセンブラで出力したものを改変していく作り方をする。苦労しても数命令しか節約できないかも。それでも、全体で 2KB なので、必要ならやらなければならない。最もここまでやるのは、2313 版のみということにしよう。

    なお、861 版であるが、メモリに余裕があるので、関数仕様を同じにして、普通に C で記述する。

    追記:間違いだった。関数テーブルなど必要ない。

    SBRC rxx,m (rxx の ビット m が 0 ならスキップ)
    SBI (CBI) DDRx,n (DDRx のビット nを 1(0) にする)

    これを 7 つ並べる。rxx は、1,2,4... とシフト。

    だが、これを意識した C のコードを書いても、想定どうりにコンパイルしてくれない。アセンブラにしないとダメなようだ。惜しいことに 変数の代わりに GPIOR0 を使うと想定どうりの結果になるのである。

      普通にループで書いた場合 184 バイト
      シフトで書いた場合 244 バイト
      GPOIR0 + シフト 126 バイト

    こんな感じ。アセンブラでちゃんと書けば、120 バイト程になりそう。

チャタリング除去

    上記は、単に状態を読み取るだけである。チャタリングを除去しないといけない。過去 N-1 回分 の状態を覚えていて、全部同じであれば、キーが押されている(離されている)と判断する。N を 8 にして その分細かくスキャンしたいところだが、RAM が厳しいので 4 でやってみようと思う。
    上記の関数仕様だが、格納アドレスを引数にして、そこに格納するというものが良いだろう。

    これで状態は分かるようになった。だが、状態の変化を検出するために、もうひとつテーブルが必要である。
     現在の状態
    を保持するテーブルを作り、スキャンの結果で更新する。更新する際に変化したかどうかチェックをする。

FN 拡張

    これで終わりではない。FN キーが押されて キーが押されたか? そしてそのキーが離されたか? を知る必要がある。このために、さらにテーブルが2つ追加になる。
     FN 拡張した現在の状態
    である。キーが押されたときに FN キーの状態を見て、どちらかのテーブルの状態を1に更新する。変化したかどうかのチェックも必要である。キーが離されたときには、どちらのテーブルも 0 にする。これもチェック。

    さて、2313 版では、ここまで作れないかも知れない。FN 拡張なし と あり、どちらも その先の処理は、同じである。単にテーブルが短いか長いか。

    テーブルが沢山必要になるわけだ。整理すると (N(4) + 1 + 2) x7バイト。49 バイト。少ないと思う人もいるだろうが、ATtiny2313 の RAM は 全部で 128 バイトである。

    なお、変化したかどうかチェックは不要な場合もある。callback を使いたいところだがダメである。実直に if 文で関数を切り分けたほうが、必要ないときのコード量が少ない。

ここまでの処理のながれとその先

    1) メインループで、タイマーのフラグが立っていたら、関数を call してキースキャンを行う。
     ※ タイマーは 100Hz - 10ms 間隔ということにする。
    2) N個 の スキャン結果を元に キーの状態を更新し、その変化を検出する。(FN 拡張あり/なし)

    変化を検出したら、そこからさらに先の処理を call する。

    先の処理とは何か? 動作モードによっていろいろある。
    ここで終わりなのが、HID 関係。HID では、USB のリクエストで、現在の状態を返す。調べていないが、HID over I2C も同じだろう。

ローカル スキャンコード

    処理は単純である。チェックした順に キーコードを割り振り FIFO につっこむ。キーが離された場合は、( キーコード | 0x80 ) 。なおキーコードは、1 オリジンである。0 は使わない。FN 拡張のコードは、+56 にする。

    オートリピートを忘れるところだった。が、これは別途検討しよう。

スキャンコード

    PS/2 の スキャンコードである。スキャンコードには種類があるようなのだが、SCANCODE1 のみ対応する。その中で対応できるのは、カーソルキーなど 2 バイトコード まで。

    基本のコードでは、離したときに 0x80 を OR する。2 バイトコードでは、コードの前に 0xE0 を 送る。
    変換テーブルでは、0x80 のフラグが立っていれば、2 バイトコードの意味にする。

    この 変換テーブルは、コード領域に作る。RAM は消費しないが、コード領域も厳しい。

アスキーコード

    悩ましいのである。アスキーコードでは、キーを離したというイベントは必要ない。そうなると FN 拡張した状態テーブルも必要なくなる。キーが押されたときに FN,SHIFT,CTRL が 押されているか チェックして コード変換したうえで FIFO につっこむことにする。

以上で、スキャンからの処理は一旦切れる。あとは FIFO の処理ということになる。

FIFO

    FIFO を作るわけだが、どれぐらいの長さかということを検討しなければならない。TI の I2C チップを見たところ 10 だった。メモリが厳しいので 一応 10 ということにしよう。

    10 ぐらいだと、リングバッファにせず、memcpy でデータをずらしていく方がコードが短いかも知れない。これも要検討である。

    さて、FIFO の仕様だが、EMPTY のときは、0 が読めるようにしようと思う。FULL になったら 一旦空にして 0xFF が読める。次のデータが来るまで ずっと 0xFF。

    FIFO状態フラグ に GPIOR を活用しようと思う。ビット操作のコードが小さく、RAM も節約できる。
    また、FIFO を流れるコードには 3種類あるが、取り出し側の処理は同じになるようにしようと思う。

オートリピート

    オートリピートは、FIFO を使うものに共通なやりかたで実装しようと思う。

    tick - 時間の単位は、タイマーと同じタイミング -- 10ms にする。1バイトで表現する。
    タイマー変数は、2つ。リピートまでの時間と リピート間隔。それは良いのだが、キーを離したコードも必要なのだろうか?
    、・キーが押されている間、メイクコードを周期的に発生させたりします。
    こんな記述を見つけた。これで少し楽になる。

    キーが押されていて、その状態が変わらずに続く場合にオートリピートの処理をする。
    送出するコードは、最後に送出されたキーコード。

    こう定義しよう。

    キーの状態が変わったというのは、(FN 拡張前の)現在の状態 を作るときにしよう。10ms 検出で タイマー変数 を +1 しておいて、変化すれば、タイマー変数をリセットする。

    キーが押されているというのは、現在の状態 を作るときに all 0 かどうかチェックすることにする。

    送出されたキーコードは覚えておく。ちょっと問題がありそうだが、こんな感じで。

コードの扱いは、こんな風にしようと思う。
posted by すz at 10:56| Comment(0) | TrackBack(0) | I2CKEYBOARD
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/183027488
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック