あしたから本気出す
いろいろ工作して情報発信してみる。自作キーボードとか。
開発ブログ
自作キーボード「TypingTrigger-SDZERO」
自作スマートフォン緊急充電器
プログラマブルな自動化キーボード実装編
プログラマブルな自動化キーボード(プロトタイプ編)を作ってみる-後編
開発ブログをもっと見る
ブログなど
ブログはこちら
ダウンロード
JW_WIN外部変形Kiefer
JW_WIN外部変形FastMeasure
お知らせ
2010/10/1 ブログを新しくした
2009/9/4 twitter始めた
2009/7/14 サイトのタイトル変更した

管理者プロフィール (-)
suda 81年神奈川県生まれ
職業:GRAFZ.NETという屋号でプログラム制作してます。
一言:実用性を形にする。工作の日々。
コンタクトは以下までお願いします。感想、応援、苦情とか
RSS
ブログをRSS配信してます。


© GRAFZ.NET 2008-2010
7月18日
テンキーを自作キーボードに改造する(ファームウェア編)。


テンキーを自作キーボードに改造する
秋葉原にて購入したジャンク品のPS/2のテンキーを改造し、USB対応のホットキー・デバイスとして息を吹き込むプロジェクトの第2回目。前回の「テンキーを自作キーボードに改造する(ついでにUSB化)。」に引き続いてファームウェア開発をご紹介します。

材料と下ごしらえ
・MPLAB IDE v8.30 (統合開発環境。2009年7月現在最新版)
・MPLAB C18 Cコンパイラ v2.20b (30日間期間限定デモ版)
・Microchipアプリケーションライブラリ(同梱のUSBフレームワークを利用)
・AKI-PICプログラマー Ver.4(ファームウェアバージョン:beta6.72)
最小構成は上記4点です。各ソフトウェアのバージョンについては、若干異なっていても大丈夫だと思います。

ビルド成功までの道
PIC製造元のMicrochip社が配布しているUSBフレームワークを雛形にして、HID(Human Interface Device)クラスのファームウェアを作成せねばなりません。USBフレームワークは、さすがに全てのデバイスに対応するソースが用意されてはおらず、一部のPIC18、PIC24,PIC32に対応するソースだけ。それらをPIC18F2550に焼けるように作り変える必要があります。試行錯誤の香りがぷんぷんしてきた。

まず、MPLABにて新規プロジェクトを作成し、各種パスを設定します。パスは相対パスでも問題ないです。コンパイラやUSBフレームワークのインストール先によっては、必要に応じてパスを調整してください。
プロジェクト名(任意) hotkey
保存先(推奨) C:\Microchip Solutions\USB Device - HID - Keyboard\hotkey
インクルードパス C:\Microchip Solutions\Microchip\Include
  C:\Microchip Solutions\USB Device - HID - Keyboard\hotkey\src
ライブラリパス C:\mcc18\lib

雛形(C:\Microchip Solutions\USB Device - HID - Keyboard\Firmware)に含まれるソースファイルのうち、以下4ファイルをコピーし、srcフォルダを作って、貼り付けします。
HardwareProfile.h
usb_config.h
Keyboard.c
usb_descriptors.c
そしたらそれら全てをプロジェクトに追加します(下図ご参照ください)。
また、フレームワークに含まれるヘッダファイル、ソースファイルもプロジェクトに追加します。

ソースをプロジェクトに追加する。

いよいよソースファイルを編集します。まず、HardwareProfile.hを書き換えます。
#define DEMO_BOARD USER_DEFINED_BOARD のコメントアウトを解除し、
「#endif //HARDWARE_PROFILE_H」直前に以下2行を追加します。
#define USB_BUS_SENSE 1
#define self_power 1

次に、usb_descriptors.cを書き換えます。
0x25, 0x65, // LOGICAL_MAXIMUM (101)を
0x25, 0x6D, // LOGICAL_MAXIMUM (109)に変更し、
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)を
0x29, 0x8B, // USAGE_MAXIMUM (Keyboard Application)に変更します。

