2018年04月03日

キーパッドめも

操作用のキーパッドを付けたい場合どうしたら良いかという話。1〜3 個ぐらいなら直結すれば良いわけだが、それ以上の場合。ただし、キーボードはまた別の話とする。

1) パラレル入力シフトレジスタ

74lv165a.png

74HC165 (など) を 1つまたは 2つ使って 〜8個 または 〜16個のキーを入力する。このやり方は FPGA で扱うには実に簡単そうである。また、このやり方は、ファミコンやスーファミのコントローラで採用されている。

    ファミコンは、CD4021B などが使われているが、74系165でも機能は同じである。また、入力はプルアップ。ボタン押下で L になるようにする。新規で作るのであれば、今は 74LV165A がある。ただ、tiny2313 で同じ機能を作ったほうが安上がりな上、機能拡張も可能。ただし、クロックは、6us でトグルされるので、適当なコードではまずい。

操作用といっても似たようなものになるわけだから、いっそのこと、そのものを使ってしまっても良いかも知れない。

    < 7 6 5 | 4 3 2 1 ]
    1 VCC , 2 CLK, 3 P/S,4 DATA,7 GND
    こういう接続らしい。(ケーブルの信号が 5本なので、他は NC ?)

    P/S を H にして、ボタンの状態を取り込む。(ボタンを押せばL)
    P/S を L にすると 取り込んだデータを シフトするモードになる。CLK の立ち上がり でシフト。
    出力されるデータは、負論理で B,Y,SELECT,START,UP,DOWN,LEFT,RIGHT,A,X,L,R の順。

      165 では SH/~LD の論理がP/S と逆で L にしたときの値を取り込むので注意。

      なお、実機では 60Hz でスキャンしているとのこと。

    ハードオフでジャンクを買える可能性もあるが、未だに、サードパーティーの コントローラも 本体側コネクタや延長ケーブルも aliexpress で売っている。
2) I2C デバイス

FPGA 内に CPU を作るような場合は、I2C デバイスとして作っても良いかもしれない。iCE40 も MachXO2 も I2C のファンクションブロックを持っている。使い方をマスターするためにトライするのも良さそうな気がする。
これまた、wii や ミニスーファミ、ミニファミコンのコントローラは I2C だそうだ。それだけではなく、今やノーパソの入力デバイスも I2C になってたりする。"HID over I2C" なるプロトコルまである(要調査)。

簡単に考えていたが、意外に深そうである。I2C であれば、AVR でデバイスを作ったり、逆に扱ったりできる。SBC で扱うのにも便利だろうし、調査しておきたい。

    ミニスーファミ系の話:基板用コネクタ や 延長ケーブルが売っている。コントローラ自体は、wii 用が多いようだ。

    I2C だからと言って、FPGA で扱うのが極端に難しくなるわけではなさそう。初期化した後は、特定のアドレスのデータを繰り返し読み込むだけとも言える。初期化も特定のパターンを出力するだけ。

その他、aliexpress で $3 以下で買えるものに、セガサターン用のものがある。売っていること自体驚きなのだが、これは 4bit パラレル出力で扱いにくい。他に bluetooth リモコンがある。これも価格に驚いた。電子工作で使うとは思えないが、車のハンドルに取り付けされそうな形状をしていて興味がある。


以上のことから考えるに、スーファミのコントローラを元に電子工作するのが面白いのではないだろうか? 直接扱っても良いし、AVR などで I2C にプロトコル変換にチャレンジしても良い。自己流でも良いし、ミニスーファミに接続するのを目標にしても良い。HID over I2C は Linux が対応するだろうから、SBC をホストにしてチャレンジしても良い。

    ミニスーファミなど Linux を利用しているものは、ゲームパッドのドライバを含む 一部のソースコードが GPL に従って 公開されている

    ここに ソースコードを元にした正確な使い方の解説がある。(使い方であって、仕様が書かれているわけではないので注意。)

    他に解析した人も。

    こういう情報があるのだから、I2C 化することは可能。I/O は、I2C 2 本、ゲームパッド 3 本だから、8ピンの AVR で十分そうだ。しかもほぼ直結ではないか? また、I2C + P/S の 3 本をインターフェイスとすることで、I2C と シフトレジスタ 両方サポートすることも可能だと思われる。



aliexpress で SNES 用のゲームパッドが 未だに買えるのは、相当に不思議である。なぜなのだろう?

snes_pad2.jpg

ひとつのヒントはこれではないか? USB タイプがあるのである。当然 HID デバイスだろう。ケーブルを変えるだけで、旧タイプになるのであれば納得できる。となると製造日は比較的最近で、パーツの劣化などはないかも知れない。

逆に、USB サポートであるならば、電源が 3.3V では動作せず 5V が必要ではないか? こういう不安もある。フラスチックの金型だけが同じでついでに別タイプを作っているのであれば良いのだが。。。こればっかりは入手してみないことには分からない。


ali-gamepad0.jpg

サターン用だと思うが、お安いゲームパッド。
 1 GND, 2 D2, 3 D3, 4 VCC, 5 S0, 6 S1, 7 D0, 8 D1, 9 VCC
だそうだ。S0,S1 で 選択した 4bit の状態が出力される。

ali-gamepad1.jpg

お安い Bluetooth リモコン。珍しく単4電池式。Android / iOS 切り替えスイッチがある。Continuous play time: about 40-120 hours だそうだ。


さて、フルキーボードの場合は、どうしたら良い。

どうやら I2C で良いようだ。ミニスーファミは、1回のスキャンで 21B ものデータを読み出す。ボタンが 128 個あったとしても 16B しか使わないわけで、問題ない。スーファミのシフトレジスタ版でも全然問題ない -- 128bit シフトすれば良いだけである。

さすがにシフトレジスタ16 個とかやりたくないので、I2C ということにして、コントローラは、ATtiny2313 を使うことにしよう。20pin だから、ISP することを考えて、I/O は 20 - 2(電源) -1(RESET) -2(I2C) で 15 本ある。これだと 7x8 のキーマトリックスが扱える。

I2C のピンは、ISP と共用であるから、MISO のみ、 キーマトリックス と共用ということにしよう。

    MISO は出力なので、マトリックス側は出力側にアサインする。

