雛形(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
}
これで一発で「お世話になっております。」と表示されるはずです。
読んで下さってありがとうございます。<(_ _)>