原文地址::http://blog.csdn.net/u011318735/article/details/17429789
相關文章
·1、STM32完成USB_Keyboard的實驗總結----http://blog.csdn.net/u014980289/article/details/62417601
下面編寫下USB鍵盤的程序,依然在CustomHID工程上修改。
依舊最先修改的是usb_desc.c文件。我們從設備描述符開始講述。
設備描述符需要修改下bMaxPacketSize(最大包長度)域爲0x08,因爲被本次的工程最大通訊長度就是8字節,正好符合USB規範,所以這裏改成0x08,還要注意在usb_prop.c的DEVICE_PROP Device_Property結構體裏註冊的最大長度也要是0x08,與設備描述符的要相同(我們在下文說到)。這裏最好還要修改下PID和VID的域的值,以防該PID和VID對應的設備已經在電腦裏有了驅動而導致功能不正常。
/* USB標準設備描述符*/
const uint8_t Keyboard_DeviceDescriptor[KEYBOARD_SIZ_DEVICE_DESC] =
{
0x12, /*bLength:長度,設備描述符的長度爲18字節*/
USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:類型,設備描述符的編號是0x01*/
0x00, /*bcdUSB:所使用的USB版本爲2.0*/
0x02,
0x00, /*bDeviceClass:設備所使用的類代碼*/
0x00, /*bDeviceSubClass:設備所使用的子類代碼*/
0x00, /*bDeviceProtocol:設備所使用的協議*/
0x08, /*bMaxPacketSize:最大包長度爲8字節*/
0x78, /*idVendor:廠商ID爲0x7788*/
0x67,
0x12, /*idProduct:產品ID爲0x1122*/
0x01,
0x00, /*bcdDevice:設備的版本號爲2.00*/
0x02,
1, /*iManufacturer:廠商字符串的索引*/
2, /*iProduct:產品字符串的索引*/
3, /*iSerialNumber:設備的序列號字符串索引*/
0x01 /*bNumConfiguration:設備有1種配置*/
}; /* keyboard設備描述符 */
接下去修改下配置描述符。找到接口的描述符的bNumEndpoints(該接口所使用的端點數)域,不用修改,但需要提下,還是0x02,表示使用2個端點。修改下接口描述符的nInterfaceProtocol (該接口使用的協議)域爲0x01,表示是鍵盤。在輸入端點描述符中端點設置端點1爲爲中斷傳輸的輸入端點,設置 wMaxPacketSize:(該端點支持的最大包長度)域的值爲0x08,因爲本次鍵盤的工程需要向USB主機發送8字節。在輸出端點描述符設置端點1爲中斷傳輸的輸出端點,設置爲中斷傳輸設置 wMaxPacketSize:(該端點支持的最大包長度)域的值爲0x01,因爲USB主機只會向USB從設備發送1個字節。
/* USB配置描述符集合(配置、接口、端點、類、廠商)(Configuration, Interface, Endpoint, Class, Vendor */
const uint8_t Keyboard_ConfigDescriptor[KEYBOARD_SIZ_CONFIG_DESC] =
{
0x09, /*bLength:長度,設備字符串的長度爲9字節*/
USB_CONFIGURATION_DESCRIPTOR_TYPE, /*bDescriptorType:類型,配置描述符的類型編號爲0x2*/
KEYBOARD_SIZ_CONFIG_DESC, /*wTotalLength:配置描述符的總長度爲41字節*/
0x00,
0x01, /*bNumInterfaces:配置所支持的接口數量1個*/
0x01, /*bConfigurationValue:該配置的值*/
0x00, /*iConfiguration:該配置的字符串的索引值,該值爲0表示沒有字符串*/
0xC0, /* bmAttributes:設備的一些特性,0xc0表示自供電,不支持遠程喚醒
D7:保留必須爲1,D6:是否自供電,D5:是否支持遠程喚醒,D4~D0:保留設置爲0*/
0x32, /*從總線上獲得的最大電流爲100mA */
// 0x96, /*MaxPower:設備需要從總線上獲取多少電流,單位爲2mA,0x96表示300mA*/
/************** 接口描述符****************/
/* 09 */
0x09, /*bLength:長度,接口描述符的長度爲9字節 */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType:接口描述符的類型爲0x4 */
0x00, /*bInterfaceNumber:該接口的編號*/
0x00, /*bAlternateSetting:該接口的備用編號 */
0x02, /*bNumEndpoints:該接口所使用的端點數*/
0x03, /*bInterfaceClass該接口所使用的類爲HID*/
0x01, /*bInterfaceSubClass:該接口所用的子類 1=BOOT, 0=no boot */
0x01, /*nInterfaceProtocol :該接口使用的協議0=none, 1=keyboard, 2=mouse */
0, /*iInterface: 該接口字符串的索引 */
/*****************HID描述符 ********************/
/* 18 */
0x09, /*bLength: HID描述符的長度爲9字節 */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID的描述符類型爲0x21 */
0x10, /*bcdHID: HID協議的版本爲1.1 */
0x01,
0x21, /*bCountryCode: 國家代號 */
0x01, /*bNumDescriptors: 下級描述符的數量*/
0x22, /*bDescriptorType:下級描述符的類型*/
KEYBOARD_SIZ_REPORT_DESC,/* wItemLength: 下一集描述符的長度*/
0x00,
/********************輸入端點描述符******************/
/* 27 */
0x07, /* bLength: 端點描述符的長度爲7字節*/
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端點描述符的類型爲0x05*/
0x81, /* bEndpointAddress: 該端點(輸入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端點號*/
0x03, /* bmAttributes: 端點的屬性爲爲中斷端點.
D0~D1表示傳輸類型:0(控制傳輸),1(等時傳輸),2(批量傳輸),3(中斷傳輸)
非等時傳輸端點:D2~D7:保留爲0
等時傳輸端點:
D2~D3表示同步的類型:0(無同步),1(異步),2(適配),3(同步)
D4~D5表示用途:0(數據端點),1(反饋端點),2(暗含反饋的數據端點),3(保留),D6~D7:保留,*/
0x08, /* wMaxPacketSize: 該端點支持的最大包長度爲8字節*/
0x00,
0x0A, /* bInterval: 輪詢間隔(32ms) */
/********************輸出端點描述符*******************/
/* 34 */
0x07, /* bLength: 端點描述符的長度爲7字節*/
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端點描述符的類型爲0x05*/
0x01, /* bEndpointAddress: 該端點(輸入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端點號*/
0x03, /* bmAttributes: 端點的屬性爲爲中斷端點.
D0~D1表示傳輸類型:0(控制傳輸),1(等時傳輸),2(批量傳輸),3(中斷傳輸)
非等時傳輸端點:D2~D7:保留爲0
等時傳輸端點:
D2~D3表示同步的類型:0(無同步),1(異步),2(適配),3(同步)
D4~D5表示用途:0(數據端點),1(反饋端點),2(暗含反饋的數據端點),3(保留),D6~D7:保留,*/
0x01, /* wMaxPacketSize: 該端點支持的最大包長度爲字節*/
0x00,
0x0A, /* bInterval: 輪詢間隔(32ms) */
/* 41 */
};
講到報告描述符,得將報告描述符的整個替換掉。該報告描述符定義了8字節的輸入域,第一個字節表示特殊件是否按下,鍵盤的特殊鍵包括:ctrl,shift,alt鍵等,該字節D0表示Ctrl鍵,D1表示Shift鍵,D2表示Alt鍵,其他位保留。第二個字節保留,固定值爲0。第三個字節到第八個字節用來保存按鍵值,如果只有一個按鍵按下,則按鍵值保存在第三個字節,如果有兩個或兩個以上的按鍵按下,則依次保存在從第三字節開始的字節中,當然最多隻能6個按鍵同時按下,超過6個鍵值,則不響應。報告描述符還定義了一個字節的輸出域,該字節是是用來控制LED燈的,我們都知道鍵盤的上有幾個LED燈,比如說大小寫鍵的LED燈等,該字節的定義:D0:Num Lock D1:Cap Lock D2:Scroll Lock D3:Compose D4:Kana,由於我一直用筆記本電腦的鍵盤,所以我也只認識前三個鍵。在這個工程,我只做了Ctrl鍵、Shift鍵、Num Lock鍵,以及A鍵。
/* HID的報告描述符*/
/*定義了8字節發送:
** 第一字節表示特殊件是否按下:D0:Ctrl D1:Shift D2:Alt
** 第二字節保留,值爲0
** 第三~第八字節:普通鍵鍵值的數組,最多能同時按下6個鍵
**定義了1字節接收:對應鍵盤上的LED燈,這裏只用了兩個位。
** D0:Num Lock D1:Cap Lock D2:Scroll Lock D3:Compose D4:Kana*/
const uint8_t Keyboard_ReportDescriptor[KEYBOARD_SIZ_REPORT_DESC] =
{
/*short Item D7~D4:bTag;D3~D2:bType;D1~D0:bSize
**bTag ---主條目 1000:輸入(Input) 1001:輸出(Output) 1011:特性(Feature) 1010:集合(Collection) 1100:關集合(End Collection)
** 全局條目 0000:用途頁(Usage Page) 0001:邏輯最小值(Logical Minimum) 0010:邏輯最大值(Logical Maximum) 0011:物理最小值(Physical Minimum)
** 0100:物理最大值(Physical Maximum) 0101:單元指數(Unit Exponet) 0110:單元(Unit) 0111:數據域大小(Report Size)
** 1000:報告ID(Report ID) 1001:數據域數量(Report Count) 1010:壓棧(Push) 1011:出棧(Pop) 1100~1111:保留(Reserved)
** 局部條目 0000:用途(Usage) 0001:用途最小值(Usage Minimum) 0010:用途最大值(Usage Maximum) 0011:標識符索引(Designator Index)
** 0100:標識符最小值(Designator Minimum) 0101:標識符最大值(Designator Maximum) 0111:字符串索引(String Index) 1000:字符串最小值(String Minimum)
** 1001:字符串最大值(String Maximum) 1010:分隔符(Delimiter) 其他:保留(Reserved)
**bType---00:主條目(main) 01:全局條目(globle) 10:局部條目(local) 11:保留(reserved)
**bSize---00:0字節 01:1字節 10:2字節 11:4字節*/
//0x05:0000 01 01 這是個全局條目,用途頁選擇爲普通桌面頁
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//0x09:0000 10 01 這是個全局條目,用途選擇爲鍵盤
0x09, 0x06, // USAGE (Keyboard)
//0xa1:1010 00 01 這是個主條目,選擇爲應用集合,
0xa1, 0x01, // COLLECTION (Application)
//0x05:0000 01 11 這是個全局條目,用途頁選擇爲鍵盤/按鍵
0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)
//0x19:0001 10 01 這是個局部條目,用途的最小值爲0xe0,對應鍵盤上的左ctrl鍵
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
//0x29:0010 10 01 這是個局部條目,用途的最大值爲0xe7,對應鍵盤上的有GUI(WIN)鍵
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
//0x15:0001 01 01 這是個全局條目,說明數據的邏輯值最小值爲0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//0x25:0010 01 01 這是個全局條目,說明數據的邏輯值最大值爲1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//0x95:1001 01 01 這是個全局條目,數據域的數量爲8個
0x95, 0x08, // REPORT_COUNT (8)
//0x75:0111 01 01 這是個全局條目,每個數據域的長度爲1位
0x75, 0x01, // REPORT_SIZE (1)
//0x81:1000 00 01 這是個主條目,有8*1bit數據域作爲輸入,屬性爲:Data,Var,Abs
0x81, 0x02, // INPUT (Data,Var,Abs)
//0x95:1001 01 01 這是個全局條目,數據域的數量爲1個
0x95, 0x01, // REPORT_COUNT (1)
//0x75:0111 01 01 這是個全局條目,每個數據域的長度爲8位
0x75, 0x08, // REPORT_SIZE (8)
//0x81:1000 00 01 這是個主條目,有1*8bit數據域作爲輸入,屬性爲:Cnst,Var,Abs
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//0x95:1001 01 01 這是個全局條目,數據域的數量爲6個
0x95, 0x06, // REPORT_COUNT (6)
//0x75:0111 01 01 這是個全局條目,每個數據域的長度爲8位
0x75, 0x08, // REPORT_SIZE (8)
//0x25:0010 01 01 這是個全局條目,邏輯最大值爲255
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
//0x19:0001 10 01 這是個局部條目,用途的最小值爲0
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
//0x29:0010 10 01 這是個局部條目,用途的最大值爲0x65
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
//0x81:1000 00 01 這是個主條目,有6*8bit的數據域作爲輸入,屬相爲屬性爲:Data,Var,Abs
0x81, 0x00, // INPUT (Data,Ary,Abs)
//0x25:0010 01 01 這是個全局條目,邏輯的最大值爲1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//0x95:1001 01 01 這是個全局條目,數據域的數量爲2
0x95, 0x02, // REPORT_COUNT (2)
//0x75:0111 01 01 這是個全局條目,每個數據域的長度爲1位
0x75, 0x01, // REPORT_SIZE (1)
//0x05:0000 01 01 這是個全局條目,用途頁選擇爲LED頁
0x05, 0x08, // USAGE_PAGE (LEDs)
//0x19:0001 10 01 這是個局部條目,用途的最小值爲0x01,對應鍵盤上的Num Lock
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
//0x29:0010 10 01 這是個局部條目,用途的最大值爲0x02,對應鍵盤上的Caps Lock
0x29, 0x02, // USAGE_MAXIMUM (Caps Lock)
//0x91:1001 00 01 這是個主條目,有2*1bit的數據域作爲輸出,屬性爲:Data,Var,Abs
0x91, 0x02, // OUTPUT (Data,Var,Abs)
//0x95:1001 01 01 這是個全局條目,數據域的數量爲1個
0x95, 0x01, // REPORT_COUNT (1)
//0x75:0111 01 01 這是個全局條目,每個數據域的長度爲6bit,正好與前面的2bit組成1字節
0x75, 0x06, // REPORT_SIZE (6)
//0x91:1001 00 01 這是個主條目,有1*6bit數據域最爲輸出,屬性爲:Cnst,Var,Abs
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0xc0 // END_COLLECTION
};
說到按鍵的鍵值的定義,那就要參考HID用途表(從54頁開始),截個開始的功能表吧,湊夠途中可以看到A鍵的鍵值是4。
其他的一些描述符,這裏也貼出來:
/* 語言ID描述符 */
const uint8_t Keyboard_StringLangID[KEYBOARD_SIZ_STRING_LANGID] =
{
KEYBOARD_SIZ_STRING_LANGID, /*bLength:本描述符的長度爲4字節*/
USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的類型爲0x03*/
0x09, /*bString:語言ID爲0x0409,表示美式英語*/
0x04
}; /* LangID = 0x0409: U.S. English*/
/*廠商字符串描述符*/
const uint8_t Keyboard_StringVendor[KEYBOARD_SIZ_STRING_VENDOR] =
{
KEYBOARD_SIZ_STRING_VENDOR, /*bLength:廠商字符串描述符的長度*/
USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的類型爲0x03*/
'B' , 0, 'y', 0, ':' , 0, 'z' , 0, 'i', 0, 'y', 0,'e', 0,'3', 0, '3', 0, '4', 0 /*自定義*/
};
/*產品的字符串描述符*/
const uint8_t Keyboard_StringProduct[KEYBOARD_SIZ_STRING_PRODUCT] =
{
KEYBOARD_SIZ_STRING_PRODUCT, /* bLength:產品的字符串描述符*/
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的類型爲0x03*/
'z', 0, 'i', 0, 'y', 0, 'e', 0, '3', 0, '3', 0 ,'4',0/*自定義*/
};
/*產品序列號的字符串描述符*/
uint8_t Keyboard_StringSerial[KEYBOARD_SIZ_STRING_SERIAL] =
{
KEYBOARD_SIZ_STRING_SERIAL, /* bLength:產品序列號*/
USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的類型爲0x03*/
'1', 0, '2', 0, '3', 0,'4', 0,'5', 0, '6', 0, '7', 0 /*自定義*/
};
這裏還要貼出usb_desc.h中的一些宏定義,這些宏定義在上面的描述符中用到:
#define USB_DEVICE_DESCRIPTOR_TYPE 0x01 //設備描述符類型
#define USB_CONFIGURATION_DESCRIPTOR_TYPE 0x02 //配置描述符類型
#define USB_STRING_DESCRIPTOR_TYPE 0x03 //字符串描述符類型
#define USB_INTERFACE_DESCRIPTOR_TYPE 0x04 //接口描述符類型
#define USB_ENDPOINT_DESCRIPTOR_TYPE 0x05 //端點描述符類型
#define HID_DESCRIPTOR_TYPE 0x21 //HID描述符類型
#define KEYBOARD_SIZ_HID_DESC 0x09 //HID描述符的長度
#define KEYBOARD_OFF_HID_DESC 0x12 //HID描述符在配置描述符集合數組中的偏移
#define KEYBOARD_SIZ_DEVICE_DESC 18 //設備描述符的長度
#define KEYBOARD_SIZ_CONFIG_DESC 41 //配置描述符的長度
#define KEYBOARD_SIZ_REPORT_DESC 61 //報告描述符的的長度
#define KEYBOARD_SIZ_STRING_LANGID 4 //語言ID字符串描述符的長度
#define KEYBOARD_SIZ_STRING_VENDOR 22 //廠商字符串描述符的長度
#define KEYBOARD_SIZ_STRING_PRODUCT 16 //產品字符串描述符的長度
#define KEYBOARD_SIZ_STRING_SERIAL 26 //序列號字符串描述符的長度
#define STANDARD_ENDPOINT_DESC_SIZE 0x09 //
#define REPORT_COUNT 8 //報告返回長度
講完了usb_desc.c的文件,下面要修改的是usb_prop.c文件。首先要修改下這個文件只需要修改下DEVICE_PROP Device_Property裏面的內容,把MaxPacketSize域的大小改爲0x08,與設備描述符對應。如下:
DEVICE_PROP Device_Property = //註冊一些CustomHID函數
{
Keyboard_init, //Keyboard的初始化函數
Keyboard_Reset, //Keyboard的復位函數
Keyboard_Status_In, //Keyboard狀態輸入函數
Keyboard_Status_Out, //Keyboard狀態輸出函數
Keyboard_Data_Setup, //Keyboard的處理有數據階段的特殊類請求函數
Keyboard_NoData_Setup, //Keyboard的處理沒有數據階段的特殊類請求函數
Keyboard_Get_Interface_Setting, //Keyboard獲取接口及備用接口設置(是否可用)
Keyboard_GetDeviceDescriptor, //Keyboard獲取設備描述符
Keyboard_GetConfigDescriptor, //Keyboard獲取配置描述符
Keyboard_GetStringDescriptor, //Keyboard獲取字符串描述符
0, //當前庫未使用
0x08 /*MAX PACKET SIZE*/ //最大的包長度爲64字節
};
接下去要修改KeyBoard_Reset()函數。這個函數只要改下端點的配置,配置端點1輸出有次,及端點1輸入有效:
/*******************************************************************************
* Function Name : Keyboard_Reset.
* Description : Keyboard reset routine.復位
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void Keyboard_Reset(void)
{
/* Set KEYBOARD_DEVICE as not configured */
pInformation->Current_Configuration = 0; //設置當前的配置爲0,表示沒有配置過
pInformation->Current_Interface = 0; //默認的接口
/* Current Feature initialization */
pInformation->Current_Feature = Keyboard_ConfigDescriptor[7];//當前的屬性,bmAttributes:設備的一些特性,0xc0表示自供電,不支持遠程喚醒
#ifdef STM32F10X_CL
/* EP0 is already configured in DFU_Init() by USB_SIL_Init() function */
/* Init EP1 IN snd EP1 OUT as Interrupt endpoint */
OTG_DEV_EP_Init(EP1_IN, OTG_DEV_EP_TYPE_INT, EP1_SIZE);
OTG_DEV_EP_Init(EP1_OUT, OTG_DEV_EP_TYPE_INT, EP1_SIZE);
#else
SetBTABLE(BTABLE_ADDRESS);
/* Initialize Endpoint 0 */
SetEPType(ENDP0, EP_CONTROL); //設置端點1爲控制端點
SetEPTxStatus(ENDP0, EP_TX_STALL); //設置端點0發送延時
SetEPRxAddr(ENDP0, ENDP0_RXADDR); //設置端點0的接收緩衝區地址
SetEPTxAddr(ENDP0, ENDP0_TXADDR); //設置端點0的發送緩衝區地址
Clear_Status_Out(ENDP0); //清除端點0的狀態
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//設置端點0的接收的計數
SetEPRxValid(ENDP0); //使能接收狀態
/* Initialize Endpoint 1 */
SetEPType(ENDP1, EP_INTERRUPT); //設置端點1爲中斷控制端點
SetEPTxAddr(ENDP1, ENDP1_TXADDR); //設置端點1的發送緩衝地址
SetEPTxCount(ENDP1, 8); //設置端點1的接收計數
// SetEPRxStatus(ENDP1, EP_RX_DIS); //設置端點1接收無效
SetEPTxStatus(ENDP1, EP_TX_NAK);
SetEPRxAddr(ENDP1, ENDP1_RXADDR); //設置接收數據的地址
SetEPRxCount(ENDP1, 1); //設置接收長度
SetEPRxStatus(ENDP1, EP_RX_VALID);//設置端點有效,可以接收數據
//
bDeviceState = ATTACHED; //設置設備狀態爲 ATTACHED狀態
/* Set this device to response on default address */
SetDeviceAddress(0); //設置設備爲默認地址
#endif /* STM32F10X_CL */
bDeviceState = ATTACHED;
}
這樣的話,端點就配置好了,我們還要編寫模擬鼠標的功能程序,這裏我使用四按鍵分別模擬鍵盤的。我們首先在hw_config.c中編寫按鍵引腳的配置函數USB_GPIO_Configuration()如下:
/*******************************************************************************
* Function Name : USB_GPIO_Confguration
* Description : USB相關引腳配置
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void USB_GPIO_Confguration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT, ENABLE);
/*上拉電阻引腳*/
GPIO_InitStructure.GPIO_Pin = USB_DISCONNECT_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(USB_DISCONNECT, &GPIO_InitStructure);
/*KEY按鍵引腳配置*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
該函數在BSP.c的BSP_Init()中調用。
接下去我們要 編寫按鍵掃描函數Keyboard_Scan(),這個函數不像鼠標工程裏的按鍵掃描函數一樣以檢測到按鍵就返回,這個函數需要檢測按鍵同時按下的情況,所以需要將所有的按鍵檢測一遍在返回,代碼如下:
/*******************************************************************************
* Function Name : Keyboard_Scan.
* Description : 鍵盤按鍵掃描
* Input : None.
* Output : None.
* Return value : 返回方向的值
*******************************************************************************/
uint8_t Keyboard_Scan(void)
{
uint8_t temp=0;
if(KEY_CTRL) //Ctrl鍵按下
{
Delay_ms(50);
if(KEY_CTRL)
temp|=CTRL_B;
}
if(KEY_SHIFT) //Shift鍵按下
{
Delay_ms(50);
if(KEY_SHIFT)
temp|=SHIFT_B;
}
if(KEY_CAPSLOCK) //大小寫鍵按下
{
Delay_ms(50);
if(KEY_CAPSLOCK)
temp|=CAPSLOCK_B;
}
if(KEY_CHARA) //字符'a'鍵按下
{
Delay_ms(50);
if(KEY_CHARA)
temp|=KEYA_B;
}
return temp;
}
之後還要編寫鍵值發送函數:
/*******************************************************************************
* Function Name : KeyBoard_Send.
* Description : 發送keyboard的信息
* Input : Keys: 檢測到被按下的按鍵值
* Output : None.
* Return value : None.
*******************************************************************************/
void Keyboard_Send(uint8_t key)
{
uint8_t Keyboad_Buf[8]={0,0,0,0,0,0,0,0};
uint8_t i=2;
if(key&CTRL_B) //Ctrl是特殊鍵,在第一個字節的D0
{
Keyboad_Buf[0]|=0x01;
printf("CTRL鍵\r\n");
}
if(key&SHIFT_B) //Shift是特殊鍵,在第一個字節的D1
{
Keyboad_Buf[0]|=0x02;
printf("SHIFT鍵\r\n");
}
if(key&CAPSLOCK_B) //Caps Lock鍵
{
Keyboad_Buf[i++]=0x39;//在HID用途表中代號是0x39
printf("CAPS LOCK鍵\r\n");
}
if(key&KEYA_B) //A鍵
{
Keyboad_Buf[i++]=0x04;//在HID用途表中代號爲0x04
printf("A鍵\r\n");
}
USB_SIL_Write(EP1_IN, Keyboad_Buf, 8);
SetEPTxValid(ENDP1);
}
這個函數有個參數key,我們通過Keyboard_Send(Keyboard_Scan())把掃描到的按鍵值傳遞到Keyboard_Send函數中,在該函數中根據鍵值來填充數組的值,如果檢測到ctrl鍵則置Keyboad_Buf[0]的D0位爲1,如果檢測到時shift鍵,則置Keyboad_Buf[0]的D1爲1。如果是檢測到是Caps lock鍵或其他鍵,置Keyboad_Buf[2]開始的字節。
最後要編寫main函數了,main()函數很簡答就是在while(1)中掃描按鍵,如果有按鍵按下,則調用Keyboard_Send()發送函數,發送按鍵值:
/********************************************************
函數:main()
描述:程序入口地址
參數:無
返回:無
********************************************************/
int main(void)
{
BSP_Init();
printf(" |===============================================|\r\n");
printf(" USB Keyboard 程序開始 \r\n");
printf("|===============================================|\r\n");
while (bDeviceState != CONFIGURED);
while(1)
{
if (bDeviceState == CONFIGURED) //如果USB已將配置好了
{
/* 檢測按鍵狀態,併發送鼠標位置數據 */
if (Keyboard_Scan()!= 0) //檢查是否有按鍵按下
{
Keyboard_Send(Keyboard_Scan()); //如果有,發送鍵值
}
else //沒有按鍵按下
{
Keyboard_Send(0); //發送空數據,如果不發,USB主機會認爲你一直在鍵入上次的鍵值
}
}
}
}
這裏需要注意的是,每次發送鍵值後,都要發送8字節的空數據到USB主機中,否則,我們舉個例子,我按下A鍵,結果USB主機收到後就會在屏幕上輸入'a',但是會一直不停得輸入’a‘,這個問題當時也困擾我許久,最後才發現,原來USB主機會一直保持上次收到的數據進行操作,我們上次按下'a',則電腦就會一直打印'a'不停,所以在發送完按鍵值後,需要在發送空數據給USB主機。
最後,我還是貼出我的readme.txt讓大家更詳細瞭解下程序軟硬件:
===================================================================================
下載方式
===================================================================================
SWD JTAG
===================================================================================
程序功能
===================================================================================
模擬鍵盤的功能,這裏模擬了4個按鍵的功能,分別爲:ctrl,shift,caps lock,A這四個鍵。
===================================================================================
硬件連接
===================================================================================
神州III號開發板:
ctrl鍵:開發板上的WAKEUP鍵,連接PA0。
shift鍵:開發板上的TEMPER鍵,連接PC13。
caps lock鍵:開發板上的USER1鍵,連接PA8。
A鍵:開發板上的USER2鍵,連接PD3
LED1:開發板上的標號爲OS1的LED,大小寫鎖鍵對應的LED
===================================================================================
軟件設置
===================================================================================
連接串口2,打開串口調試助手,選擇好相應的COM口
按如下進行設置:
----------------
波特率 | 115200 |
----------------
數據位 | 8 |
----------------
停止位 | 1 |
----------------
校驗位 | None |
----------------
流控制 | None |
----------------
===================================================================================
實驗現象
===================================================================================
按下開發板模擬的ctrl鍵,配合電腦鍵盤上的按鍵,比如ctrl+c,ctrl+v等,實現一些功能。
按下開發板模擬的shift鍵,配合模擬的A鍵,就可以輸出大寫A了
當然,同時按下ctrl+shift鍵,實現輸入法的切換
按下開發板模擬的caps Lock鍵,就能進行大小寫切換,大寫時,開發板上的LED1亮,小寫時滅
按下開發板模擬的A鍵,就能輸入a了