13

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

 4 years ago
source link: http://yiiyee.cn/blog/2021/08/15/yie002%e5%bc%80%e5%8f%91%e6%8e%a2%e7%b4%a209-usbhid%e5%8f%8c%e5%90%91%e9%80%9a%e4%bf%a1/
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.
neoserver,ios ssh client

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

编写《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双向通信设备

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

23 total views, 1 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK