*STM32F103ZET6 - USB_HID -2 (IAP升级)
前言:
记录下碰到的坑!!
USBD_Stop(&hUsbDeviceFS);// 重点: 此处需关闭USB功能, 需要时再打开.
示例详解: (参考 : https://blog.csdn.net/u014803614/article/details/100033217)
基于硬件平台: STM32F103ZET6正点原子的精英板, 使用stm32cubemx 工具自动产生的配置工程,使用KEIL5编译代码。
STM32CubeMX生成代码过程如下:
1>. 打开SWD - debug调试模式, STM32CubeMX生成的代码默认是没打开debug调试模式的
2>. 外部时钟配置, HSE选择为外部晶振
3>. CLOCK Configuration 配置如下: 选择HSE,PLL倍频为9, USB分频为1.5=48M 4>. GPIO口配置, 配置LED0,LED1和KEY0脚位
5>. USB功能选中
6>. USB_DEVICE --> 选择HID功能 . 参数设置保持默认不变.
7>. 中断配置保持默认.
8>. 生成代码配置
点击生成代码,然后烧录默认代码,连上电脑可在电脑的设备管理器中可以看到人全学输入设备中可以看到USB输入设备,但是有个叹号:
二、接下来我们将要在生成的HID框架上修改代码, 主要有修改设备描述符,传输的字节,轮询的时间,中断回调函数接收( usbd_custom_hid_if.c,usbd_conf.h,usbd_customhid.h)
/** @defgroup USBD_CUSTOM_HID_Private_Variables USBD_CUSTOM_HID_Private_Variables
* @brief Private variables.
* @{
*/
/** 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,
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,CUSTOM_HID_EPIN_SIZE, // 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,CUSTOM_HID_EPOUT_SIZE, // REPORT_COUNT (64Byte)
0x91,0x02, // OUTPUT (Data,Var,Abs)
/* USER CODE END 0 */
0xC0 /* END_COLLECTION */
};
/** @defgroup USBD_CONF_Exported_Defines USBD_CONF_Exported_Defines
* @brief Defines for configuration of the Usb device.
* @{
*/
/*---------- -----------*/
#define USBD_MAX_NUM_INTERFACES 1
/*---------- -----------*/
#define USBD_MAX_NUM_CONFIGURATION 1
/*---------- -----------*/
#define USBD_MAX_STR_DESC_SIZ 512
/*---------- -----------*/
#define USBD_DEBUG_LEVEL 0
/*---------- -----------*/
#define USBD_SELF_POWERED 1
/*---------- -----------*/
#define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE 64//2 /* 64字节 */
/*---------- -----------*/
#define USBD_CUSTOM_HID_REPORT_DESC_SIZE 33U//2 /* 设备描述符大小 */
/*---------- -----------*/
#define CUSTOM_HID_FS_BINTERVAL 0x5
/** @defgroup USBD_CUSTOM_HID_Exported_Defines
* @{
*/
#define CUSTOM_HID_EPIN_ADDR 0x81U
#define CUSTOM_HID_EPIN_SIZE 0x40U//0x02U /* 64字节 */
/*这个是USB的中断接收函数,根据USB设备的ID来接收字节,库生成的时候只能接收2个字节的,
当我们改成0x40,就能接收64个字节,USB HID一包只能64个字节*/
#define CUSTOM_HID_EPOUT_ADDR 0x01U
#define CUSTOM_HID_EPOUT_SIZE 0x40U//0x02U /* 64字节 */
#define USB_CUSTOM_HID_CONFIG_DESC_SIZ 41U
#define USB_CUSTOM_HID_DESC_SIZ 9U
#ifndef CUSTOM_HID_HS_BINTERVAL
#define CUSTOM_HID_HS_BINTERVAL 0x01U//0x05U //1ms轮询
#endif /* CUSTOM_HID_HS_BINTERVAL */
至些代码修改完成, 这时重新编译。 然后烧录代码后, 插上USB,电脑会正确识别为HID设备, 这时没有了感叹号。
三、添加USB中断接收内代码和写FLASH代码:
主要需要以下文件 usbd_custom_hid_if.c;
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usbd_custom_hid_if.h"
/* USER CODE BEGIN INCLUDE */
#include "flash.h"
/* USER CODE END INCLUDE */
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 /* add - 定义一个接收64字节的函数 */ //add
};
在上面usbd_customhid.h 中,我们将CUSTOM_HID_EPIN_SIZE改成0x40,就是从原接收2个字节改到了能接收64个字节,现在USB HID一包只能64个字节;
//USB的中断接收函数
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 /* add - 定义一个接收64字节的函数 */ //add
};
static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
return (0);
}
代码如下;
/** @defgroup USBD_CUSTOM_HID_Private_FunctionPrototypes USBD_CUSTOM_HID_Private_FunctionPrototypes
* @brief Private functions declaration.
* @{
*/
static int8_t CUSTOM_HID_Init_FS(void);
static int8_t CUSTOM_HID_DeInit_FS(void);
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state);/* 中断接收函数 */
static int8_t CUSTOM_HID_OutDulBuf_FS(uint8_t* DulBuf);//add /* add - 定义一个接收64字节的函数 */
/**
* @}
*/
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 /* add - 定义一个接收64字节的函数 */ //add
};
/** @defgroup USBD_CUSTOM_HID_Private_Functions USBD_CUSTOM_HID_Private_Functions
* @brief Private functions.
* @{
*/
/* Private functions ---------------------------------------------------------*/
__IO uint32_t flashdestination = DEVICE_INFO_ADDRESS;
__IO uint32_t transfer_error=0;
unsigned char IAP_buff[64]={0};
uint8_t USB_Received_Flag=0;
static int8_t CUSTOM_HID_OutDulBuf_FS(uint8_t* DulBuf)//add /* add - 定义一个接收64字节的函数 */
{
static uint8_t eraseAPPFlag = 0; //APP页擦除标志
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;
eraseAPPFlag = 0; //APP页擦除标志复位
flashdestination = DEVICE_INFO_ADDRESS;//地址复位
}
// if(j<64&&FLASH_If_Write(flashdestination, (uint32_t*)IAP_buff, (uint32_t)64/4)== FLASHIF_OK)
if(j<64)
{
if(eraseAPPFlag == 0)
{
eraseAPPFlag = 1; //APP页擦除标志
//擦除APP所有的空间
IAP_FlashErase( DEVICE_INFO_ADDRESS );
}
//IAP写FLASH, 不用擦除和读动作, 只需要顺序写
IAP_FlashWrite(flashdestination, IAP_buff, 64 );
// FlashWriteBuff_Word(flashdestination, IAP_buff, 64);
flashdestination+=(uint32_t)64;
}
else
transfer_error++;
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); //add
USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
return USBD_OK;
}
代码如下:
/**
* @brief USBD_CUSTOM_HID_DataOut
* handle data OUT Stage
* @param pdev: device instance
* @param epnum: endpoint index
* @retval status
*/
//这个函数是完成中断接收缓存的
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); //add
USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
return USBD_OK;
}
typedef struct _USBD_CUSTOM_HID_Itf
{
uint8_t *pReport;
int8_t (* Init)(void);
int8_t (* DeInit)(void);
int8_t (* OutEvent)(uint8_t event_idx, uint8_t state);
int8_t (* OutDulBuf)(uint8_t* DulBuf); //add
} USBD_CUSTOM_HID_ItfTypeDef;
/** @defgroup USBD_DESC_Private_Defines USBD_DESC_Private_Defines
* @brief Private defines.
* @{
*/
#define USBD_VID 1155 //VID = 0x0483
#define USBD_LANGID_STRING 1033
#define USBD_MANUFACTURER_STRING "STMicroelectronics"
#define USBD_PID_FS 22352 //PID = 0x5750
#define USBD_PRODUCT_STRING_FS "STM32 Custom Human interface"
#define USBD_CONFIGURATION_STRING_FS "Custom HID Config"
#define USBD_INTERFACE_STRING_FS "Custom HID Interface"
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
#include "gpio.h"
#include "usbd_customhid.h"
#include "usbd_custom_hid_if.h"
#include "flash.h"
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern USBD_HandleTypeDef hUsbDeviceFS;
extern void FLASH_PageErase(uint32_t PageAddress);
//首先定义APP程序入口指针和存放入口地址的变量
typedef void (*pFunction)(void);
pFunction JumpToApplication;
uint32_t JumpAddress;
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t preticks;
uint8_t ledTime;
// uint8_t report[64] = {1,2,3,4,5,6,7,8};
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
__set_FAULTMASK(0);//开启所有中断
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
/* USER CODE BEGIN 2 */
//test
// HAL_FLASH_Unlock(); // 解锁
// FLASH_PageErase( 0x08010000 );
// HAL_FLASH_Lock(); // 上锁
// FlashWriteBuff_Word(0x08010000, TestWrite_buff, 4);
// FlashReadBuff(0x08010000, TestRead_buff, 4);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// if(HAL_GetTick() - preticks > 1000)
// {
// preticks = HAL_GetTick();
//// USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, 64);
// USBD_CUSTOM_HID_SendReport_FS(report, 64);
// }
if(HAL_GetTick() - preticks > 10)
{
preticks = HAL_GetTick();
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) != GPIO_PIN_SET)
{
if(((*(__IO uint32_t*)0x08010000) & 0x2FFE0000) == 0x20000000)
{
USBD_Stop(&hUsbDeviceFS);// 重点: 此处需关闭USB功能, 需要时再打开.
HAL_RCC_DeInit(); //时钟失能
HAL_DeInit(); //外设失能
__set_FAULTMASK(1);//关闭所用中断
JumpAddress = *(__IO uint32_t*)(DEVICE_INFO_ADDRESS +4);//跳转到APP地址
JumpToApplication = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*)DEVICE_INFO_ADDRESS);//MSP指针跳转
JumpToApplication();
}
}
//LED0闪烁
if(++ledTime > 25)
{
ledTime = 0;
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
}
}
/* USER CODE END 3 */
}
四、生成APP的bin文件
用keil编写一个闪灯的APP程序,点击魔术棒设置程序起始地址
另外,在APP程序的还要进行中断向量表的重映射,只要更改system_stm32fxx.c为以下配置即可。
主循环while内写个简单的灯闪烁代码
*注意: 在Main程序初始化前,需要先打开所有中断, __set_FAULTMASK(0);//开启所有中断
因为在BOOTLOADER中跳转前将所有中断都关闭了;
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
__set_FAULTMASK(0);//开启所有中断
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
HAL_Delay(500);
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
附:APP生成.bin文件配置
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o .\ledtest\ledtest.bin .\ledtest\ledtest.axf
这样就成功生成了app.bin文件;
五、测试代码, 使用USB_IAP工具,此工具在下文会有下载链接地址。
此工具无单包的首,尾校验, 每包传输64字节数据; 只有在传输完成后,最后一包添加全为0的64字节的单包数据;
打开USB_HID_IAP上位机后,填入VID,和PID; 然后打开设备,再选择APP的bin文件,最后点击发送就OK;
至此USB-HID_IAP 的APP和BootLoader程序的设计和测试就已经完成。
USB_HID_IAP 上位机工具下载地址: https://download.csdn.net/download/mimo6086/12405634
flash驱动代码下载地址: https://download.csdn.net/download/mimo6086/12089123
BootLoader代码下载地址: https://download.csdn.net/download/mimo6086/12264213