本文介绍如何实现 STM32 USB_HID 的双向通信以及适配跳兔科技的 HID_Firmware_Upgrade 的固件下载协议
HID_Firmware_Upgrade 项目地址 https://gitee.com/mzy2364/HID_Firmware_Upgrade
跳兔科技官方网站 www.whtiaotu.com
野牛开发板购买地址 www.whtiaotu.com/mall_taobao.html
本教程使用 STM32F429,利用 STM32CubeMX 和 HAL 库,程序和教程兼容性会很强
这里根据目标板使用的是 USB_FS 或者 USB_HS 来确定,野牛开发板使用的是USB_FS ,已经适配的 Boot 中,野火的 STM32-V1 使用的是 HS
USB 默认的中断优先级是 0,太高了,我们将其配置为 15
USB 配置的其他地方我们暂时保持默认,在代码中进行修改,这里的描述符长度在后面解释,数据长度设置最大值 64,与描述符也保持一致
设置以下设备名称和厂商
STM32F429 最高频率 180MHz ,但是此频率下无法获得 48MHz 的 USB 时钟,所以将时钟配置到 168MHz,配置到 192 进行小小的超频也是可以的
生成的工程修改后如果再次用 CubeMX 生成代码的话需要防止 CubeMX 覆盖我们修改过的代码,如果经用户修改的工程需要再次用 CubeMX 更新某些配置,请尽量在 CubeMX 配置或者在合适的位置添加用户代码。
在 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 */
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1) 表示一个报文标签之类的用途类页
0x09, 0x01, // USAGE (Vendor Usage 1) 表示一个报告ID标志
0xa1, 0x01, // COLLECTION (Application) 表示应用集合,要以下面最后的0xc0结束它
0x09, 0x01, // USAGE (Vendor Usage 1)同下同名解析
0x15, 0x00, // LOGICAL_MINIMUM (0) 同下同名解析
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 同下同名解析
0x95, 0x40, // REPORT_COUNT (64) 同下REPORT_COUNT
0x75, 0x08, // REPORT_SIZE (8) 同下REPORT_SIZE
0x81, 0x02, // INPUT (Data,Var,Abs) 表示USB要输入数据到PC的功能
0x09, 0x01, // USAGE (Vendor Usage 1) 每个功能的一个卷标志
0x15, 0x00, // LOGICAL_MINIMUM (0) 表示每个传输数据限定为0
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 表示每个传输数据的最大值限定为255
0x95, 0x40, // REPORT_COUNT (64) 每次接收的数据长度,这里是64位
0x75, 0x08, // REPORT_SIZE (8) 传输字段的宽度为8bit,表示每个传输的数据范围为0~ffff ffff
0x91, 0x02, // OUTPUT (Data,Var,Abs) 表示USB设备要接收PC的数据的功能
//0xc0 // END_COLLECTION 结束标志
/* USER CODE END 1 */
0xC0 /* END_COLLECTION */
};
关于以上描述符的含义,可以参考博客 https://www.cnblogs.com/xingboy/p/9913963.html
到这里我们已经可以将程序下载到设备中,用 USB 线连接设备和电脑,在电脑上打开 HID_Firmware_Upgrade 可以看到设备已经连接
此时证明 USB 连接已经正常了,但是还没有任何通信逻辑
由于 STM32 的 USB 库复杂多变版本不一,同一个芯片同一个 CubeMX 同一个 HAL 库不同时间生成的工程,USB 核心库代码竟然不一样,这里为了方便,我们仍然使用以前生成的,只需要在 gitee 上下载已经适配的目标板的 USB 核心库 Middlewares 文件夹替换下当前工程的同名文件夹。
HID 的数据传输是轮询的方式实现的,USB 库默认是 20ms ,显然这个时间用来进行固件的传输是不可取的,我们对其进行修改,在 usbd_customhid.c,我们将其修改为 1ms
/* USB CUSTOM_HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
...
0x01, /*bInterval: Polling Interval (1 ms)*/
/* 34 */
...
0x01, /* bInterval: Polling Interval (1 ms) */
/* 41 */
} ;
利用 uint8_t USBD_CUSTOM_HID_SendReport(*pdev, *report,len)
函数进行数据的发送,直接在 main.c 里面添加发送部分,由于我们的包长定义为了64,所以每次收发都是64字节,不满64字节的请填充,也可以利用64来判断是否接收完成数据
/* USER CODE BEGIN PFP */
extern uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report,uint16_t len);
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE END PFP */
...
int main(void)
{
...
/* USER CODE BEGIN 2 */
uint8_t send_buffer[64] = {0};
uint8_t i = 0;
for(i=0;i<64;i++)
{
send_buffer[i] = i;
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,send_buffer,64);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
下载程序连接 USB 到电脑,打开 HID_Firmware_Upgrade 软件,勾选接在接收数据到接收区,可以看到数据的上传
USB 的数据在中断中进行接收,我们需要定义一个数据接收缓冲区,在中断中进行数据的接收。
在 usbd_custom_hid_if.c 里面,当有数据传来的时候,USB 库会调用 static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
函数,我们在这个函数里面进行数据的接收
先在 main.c 定义一个接收缓冲区
/* USER CODE BEGIN PV */
uint8_t USB_Recive_Buffer[64]; //USB接收缓存
uint8_t USB_Received_Count = 0; //USB接收数据计数
/* USER CODE END PV */
然后在 usbd_custom_hid_if.c 处理接收
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
extern uint8_t USB_Recive_Buffer[64];
extern uint8_t USB_Received_Count;
/* USER CODE END PV */
...
/**
* @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 */
uint8_t i;
USBD_CUSTOM_HID_HandleTypeDef *hhid; //定义一个指向USBD_CUSTOM_HID_HandleTypeDef结构体的指针
hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;//得到USB接收数据的储存地址
for(i=0;i<64;i++)
{
USB_Recive_Buffer[i]=hhid->Report_buf[i]; //把接收到的数据送到自定义的缓存区保存(Report_buf[i]为USB的接收缓存区)
USB_Received_Count ++ ;
}
return (USBD_OK);
/* USER CODE END 6 */
}
然后把 main.c 里面的收发逻辑修改以下,这里将收到的数据原封不动地发送出去
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* 如果收到数据 */
if(USB_Received_Count >= 64)
{
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,USB_Recive_Buffer,64);
USB_Received_Count = 0;
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
下载程序连接 USB 到电脑,打开 HID_Firmware_Upgrade 软件,勾选接在接收数据到接收区,待设备连接后,打开一个 bin 文件点击开始下载,就可以看到下位机将收到的数据原封不动地发送到电脑
这里仅展示了数据的双向传输,而上位机是根据协议传输文件的,下位机直接返回 USB 数据。所以上位机接收到的就是协议的第一包数据,然后会提示下载失败,因为下位机还没有适配协议。
跳兔科技为 HID_Firmware_Upgrade 适配了一套用于 USB 每包64字节固件传输的通信协议,可以在 gitee 下载,关于协议的详解,请查看具体文件。
只要适配了通信协议,就能实现从上位机接收完整的固件,关于固件的存储,有必要定义一个缓冲区,可以像野牛开发板一样,定义一个比最大固件还大的缓冲区到外部 SDRAM ,待整个固件接收完成以后再进行 Flash 的擦除和烧写,也可以类似野火 F429-V1 和 NUCLEO 144 -F7 一样开一个小的缓冲区,边接收边烧写。
跳兔科技提供了一个 iap.c ,用户使用的时候可以只关心 USB 数据的接收和接收完毕判断、Flash 的擦除和烧写的函数接口,然后就可以运作。
有几个注意事项
具体的通信请查看 iap.c 源码
在 Bootloader 已经实现了跳转功能,APP 程序需要设备程序地址和中断向量偏移,根据具体芯片和目标板进行设置
SCB->VTOR = FLASH_BASE | 0x10000;//设置偏移量
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。