zoukankan      html  css  js  c++  java
  • 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 ");

    }

     

    //设备复位

    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 ");

    }

     

    //不知道干嘛的

    void DinkUsbStatus_In(void)

    {

        return;

    }

    //不知道干嘛的

    void DinkUsbStatus_Out(void)

    {

        return;

    }

     

    u8 *DinkUsbGetReportDescriptor(u16 Length)

    {

        usb_debug_printf("获取报告描述符 ");

        return Standard_GetDescriptorData(Length, &DinkUsb_Report_Descriptor);

    }

     

    u8 *DinkUsbGetHIDDescriptor(u16 Length)

    {

        usb_debug_printf("获取HID述符 ");

        return Standard_GetDescriptorData(Length, &DinkUsb_Hid_Descriptor);

    }

     

    u8 *DinkUsbGetProtocolValue(u16 Length)

    {

        usb_debug_printf("获取协议 ");

        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("设置协议 ");

            return DinkUsbSetProtocol();

        }

        else

        {

            return USB_UNSUPPORT;

        }

    }

     

     

    RESULT DinkUsbGetInterfaceSetting(u8 Interface, u8 AlternateSetting)

    {

        if (AlternateSetting > 0)//配置数量

        {

            usb_debug_printf("设置配置 ");

            return USB_UNSUPPORT;

        }

        else if (Interface > 1)//接口数量

        {

            usb_debug_printf("设置接口 ");

            return USB_UNSUPPORT;

        }

        return USB_SUCCESS;

    }

     

    //获取设备描述符

    u8 *DinkUsbGetDeviceDescriptor(u16 Length)

    {

        usb_debug_printf("获取设备描述符 ");

        return Standard_GetDescriptorData(Length, &Device_Descriptor);

    }

    //配置描述符

    u8 *DinkUsbGetConfigDescriptor(u16 Length)

    {

        usb_debug_printf("获取配置描述符 ");

        return Standard_GetDescriptorData(Length, &Config_Descriptor);

    }

    //字符串描述符

    u8 *DinkUsbGetStringDescriptor(u16 Length)

    {

     

        u8 wValue0 = pInformation->USBwValue0;

        usb_debug_printf("获取字符串描述符 %d ",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("设置配置 ");

        if (pInfo->Current_Configuration != 0)

        {

            bDeviceState = CONFIGURED;

        }

    }

    //将地址设置上传

    void DinkUsbSetDeviceAddress (void)

    {

        usb_debug_printf("设置地址 ");

        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(" ");

               

                return length;//返回接收到的数据

            }

        }

        else

        {

            //没有数据,直接为0

            return 0;

        }

    }

     

     

    做好这里,基本上就能实现通讯了,详细工程请查看文章最后的链接

     

     

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

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    ELK学习实验004:Elasticsearch的简单介绍和操作
    ELK学习实验003:Elasticsearch 集群安装
    ELK学习实验002:Elasticsearch介绍及单机安装
    ELK学习实验001:Elastic Stack简介
    Eclipse 笔记
    自动
    Kali 无线网络
    安全和匿名
    Java 异常处理
    Java 构造结构私有化
  • 原文地址:https://www.cnblogs.com/dengxiaojun/p/4357720.html
Copyright © 2011-2022 走看看