i2c_keyboad.png

こんな風に考えていくと基板を設計したくなる。ボチボチやってると、だいたい出来上がってきた。
幅 88.9mm 奥行 38.0 mm の大きさに、3x4mm のタクトスイッチを置いてみた。かざりのようなものではあるが、Nanopi-DUO なり iCEDIP ボードなりに付けられると良いなと考えた。

キーレイアウトの イメージは、APPLE II 。別にそのものを目指すわけではないのだが、最もシンプルそうなので。そんなことより、予定したエリアにどれだけキーを置けるかである。けっこう ぎりぎりで ESC ぐらい置きたかったが、とりあえずパス。3つのスペースは独立なので、そこに割り当てても良い。

    作るボードが 8cm x 10cm をタテに使うわけで、取り付けタブを含めて 幅 90mm ぐらいというのが重要なのだ。NTSC モニタも手配中だし、レトロな感じのミニチュア・パソコンみたいな何かに仕上げてみたいと計画はしている。

さて、このキーボードは二階建てになった。配線で目いっぱいでコントローラを置けなかった。2枚のッボードは、ピンヘッダをハンダ付けして固定してしまおうと思う。あまり背を高くするのは嫌なのだ。ホストとの配線のコネクタも余計なスペースを取らないようにしてみた。あと、同時押し対応を検討しようと思っていて、裏面にはダイオードを入れてみた。

    ルネサスの分かりやすい解説には、「他のキーと同時に押すキーにだけダイオードを付ける場合もあります。」と書いてあった。全部に付けると思っていたので、めんどくさいと思っていたのだが、効果的方法があるらしい。あまり考えたくないので、とりあえず 10個に入れた。

    ダイオードを入れると、他のキーに影響を与えない? だから、他のキーから見ればないのと同じ -- という理解で良いのかな?ならば、組み合わせて使うのが前提の CTRL や SHIFT などには、ダイオードを入れるべきか。よし OK 。

 ・i2c_keyboard-02-out.zip 提出ファイル(ボードサイズ 89 x 75)
 ・nazo_prj-02.zip (EAGLE ソース)

    i2c_keyboad-tmp.png
    他にこういうのも作ってみたいような。タクトスイッチの中のドーム状のバネ接点を直接置くのである。固定は幅広テープ。製品にはこういうタイプもある。金フラッシュ仕上げにしないといけなくて高いうえに、うまく行くかどうか分からないので、保留。



あとは、Linux で 自作 I2C キーボードを入力デバイスとして使うには?というのをクリアしておきたい。多分何通りか実現方法があって、ユーザランドのプログラムで入力し、入力イベントをシステムに投げるというのもありだろう。既にドライバがあるならば、それに適合するように I2C のデバイス側を作るのもありだろう。ここは、要調査である。



キー配列について

作ったキーボードのキーの数が全然足りないようだ。他のキーボードを調査してどうするか決めなければならない。
作り直すことは可としても 7x8 は決定である。現状 54 キー使っているから増やせて 2 キーである。

さて、割と少ないキー数のキー配列

ESC 1 2 3 4 5 6 7 8 9 0 - = BS 14
TAB Q W E R T Y U I O P [ ] \ 14
CAPS A S D F G H J K L : " RET 13
SHFT Z X C V B N M , . / SHFT 12
CTRL WIN ALT SPACE FN 5

56 より 2 キー多い。配列は少し違うが、初代マックも 58 キーのようである。
これでも足りないのである。〜キーである -- ESC の位置にあったりするキー。HHK professional も良く似ていて、最上段が 15 キーで ¥キーの右に 〜キーが置かれている。

さて、作ったキーボードに割り当てて考えてみると ...

1 2 3 4 5 6 7 8 9 0 - = BS 13
TAB Q W E R T Y U I O P [ ] 13
CTRL A S D F G H J K L : " RET 13
SHFT Z X C V B N M , . / ALT 12
FN SP SP

〜キーどころか ¥キー ESC がない。また 左右 SHFT のどちらかを ALT に割り当てざるを得ない。
下の段は3つだが、FN を割り当てることにして、足りないキーをシフトする。WIN キーはなしの方向で。

動かすことが最初の目標であり、それをクリアした後である程度使ってみて、作りなおすかどうか決めることにしよう。

    作りなおしたい点のメモ
    ・ CTRL の位置がずれている。
    ・ 上下に少し詰めたい。(使ってみて行けそうなら)
    ・ ESC を(できれば)入れたい
    ・ 左右にタブを少し広げたい


ちなみに部品

td-85u.jpg
・3x4mm タクトスイッチ TD-85XU 100個 $1.75
  (タクトスイッチは2台目を作るには足りない)
・ダイオード 1N4148WS (SOD323) 100 個 $1.09

ts-1234u.jpg
・3.2x4.2mm タクトスイッチ TS-1234U 100個 $1.99
 新たに作るなら、こちらの方を使いたい。

    とりあえず、作ってみたところ、コンパクトにできた。4ピンなので、ジャンパとして利用できる。そうすると、VIA を作らずにすみ、ATtiny2313 が、裏面に置けるようになった。

    ただ、これ手ハンダできるのか?というものになってしまった。リフローすれば良いのだろうが、部品が溶けてしまわないか心配。

    i2c_keyboad-03.jpg


6x6x5-tact.jpg
他にこういうのを見つけた。100個 $6
追記:これではダメだ。密集させると手ハンダできない。
6x6x5-tact2.jpg
同じ 6x6mm ならば、スルーホールの 2ピンタイプが良い。密集させてもハンダ付けが楽で位置決めの苦労も少ない。ためしに レイアウトしてみたところ、92mm x 37mm でコントローラ基板と互換性のある基板が作れる。気が向いたらパターンを作っておこう。

    i2c_keyboard_6x6.png
    気が向いたので6x6 mm を 6.2mm ピッチで置いて、パターンを引いた。結構綺麗に収まるのである。4 ピンタイプではこうはいかない。また SMD では、こんな風に密集配置はできない。十分良い感じではあるが、3D プリンタでキートップを作るとか先があればなぁと思ってしまう。発注した 3x4mm のもので不満出たら作るかもしれないが、とりあえず寝かしておこう。

