一、STM32F070 USB基础知识了解:
STM32F070 USB采用USB 2.0规范,时钟频率为48MHZ,有一个专用的USB数据存储区,大小为1024字节;支持16个单向传输端点、8个双向传输端点
USB外设模块构成图:
USB物理接口模块:
USB_DM、USB_DP —— 数据正、负信号
Analog Transceiver —— 模拟收发器
Embedded pull-up resister —— 可控嵌入负载电阻
Battery Charging Detection(BCD) —— 电池充电检测
USB_NOE —— 允许输出信号,可用于驱动LED或提供通信信息
Serial Interface Engine(SIE)串行接口引擎:
这个模块主要用于同步模式识别、CRC/PID的生成和检验、SOF/reset信号的生成、产生中断信号等
Timer定时器模块:
生成起始帧的锁定时钟脉冲以及检测阻塞
USB连接到到APB总线的APB接口模块构成有:Packet Memory、Arbiter(仲裁器,解决冲突访问APB的问题)、Register Mapper(寄存器映射器)、APB封装器(将USB外设映射到APB的地址空间)、中断映射
二、STM32Cube USB中间件文件了解:
在STM32CubeMX中选择USB中间件,点击生成代码就会自动加载USB的库文件,查看STM32_USB_Device_Library文件夹可见,USB库文件分为两类:一类是Class即设备类文件;一类是Core即内核文件。内核文件和设备类文件分别如下两图所示:
usbd_core.c /h —— 处理USB通信和状态机的函数
usbd_ctlreq.c/h —— 处理USB事务结果
usbd_conf_template.c/h —— 底层接口文件的模板文件,用户对其进行修改包含在应用文件中
usbd_ioreq.c/h —— 包含了USB规范列出的请求实现
三、使用STM32Cube MX配置USB
配置RCC时钟,激活HSE时钟源,作为USB时钟的输入,选择USB类为HID设备类
Configuration页面进行USB的相关设置、描述符设置,这里配置USB为用户自定义的HID设备,生成的MDK代码文件如下所示,建议先用工具生成报告描述符再根据它的长度进行配置
分析描述符
首先是USB设备标准描述符,Cube MX 已经根据配置自动生成了
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
0x12, /*bLength */
USB_DESC_TYPE_DEVICE, /*bDescriptorType*/
0x00, /*bcdUSB */
0x02,
0x00, /*bDeviceClass*/
0x00, /*bDeviceSubClass*/
0x00, /*bDeviceProtocol*/
USB_MAX_EP0_SIZE, /*bMaxPacketSize*/
LOBYTE(USBD_VID), /*idVendor*/
HIBYTE(USBD_VID), /*idVendor*/
LOBYTE(USBD_PID_FS), /*idProduct*/
HIBYTE(USBD_PID_FS), /*idProduct*/
0x00, /*bcdDevice rel. 2.00*/
0x02,
USBD_IDX_MFC_STR, /*Index of manufacturer string*/
USBD_IDX_PRODUCT_STR, /*Index of product string*/
USBD_IDX_SERIAL_STR, /*Index of serial number string*/
USBD_MAX_NUM_CONFIGURATION /*bNumConfigurations*/
};
接下来是修改HID报告描述符,STM32HAL为用户提供了一个USB设备HID接口类的文件,即usbd_custom_hid_if.c/h,以便用户自行配置HID报告描述符,要修改的数组是CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE],建议使用 HID descriptor tool工具生成报告符。这里要注意的是报告描述符大小USBD_CUSTOM_HID_REPORT_DESC_SIZE,一定要与实际生成的报告描述符大小(使用HID descriptor tool可查看)相对应,否则会导致HID设备配置失败。
HID_Usage()等数值我在usbd_custom_hid_if.h中有定义
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
/* USER CODE BEGIN 0 */
HID_UsagePageVendor(0xa0),
HID_Usage(0xa5),
HID_Collection(0x01),
HID_Usage(0xa6),
/* input */
HID_Usage(0xa7),
HID_LogicalMin(0x00),
HID_LogicalMax(0xFF),
HID_ReportSize(8),
HID_ReportCount(64),
HID_Input(HID_Data | HID_Variable | HID_Absolute),
/* output */
HID_Usage(0xa9),
HID_LogicalMin(0x00),
HID_LogicalMax(0xff),
HID_ReportSize(8),
HID_ReportCount(64),
HID_Output(HID_Data | HID_Variable | HID_Absolute),
/* USER CODE END 0 */
0xC0 /* END_COLLECTION */
};
到这里为止,下载程序后PC机就可以识别出HID设备了
发送数据:
使用USBD_CUSTOM_HID_SendReport,这个函数在usbd_customhid.c中定义,在while循环中调用这个函数
uint8_t USBD_CUSTOM_HID_SendReport (USBD_HandleTypeDef *pdev,
uint8_t *report,
uint16_t len)
{
USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;
if (pdev->dev_state == USBD_STATE_CONFIGURED )
{
if(hhid->state == CUSTOM_HID_IDLE)
{
hhid->state = CUSTOM_HID_BUSY;
USBD_LL_Transmit (pdev,
CUSTOM_HID_EPIN_ADDR,
report,
len);
}
}
return USBD_OK;
}
/* USER CODE BEGIN WHILE */
while (1)
{
USB_Status = USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,InReport,64);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER C
关于端点最大传输包大小和HID报告描述符中的ReportCount,在usbd_customhid.c中有如下定义
#define CUSTOM_HID_EPIN_ADDR 0x81
#define CUSTOM_HID_EPIN_SIZE 0x02
#define CUSTOM_HID_EPOUT_ADDR 0x01
#define CUSTOM_HID_EPOUT_SIZE 0x02
这里定义中断传输端点的最大传输包大小为2,但其实实际传输时仍然可以传输64个数据,原因是在HID报告描述符中有对Input和Output的传输数据包大小做定义,实际传输大小应该以这个为标准,即每次传输最大数据量为64个数据,每个数据8位。不过建议将CUSTOM_HID_EPIN_SIZE/CUSTOM_HID_EPOUT_SIZE修改成和报告符中定义的大小一致。
HID_ReportSize(8),
HID_ReportCount(64),
接收数据:
USB中断传输方式中,每次PC机发送数据后USB设备都会产生中断,设备每完成一次从PC机的Out data的接收都会响应一次OutEvent,因此可以通过修改usbd_custom_hid_if.c中的static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)来实现对接收到数据做处理。
我这里在OutEvent中置位一次接收完成的标志OutComplete,然后在main函数中处理数据
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
/* USER CODE BEGIN 6 */
OutComplete = 1;
return (USBD_OK);
/* USER CODE END 6 */
}
while (1)
{
USB_Status = USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,InReport,256);
if(OutComplete)
{
hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData; //µÃµ½½ÓÊյĵØÖ·
for(uint16_t i=0;i<64;i++)
OutReport[i] = hhid->Report_buf[i];
HAL_UART_Transmit(&huart2, OutReport, 64, HAL_MAX_DELAY);
OutComplete = 0;
}
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
这里有一个小插曲,我调用HAL_UART_Transmit函数时习惯给延时参数直接赋值10,然后发现串口助手每次只接收到OutReport的前12个数据,但我明明指定了传输长度为64,后来才知道是延时参数设置不当,串口还没发送完数据就被迫停止传输了,后来改成HAL_MAX_Delay后数据就正常了。