

UEFI开发探索85- YIE002USB开发板(08 制作HID设备)
source link: http://yiiyee.cn/blog/2021/05/04/uefi%e5%bc%80%e5%8f%91%e6%8e%a2%e7%b4%a285-yie002usb%e5%bc%80%e5%8f%91%e6%9d%bf%ef%bc%8808-%e5%88%b6%e4%bd%9chid%e8%ae%be%e5%a4%87%ef%bc%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
在介绍完所有背景知识后,终于可以进入实质的嵌入式编程了。
本篇拟使用YIE002开发板,制作一个USB HID设备,支持三种通信方式,以对应UEFI开发探索73和74所讨论的三种上位机通信。
这个系列的博客,主要还是偏向UEFI编程的探索,对于嵌入式的编程,不想讨论过多。对于YIE002的嵌入式开发,请移步我另外一个专栏“嵌入式开发”,在其中我开了一个新坑,用来探索YIE002的编程。
1 YIE002-STM32的USB编程
这款开发板的主芯片是STM32F103C8T6,是我出差最常带的版型。本篇所写的代码,适用于所有F1系列作为主芯片的开发板,比如正点原子的战舰开发板。
从意法的官方资料可知,STM32 MCU有如下USB IP:
- USB IP 可作为全速USB设备,存在于STM32F102、STM32F103;
- USB+ IP 可作为全速USB设备,存在于STM32F0x2;
- FS OTG IP 可作为全速和低速USB主机、全速USB设备,存在于STM32F105、STM32F107、STM32F2、STM32F4;
- HS OTG IP 可作为高速、全速和低速USB主机,可作为高速和全速USB设备,存在于STM32F2、STM32F4。
官方也提供了不同的USB库,以适应开发需求。比如针对USB IP和USB+ IP,提供的库如图1所示。
图1 USB(+) IP对应的USB库
因此,对F103系的MCU而言,可以选择Legacy library进行开发,也可以使用Cube library进行开发。
本篇的开发,是基于Legacy library的示例工程Custom_HID改造的。使用Cube library开发的方式,可以博客中参考嵌入式开发的专栏。
2 调整示例工程Custom_HID
我平常开发嵌入式产品,主要使用的是MDK Keil工具。Legacy library(STSW-STM32121)中提供的示例工程,支持各种编译工具。当然,也带来很多我不需要的冗余代码。另外,由于共用了外围设备库和评估板的各种文件,Legacy library组织了很好的代码结构,但对我而言是累赘,总不能每次都在库的文件夹下进行修改吧。
因此,我对示例工程Custom_HID进行了调整,删除了很多不需要的代码,重新组织了代码结构。
如图2所示,是调整过的代码结构。
图2 调整过的代码结构
另一个需要注意的地方,是头文件的路径,如图3所示。
图3 头文件包含路径
另外,在选择设备的时候,选择STM32F103C8,编译针对YIE002-STM32型开发板的生成文件。
其他的细节,请以本篇博客提供的工程,对照原始的Custom_HID工程,即可了解。
3 修改代码
修改代码的过程主要包括:
- 准备各种描述符,包括设备描述符、配置描述符和报表描述符等;
- 实现三种通信方式的代码支持;
详细过程如下。
3.1 准备描述符
描述符主要在文件usb_desc.c中修改。配置描述符比较简单,主要将厂商ID和产品ID修改为自己需要的值。主要的修改在配置描述符中,需要注意的是,端点描述符和接口描述符与配置描述符在同一数组内。
示例1 配置描述符
const uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
……//前略
/******************** Descriptor of Custom HID endpoints ******************/
/* 27 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x81, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x20, /* bInterval: Polling Interval (32 ms) */
/* 34 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
/* Endpoint descriptor type */
0x01, /* bEndpointAddress: */
/* Endpoint Address (OUT) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x20, /* bInterval: Polling Interval (20 ms) */
}
从示例1可以看出,通信用的端点号为1,可以输入输出。包括缺省的端点0在内,使用了两个端点号,因此在接口描述符中,bNumEndpoints必须为2(示例1中没有列出)。
而报表描述符,设置了16字节的通道,如示例2所示。
示例2 报表描述符
const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x00, // USAGE (0)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xff, // LOGICAL_MAXIMUM (255)
0x19, 0x01, // USAGE_MINIMUM (1)
0x29, 0x10, // USAGE_MAXIMUM (16)
0x95, 0x10, // REPORT_COUNT (16)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x19, 0x01, // USAGE_MINIMUM (1)
0x29, 0x10, // USAGE_MAXIMUM (16)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x19, 0x01, // USAGE_MINIMUM (1)
0x29, 0x10, // USAGE_MAXIMUM (16)
0xB1, 0x02, // Feature(Data, Variable, Absolute)
0xc0 // END_COLLECTION
}; /* CustomHID_ReportDescriptor */
从中可以看出,构建的通道中,Input报告、Output报告和Feature报告均为16字节长。
3.2 支持ReadFile()和WriteFile()方式的代码
在源文件usb_prop.c中,修改端点的通信能力:
SetEPTxCount(ENDP1, 0x40); //robin: 修改为64字节大小
SetEPRxCount(ENDP1, 0x40); //robin: 修改为64字节大小
修改通信函数,在源文件usb_endp.c中进行修改,代码如下:
uint8_t Receive_Buffer[0xff];
void EP1_OUT_Callback(void)
{
// BitAction Led_State;
uint32_t DataLength = 0;
/* Read received data (2 bytes) */
DataLength=USB_SIL_Read(EP1_OUT, Receive_Buffer); //读取端点得到的数据
SetEPRxStatus(ENDP1, EP_RX_VALID);
if (Receive_Buffer[0] == 0xA0)//将第二个字节改为1返回,表示是采用端点发送的方式
{
Receive_Buffer[1]=0x1;
}
USB_SIL_Write(EP1_IN,Receive_Buffer,DataLength);
SetEPTxStatus(ENDP1,EP_TX_VALID);
}
其他代码不用动,并将usb_prop.c下的 void CustomHID_Status_In(void)函数中的内容全部注释掉。
至此,就完成了这种通信方式的编写。
3.3 支持Input报告和Output报告的方式、以及Feature报告的代码
为了支持这两种通信方式,需要实现Set_Report和Get_Report类命令。这两个类命令,有三种报告使用,包括Input报告、Output报告和Feature报告。其中,Input报告和Output报告作为输入输出通信,对应上层的函数为HidD_GetInputReport()和HidD_SetOutputReport();Feature报告对应上层的函数为HidD_GetFeature()和HidD_SetFeature()。
这三种报告都是使用Set_Report 和Get_Report命令来传输数据的,只是通过类命令中的wValue来区分报告类型:1为Input报告、2为Output报告、3为Feature报告。
需要注意的是,USB通信中,都是站在主机的角度来看通信过程的,所有命令也都是由主机发起的。Set_Report对设备而言,是接收数据;Get_Report对设备而言,是发送数据。
修改过程如下:
3.3.1 准备通信用的标志和缓冲区
uint8_t Report_Buf[16]; //Robin: 报告长度为16,见报告描述符
uint8_t Report_InOut_Flag=0; //Robin: Input报告和Output报告标志
uint8_t Report_Feature_Flag=0;//Robin: Feature报告标志
3.3.2 修改RESULT CustomHID_Data_Setup(uint8_t RequestNo)
......
/*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) )
{
switch( RequestNo )
{
case GET_PROTOCOL:
CopyRoutine = CustomHID_GetProtocolValue;
break;
case SET_REPORT:
CopyRoutine = CustomHID_SetReport_Feature;
Request = SET_REPORT;
break;
//robin add for get_report
case GET_REPORT:
if((Report_InOut_Flag==0)&&(Report_Feature_Flag==0))
return USB_NOT_READY; //Robin: Inform the host that the data is not ready
CopyRoutine = CustomHID_GetReport_Feature;
Request = GET_REPORT;
break;
default:
break;
}
}
if (CopyRoutine == NULL)
{
return USB_UNSUPPORT;
}
pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
(*CopyRoutine)(0);
return USB_SUCCESS;
}
也即增加了Get_Report和Set_Report的处理函数。
3.3.3 修改Get_Report和Set_Report的处理函数
/*******************************************************************************
* Function Name : CustomHID_SetReport_Feature
* Description : Set Feature request handling
* Input : Length.
* Output : None.
* Return : Buffer
*******************************************************************************/
uint8_t *CustomHID_SetReport_Feature(uint16_t Length)
{
if(pInformation->USBwValues.bw.bb1 == OUT_REPORT)
Report_InOut_Flag=1;
else if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
Report_Feature_Flag=1;
if (Length == 0)
{
pInformation->Ctrl_Info.Usb_wLength = 16;//2; //robin
return NULL;
}
else
{
// return Report_Buf;
return &Report_Buf[pInformation->Ctrl_Info.Usb_wOffset];
}
}
/*************************************************************************
* Function Name : CustomHID_GetReport_Feature
* Description : Set Feature request handling
* Input : Length.
* Output : None.
* Return : Buffer
*************************************************************************/
uint8_t *CustomHID_GetReport_Feature(uint16_t Length)
{
if(pInformation->USBwValues.bw.bb1 == IN_REPORT)
Report_InOut_Flag=0;
else if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
Report_Feature_Flag=0;
if (Length == 0) //此处报告需要发送的长度
{
pInformation->Ctrl_Info.Usb_wLength = 16;//2; //robin
return NULL;
// return (uint8_t *)16;
}
else //此处返回需要处理的数据
{
if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
{
if(Report_Buf[0] == 0xA0)
Report_Buf[1]=0x3;
}
else
{
if(Report_Buf[0] == 0xA0)
Report_Buf[1]=0x2;
}
return Report_Buf;
}
}
从中很容易看出,针对不同的报告,函数进行了不同的处理。为了方便上位机测试,简单地将收到的数据,进行了局部修改,然后返回。
将固件代码编译,下载到YIE002开发板中,使用上位机工具进行测试,如图4所示。
图4 使用UsbHID工具测试HID设备
根据HID设备的固件代码,不同的通信方式,在第一个字节为0xA0的时候,返回的第二个字节分别为0x1、0x2和0x3。从图3中可以看出,是按照代码设定的逻辑在正常工作的。
至此,我们完成了USB HID设备的编写。下一篇开始,将在UEFI环境下对HID设备进行访问。
Gitee地址:https://gitee.com/luobing4365/uefi-exolorer
项目代码位于:/84 YIE2HID下
48 total views, 2 views today
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK