今まで書いてきたことをコードにしたのが、これ
・i2ckbd-0.0.zip
コンパイルが通っただけで、動くはずもない状態だが、現状はサイズの見積もりが最重要。
念のためコンパイル環境を明記しておく
・ WINAVR-20100110
である。なんか古いのだが、使うチップも古いし。
(追記) rm とかのコマンドがエラーになったり、使いにくいので変更
・ avr-toolchain-installer-3.4.1.1195-win32
make すると
avr-gcc -I. -mmcu=attiny2313 -Wall -DF_CPU=8000000UL \
-Os -fsigned-char -c i2ckbd.c
avr-gcc -I. -mmcu=attiny2313 -Wall -DF_CPU=8000000UL \
-Os -fsigned-char -c usi.c
avr-gcc -mmcu=attiny2313 -Wl,-Map=i2ckbd.map i2ckbd.o usi.o -o i2ckbd.elf
avr-objcopy -O ihex -R .eeprom i2ckbd.elf i2ckbd.hex
text data bss dec hex filename
1966 0 69 2035 7f3 i2ckbd.elf
こうなった。なんと! ATtiny2313 に入ってしまった。しかしかなり微妙。
また、RAM が厳しい。全部で 128 しかないが、69 バイトを消費している。割り込みを使うので、レジスタ全部退避みたいなところがあり、多分スタックオーバーフローを起こす。
プログラムメモリの節約
ソフト編(1)で、GPIOR を変数として使えば、コードが小さくなるのに・・・ということを書いた。アセンブラ化して頑張らなくとも、実際にそうすれば良いのであった。
i2ckbd.h で、
#define USE_GPIOR1
とすると、
1966 → 1908 と 58 バイトも節約できる。
これぐらい空くと、ちょっと手直しできるだけの余裕が出来る。
さて、今まで書いてきたことの中身だが、ここから機能を外してみよう。
#define SUPPORT_SCANCODE1
をコメントすると、
text data bss dec hex filename
1656 0 69 1725 6bd i2ckbd.elf
1908 → 1656 だから、252 バイト使用している。そのうち 変換テーブルが 112 バイト。
代わりに
#define SUPPORT_ASCIICODE
をコメントすると、
text data bss dec hex filename
1672 0 69 1741 6cd i2ckbd.elf
こちらは、236 バイトで、同様に変換テーブル 112 バイト。
しかし 厳しい RAM は減らない。
#define FN_EXTEND
FN 拡張を OFF にすれば、
#undef SUPPORT_SCANCODE1
text data bss dec hex filename
1396 0 55 1451 5ab i2ckbd.elf
#undef SUPPORT_ASCIICODE
text data bss dec hex filename
1412 0 55 1467 5bb i2ckbd.elf
#undef SUPPORT_SCANCODE1
#undef SUPPORT_ASCIICODE
text data bss dec hex filename
1144 0 55 1199 4af i2ckbd.elf
とここまで減るのであった。
ATtiny2313 は、HID をサポートできるか?
さて、ASCII と SCANCODE1 を捨てて HID に対応することは可能か?
実は HID のキーコードを生成する部分だけ作ったのだ。
#define FN_EXTEND
text data bss dec hex filename
1404 0 69 1473 5c1 i2ckbd.elf
#define FN_EXTEND
#define SUPPORT_HIDCODE
text data bss dec hex filename
1768 0 69 1837 72d i2ckbd.elf
FN拡張はどうしても欲しい。キーコード生成に 364 バイトでやはり変換テーブルが 112 バイト。
あとプロトコルが必要なのだが、全然入らないというわけでもなさそう。こうなるとちゃんと作りこみたくなってくるが、問題は割り込みでのスタック消費。これさえクリアできれば、ATtiny2313 でも随分と遊べそうなのである。
C で記述する割り込み
SIGNAL(SIG_OUTPUT_COMPARE0A) {
set_F_TIMER_TICK(); (1命令)
}
この 1命令だけ実行したい。AVR の場合は、お手軽に記述できるのある。だが、割り込みを使えばレジスタの退避をするので、スタックが消費される。どうなっているのか確認しないと。
__vector_13:
push __zero_reg__
push r0
in r0,__SREG__
push r0
clr __zero_reg__
sbi 51-32,0 # set_F_TIMER_TICK()
pop r0
out __SREG__,r0
pop r0
pop __zero_reg__
reti
あれ?コードも別に悪いというほどでもない。
いや問題は、USI の割り込みでどれだけスタックを消費するかということだ、安心してはいけない。
/* epilogue start */
pop r24
pop r0
out __SREG__,r0
pop r0
pop __zero_reg__
reti
.size __vector_15, .-__vector_15
/* epilogue start */
pop r31
pop r30
pop r27
pop r26
pop r25
pop r24
pop r19
pop r18
pop r0
out __SREG__,r0
pop r0
pop __zero_reg__
reti
.size __vector_16, .-__vector_16
USI では、2つの割り込みを使っている。それぞれの、epilogue を見ると、そんなには悪くない。
といっても、69 バイト使っているから 残りは全部で 59 バイト。これで、main のスタックと割り込みでのスタックを賄わなければならない。割り込みの epilogue だけで 13 バイト の pop ?
なんか微妙ですな。ひょっとして行けるのか?やっぱり無理なのか?
スタックを使わないところで割り込みを受ける
ちょっと思いついたのが、割り込みを受ける場所を限定するということ。割り込みを禁止しておいて、アイドルになったら 割り込み許可というアイディア。ただし、main 関数で inline 関数を動かすとスタックフレームを割り当てしまうので、main が call する関数から先で inline を使う。
こうすることで、とりあえず・・・・・プログラムメモリが増える。
text data bss dec hex filename
1978 0 69 2047 7ff i2ckbd.elf
1908 → 1978 か。これは ... 厳しい。
HID について、全くわかっていないのが Report Descriptor これの実装例を見てみる。
/* USB report descriptor */
const PROGMEM char usbHidReportDescriptor[35] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0 // END_COLLECTION
};
/* We use a simplifed keyboard report descriptor which does not support the
* boot protocol. We don't allow setting status LEDs and we only allow one
* simultaneous key press (except modifiers). We can therefore use short
* 2 byte input reports.
* The report descriptor has been created with usb.org's "HID Descriptor Tool"
* which can be downloaded from http://www.usb.org/developers/hidpage/.
* Redundant entries (such as LOGICAL_MINIMUM and USAGE_PAGE) have been omitted
* for the second INPUT item.
*/
なるほど ... いや分からないのだが、35 バイトということは分かった。
(追記) これは、レポートが、2 バイトの場合で、作りたいものとは違う。8 バイトの形式は、たぶん次の 41 バイト。
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
/* modifier E0 - E7 */
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
/* Reserved Byte */
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x01, // INPUT (Constant); Reserved Byte
/* KeyCode Array [6] */
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x25, 0x9A, // LOGICAL_MAXIMUM (0x9A)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x9A, // USAGE_MAXIMUM (Keyboard SysRq)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0 // END_COLLECTION
HID over I2C 再読
他に SET_POWER だとか RESET だのコマンドがどうなっているか? もう一度読んでみる・・・と、誤解しているところが見つかった。まずそこから。
コマンド:GET_REPORT の例
ホスト: レジスタ(2B:コマンド) コマンド(2B) レジスタ(2B:データ)
デバイス: レングス(2B) レポ−ト(8B)
必ずレジスタのアドレスを WRITE してから READ するのだが、コマンドに続いてもアドレス指定が 来るのであった。
レポートを送るレジスタは、データレジスタであった。なお、READ での レングスは 全体のサイズで、この場合 10 になる。
コマンド:SET_REPORT の例
ホスト: レジスタ(2B:コマンド) コマンド(2B) レジスタ(2B:データ) レングス(2B) レポ−ト(XB)
同様に、データレジスタ を指定して 続いてレングス付きのデータが来る。
コマンド:SET_IDLE
ホスト: レジスタ(2B:コマンド) コマンド(2B) レジスタ(2B:データ) レングス(2B:4) レポ−ト(2B)
レポートは 2 バイトで、レングスは 4 である。
コマンド:GET_IDLE
ホスト: レジスタ(2B:コマンド) コマンド(2B) レジスタ(2B:データ)
デバイス: レングス(2B:4) レポ−ト(2B)
コマンドのレスポンスは、すべて データレジスタのようである。
また、ホストは、レングスだけ READ して、再度 READ するようなことが書いてある。残りを読むみたいなことが書いてあるが、頭から読むような気がする。
さて、読み込みは全部 レングス付きなのだろうか?
有効なデータは、すべてそのようである。しかしデータがない場合 0 を返すようである。
例えば
コマンド:RESET
多分本当にリセットすれば良い。
リセット後、Input Register を READ すると 0x0000 の 2 バイトが読めるようになる。
ということだそうだ。
コマンド:SET_POWER
これは、なにもしない。
現在のサイズ
いろいろ小変更を繰り返した。いまどうなっているか?チェック。
HID がターゲットに入ってきたので、意識していく。また、FN 拡張は是非入れたい。その上、スタックのチューニングをありにしないと動く気がしない。この条件でどうなっているか?
#define STACK_TUNE
#define USE_GPIOR1
#define FN_EXTEND
これが標準
#define SUPPORT_HIDCODE
text data bss dec hex filename
1952 0 68 2020 7e4 i2ckbd.elf
#define SUPPORT_ASCIICODE
#define SUPPORT_SCANCODE1
text data bss dec hex filename
1976 0 68 2044 7fc i2ckbd.elf
どちらも一応入っている。2048 までだから、まだ僅かに余裕がある。
で、すっかり忘れていた機能があった。オートリピートである。HID は関係ないのだが、ASCIICODE と SCANCODE1 の両方をサポートするのは難しくなってきた。
逆に HID のみとして、ローカルスキャンコードなどもサポートしないのであれば、随分とコードを減らせる。
1952 → 1750 (202 バイト減)
i2c 側でのプロトコル解析の部分が減らせると思ったのだが、そこまでしなくとも減ってしまった。なにかの間違いかと思ったがそうではなかった。key_make() という関数でコードを生成するのだが、それが実質空になる。そうすると、引数のために使った key_pressed_ex() という関数が不要になる。
安易に使った key_pressed_ex() の効率がひどく悪いようだ。
・i2ckbd-0.1.zip
そのあたりを見直しチューニングした。
ASCII + SCANCODE1 + LOCAL
1976 → 1798
HID + LOCAL
1952 → 1760
HID only
1610
ここまでサイズが減った。これぐらい余裕があるのであれば、オートスキャンぐらいは入れられる。HID についても足りない機能を入れていけそうだ。
RAMの削減
コードは、随分と楽にはなった。しかし RAM が減るわけではない。GPIOR0-2 は普通に使ってしまったが、他にも使わない機能のレジスタを メモリとして使える。
随分とせせこましいが、背に腹は代えられぬ。減らさないとオートリピートとか機能の追加が厳しくなる。
タイマ0 比較Bレジスタ OCR0B
タイマ1 タイマレジスタ TCNT1L (16bit: TCNT1)
タイマ1 比較Aレジスタ OCR1AL (16bit : OCR1A)
タイマ1 比較Bレジスタ OCR1BL (16bit : OCR1B)
USART ボーレートレジスタ UBRRL (12bit : UBRR)
このあたりを変数に使ってみよう。
HID + LOCAL
text data bss dec hex filename
1766 0 68 1834 72a i2ckbd.elf
HID + LOCAL
#define USE_IOR_AS_MEM
text data bss dec hex filename
1708 0 62 1770 6ea i2ckbd.elf
結果はこう。対応するために、通常コードを少し変更したら 6 バイト増えた。が、割り当てると、メモリが 6バイト 減るだけではなく、コードまで 58 バイト減った。
これは、確認だけしておいてディスエーブルにしておく。
#undef USE_IOR_AS_MEM
オートリピート
RAM を削減できることが確認できたので、 オートリピートを入れることにした。
追加になる変数
・idle_tick 状態が変わらない時間
main() から 10ms 間隔で keystat_scan() という関数が call される。とりあえず インクリメントするが、なにかキーを押したり・離したりすれば 0 にする。ただし、モデファイアは対象外。
・last_key 最後に押された キーコード
最後に押された ローカルのキーコードを覚えておく。ただし、モデファイアは対象外。
これは、keystat_scan() の処理で、key_make() という関数が call されるのだが、その処理で行う。
オートリピートを実行するのは、keystat_scan() の最後で、last_key を元に key_make()を call して行う。
オートリピートの条件
・モディファイア以外のキーが 1つしか押されていない。
・最後にキーの状態が変化してから REPEAT_TICK の時間が経過した
・あるいは、繰り返しリピートしている場合は、REPEAT2_TICK の時間が経過した
REPEAT_TICK, REPEAT2_TICK は、define で決め打ちにする。変数にして、設定できるようにするとかは、全部が動いた後考える。
・i2ckbd-0.2.zip
ここまでを 0.2 とした。
ASCII + SCANCODE1 + LOCAL
text data bss dec hex filename
1752 0 68 1820 71c i2ckbd.elf
ASCII + SCANCODE1 + LOCAL + AUTO_REPEAT
text data bss dec hex filename
1814 0 70 1884 75c i2ckbd.elf
最後にサイズ確認。これぐらいなら、多少の修正に耐えられそう。
なお、HID は、関係ない。.... というより関係ないようにしないといけない。あぁダメそう。
HID では、ASCII , SCANCODE1 , LOCAL のどれもを抑止しないといけない。これは、未着手なのは分かっているのだが、オートリピートもまた抑止しないといけない -- 今気が付いた。
(続く)