他には、7.8mmx7.8mm でラバードーム型がある。たとえば これ 20個 $1 前後。 横 8x 14.5mm のエリアが必要で、残念なことに基板を起こせない。6.2mm ピッチを 8.0mm ピッチに変更すると 上の基板が 119mm x 48mm になってしまう。EAGLE の都合、作れる最大ピッチは、6.8mm である。基板サイズ自体は 部品を置かなければ良いので制限はない。

    実は、縮小サイズで設計して ガーバーを拡大するという方法はある。部品の種類が 3 つしかないので簡単なほうなのだが ...。

    検討だけしてみよう。4/5 サイズで設計すると、8mm ピッチは 6.4mm ピッチになる。2012 は、1608 に変更する。2.54mm ピッチコネクタは、2mm ピッチで代用。

    コネクタに合わせて 2/2.54 サイズでも良いかも知れない。6.3mm ピッチが 8.001mm ピッチになる。
    コネクタの配置は、ちょっと考えよう。上部に集めて、コントローラ基板と一面で接続、ネジ止めもできるようにする。

    作ることは出来そうだ。あとはキートップか。3Dプリンタで作るか、CNC で削り出す? それに加えてキートップを抑えるボード。削り出すか、あるいはステンシルとして作る方法がありそうだ。

    そこまでして、しょぼいものになってはダメだ。やっぱり寝かしておこう。




I2C 通信について

どういうプロトコルが良いのか検討しておこう。

    I2C は、装置番号と Read か Write かの情報をホストがまず送出する。ACK が返ってきたら データを通信する。転送長は予め決まっていない。STOP 条件になれば1パケット終わりになる。また、1 バイトごとに 受信側が ACK を返すことになっている。ホストのREAD で ACK をださなければ、そこで終わりという通知になる。

    I2C ROM での Write の場合、2 バイトのアドレスを送出し、続いて 1 〜 32 バイトの Write データを送る。0 バイトの場合、カレントアドレスのセットになる。
    Read では、カレントアドレスから逐次データを読み出す。

    典型的な使い方は、このような感じ。SPI のようにコマンドを送り続いて READ というプロトコルにはならない。

最も簡単なプロトコル

    ミニスーファミのように、押下状態の ビットマップをあるアドレスに置いて、それを HOST が定期的に読み出す。Write は、カレントアドレス のセットのみ。

    これだけで十分機能する。が、頻繁(100Hz 前後)にアクセスし、チャタリングの対応もホストがしなければならない。

TI の TCA8418 という IC があるのだが、これは、もう少し複雑。いわゆるスキャンコード方式で、キーが押されたか離されたかの情報+キーコードを FIFO から読み出す。

    カレントアドレス を FIFO にセットしたら 以降 アドレスを+1 しないで FIFO から読みつづけることにしよう。

    幸いなことに キーは 最大 56 である。EMPTY 1bit + PRESS 1bit + キーコード 6bit で 8bit に収まる。

    この方式だと ビットマップと両立する。 が、ちょっと方式を変える。押下状態は、スキャンコードと一致させる。チャタリングの処理が終わったものをビットマップに格納しようと思う。

    また PC で使われている スキャンコード に変換する必要があるかも知れない。その場合 1 バイトでは足りない。2 バイトとなると 少し複雑になる。

ASCII コード。必要かどうか分からないのだが、あっても良さそうだ。

    同じように FIFO から読み出すこととしよう。EMPTY にはひとつのコードを割り当てる。スキャンコードとは両立しないので、モードレジスタを用意して切り分けるか、あるいは FIFO のアドレスを変えるかする。

    ASCII コードは、押されたときに送出ではない。オートリピートがある。そうなると、リピート間隔を設定したりしたくなるだろう。その上 EEROM に覚えたり・・・ちょっとコードが増えそうだ。

プロトコルは、まぁ自己流で良いのではないかと思う。自己流で困るのは、汎用のドライバがあって、それを使いたい場合だけである。SBC で言うと U-boot が対応しているものがある.. とか。

    そこらへん、ちょっと調べておこう。

    http://linux-sunxi.org/Mainline_U-Boot

    ここ見ると USB keyboard はサポートしている。i2c 自体は 電源IC の制御に使われる。が、他のデバイスのサポートはなさそうだ。
    ついでだが、Composite video output support もある。

    他のデバイス用では、PS/2 キーボードに対応したものがある。



作った基板のチェックとアルゴリズムの検討

    COL1 〜 COL7 , ROW1 〜 ROW8 でスキャンする。ダイオードがなければ、どっちがどっちでも良いのだが、全ピンプルアップしておいて、出力の1ラインだけ L にする。次に入力側の ポートを逐次読み込む。L であれば押されている。入力ポートは 配線の都合で散らばっているので、一気にバイト分読み込むことはできない。

    さて、唯一の 共有ポートは MISO で出力。これが COL1 に接続されている。となると、COL が出力で、ダイオードは、ROW → COL の向きでなければならない。これが見事に逆であった。全部シルクと逆に付けないと。

    bit 単位で データを格納していくが、7 バイトになるよう決める。すなわち COL に対して 1 バイトのデータということになる。

    これが現在のデータである。チャタリング対策のため N-1 回前まで のデータを保持して、N 個全部同じ値なら、L または H に変化したと判定する。判定したデータは、また別の配列に置いて、i2c から読み出せるようにする。メモリが厳しいので N は 4 あたり、できれば 8 にしたいところだが、全部で 128B しかないのだ。

    さらに L または H に変化したときに FIFO に データを置く。FULL になったとき どうするか? 考えておかないと。

      EMPTY コードを作ったから エラーコードも作ろう。FIFO を空にして エラーコードを 入れておく。ホスト側では、エラーコードを拾ったら、ビットマップを読み込んで初期化しなければならない。

ここまでで、簡単な機能が実装できたとしよう。一応キーの状態を矛盾なく読み取れるのであるから、後はホストが勝手にすれば良い。ポーリングしなければならないのだが、スキャンコード方式で、100Hz でのポーリングが 10Hz 程度まで 頻度を落とせる。そして、いくら改良したとしても スキャン頻度は変わらない。十分な機能であるという見方はできる。

