1. 程式人生 > >USB自定義HID裝置實現-STM32

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(= 0; i < length; i++)

            {

                printf("%c ",buffer[i]);

            }

            printf("\r\n");

            return length;//返回接收到的資料

        }

    }

    else

    {

        //沒有資料,直接為0

        return 0;

    }

}

做好這裡,基本上就能實現通訊了,詳細工程請檢視文章最後的連結

 http://download.csdn.net/detail/dengrengong/8523351