请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
(代码仓库地址:https://gitee.com/luobing4365/yie002-explorer
具体参考博客:YIE002开发探索-Gitee代码仓库说明)
编写《UEFI编程实践》的时候,为了演示如何在UEFI下访问USB HID设备,我使用了正点原子的探索者F4开发板,做了一个双向通信的HID设备。随后考虑到版权问题,觉得还是不要放到书中较好。
毕竟书是需要出版的,而探索者F4的HID代码是在正点原子的USB代码上修改的。因此自己弄了个YIE002开发板,重新做了个HID设备。
以上的过程,在UEFI开发探索的系列博客中都写过,相关的代码也贴出过。
本篇所做的工作,是使用STM32 Cube MX重写USB代码,实现双向通信的HID设备。
1 YIE002上的USB
YIE002上使用的主芯片为STM32F103C8T6,它自带了USB,不过只能用作设备,不能作为主机使用。
USB 发展到现在已经有 USB1.0/1.1/2.0/3.0/4.0 等多个版本。目前用的最多的就是 USB1.1 (比如大部分的USB键鼠)和USB2.0、 USB3.0。实际上USB2.0也基本上接近淘汰,常见的USB设备基本都是USB3.0设备了。STM32F103 自带的 USB 符合 USB2.0 规范。
STM32F103的单片机自带USB从控制器,PC主机和单片机在传输时,是通过共享一个专用的数据缓冲区来完成的。缓冲区的大小由所用的端到数目和每个端到最大的数据分组决定,每个端点最大可使用512字节缓冲区,最多可用于16个单向或8个双向端点。
USB 模块同 PC 主机通信,根据 USB 规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括 CRC 的生成和校验。每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。
USB 的中断映射单元:将可能产生中断的 USB 事件映射到三个不同的 NVIC 请求线上:
1) USB 低优先级中断(通道 20):可由所有 USB 事件触发(正确传输, USB 复位等)。固件在处理中断前应当首先确定中断源;
2)USB 高优先级中断(通道 19):仅能由同步和双缓冲批量传输的正确传输事件触发,目的是保证最大的传输速率;
3)USB 唤醒中断(通道 42):由 USB 挂起模式的唤醒事件触发。
如图1所示,给出了USB设备框图。
在之前UEFI开发探索的系列博客中,对USB相关的知识介绍很多了,具体可以参考UEFI开发探索的81至85篇。其中也描述了如何在STM32提供的官方示例上,使用Legacy Library编写HID设备的过程。
UEFI开发探索85中,谈到了ST官方提供的USB开发库。对于STM32F1系列的单片机,也提供了相应的USB的Cube Library。之前几篇开发的例程中,在查看STM32CubeF1库代码的时,也能看到对应的支持USB部分的代码。
下面开始进入实现USB HID双向通信设备的实践部分。
2 YIE002-STM32的USB编程(HID双向通信)
与UEFI开发探索85篇一样,我们准备实现对应三种通信方式的HID设备,也即对应上位机的下述三种通信方式:
1) 读文件和写文件的方式(Windows系统上是ReadFile()和WriteFile();Linux上为hid_read()和hid_write());
2) Input Report和Output Report的方式;
3) Feature Report的方式。
实际编程过程如下。
2.1 USB HID的Cube MX图像配置
首先在Pinout&Configuration栏的Connectivity下的USB项中,勾选“Device(FS)”选择框,打开对USB全速设备的支持。
然后在Middleware下的USB_DEVICE项中,修改对应的选项值。如图2所示。
选择USB设备为“Custom Human Interface Device Class(HID)”,再对参数设置和设备描述符相应的项进行修改。我所设置的报表描述符为33字节,PC主机和HID设备传输数据包设置为16字节,可以根据自己的需求修改这些值。
最后对时钟树进行调整,USB的时钟频率为48MHz,如图3所示。
完成上述工作后,点击GENERATE CODE按钮,生成代码。
2.2 添加应用代码
编写代码的步骤如下。
1)添加报表描述符,修改传输数据包大小
在源文件usbd_custom_hid_if.c中,修改报表描述符如下:
/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
// /* USER CODE BEGIN 0 */
// 0x00,
// /* USER CODE END 0 */
// 0xC0 /* END_COLLECTION */
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x00, // USAGE (0)
//这是一个主条目(bType为0)条目,开集合,后面跟的数据0x01表示
//该集合是一个应用集合。它的性质在前面由用途页和用途定义为
//用户自定义。
0xa1, 0x01, // COLLECTION (Application)
//这是一个全局条目,说明逻辑值最小值为0。
0x15, 0x00, // LOGICAL_MINIMUM (0)
//这是一个全局条目,说明逻辑值最大为255。
0x25, 0xff, // LOGICAL_MAXIMUM (255)
//这是一个局部条目,说明用途的最小值为1。
0x19, 0x01, // USAGE_MINIMUM (1)
//这是一个局部条目,说明用途的最大值64。
0x29, 0x10, // USAGE_MAXIMUM (16)
//这是一个全局条目,说明数据域的数量为16个。
0x95, 0x10, // REPORT_COUNT (16)
//这是一个全局条目,说明每个数据域的长度为8bit,即1字节。
0x75, 0x08, // REPORT_SIZE (8)
//这是一个主条目,说明有8个长度为8bit的数据域做为输入。
0x81, 0x02, // INPUT (Data,Var,Abs)
//这是一个局部条目,说明用途的最小值为1。
0x19, 0x01, // USAGE_MINIMUM (1)
//这是一个局部条目,说明用途的最大值64。
0x29, 0x10, // USAGE_MAXIMUM (16)
//这是一个主条目。定义输出数据(16字节,注意前面的全局条目)。
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x19, 0x01, // USAGE_MINIMUM (1)
0x29, 0x10, // USAGE_MAXIMUM (16)
0xB1, 0x02, // Feature(Data, Variable, Absolute)
//下面这个主条目用来关闭前面的集合。bSize为0,所以后面没数据。
0xc0 // END_COLLECTION
};
修改头文件usbd_customhid.h中对应传输数据包的大小:
#define CUSTOM_HID_EPIN_ADDR 0x81U
//#define CUSTOM_HID_EPIN_SIZE 0x02U
#define CUSTOM_HID_EPIN_SIZE 0x10U //robin 20210813 修改
#define CUSTOM_HID_EPOUT_ADDR 0x01U
//#define CUSTOM_HID_EPOUT_SIZE 0x02U
#define CUSTOM_HID_EPOUT_SIZE 0x10U //robin 20210813 修改
2)添加USB通信所需要的全局变量
在main.c中,添加对头文件usbd_customhid.h的包含,后续需要用到相关的函数。并在main.c的USER CODE BEGIN 0处,添加USB通信所需要的全局变量。
//USB通讯所用到的全局变量
uint8_t USBdataFlag=0;
uint8_t USBRxBuff[USBD_CUSTOMHID_OUTREPORT_BUF_SIZE];
uint8_t Report_InOut_Flag=0; //Robin: Input报告和Output报告标志
uint8_t Report_Feature_Flag=0;//Robin: Feature报告标志
extern USBD_HandleTypeDef hUsbDeviceFS;
3)读文件和写文件的方式
修改源文件usbd_custom_hid_if.c中的函数CUSTOM_HID_OutEvent_FS(),内容如下:
extern uint8_t USBdataFlag;
extern uint8_t USBRxBuff[16];
/**
* @brief Manage the CUSTOM HID class events
* @param event_idx: Event index
* @param state: Event state
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
/* USER CODE BEGIN 6 */
//robin 20210814
uint16_t i;
USBD_CUSTOM_HID_HandleTypeDef *hhid;
hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;
USBdataFlag=1;
for(i=0;i<16;i++)
USBRxBuff[i] = hhid->Report_buf[i];
return (USBD_OK);
/* USER CODE END 6 */
}
以上是对上位机发送数据的接收,下面实现设备对主机发送数据的功能。在主程序main()中添加如下代码:
if(USBdataFlag == 1)
{
USBdataFlag=0;
USBRxBuff[0]=0x11;
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,USBRxBuff,USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
}
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE是在图形界面中设定的传输数据包大小,也即16,它定义在usbd_conf.h中。
这就实现了对应上位机读文件和写文件的方式。
4)Input Report&Output Report的方式,以及Feature Report方式
Cube Library中没有提供相应的接口,这部分的代码实现,必须去修改原始的类命令处理。在usbd_customhid.c中,修改函数USBD_CUSTOM_HID_Setup(),内容如下:
extern uint8_t USBdataFlag;
extern uint8_t USBRxBuff[16];
extern uint8_t Report_InOut_Flag; //Robin: Input报告和Output报告标志
extern uint8_t Report_Feature_Flag;//Robin: Feature报告标志
static uint8_t USBD_CUSTOM_HID_Setup(USBD_HandleTypeDef *pdev,
USBD_SetupReqTypedef *req)
{
//……前略
case CUSTOM_HID_REQ_SET_REPORT:
//robin add for in/out/feature report
//类命令的wValue高字节,1-Input Report; 2-Output Report; 3-Feature Report
if(((req->wValue)&0xff00) == 0x0200)
Report_InOut_Flag=1;
else if(((req->wValue)&0xff00) == 0x0300)
Report_Feature_Flag=1;
hhid->IsReportAvailable = 1U;
USBD_CtlPrepareRx(pdev, hhid->Report_buf, req->wLength);
break;
case CUSTOM_HID_REQ_GET_REPORT: //robin 20210815
if(((req->wValue)&0xff00) == 0x0100)
{
Report_InOut_Flag=0;
for(uint8_t i=0;i<USBD_CUSTOMHID_OUTREPORT_BUF_SIZE;i++)
USBRxBuff[i] = hhid->Report_buf[i];
USBRxBuff[0] = 0x22;
}
else if(((req->wValue)&0xff00) == 0x0300)
{
Report_Feature_Flag=0;
for(uint8_t i=0;i<USBD_CUSTOMHID_OUTREPORT_BUF_SIZE;i++)
USBRxBuff[i] = hhid->Report_buf[i];
USBRxBuff[0] = 0x33;
}
USBD_CtlSendData (pdev, (uint8_t *)&USBRxBuff, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE); // to pc
break;
//……后略
}
也即对类命令SET_REPORT和GET_REPORT进行处理,并对发送过来的类命令进行了相应的分析,以对应不同的上位机访问方式。
具体细节就不一一分析了,可对照UEFI开发探索84篇中的类命令,对代码进行阅读。
至此,就完成了所有应用代码的编写了。
2.3 测试
将代码编译后,下载到YIE002开发板中。可使用之前我开发的HID通信的测试工具UsbHID进行测试(UEFI开发探索74篇附带的测试工具)。如图4所示。
完成了上述实验后,下一篇将使用现在的代码,构建一个随机数生成设备。
2,045 total views, 5 views today