とは言え、メモリさえ余裕があれば ASCII 版まで 作りたい。

    ASCII 版は、どうすれば良いだろう? FIFO 自体は、そのまま使いたい。FIFO に IN するときに、コード変換をすれば良いだろう。

    キーが押されたら、FN や CTRL, SHIFT の状態を見て、変換テーブルで変換したコードを FIFO に入れる。
    コードは 56 しかないのだから、56 バイト x 4 (default, FN , SHIFT , FN+SHIFT ?) これは ROM に置けるから 危惧するほどの量ではない。

    同じように、スキャンコード自体の変換もやって良いかも知れない。ただし、2 バイトコードになる場合がある。また、例えば FN が先に離された場合どうするか? これがちょっと難しそう。出来ないかも知れない。

    https://www.pfu.fujitsu.com/hhkeyboard/hhkb_support/scancode.html
    HHK のスキャンコード の定義があった。見るからにぞっとするような量である。やっぱりヤメ。ASCII なら、押されたときだけである。(ちなみに USB HID も 押されたときだけ) 。変換されたスキャンコードでは、離されたときは、どのキーが押されたことになっているか調べないといけないのである。処理も多いしメモリも使うのである。

    PS/2 英語キーボードの説明:
    http://www.eecg.toronto.edu/~jayar/ece241_08F/AudioVideoCores/ps2/ps2.html
    ps2-scan-code.png
    ちょっとメモ。作れるような気がしないんだが、一応。

ASCII版 が出来たら、次はオートリピート。ひとつのキーだけが押されているという状態を作って、時間で FIFO にそのキーを突っ込む。面倒だが、困難という感じではない。

だいたいこんな感じで行こう。次は、全体の構造とか。

    今まで AVR で作ってきたものは、メインループで動かす平タスク+割り込みという構造で、割り込みは極力処理を少なくする方針であった。これ自体は変わらない。

    以前作った USI を使う I2C デバイス は、基本割り込みで動作する。READ では、カレントアドレスをINDEX としたテーブルの読み込みと FIFO の読み込みだけなので、この構造で問題ない。WRITE もカレントアドレスの変更のみ。基本問題ないが、動作が変わるところが出来る。その部分は、イベントフラグを作って平タスクに通知しようと思う。今やってる処理を完了してから、改めてモード変更という流れ。

    平タスクでは、定期的にスキャンしてテーブル更新。キーの状態が変わったら FIFO にデータを入れる処理。これも割り込みでという実装はあるのだが、平タスクでは処理全体を inline 関数化できたりして、コード効率が良い。定期的に動かす部分については、タイマー割り込みだが、これもイベント通知のみで行こうと思う。

    イベント通知は、GPIOR にフラグを立てる。SBI 1命令であり、レジスタの退避も必要ない。なお、割り込みをわざわざ起こすのは、SLEEP 命令でのアイドル対応のため。

次は内部コード

    FIFO は 8bit で変な細工をせずにそのまま通したい。スキャンコードと ASCII コードの両方に対応する。
    EMPTY コードは 0x00 で、あと FULL が起きたことを示す ERROR コードを 0xFF としよう。
    ASCII コードは、0 〜 0x7F で 0x00 は送れない。スキャンコードは 1 〜が普通で、COL,ROW から計算した値 +1 としよう。1〜 56 の範囲。具体的には (COL-1)x8 + (ROW-1) + 1 。

    ( ESC と \ キーを追加し 全 56 キー)

    ESC 1 2 3 4 5 6 7 8 9 0 - = BS   
    C1R8 C1R1 C2R1 C3R1 C4R1 C5R1 C6R1 C7R1 C6R5 C5R5 C4R5 C3R5 C2R5 C1R5 (COL/ROW)
    8 1 9 17 25 33 41 49 45 37 29 21 13 5 (scan code)


    TAB Q W E R T Y U I O P [ ] \
    C1R2 C2R2 C3R2 C4R2 C5R2 C6R2 C7R2 C6R6 C5R6 C4R6 C3R6 C2R6 C1R6 C7R5
    2 10 18 26 34 42 50 46 38 30 22 14 6 53

    CTRL A S D F G H J K L : " RET
    C1R3 C2R3 C3R3 C4R3 C5R3 C6R3 C7R3 C6R7 C5R7 C4R7 C3R7 C2R7 C1R7
    3 11 19 27 35 43 51 47 39 31 23 15 7

    ALT Z X C V B N M , . / SHFT
    C1R4 C2R4 C3R4 C4R4 C5R4 C6R4 C7R4 C6R8 C5R8 C4R8 C3R8 C2R8
    4 12 20 28 36 44 52 48 40 32 24 16

    FN SP SP
    C7R6 C7R7 C7R8
    54 55 56

    なんとめんどくさいことだ。今度は ASCII コードへの変換テーブルを作らないといけない。

    1 '1', TAB, CTRL, ALT , BS, ']', RET, ESC ,
    9 '2', 'q', 'a', 'z', '=', '[', '\'', SHFT,
    17 '3', 'w', 's', 'x', '-', 'p', ':', '/',
    25 '4', 'e', 'd', 'c', '0', 'o', 'l', '.',
    33 '5', 'r' 'f', 'v', '9', 'i', 'k' , ',',
    41 '6', 't', 'g', 'b', '8', 'u', 'j', 'm',
    49 '7', 'y', 'h', 'n', '\\', FN, ' ', ' '

    SHIFT
    1 '!', TAB, CTRL, ALT , BS, '}', RET, ESC ,
    9 '@', 'Q', 'A', 'Z', '+', '{', '"', SHFT,
    17 '#', 'W', 'S', 'X', '_', 'P', ';', '?',
    25 '$', 'E', 'D', 'C', ')', 'O', 'I', '>',
    33 '%', 'R', 'F', 'V', '(', 'I', 'K' , '<',
    41 '^', 'T', 'G', 'B', '*', 'U', 'J', 'M',
    49 '&', 'Y', 'H', 'N', '|', FN, ' ', ' '


    FN (足りないキー)
    ~
    ^ CAPS INS DEL (ESC: 最初の版にはない)




USB HID 化

最初のボードは2段構成なので、コントローラを変えることが出来る。実際に作るかどうかは別の話だが、少し検討してみよう。

