YIE002开发探索09-USB(HID双向通信)

请保留-> 【原文:  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设备框图。

图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所示。

图2 设置USB HID

选择USB设备为“Custom Human Interface Device Class(HID)”,再对参数设置和设备描述符相应的项进行修改。我所设置的报表描述符为33字节,PC主机和HID设备传输数据包设置为16字节,可以根据自己的需求修改这些值。

最后对时钟树进行调整,USB的时钟频率为48MHz,如图3所示。

图3 设置USB时钟频率

完成上述工作后,点击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所示。

图4 测试HID双向通信设备

完成了上述实验后,下一篇将使用现在的代码,构建一个随机数生成设备。

145 total views, 2 views today

发表评论

电子邮件地址不会被公开。