Keyboard.cを書き換えます。
コンフィギュレーション部分の#ifコンパイラディレクティブに以下を追加します。
#elif defined(DEMO_BOARD)
#pragma config FOSC = HSPLL_HS
#pragma config WDT = OFF
#pragma config PLLDIV = 5
#pragma config CPUDIV = OSC1_PLL2
#pragma config USBDIV = 2
#pragma config PWRT = ON
#pragma config BOR = ON
#pragma config BORV = 3
#pragma config LVP = OFF
#pragma config VREGEN = ON
#pragma config MCLRE = OFF
#pragma config PBADEN = OFF

変数宣言部の以下をコメントアウトします。
BYTE old_sw2,old_sw3;
char buffer[8];
unsigned char OutBuffer[8];

関数UserInit()の以下をコメントアウトします。
mInitAllLEDs();
mInitAllSwitches();
old_sw2 = sw2;
old_sw3 = sw3
関数Keyboard()の
if(Switch2IsPressed()){・・・}else{・・・}を削除し、
if(!HIDRxHandleBusy(lastOUTTransmission))のブロックの、以下記述以外をコメントアウト
lastOUTTransmission = HIDRxPacket(HID_EP,(BYTE*)&hid_report_out,1);

関数Switch2IsPressed、Switch3IsPressed、BlinkUSBStatusの定義・宣言をコメントアウトします。

関数ProcessIO()のBlinkUSBStatus();をコメントアウトします。

本プロジェクトで利用されない関数や変数などは数え上げたらきりがなく、あえてコメントアウトする必要性もない部分についてはそのままにしておきます。いよいよプロジェクトをビルドします(Build All実行)。「BUILD SUCCEDED」となれば成功です。ビルドによって生成されたHEXファイル(マシン語)を秋月のPICライタでPICに書き込みます。そして、前回製作した基板のICソケットに挿入し、PCに差してみましょう。Windowsのコントロールパネル>キーボード>ハードウェアで、キーボードとして認識されていることが表示されているはずです。

ホットキーの実装
テンキーにはキーが17個あり、1のキーを押すとかつてPCに「1」と入力されていたのです(当然だが)。ただ、PICを使ってUSB対応に改造してまで標準のテンキーの機能を再現したところで、芸が無いと同時に改造の意義も限りなくゼロに等しくなりそうなので、ホットキーを実装することにします。つまり、1キーを押すと、(例えば)「お世話になっております。」と入力されるようにして、入力支援のようなものを目指します。

HIDクラスのデバイスは、ユーザーからの入力があっても無くても、一定間隔でホストに情報を送り続けます。その「情報」とは、現在押されている全てのキーコードで、関数Keyboardの中の
lastINTransmission = HIDTxPacket(HID_EP, (BYTE*)hid_report_in, 0x08);
という一行の「hid_report_in」がそれです。第1引数HID_EPは転送データ用のバッファで、 第2引数はホストに送るデータのポインタ、続いてデータの長さです。データは8バイトで、第2バイトに修飾キー(Ctrl,Alt,Shift,Windowsキーなど)情報、第3バイトは予約されているため非使用で、残りが一般的な文字のキーコード。つまり一度に押せる文字キーの数は(修飾キー以外で)6つ。 上記を踏まえると、例えば「a」が押された状態のパケットは、
0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00
であり、離された状態
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
を続けざまに送信してはじめてホストに文字が入力される仕組み。ちなみに装飾キー(Shift,Ctrl,Alt等)は一般的な文字キーと同時押しされるケースが想定されているため別扱いで、パケットの第2バイトが0b00000010(0x02)だとShiftキー、0b00001000(0x0F)だとWindowsキーとなる。
これらパケットの送信部分をぬかりなく実行すれば、自作関数内で実行しようが何しようが勝手です。
ホットキーに必要な関数(続けざまにキー入力させる関数)を実装します。ソースを一行ずつ追っての解説は割愛しますが、大まかな構造は以下の通りです。
・関数Keyboardは、関数Intervalfunc(自作関数)を実行。 ・関数Intervalfuncで、キーの入力を感知し(キーマトリクス、チャタリングの処理)、ホットキー入力をトリガします。 ・ホットキー実行中を意味するフラグを立て、キー出力を排他的に(ホットキー実行中はキー入力は無視)処理します。 ・カウンタを回してキーを逐次出力し、最後の文字まで出力したらフラグを降ろし、キー入力を有効にします。