小型USBキーボードは自作できるのか下調べをしてみる
Arduino Micro を使って、USB 小型 キーボードを自作 (製作途中、暫定公開)

ここが非常に良くまとめられている。

V-USBを利用してHIDデバイスを作る

ここも参考になりそうな。V-USB 自体は tiny2313 では無理 - ピンもメモリも足りない。

HID Keyboard Demo

ここも参考にしよう。

コントローラであるが、ATmega328 とかで V-USB ? あるいは、ATmega32U4 ? このあたりがメジャーどころ。だが、ATmega32U2,(16U2) あるいは、その前身の AT90USB162 は?

QFN32 で良いのであれば、AT90USB162 が 5 個 $4.61 で買える。QFN に慣れたいと思っているので、使ってみるのも良いかも知れない。あるいは、ATmega328p-MU -- 5個 $6.5 である。これなら、V-USB でキーボードが扱えるだろう。ADC も付いているが、V-USB で使う場合、あまり精度が良くないかも。

    AT90USB162 は、21 I/O あるのだが、I2C が扱えない。ATMEGA328P は、18 I/O しかない。水晶を付けて V-USB と I2C slave 両用にしようなんて考えると 12 I/O しか キーマトリックスに使えないという。
    AT90USB162 は使わないとして、ATMEGA328P の I/O を節約しよう。ひとつの方法は、シフトレジスタの HC595 などを使う方法 。もうひとつは、HCF4051 等の 8 to 1 アナログスイッチを使う方法。1 の方を L に接続し 8 の方のどれかを L にする。無接続の7つは HiZ 。こちらの方が都合が良さそうだ。また同じ IC を入力にも使える。2 個使えば 7bit で済む 。それならば、ATtiny861 (10個 $8.79)でも良さそうだ。15 I/O しかないが、水晶と V-USB , I2C に 6 ピン使っても 9 ピン残っている。--- 実をいうと I2C slave の実装で USI がある tiny の方が都合が良いのだ。

    ところで、8ピンの ATtiny85 の V-USB ボードが出ている。なんと 水晶なしで動くようだ。14 ピンの ATtiny44 ボードもある。こちらは水晶が載っている。IO は 4pin だけ出力で USBtinyISP とか。価格は $1.65 --- なんだこれ。調べてみよう。

      usbtinyisp.jpg
      OSHPARK MicroTinyISP
      https://github.com/cnlohr/tinyisp-micro

      これか? あるいは、これ?
      FabISP

      FabUSB の大元は、USBtiny だと書いてある。avrdude で -c usbtiny を指定するのだろう。
      回路図は、FabISP と同じに見える。ファームウェアがそのまま使えそう。SJVCC ジャンパは、ISP コネクタに 5V を供給。SJFAB は、ISP コネクタに ATtiny44 の RESET を接続し、ファームウェアを書き換えるときに使う。そして注意すべきことは、I/O 電圧が 5V だということ。
      5V 系は、今や持ってないので、ひとつ持ってても良いかも知れない。

      しかし何故、流行ったのだろうか? micro usb になってるから、比較的最近のものであるはずだ。

      digispark.jpg
      ATtiny85 のほうは、digispark ? -- Arduino に対応したもののようだ。しかし RESET を除くと 3 pin しか I/O がない。

      ちょっと脱線してしまった。ここまでにしておこう。

    Linux に、i2c-hid.c ってのがあった。
    drivers/hid/i2c-hid/i2c-hid.c

    どうやら USB の HID とあまり変わらないような印象。ディスクリプタもある。ただ、USB と比べてシンプルな感じ。

    あまり分かってないが、コマンドパケットを Write してきて 続いて Report パケットを Read するような。
    Attiny2313 では入りきらないだろうが、i2c-hid を想定した構造にしておくのが良さそう。
    ということは、V-USB の制御構造に似せておくのが良いのかも知れない。

    static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01),
    .wait = true };
    static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) };
    static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) };
    static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) };
    static const struct i2c_hid_cmd hid_no_cmd = { .length = 0 };
    /*
    * static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) };
    * static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) };
    * static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) };
    * static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) };
    */

コマンドパケットの種類は少ない。このコマンドを Write する アドレスというのがディスクリプタにある。

__le16 wInputRegister;
__le16 wOutputRegister;
__le16 wCommandRegister;
__le16 wDataRegister;

それを次のようにして Write する。

cmd-<data[0] = ihid->hdesc_buffer[registerIndex];
cmd-<data[1] = ihid->hdesc_buffer[registerIndex + 1];

if (length > 2) {
cmd->c.opcode = command->opcode;
cmd->c.reportTypeID = reportID | reportType << 4;
}

2 バイトのアドレスに続いて、(コマンドがあるときは) 2 バイトのコマンドを送る。
ディスクリプタの読み込み自体も同じ仕組みだが、アドレスは装置によって決まっているようだ。

あとは、Report の中身を理解すれば、どういうものか把握できそうだ。だが、これは USB HID とおそらく同じ。

