USB自定義HID裝置實現-STM32
該文件使用USB韌體庫,在其基礎上進行了自己的定製,完成了一個USB-HID裝置,首先是usb_desc.c檔案,裡面存放了usb各種描述符的存在
#include "usb_desc.h"
//usb標準裝置描述符
const u8 DinkUsbDeviceDescriptor[DINK_USB_SIZ_DEVICE_DESC] = {
USB_DEVICE_DESC_SIZE, //bLength欄位。裝置描述符的長度為18(0x12)位元組
USB_DEVICE_DESCRIPTOR_TYPE, //bDescriptorType欄位。裝置描述符的編號為0x01
WBVAL(0x0200), //bcdUSB欄位。這裡設定版本為USB1.1,即0x0110。
0x00, //bDeviceClass欄位。我們不在裝置描述符中定義裝置類,
0x00, //bDeviceSubClass欄位。bDeviceClass欄位為0時,該欄位也為0。
0x00, //bDeviceProtocol欄位。bDeviceClass欄位為0時,該欄位也為0。
0x40, //bMaxPacketSize0欄位。端點0的最大包長度。
WBVAL(0x7777), //idVender欄位。廠商ID號,我們這裡取0x8888,僅供實驗用。
WBVAL(0x8888), //idProduct欄位。產品ID號,由於是第一個實驗,我們這裡取0x0001。\。
WBVAL(0x0100), // 裝置的版本
0x01, //iManufacturer欄位。廠商字串的索引值,為了方便記憶和管理
0x02, //iProduct欄位。產品字串的索引值。剛剛用了1,這裡就取2吧。
0x03, //iSerialNumber欄位。裝置的序列號字串索引值。
0x01 //bNumConfigurations欄位。該裝置所具有的配置數。
};
//USB報告描述符的定義
const u8 HID_ReportDescriptor[]=
{
0x06,0xA0,0xFF,//用法頁(FFA0h, vendor defined)
0x09, 0x01,//用法(vendor defined)
0xA1, 0x01,//集合(Application)
0x09, 0x02 ,//用法(vendor defined)
0xA1, 0x00,//集合(Physical)
0x06,0xA1,0xFF,//用法頁(vendor defined)
//輸入報告
0x09, 0x03 ,//用法(vendor defined)
0x09, 0x04,//用法(vendor defined)
0x15, 0x80,//邏輯最小值(0x80 or -128)
0x25, 0x7F,//邏輯最大值(0x7F or 127)
0x35, 0x00,//物理最小值(0)
0x45,0xFF,//物理最大值(255)
0x75, 0x08,//報告長度Report size (8位)
0x95, 0x40,//報告數值(64 fields)
0x81, 0x02,//輸入(data, variable, absolute)
//輸出報告
0x09, 0x05,//用法(vendor defined)
0x09, 0x06,//用法(vendor defined)
0x15, 0x80,//邏輯最小值(0x80 or -128)
0x25, 0x7F,//邏輯最大值(0x7F or 127)
0x35, 0x00,//物理最小值(0)
0x45,0xFF,//物理最大值(255)
0x75,0x08,//報告長度(8位)
0x95, 0x40,//報告數值(64 fields)
0x91, 0x02,//輸出(data, variable, absolute)
0xC0,//集合結束(Physical)
0xC0//集合結束(Application)
};
//通過上面的報告描述符的定義,我們知道返回的輸入報告具有8位元組。
//輸出報告也有64位元組。至於這64位元組的資料是幹什麼用的,就要由使用者
//自己來決定了。
///////////////////////////報告描述符完畢////////////////////////////
//usb配置描述符
const u8 DinkUsbConfigDescriptor[DINK_USB_SIZ_CONFIG_DESC] = {
/***************配置描述符***********************/
USB_CONFIGUARTION_DESC_SIZE, //bLength欄位。配置描述符的長度為9位元組。
USB_CONFIGURATION_DESCRIPTOR_TYPE, //bDescriptorType欄位。配置描述符編號為0x02。
//wTotalLength欄位。配置描述符集合的總長度,
//包括配置描述符本身、介面描述符、類描述符、端點描述符等。
WBVAL(
USB_CONFIGUARTION_DESC_SIZE + //配置描述符
USB_INTERFACE_DESC_SIZE + //介面1描述符
9 + //hid描述符
USB_ENDPOINT_DESC_SIZE + //端點描述符
USB_ENDPOINT_DESC_SIZE //端點描述符
),
0x01, //bNumInterfaces欄位。該配置包含的介面數,只有一個介面。
0x01, //bConfiguration欄位。該配置的值為1。
0x00, //iConfigurationz欄位,該配置的字串索引。這裡沒有,為0。
USB_CONFIG_BUS_POWERED , //bmAttributes欄位,該裝置的屬性
USB_CONFIG_POWER_MA(500), //bMaxPower欄位,該裝置需要的最大電流量
/*********************第一個介面描述符,hid裝置**********************/
USB_INTERFACE_DESC_SIZE, //bLength欄位。介面描述符的長度為9位元組。
USB_INTERFACE_DESCRIPTOR_TYPE, //bDescriptorType欄位。介面描述符的編號為0x04。
0x00, //bInterfaceNumber欄位。該介面的編號,第一個介面,編號為0。
0x00, //bAlternateSetting欄位。該介面的備用編號,為0。
0x02, //bNumEndpoints欄位。非0端點的數目。該介面有2個批量端點
USB_DEVICE_CLASS_HUMAN_INTERFACE, //bInterfaceClass欄位。該介面所使用的類。大容量儲存裝置介面類的程式碼為0x08。,
0x00, //bInterfaceSubClass欄位。該介面所使用的子類。在HID1.1協議中,
//只規定了一種子類:支援BIOS引導啟動的子類。
//USB鍵盤、滑鼠屬於該子類,子類程式碼為0x01。
//但這裡我們是自定義的HID裝置,所以不使用子類。
0x00, //bInterfaceProtocol欄位。如果子類為支援引導啟動的子類,
//則協議可選擇滑鼠和鍵盤。鍵盤程式碼為0x01,滑鼠程式碼為0x02。
//自定義的HID裝置,也不使用協議。
0x00, //iConfiguration欄位。該介面的字串索引值。這裡沒有,為0。
/*********************HID報告描述符*************************/
//bLength欄位。本HID描述符下只有一個下級描述符。所以長度為9位元組。
0x09,
//bDescriptorType欄位。HID描述符的編號為0x21。
0x21,
//bcdHID欄位。本協議使用的HID1.1協議。注意低位元組在先。
0x10,
0x01,
//bCountyCode欄位。裝置適用的國家程式碼,這裡選擇為美國,程式碼0x21。
0x21,
//bNumDescriptors欄位。下級描述符的數目。我們只有一個報告描述符。
0x01,
//bDescriptorType欄位。下級描述符的型別,為報告描述符,編號為0x22。
0x22,
//bDescriptorLength欄位。下級描述符的長度。下級描述符為報告描述符。
sizeof(HID_ReportDescriptor)&0xFF,
(sizeof(HID_ReportDescriptor)>>8)&0xFF,
/*********************端點描述符**********************************/
/* 端點描述符 */
USB_ENDPOINT_DESC_SIZE, //bLength欄位。端點描述符長度為7位元組。
USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType欄位。端點描述符編號為0x05。
USB_ENDPOINT_IN(1), //bEndpointAddress欄位。端點的地址。我們使用D12的輸入端點1。
USB_ENDPOINT_TYPE_INTERRUPT, //bmAttributes欄位。D1~D0為端點傳輸型別選擇。
WBVAL(0x0040), //wMaxPacketSize欄位。該端點的最大包長。最大包長為64位元組。
0x01, //bInterval欄位。端點查詢的時間,端點查詢的時間,此處無意義。
/***********************端點描述符*******************************************/
USB_ENDPOINT_DESC_SIZE, //bLength欄位。端點描述符長度為7位元組。
USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType欄位。端點描述符編號為0x05。
USB_ENDPOINT_OUT(1), //bEndpointAddress欄位。端點的地址。我們使用D12的輸入端點1。
USB_ENDPOINT_TYPE_INTERRUPT, //bmAttributes欄位。D1~D0為端點傳輸型別選擇。
WBVAL(0x0040), //wMaxPacketSize欄位。該端點的最大包長。最大包長為64位元組。
0x01, //bInterval欄位。端點查詢的時間,端點查詢的時間,此處無意義。
};
/************************語言ID的定義********************/
const u8 DinkUsbLanguageId[DINK_USB_SIZ_STRING_LANGID]=
{
0x04, //本描述符的長度
0x03, //字串描述符
//0x0409為美式英語的ID
0x09,
0x04
};
////////////////////////語言ID完畢//////////////////////////////////
//Unicode 字串描述符
//鄧小俊的usb滑鼠
const u8 DinkUsbManufacturerStringDescriptor[DINK_USB_SIZ_STRING_VENDOR]=
{
32, //該描述符的長度為32位元組
0x03, //字串描述符的型別編碼為0x03
0x44, 0x00, //D
0x49, 0x00, //I
0x4e, 0x00, //N
0x4b, 0x00, //K
0x5f, 0x00, //_
0x48, 0x00, //H
0x49, 0x00, //I
0x44, 0x00, //D
0x5f, 0x00, //_
0x44, 0x00, //D
0x45, 0x00, //E
0x56, 0x00, //V
0x49, 0x00, //I
0x43, 0x00, //C
0x45, 0x00 //E
};
/////////////////////////廠商字串結束/////////////////////////////
//產品字串描述符
const u8 DinkUsbProductStringDescriptor[DINK_USB_SIZ_STRING_PRODUCT]=
{
32, //該描述符的長度為32位元組
0x03, //字串描述符的型別編碼為0x03
0x44, 0x00, //D
0x49, 0x00, //I
0x4e, 0x00, //N
0x4b, 0x00, //K
0x5f, 0x00, //_
0x48, 0x00, //H
0x49, 0x00, //I
0x44, 0x00, //D
0x5f, 0x00, //_
0x44, 0x00, //D
0x45, 0x00, //E
0x56, 0x00, //V
0x49, 0x00, //I
0x43, 0x00, //C
0x45, 0x00 //E
};
////////////////////////產品字串結束////////////////////////////
//字串“2008-07-07”的Unicode編碼
//8位小端格式
const u8 DinkUsbSerialNumberStringDescriptor[DINK_USB_SIZ_STRING_SERIAL]={
22, //該描述符的長度為22位元組
0x03, //字串描述符的型別編碼為0x03
0x32, 0x00, //2
0x30, 0x00, //0
0x31, 0x00, //1
0x35, 0x00, //5
0x2d, 0x00, //-
0x30, 0x00, //0
0x33, 0x00, //3
0x2d, 0x00, //-
0x32, 0x00, //2
0x31, 0x00 //1
};
//////////////////////產品序列號字串結束/////////////////////////
//產品序列號
u8 DinkUsbStringSerialUniqueId[DINK_USB_SIZ_STRING_SERIAL_UNIQUE_ID] =
{
DINK_USB_SIZ_STRING_SERIAL_UNIQUE_ID, //描述符長度
0x03 //描述符型別編碼
/* Serial number該編碼將會
可以通過修改該檔案實現不同的裝置,第二是usb_prop.c檔案,定義了一系列的回撥函式,在usb列舉階段使用
#include "usb_prop.h"
u32 ProtocolValue;
//表明有多少端點,多少種配置
DEVICE Device_Table =
{
EP_NUM,
1
};
//static u8 s_Request = 0;//記錄當前請求值
//裝置描述符
ONE_DESCRIPTOR Device_Descriptor =
{
(u8*)DinkUsbDeviceDescriptor,
DINK_USB_SIZ_DEVICE_DESC
};
//配置描述符
ONE_DESCRIPTOR Config_Descriptor =
{
(u8*)DinkUsbConfigDescriptor,
DINK_USB_SIZ_CONFIG_DESC
};
//報告描述符
ONE_DESCRIPTOR DinkUsb_Report_Descriptor =
{
(u8*)HID_ReportDescriptor,
HID_ReportDescSize
};
//報告描述符
ONE_DESCRIPTOR DinkUsb_Hid_Descriptor =
{
(u8*)(DinkUsbConfigDescriptor+9),
9
};
//字串描述符
ONE_DESCRIPTOR String_Descriptor[4] =
{
{(u8*)DinkUsbLanguageId, DINK_USB_SIZ_STRING_LANGID},
{(u8*)DinkUsbManufacturerStringDescriptor, DINK_USB_SIZ_STRING_VENDOR},
{(u8*)DinkUsbProductStringDescriptor, DINK_USB_SIZ_STRING_PRODUCT},
{(u8*)DinkUsbSerialNumberStringDescriptor, DINK_USB_SIZ_STRING_SERIAL}
};
//USB過程處理函式陣列
DEVICE_PROP Device_Property =
{
DinkUsbInit,
DinkUsbReset,
DinkUsbStatus_In,
DinkUsbStatus_Out,
DinkUsbData_Setup,
DinkUsbNoDataSetup,
DinkUsbGetInterfaceSetting,
DinkUsbGetDeviceDescriptor,
DinkUsbGetConfigDescriptor,
DinkUsbGetStringDescriptor,
0,
0x40 /*MAX PACKET SIZE*/
};
//usb標準資料請求結構體
//只實現了兩個,剩下的用nop方式解決了
USER_STANDARD_REQUESTS User_Standard_Requests =
{
DinkUsbGetConfiguration,
DinkUsbSetConfiguration,
DinkUsbGetInterface,
DinkUsbSetInterface,
DinkUsbGetStatus,
DinkUsbClearFeature,
DinkUsbSetEndPointFeature,
DinkUsbSetDeviceFeature,
DinkUsbSetDeviceAddress
};
//裝置初始化
void DinkUsbInit(void)
{
Get_SerialNum(); //構建字串描述符
pInformation->Current_Configuration = 0; //當前選擇的配置為0
PowerOn(); //連線USB
_SetISTR(0);
wInterrupt_Mask = IMR_MSK;
_SetCNTR(wInterrupt_Mask);
bDeviceState = UNCONNECTED; //裝置狀態初始化為未連線狀態
usb_debug_printf("USB Init\r\n");
}
//裝置復位
void DinkUsbReset(void)
{
Device_Info.Current_Configuration = 0; //選擇當前配置為0
pInformation->Current_Feature = DinkUsbConfigDescriptor[7]; //獲取配置描述符中當前裝置屬性
pInformation->Current_Interface = 0;//設定當前裝置介面
SetBTABLE(BTABLE_ADDRESS);//設定緩衝區地址
SetEPType(ENDP0, EP_CONTROL);//控制端點
SetEPTxStatus(ENDP0, EP_TX_STALL);
SetEPRxAddr(ENDP0, ENDP0_RXADDR);//設定端點緩衝區地址
SetEPTxAddr(ENDP0, ENDP0_TXADDR);
Clear_Status_Out(ENDP0);
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//設定接收最大長度
SetEPRxValid(ENDP0);
SetEPType(ENDP1, EP_INTERRUPT);//初始化端點1為中斷傳輸模式,用來報告一些狀態
SetEPTxAddr(ENDP1, ENDP1_TXADDR);//設定端點地址
SetEPRxAddr(ENDP1, ENDP1_RXADDR);//設定端點地址
SetEPRxStatus(ENDP1, EP_RX_VALID);//使能接收
SetEPTxStatus(ENDP1, EP_TX_NAK); //不使能傳送
SetEPRxCount(ENDP1, 64);//設定接收最大長度
Clear_Status_Out(ENDP1);
bDeviceState = ATTACHED;//裝置插入
SetDeviceAddress(0);//設定當前地址為0
usb_debug_printf("USB Reset\r\n");
}
//不知道幹嘛的
void DinkUsbStatus_In(void)
{
return;
}
//不知道幹嘛的
void DinkUsbStatus_Out(void)
{
return;
}
u8 *DinkUsbGetReportDescriptor(u16 Length)
{
usb_debug_printf("獲取報告描述符\r\n");
return Standard_GetDescriptorData(Length, &DinkUsb_Report_Descriptor);
}
u8 *DinkUsbGetHIDDescriptor(u16 Length)
{
usb_debug_printf("獲取HID述符\r\n");
return Standard_GetDescriptorData(Length, &DinkUsb_Hid_Descriptor);
}
u8 *DinkUsbGetProtocolValue(u16 Length)
{
usb_debug_printf("獲取協議\r\n");
if (Length == 0)
{
pInformation->Ctrl_Info.Usb_wLength = 1;
return NULL;
}
else
{
return (u8 *)(&ProtocolValue);
}
}
RESULT DinkUsbData_Setup(u8 RequestNo)
{
u8 *(*CopyRoutine)(u16);
CopyRoutine = NULL;
if ((RequestNo == GET_DESCRIPTOR)
&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
&& (pInformation->USBwIndex0 == 0))
{
//獲取報告描述符
if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)
{
CopyRoutine = DinkUsbGetReportDescriptor;
}
//獲取HID描述符
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
CopyRoutine = DinkUsbGetHIDDescriptor;
}
}
/*** GET_PROTOCOL ***/
else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
&& RequestNo == GET_PROTOCOL)
{
CopyRoutine = DinkUsbGetProtocolValue;//獲取協議值
}
if (CopyRoutine == NULL)
{
return USB_UNSUPPORT;
}
pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
(*CopyRoutine)(0);
return USB_SUCCESS;
}
RESULT DinkUsbSetProtocol(void)
{
u8 wValue0 = pInformation->USBwValue0;
ProtocolValue = wValue0;
return USB_SUCCESS;
}
RESULT DinkUsbNoDataSetup(u8 RequestNo)
{
if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
&& (RequestNo == SET_PROTOCOL))
{
usb_debug_printf("設定協議\r\n");
return DinkUsbSetProtocol();
}
else
{
return USB_UNSUPPORT;
}
}
RESULT DinkUsbGetInterfaceSetting(u8 Interface, u8 AlternateSetting)
{
if (AlternateSetting > 0)//配置數量
{
usb_debug_printf("設定配置\r\n");
return USB_UNSUPPORT;
}
else if (Interface > 1)//介面數量
{
usb_debug_printf("設定介面\r\n");
return USB_UNSUPPORT;
}
return USB_SUCCESS;
}
//獲取裝置描述符
u8 *DinkUsbGetDeviceDescriptor(u16 Length)
{
usb_debug_printf("獲取裝置描述符\r\n");
return Standard_GetDescriptorData(Length, &Device_Descriptor);
}
//配置描述符
u8 *DinkUsbGetConfigDescriptor(u16 Length)
{
usb_debug_printf("獲取配置描述符\r\n");
return Standard_GetDescriptorData(Length, &Config_Descriptor);
}
//字串描述符
u8 *DinkUsbGetStringDescriptor(u16 Length)
{
u8 wValue0 = pInformation->USBwValue0;
usb_debug_printf("獲取字串描述符 %d\r\n",wValue0);
if (wValue0 > 4)
{
return NULL;
}
else
{
return Standard_GetDescriptorData(Length, &String_Descriptor[wValue0]); //返回字串描述符
}
}
//將裝置狀態上傳到配置資料中
void DinkUsbSetConfiguration(void)
{
DEVICE_INFO *pInfo = &Device_Info;
usb_debug_printf("設定配置\r\n");
if (pInfo->Current_Configuration != 0)
{
bDeviceState = CONFIGURED;
}
}
//將地址設定上傳
void DinkUsbSetDeviceAddress (void)
{
usb_debug_printf("設定地址\r\n");
bDeviceState = ADDRESSED;
}
其中最核心的兩個函式分別是復位和初始化,復位的時候要將端點配置好,並且接受最好要使能,否則無法接收資料(後期自己使能也可以),然後就是端點的處理函數了usb_endp.c
#include "usb_endp.h"
//傳送完成置1 傳送未完成置0
u8 sendOk = 1;
//接收到資料該設定為1,資料處理完成之後修改為0
u8 ReceiveOk = 0;
void EP1_IN_Callback(void)
{
//裝置向主機發送資料的回撥函式
sendOk = 1;//傳送成功為1
SetEPTxStatus(ENDP1, EP_TX_NAK);//傳送成功等待第二次設定為valid
}
void EP1_OUT_Callback(void)
{
//接收了一次資料之後等待資料處理,將接受響應設定為NAK
//處理完成之後再設定為VALID
SetEPRxStatus(ENDP1, EP_RX_NAK);//NAK接收
ReceiveOk = 1;//有資料標誌為1
}
要想使能這些函式,需要將端點響應函式開啟
另外,微控制器應當來處理或者傳送資料,依靠usb_data_process.h檔案完成
#include "usb_data_process.h"
//HID傳送資料
//返回1傳送失敗 返回0傳送成功
u8 HID_Send_Data(u8* buffer,u8 length)
{
if(sendOk == 1)
{
if(length == 0)
{
SetEPTxStatus(ENDP1, EP_TX_NAK);//不傳送
}
else
{
UserToPMABufferCopy(buffer, GetEPTxAddr(ENDP1), length);
SetEPTxCount(ENDP1, length);
SetEPTxValid(ENDP1);//使能傳送
sendOk = 0;//設定傳送未完成狀態,等待發送回調函式將資料傳送到主機
}
return 0;
}
else
{
return 1;//上一次的資料還沒傳送出去,所以這次傳送失敗
}
}
//HID接收資料處理
u8 HID_Receive_Data(u8* buffer)
{
u16 length = 0;//獲取接收到的資料長度
u8 i = 0;
if(ReceiveOk == 1)//有資料
{
length = GetEPRxCount(ENDP1);
if(length == 0)return 0;
else
{
PMAToUserBufferCopy(buffer, GetEPRxAddr(ENDP1), length);
SetEPRxValid(ENDP1);//使能接收
ReceiveOk = 0;
printf("hid receive : ");
for(i = 0; i < length; i++)
{
printf("%c ",buffer[i]);
}
printf("\r\n");
return length;//返回接收到的資料
}
}
else
{
//沒有資料,直接為0
return 0;
}
}
做好這裡,基本上就能實現通訊了,詳細工程請檢視文章最後的連結
http://download.csdn.net/detail/dengrengong/8523351