找了网上大量的资料,最后发现这个东西人家还出售源码。又不是什么算法级的东西,实在理解不了。
至于为什么要用HID,不用官方的DFU,因为驱动呀,DFU识别USB的时候还是要装驱动,客户你永远理解不了他的水平,所以研发需要cover住所有case.
我是在STM32F4的平台上做的,Cubemax配置工程。以前反感这个UI配置,现在这个东西BUG少了,以前定义一个局部的结构体变量都不初始化。下面是配置图,注意RCC配置USB的时钟。
CubeMax配置完生成代码打开,ST已经给你弄好代码框架,你只需要修改设备描述符,传输的字节,轮询的时间,中断回调函数接收。
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
/* USER CODE BEGIN 0 */
0x05, 0x8c, /* USAGE_PAGE (ST Page) */
0x09, 0x01, /* USAGE (Demo Kit) */
0xa1, 0x01, /* COLLECTION (Application) */
// The Input report
0x09,0x03, // USAGE ID - Vendor defined
0x15,0x00, // LOGICAL_MINIMUM (0)
0x26,0x00, 0xFF, // LOGICAL_MAXIMUM (255)
0x75,0x08, // REPORT_SIZE (8bit)
0x95,0x40, // REPORT_COUNT (64Byte)
0x81,0x02, // INPUT (Data,Var,Abs)
// The Output report
0x09,0x04, // USAGE ID - Vendor defined
0x15,0x00, // LOGICAL_MINIMUM (0)
0x26,0x00,0xFF, // LOGICAL_MAXIMUM (255)
0x75,0x08, // REPORT_SIZE (8bit)
0x95,0x40, // REPORT_COUNT (64Byte)
0x91,0x02, // OUTPUT (Data,Var,Abs)
/* USER CODE END 0 */
0xC0 /* END_COLLECTION */
};
相关的宏修改 #define USBD_CUSTOM_HID_REPORT_DESC_SIZE 33U//2
#define CUSTOM_HID_EPIN_ADDR 0x81U
#define CUSTOM_HID_EPIN_SIZE 0x40U //64字节
#define CUSTOM_HID_EPOUT_ADDR 0x01U
#define CUSTOM_HID_EPOUT_SIZE 0x40U
#define CUSTOM_HID_FS_BINTERVAL 0x01U//1ms轮询
这个是USB的中断接收函数,根据USB设备的ID来接收字节,库生成的时候只能接收2个字节的,
当我们改成0x40,就能接收64个字节,USB HID一包只能64个字节
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
/* USER CODE BEGIN 6 */
return (USBD_OK);
/* USER CODE END 6 */
}
梳理USB HID库里面的接收思路,我们可以单独定义一个接收64字节的函数 CUSTOM_HID_OutDulBuf_FS
USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =
{
CUSTOM_HID_ReportDesc_FS,
CUSTOM_HID_Init_FS,
CUSTOM_HID_DeInit_FS,
CUSTOM_HID_OutEvent_FS,
CUSTOM_HID_OutDulBuf_FS
};
static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
return (0);
}
重点关注这个函数,这个函数是完成中断接收缓存的,Report_buf[],我们插入
((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutDulBuf(hhid->Report_buf);完成64字节包的接收
static uint8_t USBD_CUSTOM_HID_DataOut (USBD_HandleTypeDef *pdev,
uint8_t epnum)
{
USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;
((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0],
hhid->Report_buf[1]);
((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutDulBuf(hhid->Report_buf);
USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , hhid->Report_buf,
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
return USBD_OK;
}
自此,USB HID64字节包的接收我们就完成,关于HID的发送,官方声明了一个static函数,注释掉了,调用发送就好了
有些宏定义的查找,不需要去翻文件了,编译然后goto definition
/*
static int8_t USBD_CUSTOM_HID_SendReport_FS(uint8_t *report, uint16_t len)
{
return USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len);
}
*/
上面的工作已经完成了USB HID的双向通信。重点来,IAP升级,我之前用官方的串口IAP升级做过,用的Ymodem协议传输,波特率921600,Ymodem我们可以使用secureCRT去进行升级传输,官方库的下位机Ymodem已经做的很好了,也有防错机制。现在我们用USB传输,64字节一包,尴尬的是没有上位机去升级,网上或许有下载,但是人家有握手协议,以及包协议,所以基本不能用。由于没有上位机开发经验,甚至一度下载了QT Crator准备开发,后面思考了下还是偷懒了,我用BUS Hound去抓包人家上位机传输的USB格式,关键是这个USB上位机只负责发,没有握手协议。还是被我下载到了。
当你的板子下载程序USB HID识别成功之后,输入VID 0483 PID 5750 连接打开设备。
下面是stm32 APP端的bin文件输出了,在app里面重新映射中断向量表,以及代码flash段,我的是Loader 0x4000 16K
所以APP的开始地址是0x08004000 然后在工程选项下的C++栏 生成bin文件选项 记得勾选After build。
生成app端的bin文件之后,需要想的是,这个USB上位机虽然不握手,只负责发送,但是人家到底有没有发送格式呢,这就需要我们对比它发送的文件,以及我们bin文件的内容。所以用到了visual studio2015 和 Bus hound两个工具,嵌入式工程师必备吧,工具的使用是人类的进化的标志。
咳咳,暴露了我双显示器的配置。 对比发现这个USB上位机就是发送的bin文件,发送完成最后发了一个空包,全是0,这就好办了,空包作为传输结束标志。
下面的函数就是缓冲接收64字节包的代码,这只是demo,在中断回调里面缓冲,并且写了flash,道理上是不合理的哈。
但是loder又不需要多任务处理,干就完事了。调试的时候思路卡了一会在flash烧写的地方,一个是需要先擦除app位置的flash,另一个flash的API需要32位对齐,所以需要64/4,其它就没啥了
__IO uint32_t flashdestination=APPLICATION_ADDRESS;
__IO uint32_t transfer_error=0;
static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
unsigned char j=0;
//memcpy(IAP_buff,DulBuf,64);//缓存64字节数据
for(unsigned char i=0;i<64;i++)
{
IAP_buff[i]=DulBuf[i];
if(IAP_buff[i]==0x00)
j++;
}
if(j==64)
USB_Received_Flag=1;
if(j<64&&FLASH_If_Write(flashdestination, (uint32_t*)IAP_buff, (uint32_t)64/4)== FLASHIF_OK)
{
flashdestination+=(uint32_t)64;
}
else
transfer_error++;
return (0);
}
再上传一个loader跳转到APP,APP跳转到Loader的代码
loader跳转到APP __set_FAULTMASK(1);//关闭所用中断 至于考虑APP是否使用了RTOS,加上__set_CONTROL(0); 吧
void LoderToApp(void)
{
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)//栈顶地址检查
{
HAL_RCC_DeInit(); //时钟失能
HAL_DeInit(); //外设失能
__set_FAULTMASK(1);//关闭所用中断
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);//跳转到APP地址
JumpToApplication = (pFunction) JumpAddress;
__set_CONTROL(0); //RTOS指针使能
SCB->VTOR = FLASH_BASE |0x4000;//中断向量表重映射
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);//MSP指针跳转
JumpToApplication();
}
}
void AppToLoder(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);
__set_FAULTMASK(1);
HAL_NVIC_SystemReset();
}
APP跳转到Loader就简单点吧HAL_NVIC_SystemReset();干脆高效
致敬开源
--骚年追梦
上位机:https://download.csdn.net/download/u014803614/11603107
下位机:https://download.csdn.net/download/u014803614/11603069