ついに、i2c の話なのに、V-USB の API が関係してきた。

    もう記憶が定かではないのだが、
    ・usbInit() を 呼び出して初期化。あとは、メインループで usbPoll() を呼ばなければならない。
    ・cllback を登録しておくと、usbPoll() の中から呼び出される。
    ・デスクリプタは、定義データを登録しておくと勝手に やってくれる。
    ・割り込み禁止にできるクロック数が決まっている。(他の割り込み処理も含む)

    こんな感じだった。AT90USB162 用の V-USB ライクなライブラリも作ったはずで、
    ・USB インターフェイスがあると 割り込みを一切使わず usbPoll() のみで処理できる。
    という大きな利点があった。

    エンドポイントがある場合は、xxx_getc という API を作りエンドポイントのバッファから取り出している。(送る場合も同様で xxx_putc を使う)。で、バッファーの面倒を見るのが xxx_poll 。というルールにしたが、HID の場合は、たしかエンドポイントがない。usb_control_msg() だけでどうの。... あぁ忘れてしまった。少し復習しなければ。

    自分のweb を調べたところ、angel_loader というのを作っていた。これは、USBasp 互換の AT90USB162 用ブートローダで、
    angel_loader-1.4c.zip
    が最新のようである。また、USBasp AVR ライタも同梱してたはず。ここから再スタートが良さそうだ。

    usb_control_msg()はホスト側の libusb の API で、

    typedef struct usbRequest{
    uchar bmRequestType;
    uchar bRequest;
    usbWord_t wValue;
    usbWord_t wIndex;
    usbWord_t wLength;
    }usbRequest_t;

    の8バイトのパケットを送ると、ターゲットのUSBデバイスでは、usbFunctionSetup が呼ばれて ...
    例えば callback の asp_setup() が 呼ばれる。だいたいの ISP コマンドはこの中で処理されるのだが、asp_out() という callback もあって、8バイトで収まらないような 書き込みデータを受け取っている。

    usb_vendor_setup = asp_setup;
    usb_user_out = asp_out;

    その2つの関数をこんな風に登録している。asp_setup() では、メモリの読み込みなどもするのだが、200 バイトの要求が来る。サイズの制限は、用意できるバッファによって決まるが、(usbasp では) 200 バイト以上でないとマズイ。このリプライは そういうルールにしているだけの話で、USB 的には、単に IN (READ) で送るデータ。また、asp_out() は、単に OUT (WRITE) されたデータの処理である。どんな処理をするかは、直前の SETUP で指定される。

    さて、本題は i2c デバイスでどういう API にしたら対応できるかであった。i2c は read と write しかないが、write の最初は アドレスで、次にコマンドの形式であれば、xxx_setup() で対応できる。続く WRITE データは、xxx_out() を call することで対応できる。ただし、無制限にデータが来た場合 対応できない。割り込み処理の延長で xxx_out() を call するのではなく、一回バッファに入れて、xxx_poll から callback する必要がある。

    READ の場合も、あらかじめ データ長が分かっているのであれば、対応できる。無制限に READ が来た場合対応できない。i2c rom が、そういう使い方をする。これについては、READ データがバッファに用意できていなければ、別の callback 関数を呼ぶ ことにすれば対応できる。この callback は、割り込みの延長で呼ばれるので、メモリデータなど 即座に返せるものに限られる。

    ー こんな感じで良いのだとは思う。



基板2つめ

キーボード自体に ESC を追加することが出来、他に気に入らないところを修正した。その上でコントローラーも変えたくなったので、2つめの基板を設計中。2つめも 2段構成で 前の基板と互換性がある。

コントローラを変えたくなったのは、上で書いたとおり、V-USB と I2C 両用にしたくなったため。プログラムに 8KB ぐらいは欲しい。たぶん V-USB が 2KB, I2C スレーブが 1KB ぐらい使う。RAM も 随分と増える。ATtiny861A を選択し、HCF4051 を 2つ使って、I/O が足りないのを補う計画。

    8KB でピンが多く、かつお安いチップはある。ATtiny88である。しかしこれを使ってもピンが足りないのだ。HCF4051 が相当安いので、1つ使うなら 2つ使ってしまえということになった。

    ATtiny88 だが、ATmega88 と随分ちがう。TQFP32/QFN32 だと 電源ピン 2本と AREF が I/O ピンになっていて、ADC 専用ピン 2 本も I/O になった。合計 5 本も増えている。だが、水晶発振が出来ない。SPI と TWI はあるが UART がないという変則なものになっている。水晶発振が出来ないのは、V-USB を使う上では不利な要素。外部クロックが必要だが、26 I/O あるから こちらを使っても良かったかも知れない。

HCF4051(CD4051) は、8 to 1 のアナログマルチプレクサで、1つめは、GND を入力として COL1〜COL7 のどれかを GND に接続する。もうひとつは、ROW1〜ROW8 のどれかを選んで 入力ピンに接続。入力ピンは プルアップしておいて、L になれば、押されたと判断する。7 ピンで制御でき外付けのプルアップ抵抗も不要にできる。

ATtiny861Aは、水晶を付け、I2C に 2ピン使い、V-USB にも 2ピン使う。合計 13 ピン。余ったピンは、MISO で、ISP 用に外部に出すが、遊ばせておくのもなんだから、LED を付けた。もうひとつ、AREF 。これを I/O ピンとして使い、V-BUS から電源を供給されているかどうかのセンスに使う。 最初にこれで、動作モードの切り分けを行う。

水晶は、SMD のもの。(たぶん) 16MHz を使う。ハンダ付けが難しいが、高さを制限したい。基板間を最小 2.54mm に抑えたいのだ。
5V → 3.3V レギュレータは、TAR5SB33 を予定するが、MIC5205 を代替できる。その場合 NOISE 端子のコンデンサは不要。

また、COL,ROW の選択を配線に合わせていたが、ちゃんと順番になるように配慮した。

だいたいこんなところ。
i2c_keyboard_v2.png
i2c_keyboard_sch.png
i2c_keyboard_matrix.png
(i2c_keybrd-02b.pdf)

・・・・チェック中:
・D+ = PB6(INT0), D- = PB3 にしたのは問題なさそうだ。が、抵抗を直列に入れて、D- を無条件にプルアップするよう変更 −忘れていた。
・基板サイズ 92mm x 74.5mm を 92mm x 73mm に詰めた。下の基板は 38mm → 36.5mm 上の基板は 35.5mm 。
・HCF4051 が 3.3V で動作するか? Ron がかなり大きい 200 Ωとか? だが多分いける。ダメなら LV4051 に交換。
・COL,ROW が正しく配線されているか? −PA0-2 で ROW を選択、PA4-6 で COL を選択 PA7 入力。
配線の都合で MSB から A,B,C と逆にしている。ポートの順も 0-4-2-6-1-5-3-7 に入れ替え。
キーボード側、ダイオードと干渉しないか? -- 目測では大丈夫。
・コネクタの位置関係のチェック -- わずかにずれていたので修正。上下間隔 1.2 in, 左右は、2.4 in。
・ \ キー追加。(GH60 を見つけてどうしても入れたくなったし。)
・2313版 修正忘れがあった。
 ・i2c_keyboard-02.zip
 ・i2c_keyboard-02-861-out.zip
 ・i2c_keyboard-02-2313-out.zip