ソースコードの主要部分が以下です(全コードではないので、必要に応じて改変して使用して下さい。)。
//チャタリング対策に必要な変数(キーの数に応じて必要数追加)
BYTE bufSW0L,bufSW0H;
BYTE bufSW1L,bufSW1H;
//その他変数
BYTE nTimerAutomation0,nTimerAutomation1;//(キーの数に応じて必要数追加)
BYTE stateAutomation;
BYTE bSpecial;

void Keyboard(void){
if(!HIDTxHandleBusy(lastTransmission))Intervalfunc();
}

void Intervalfunc(void){
BYTE bufPortB0,bufPortB1;
BYTE bufPortA0,bufPortA1;
BYTE i;

//キーマトリクスRC、RC1が出力、PORTAとPORTBで入力
LATCbits.LATC0 = 1;
LATCbits.LATC1 = 0;
for(i=0;i<10;i++)Nop(); //ウェイト。ダイオードの特性により要調整
bufPortB0 = PORTB;
bufPortA0 = PORTA;

LATCbits.LATC0 = 0;
LATCbits.LATC1 = 1;
for(i=0;i<10;i++)Nop();
bufPortB1 = PORTB;
bufPortA1 = PORTA;

LATCbits.LATC0 = 0;
LATCbits.LATC1 = 0;

//チャタリングをクリアし、キー入力を感知
//RC0→RB0が短絡するようにキーが押された場合
bufSW0H <<= 1;
if(bufSW0L & 0b10000000)bufSW0H |= 0b00000001;
else bufSW0H &= 0b11111110;
bufSW0L <<= 1;
if(bufPortB0 & 0b00000001)bufSW0L &= 0b11111110;
else bufSW0L |= 0b00000001;
if(bufSW0H == 0b00000000 && bufSW0L == 0b00000001)StartAutomation0();

//RC1→RA1が短絡するようにキーが押された場合
bufSW1H <<= 1;
if(bufSW1L & 0b10000000)bufSW1H |= 0b00000001;
else bufSW1H &= 0b11111110;
bufSW1L <<= 1;
if(bufPortA1 & 0b00000010)bufSW1L &= 0b11111110;
else bufSW1L |= 0b00000001;
if(bufSW1H == 0b00000000 && bufSW1L == 0b00000001)StartAutomation1();
//同様にその他のキーも処理。ソースは割愛

//timeline
if(stateAutomation == 0b00000001)AutomationProcess0();
else if(stateAutomation == 0b00000010)AutomationProcess1();
//同様にその他のキーも処理。ソースは割愛
}

//キー出力をスタートさせる
void StartAutomation0(void){
stateAutomation = 0b00000001;
nTimerAutomation0 = 0;
}

void AutomationProcess0(void){
BYTE buf = GetCodeAutomation0(nTimerAutomation0++);
if(buf != 0xFF)SendLetter(buf);
else stateAutomation = 0b00000000;
}

BYTE GetCodeAutomation0(BYTE nIndex){
switch(nIndex){
case 0: return 0x88; //hiragana
case 1: return 0x12; //o
case 2: return 0x16; //s
case 3: return 0x08; //e
case 4: return 0x1A; //w
case 5: return 0x04; //a
case 6: return 0x11; //n
case 7: return 0x0C; //i
case 8: return 0x11; //n
case 9: return 0x04; //a
case 10: return 0x17; //t
case 11: return 0x00; //
case 12: return 0x17; //t
case 13: return 0x08; //e
case 14: return 0x12; //o
case 15: return 0x15; //r
case 16: return 0x0C; //i
case 17: return 0x10; //m
case 18: return 0x04; //a
case 19: return 0x16; //s
case 20: return 0x18; //u
case 21: return 0x37; //
case 22: return 0x2C; //spacebar
case 23: return 0x28; //enter
case 24: return 0x00; //
case 25: return 0xFF; //
}
return 0x00;
}

void SendLetter(BYTE nIndex){
hid_report_in[0] = bSpecial;
hid_report_in[1] = 0;
hid_report_in[2] = nIndex;
hid_report_in[3] = 0;
hid_report_in[4] = 0;
hid_report_in[5] = 0;
hid_report_in[6] = 0;
hid_report_in[7] = 0;
lastTransmission = HIDTxPacket(HID_EP,(BYTE*)hid_report_in,0x08);//Send
}
これで一発で「お世話になっております。」と表示されるはずです。

読んで下さってありがとうございます。<(_ _)>