とりあえず、これで FIX 。以前の版も、これに合わせておく。発注はまだまだ先。

    以前の版 - ATtiny2313 版を放棄したわけではない。これはこれで、完成させたいとは思っている。お手軽に I2C デバイスを作れるようになれば、SBC との組み合わせで 面白いものが作れるかも知れない。
    ATtiny861 版は、HID 対応をするのがメイン。HID 対応 をマスターできれば、ESP32 での BT over HID とかに幅が広がるはず。(調べてないけど)。ついでに書くと V-USB 対応は、おまけというか デバッグ目的。あくまで HID のマスターが目的。といっても あんまりお手軽にテストしたらマズイ。ホストのポートを壊してしまう恐れがある。

    ところで、ATtiny861-SU (SOIC20)が 10個 $8.79で買えて安い!とか思ったのだが、ATtiny88-MU (QFN32)は、もう一段安かった。$6.84 とか 1個あたり $1 切ってると、ついつい買ってしまう。ATmega328p-MU (QFN32)は、倍の 5個 $6.18 。それでも $1 ちょっとなのである。0.5mm ピッチの QFN のはんだ付け の経験を積もうと、題材を探していたのだが、$1 を切るものがなかなかない。いずれ何か作ってみたい。



スキャンコード 追加
 ・http://www3.airnet.ne.jp/saka/hardware/keyboard/109scode.html より
存在するキーのスキャンコード(1?) なら対応は楽なのである。FN でどう拡張するかと、拡張したキーの離したときのコードをどうするか? これが問題。また、英語キーボードのみを対象に検討する。
HID では Usage Page / Usage ID という形式? Usage Page は 07 固定のようだ。


ESC 1 2 3 4 5 6 7 8 9 0 - = BS   
C1R8 C1R1 C2R1 C3R1 C4R1 C5R1 C6R1 C7R1 C6R5 C5R5 C4R5 C3R5 C2R5 C1R5 (COL/ROW)
8 1 9 17 25 33 41 49 45 37 29 21 13 5 (local scan code)
110 2 3 4 5 6 7 8 9 10 11 12 13 15 (keyid)
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E (scan code1)
29 1E 1F 20 21 22 23 24 25 26 27 2D 2E 2A (USB HID Usage ID)

TAB Q W E R T Y U I O P [ ] \
C1R2 C2R2 C3R2 C4R2 C5R2 C6R2 C7R2 C6R6 C5R6 C4R6 C3R6 C2R6 C1R6 C7R5
2 10 18 26 34 42 50 46 38 30 22 14 6 53
16 17 18 19 20 21 22 23 24 25 26 27 28 14
0F 10 11 12 13 14 15 16 17 18 19 1A 1B 7D
2B 14 1A 08 15 17 1C 18 0C 12 13 2F 30 89

CTRL A S D F G H J K L : " RET
C1R3 C2R3 C3R3 C4R3 C5R3 C6R3 C7R3 C6R7 C5R7 C4R7 C3R7 C2R7 C1R7
3 11 19 27 35 43 51 47 39 31 23 15 7
58 31 32 33 34 35 36 37 38 39 40 41 43
0F 1E 1F 20 21 22 23 24 25 26 27 28 1C
E0 04 16 07 09 0A 0B 0D 0E 0F 33 34 28

ALT Z X C V B N M , . / SHFT
C1R4 C2R4 C3R4 C4R4 C5R4 C6R4 C7R4 C6R8 C5R8 C4R8 C3R8 C2R8
4 12 20 28 36 44 52 48 40 32 24 16
60 46 47 48 49 50 51 52 53 54 55 44
38 2C 2D 2E 2F 30 31 32 33 34 35 2A
E2 1D 1B 06 19 05 11 10 36 37 38 E1

FN SP SP
C7R6 C7R7 C7R8
54 55 56
-- 61 61
-- 39 39
-- 2C 2C

^~ CAPS
1 30 42 56 (keyid)
29 3A 2B 73 (scan code1)
35 39 31 87 (USB HID Usage ID)

F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 INS DEL
112 113 114 115 116 117 118 119 120 121 122 75 76
3B 3C 3D 3E 3F 40 41 42 43 44 57 E052 E053
3A 3B 3C 3D 3E 3F 40 41 42 43 44 49 4C

SysRQ ↑ PgUp ← → PgDn ↓
124 83 85 79 89 86 84
54 E048 E049 E04B E04D E051 E050
46 52 4B 50 4F 4E 51

コネクタと マトリックス

メモしておこう。

マトリックス基板
上コネクタ
COL1 COL2 COL3 COL4 COL5 COL6 COL7
下コネクタ
ROW1 ROW2 ROW3 ROW4 ROW8 ROW7 ROW6 ROW5

ROW1 ROW2 ROW3 ROW4 ROW5 ROW6 ROW7 ROW8
COL1 1 TAB CTRL ALT BS ] ENTER ESC
COL2 2 Q A Z = [ " SHFT
COL3 3 W S X - P : /
COL4 4 E D C 0 O L .
COL5 5 R F V 9 I K ,
COL6 6 T G B 8 U J M
COL7 7 Y H N \ FN SP2 SP3

2313 コントローラ基板
上コネクタ
COL1 COL2 COL3 COL4 COL5 COL6 COL7
PB6 PB5 PB4 PB3 PB1 PB0 PD6
下コネクタ
ROW1 ROW2 ROW3 ROW4 ROW8 ROW7 ROW6 ROW5
PD0 PD1 PA1 PA0 PD2 PD3 PD4 PD5
I2C+ISP
VCC SDA SCL GND |
VCC MOSI SCK GND | MISO RESET
PB5 PB7 PB6 RESET


Fn キー拡張(案)
最新版では、ESC と \ キーを付けることができた。が、発注してしまった旧版も対応しておかないといけない。
あと ^~ キーと CAPS がない。それに加えて F1-F12 , INS, DEL, PgUP, PgDn , SysRQ , カーソルキー ぐらいはないとまずいかも知れない。
また、重要な組み合わせが入力できないとまずい。CTRL+ALT+DEL みたいなやつ。

ESC 1 2 3 4 5 6 7 8 9 0 - = BS
^~ F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 INS DEL
TAB Q W E R T Y U I O P [ ] \|
CAPS ESC ^~ SysRQ ↑ \|
CTRL A S D F G H J K L : " RET
                         PgUp ← →
ALT Z X C V B N M , . / SHFT
PgDn ↓
FN SP SP
SysRQ は USB でどう扱われるか分からない。単なる Print Screen かも

i2c_keyboard_asign.png
とりあえずこんなのでどうか?


scancode1 table
1 0x02, 0x0F, 0x0F, 0x38, 0x0E, 0x1B, 0x1C, 0x01,
9 0x03, 0x10, 0x1E, 0x2C, 0x0D, 0x1A, 0x28, 0x2A,
17 0x04, 0x11, 0x1F, 0x2D, 0x0C, 0x19, 0x27, 0x35,
25 0x05, 0x12, 0x20, 0x2E, 0x0B, 0x18, 0x26, 0x34,
33 0x06, 0x13, 0x21, 0x2F, 0x0A, 0x17, 0x25, 0x33,
41 0x07, 0x14, 0x22, 0x30, 0x09, 0x16, 0x24, 0x32,
49 0x08, 0x15, 0x23, 0x31, 0x7D, FN, 0x39, 0x39

FN
1 0x3B, 0x3A, CTRL, SHFT , 0xD3, 0x7D, RET, 0x29 ,
9 0x3C, 0x01, 'a', 'z', 0xD2, 0xD2, '0xCD, ALT,
17 0x3D, 0x29, 's', 'x', 0x57, 'p', 0xCB, 0xD0
25 0x3E, 'e', 'd', 'c', 0x44, 0x54, 0xC9, 0xD1,
33 0x3F, 'r' 'f', 'v', 0x43, 'i', 'k' , ','
41 0x40, 't', 'g', 'b', 0x42, 'u', 'j', 'm',
49 0x41, 'y', 'h', 'n', '\\', FN, ' ', ' '

USB HID code table
1 0x1E, 0x2B, 0xE0, 0xE2, 0x2A, 0x30, 0x28, 0x29,
9 0x1F, 0x14, 0x04, 0x1D, 0x2E, 0x2F, 0x34, 0xE1,
17 0x20, 0x1A, 0x16, 0x1B, 0x2D, 0x13, 0x33, 0x38,
25 0x21, 0x08, 0x07, 0x06, 0x27, 0x12, 0x0F, 0x37,
33 0x22, 0x15 0x09, 0x19, 0x26, 0x0C, 0x0E, 0x36,
41 0x23, 0x17, 0x0A, 0x05, 0x25, 0x18, 0x0D, 0x10,
49 0x24, 0x1C, 0x0B, 0x11, 0x89, FN, 0x2C, 0x2C
FN
1 0x3A, TAB, CTRL, SHFT , 0x4C, 0x89, RET, 0x35 ,
9 0x3B, 0x29, 'a', 'z', 0x49, 0x52, 0x4F, ALT,
17 0x3C, 0x35, 's', 'x', 0x44, 'p', 0x50, 0x51,
25 0x3D, 'e', 'd', 'c', 0x43, 0x46, 0x4B, 0x4E,
33 0x3E, 'r' 'f', 'v', 0x42, 'i', 'k' , ','
41 0x3F, 't', 'g', 'b', 0x41, 'u', 'j', 'm',
49 0x40, 'y', 'h', 'n', '\\', FN, ' ', ' '

Fn キー拡張の方法

    随分と難しいかなと思っていたのだが、そうでもない?
    上で検討した内容を踏まえると、チャタリング対応で 7B x 4 世代のスキャンした bitmap を覚えている。
    同じ状態が 4 連続続くと、状態確定だということで、キーの状態の bitmap 7B を更新する。このときにキーの状態が変化したことを検出する。

    Fn キー拡張は、ここから先である。拡張した bitmap 7B x2ページ をさらに作る。キーが押されたときに、Fn が押されてなかったら ページ0 を更新し、押されていれば ページ1 を更新 する。キーが離されたときは、両方を更新。この処理をする際に、Fn拡張 のキーの変化が検出できる。

    スキャンコードだが、なんだかよく分からないのであるが、最もシンプルなものは、だいたいのキーは、離したときに 0x80 を or したコードを出力する。カーソルキーなどは 2バイトコードで 頭に 0xE0 を付ける。が、離したときに 0x80 を or するのは同じ。それで済む範囲でだけ対応しようかと思う。そうすることで、2バイトコードを 1bit のフラグで表現でき、変換テーブルを小さくできる。なお、Fn拡張なしだと、2バイトコードは出ない。

    USB HID では、押したとき離したときはなにもしない。要求が来たときに、押されているキーを調べて 返す。オートリピートもない。返せるキーの数は 6 つまでで、それを超えるとエラーにする。なお、USB HID でのコード体系は、スキャンコードとは別。別々の変換テーブルが必要になる。

    ASCII では、押されたときに コードを返すが、独自の変換テーブルが必要になる。シフトや CTRL の状態も見なければならない。オートリピートもあった方が良いのだろう。なお、Fn拡張しなくとも Fn に対応できる。押したときに Fn の状態を見れば良いからである。

    これに加えて I2C HID の対応がある。USB と同じだろうとは思うが調べていない。

    スキャンコードと ASCII は、FIFO を使うが、0 も 0xFF もコードでは使わない。予定通り、内部で 0 と 0xFF を使うことにする。

    これで実装のイメージはだいたい出来た。コード量の問題があって、ATtiny2313 では、採用できる機能に制限が出来るが、ATtiny861 版では、全部実装したい。



ここまで書いてきたが、世の中では自作キーボードが流行っていて、オープンソースのファームウェアがあるらしい。
 ・ https://github.com/qmk/qmk_firmware
どうも AVR の USB ATmega32u4 がメジャーらしい。これを移植するのが楽そうである。が、作ることも楽しみなので、まずは自己流でいこうと思う。

自作キーボードというのは、
 ・自作キーボード用パーツをそろえて、お手軽自作に挑戦
 ・キーボードを自作するための情報メモ
ここらへんが、良くまとまっているように思う。ぼちぼち読んでみよう。ただ、今の目標は、SBC に添える小さなキーボードである。実用本位ではなく、かざりだがちゃんと動く・・みたいなもの。できれば、ちゃんと使えるというのをクリアしたいが、それ以上は今は求めない。
posted by すz at 20:20| Comment(0) | TrackBack(0) | MachXO2
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

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


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

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