【USB】STM32模拟U盘进行IAP程序更新 您所在的位置:网站首页 loadapp下载 【USB】STM32模拟U盘进行IAP程序更新

【USB】STM32模拟U盘进行IAP程序更新

2024-07-16 21:58| 来源: 网络整理| 查看: 265

目录

一、前言

二、将STM32模拟成大容量存储设备

三、添加外部SRAM模块和FatFs文件系统

四、添加大容量存储设备读写接口及相关参数改动

五、获取bin文件和程序更新

六、App程序设置

七、调试问题

八、参考资料

一、前言

本文以STM32F103ZET6为例进行实验,讲述如何利用STM32加外部SRAM模拟一个容量为1MB的大容量存储设备,然后利用FatFs作为文件系统,读取从PC端放入的bin文件,然后将bin文件更新到MCU内部ROM,以实现IAP程序更新的功能。之前在网上也找过一些例子来参考,但是很少,模拟成U盘的话电脑是自带驱动的(也就是所谓的免驱),下载速度也比模拟成HID设备要快,使用起来更加的方便,直接拖动bin文件到U盘里就可以了。本文详细记录了实验中遇到的问题以及解决方法。

 

二、将STM32模拟成大容量存储设备

利用STM32CubeMX可以快速地搭建一个大容量存储设备工程,本文使用的CubeMX版本号5.0.0,工程配置如下。

 

在CubeMX创建一个MCU型号为STM32F103ZET6的模板工程,ROM容量512KB,RAM为64KB。

使用开发板外部的8M高速时钟,

使用USB功能,STM32F103ZET6只有USB Device功能,功能引脚位于PA11(USB_DM)和PA12(USB_DP),速度为全速USB设备,USB设备只有1个选项“Device FS”勾上。然后在USB_DEVICE功能上选中“Mass Storage Class”大容量存储设备。

时钟选择自动配置,当然也可以自己改一下参数,但是USB设备的时钟要是48M。

工程配置栏那里把栈空间“Minimum Stack Size”从1KB修改为4KB,根据实际工程使用程度而定。工程只拷贝用得到的文件,勾选“Copy only the necessary library files”,保持工程简洁。最后点击“GENERATE CODE”按钮,生成模板工程。

工程目录结构如上图所示,其中“Application/MDK-ARM”里的东西和“Application/User”里的东西是一样的,只是前者多了一个启动文件,我把“Application/User”整个文件屏蔽掉了不编译,不然会报错。右键目录图标,选择“Options for Group "Application/User"”。

取消勾选“Include in Target Build”,这样Keil编译的时候就不理这个目录下的文件了。编译成功后的内存占用情况如下。

用USB线连接开发板和电脑,电脑上会多出一个未经格式化的U盘设备,查看格式化选项页面如下。

上面显示的32MB容量是假的,你愿意改成几个TB容量都可以,只是设备上传的一个参数而已,我们对这个设备进行的读写操作全部都会失败,原因是这个STM32工程模板的读写函数是空的没有处理。接下来我们要用外部的SRAM模块作为存储介质,完善读写函数,实现一个真正的可读写的U盘。

 

三、添加外部SRAM模块和FatFs文件系统

SRAM模块型号为IS62WV51216,16位宽的数据接口,19位宽的地址线,512x1024个寻址单位,容量为512x1024x2=1MB,原理图如下:

回到刚刚的CubeMX工程,添加FSMC功能如下:

下面继续添加FatFs如下:

因为使用的是外部SRAM,勾选“External SRAM”,后面我还想能修改U盘的名字,勾选“USE_LABEL”。CubeMX非常智能,因为前面设置了FSMC使用SRAM且定义了SRAM区域,这里FatFs的“Advanced Settings”选项自动关联到了这块内存,而且只能选择SRAM3,想搞错都没机会(点赞)。

添加完成后点击“GENERATE CODE”重新生成工程。

 

四、添加大容量存储设备读写接口及相关参数改动

在main函数中有一些初始化函数,如下:

/** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* 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(); MX_USB_DEVICE_Init(); MX_FSMC_Init(); MX_FATFS_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

USB设备的初始化函数MX_USB_DEVICE_Init如下:

/** * Init USB device Library, add supported class and start the library * @retval None */ void MX_USB_DEVICE_Init(void) { /* USER CODE BEGIN USB_DEVICE_Init_PreTreatment */ /* USER CODE END USB_DEVICE_Init_PreTreatment */ /* Init Device Library, add supported class and start the library. */ USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS); USBD_RegisterClass(&hUsbDeviceFS, &USBD_MSC); USBD_MSC_RegisterStorage(&hUsbDeviceFS, &USBD_Storage_Interface_fops_FS); USBD_Start(&hUsbDeviceFS); /* USER CODE BEGIN USB_DEVICE_Init_PostTreatment */ /* USER CODE END USB_DEVICE_Init_PostTreatment */ }

其中USBD_Storage_Interface_fops_FS这个结构体里面注册了包含底层读写接口的函数STORAGE_Read_FS和STORAGE_Write_FS。

USBD_StorageTypeDef USBD_Storage_Interface_fops_FS = { STORAGE_Init_FS, STORAGE_GetCapacity_FS, STORAGE_IsReady_FS, STORAGE_IsWriteProtected_FS, STORAGE_Read_FS, STORAGE_Write_FS, STORAGE_GetMaxLun_FS, (int8_t *)STORAGE_Inquirydata_FS };

修改这两个函数如下,实现对SRAM的读写操作:

/** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 6 */ uint32_t copyNum = blk_len * (512 >> 1); //SRAM是16位宽数据总线,1次读取2字节加快速度 BSP_SRAM_ReadData(SRAM_DEVICE_ADDR + blk_addr * 512, (uint16_t *)buf, copyNum); return (USBD_OK); /* USER CODE END 6 */ } /** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 7 */ uint32_t copyNum = blk_len * (512 >> 1); BSP_SRAM_WriteData(SRAM_DEVICE_ADDR + blk_addr * 512, (uint16_t *)buf, copyNum); return (USBD_OK); /* USER CODE END 7 */ }

这里调用BSP_SRAM_ReadData和BSP_SRAM_WriteData实现SRAM读写,当然也可以自己写个for循环赋值。当前工程里Block Size 和Sector Size都是512字节,1个Block包含1个Sector。因为只有1个U盘设备,lun不用管,buf是数据存放的Buffer,blk_addr是Block号,Block号乘以Sector大小就是实际的内存偏移地址,blk_len指要传输多少个Block的数据。这两个接口每次读写数据最小单位是1个Block即512字节。copyNum表示拷贝的数据个数,这里1个数据是指16位即两个字节,因为SRAM是16位的,SRAM_DEVICE_ADDR这个宏定义是SRAM的内存基地址0x68000000。

还有1个地方要修改,就是一开始我们格式化的时候U盘看到是32MB的容量,我们要把这个容量改成和SRAM容量真实对应的1MB。修改的地方在STORAGE_GetCapacity_FS函数如下:

/** * @brief . * @param lun: . * @param block_num: . * @param block_size: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) { /* USER CODE BEGIN 3 */ *block_num = STORAGE_BLK_NBR; *block_size = STORAGE_BLK_SIZ; return (USBD_OK); /* USER CODE END 3 */ }

这里会上报设备的Block数和一个Block的大小,去到它们宏定义的地方把它们修改成如下的参数。

#define STORAGE_LUN_NBR 1 #define STORAGE_BLK_NBR 0x800 #define STORAGE_BLK_SIZ 0x200

0x800 * 0x200 = 0x100000 Byte = 1024 KB = 1MB。还有SRAM_DEVICE_SIZE这个宏定义代表SRAM的容量记得改成0x100000(工程里是0x200000),FatFs需要用到,不改过来的话使用FatFs会出问题。

#define SRAM_DEVICE_SIZE ((uint32_t)0x100000) /* SRAM device size in MBytes */

最后因为上面调用了“bsp_driver_sram.h”这个文件里函数,记得包含这个头文件。

到此为止,重新编译程序,一个可以使用的U盘工程完成了,在电脑上对U盘进行fat格式化后如下:

用一张800多KB的图片测试一下读写速度基本都是700KB/S左右:

 下面是利用USB分析工具Bushound抓取的数据传输速率。

对于USB全速设备而言,最大带宽是12Mbps(1.5MB/S),再加上USB数据包除了传输的用户数据外还有令牌包等开销,所以咱这有个700KB/S左右的速度算是可以了,下载代码都是秒下,比用串口传数据快多了。

 

五、获取bin文件和程序更新

上面的U盘已经可以放文件了,我们还需要在单片机内部实时读取电脑上写入的文件判断是不是bin文件,是bin文件的话我们就默认当成程序更新到ROM里面来,然后跳转到新程序的入口地址运行这个程序。我们就把这个U盘工程称为BootLoader,它只是用来下载程序的,下载完程序后就没用了。把要下载的bin文件这个程序叫做App(应用程序),就是产品要用的最终运行的程序。先看看BootLoader这个工程用了多少空间了。

Code + RO-Data的空间占用加起来不到13KB,我们取个整分配32KB给BootLoader,那么剩下512KB - 32KB = 480KB的ROM空间给App用。App起始地址=0x08000000 + 0x8000 = 0x08008000。如果BootLoader还加了其他东西就看情况调整空间了。

我希望一插上USB到电脑上U盘能显示“IAP”字样以区别于电脑上其他的盘,U盘在单片机内部格式化好不需要用户再格式化,我的开发板上有一个按键key0,原理图如下:

当单片机初始上电时,检测一下key0是否按下,如果按下了就进入BootLoader,没按下直接跳转App,因为BootLoader是特殊需求,所以搞个按键做指示,当然弄个其他的串口指令等等也行,看实际什么需求。

在CubeMX继续添加按键模块,PA0下拉输入,之前上面刚刚说的改过的一些宏定义CubeMX会还原的,重新生成工程后记得改回来。

最终代码如下main.c:

/* Includes ------------------------------------------------------------------*/ #include "main.h" #include "fatfs.h" #include "usb_device.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ UART_HandleTypeDef huart3; SRAM_HandleTypeDef hsram3; /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_FSMC_Init(void); static void MX_USART3_UART_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xffff); return ch; } int fgetc(FILE *f) { uint8_t ch = 0; HAL_UART_Receive(&huart3, &ch, 1, 0xffff); return ch; } #define APP_BIN_ADDR (FLASH_BASE + 0x8000) void(*Boot_Jump2App)(); //传入要跳转到的程序地址进行跳转 void Boot_LoadApp(uint32_t dwAddr) { uint8_t i; //检查栈顶地址是否合法 // if (((*(volatile long *)dwAddr) & 0x2FFE0000) == 0x20000000) { //设置跳转地址 Boot_Jump2App = (void(*)())*(volatile long*)(dwAddr + 4); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) __set_MSP(*(volatile long*)dwAddr); //关闭所有中断 for (i = 0; i < 8; i++) { NVIC->ICER[i] = 0xFFFFFFFF; NVIC->ICPR[i] = 0xFFFFFFFF; } //跳转到APP Code Boot_Jump2App(); //跳转之前用死循环卡住 while (1); } } void WriteFlash(uint32_t start_Addr, uint32_t *data, uint32_t len) { uint32_t i; uint32_t PageError; FLASH_EraseInitTypeDef f; HAL_FLASH_Unlock(); f.TypeErase = FLASH_TYPEERASE_PAGES; f.PageAddress = start_Addr; f.NbPages = (len + (2048 - 1)) / 2048; //首尾都不对齐2KB页地址 if (((start_Addr & (2048 -1)) != 0) && (((start_Addr + len) & (2048 -1)) != 0)) { f.NbPages++; } PageError = 0; HAL_FLASHEx_Erase(&f, &PageError); for(i = start_Addr; i < (start_Addr + len); i += 4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, i, *data); data++; } HAL_FLASH_Lock(); } char romSectorBuf[2048]; //STM32F103ZET6分为256个2KB的Sector Flash擦写最小单位为Sector,准备2KB的内存放数据 FRESULT Scan_files ( char* path /* Start node to be scanned (***also used as work area***) */ ) { FRESULT res; DIR dir; static FILINFO fno; FIL fil; /* File object */ UINT i; UINT fileNameLen; UINT recvBytes; res = f_opendir(&dir, path); /* Open the directory */ if (res == FR_OK) { for (;;) { res = f_readdir(&dir, &fno); /* Read a directory item */ if (res != FR_OK || fno.fname[0] == 0) { break; /* Break on error or end of dir */ } if (fno.fattrib & AM_DIR) /* It is a directory */ { //不处理 } else { /* It is a file. */ printf("\r\n%s/%s,size = %d", path, fno.fname, fno.fsize); fileNameLen = strlen(fno.fname); if (fileNameLen > 4) { if ((strcmp(&fno.fname[fileNameLen - 4], ".bin") == 0) || (strcmp(&fno.fname[fileNameLen - 4], ".BIN") == 0)) { /* Open a text file */ res = f_open(&fil, fno.fname, FA_READ); if (res) printf("\r\nopen file fail");; recvBytes = 0; while(recvBytes < fno.fsize) { f_read(&fil, romSectorBuf, sizeof(romSectorBuf), &i); /* Read a chunk of data from the source file */ WriteFlash(APP_BIN_ADDR + recvBytes, (uint32_t *)romSectorBuf, i); recvBytes += i; if (i < sizeof(romSectorBuf)) { break; } } /* Close the file */ f_close(&fil); printf("\r\nrecvBytes = %d", recvBytes); if (recvBytes == fno.fsize) { //跳转App运行 Boot_LoadApp(APP_BIN_ADDR); } } } } } f_closedir(&dir); } return res; } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ FRESULT res; /* 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(); //检测key0按键,按下了就继续往下走BootLoader,否则跳转App if (HAL_GPIO_ReadPin(key0_GPIO_Port, key0_Pin) == GPIO_PIN_SET) { HAL_Delay(10); if (HAL_GPIO_ReadPin(key0_GPIO_Port, key0_Pin) == GPIO_PIN_RESET) { Boot_LoadApp(APP_BIN_ADDR); } } else { Boot_LoadApp(APP_BIN_ADDR); } MX_USB_DEVICE_Init(); MX_FSMC_Init(); MX_FATFS_Init(); MX_USART3_UART_Init(); /* USER CODE BEGIN 2 */ res = f_mount(&SRAMDISKFatFS, SRAMDISKPath, 1); printf("\r\n[f_mount] res = %d", res); if (res != FR_OK) { res = f_mkfs(SRAMDISKPath, 0, 0); printf("\r\n[f_mkfs] res = %d", res); res = f_mount(&SRAMDISKFatFS, "", 1); printf("\r\n[f_mount] res = %d", res); } res = f_setlabel("IAP"); printf("\r\n[f_setlabel] res = %d", res); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { f_mount(&SRAMDISKFatFS, SRAMDISKPath, 1); Scan_files("/"); HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; /**Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /**Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB; PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } } /** * @brief USART3 Initialization Function * @param None * @retval None */ static void MX_USART3_UART_Init(void) { /* USER CODE BEGIN USART3_Init 0 */ /* USER CODE END USART3_Init 0 */ /* USER CODE BEGIN USART3_Init 1 */ /* USER CODE END USART3_Init 1 */ huart3.Instance = USART3; huart3.Init.BaudRate = 115200; huart3.Init.WordLength = UART_WORDLENGTH_8B; huart3.Init.StopBits = UART_STOPBITS_1; huart3.Init.Parity = UART_PARITY_NONE; huart3.Init.Mode = UART_MODE_TX_RX; huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart3.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart3) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART3_Init 2 */ /* USER CODE END USART3_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); /*Configure GPIO pin : key0_Pin */ GPIO_InitStruct.Pin = key0_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(key0_GPIO_Port, &GPIO_InitStruct); } /* FSMC initialization function */ static void MX_FSMC_Init(void) { FSMC_NORSRAM_TimingTypeDef Timing; /** Perform the SRAM3 memory initialization sequence */ hsram3.Instance = FSMC_NORSRAM_DEVICE; hsram3.Extended = FSMC_NORSRAM_EXTENDED_DEVICE; /* hsram3.Init */ hsram3.Init.NSBank = FSMC_NORSRAM_BANK3; hsram3.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE; hsram3.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM; hsram3.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; hsram3.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE; hsram3.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW; hsram3.Init.WrapMode = FSMC_WRAP_MODE_DISABLE; hsram3.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS; hsram3.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE; hsram3.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE; hsram3.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE; hsram3.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE; hsram3.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE; /* Timing */ Timing.AddressSetupTime = 0; Timing.AddressHoldTime = 15; Timing.DataSetupTime = 3; Timing.BusTurnAroundDuration = 0; Timing.CLKDivision = 16; Timing.DataLatency = 17; Timing.AccessMode = FSMC_ACCESS_MODE_A; /* ExtTiming */ if (HAL_SRAM_Init(&hsram3, &Timing, NULL) != HAL_OK) { Error_Handler( ); } /** Disconnect NADV */ __HAL_AFIO_FSMCNADV_DISCONNECTED(); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

首先上盘看下效果:

 

 

六、App程序设置

被更新的这个程序需要进行一些设置才能正常运行(注意接下来的界面不是U盘的这个工程的了,是另外App的设置界面):

1、设置程序开始位置和程序区大小:

程序起始位置不是原来的0x08000000了,变成了0x08008000,大小由0x80000改为0x78000。

2、勾选上“Use Memory Layout from Target Dialog”,让keil使用默认的分散加载文件,第1点的IROM1设置会同步到这个默认的文件里,要不然我们要手动改分散加载文件。

3、App程序的main函数第一条语句使用NVIC_SetVectorTable函数来重定位向量表。告诉单片机当发生中断的时候要跳到新的中断向量表去,原来那个已经不用了。

int main() { u16 i=0; NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000); SysTick_Init(72); LED_Init(); BEEP_Init(); while(1) { i++; if(i%10==0) { beep=!beep; } if(i%20000==0) { led1=!led1; } delay_us(10); } }

4、生成bin的方法如下图添加命令行处理,在工程编译完成后,keil调用该命令行由axf文件通过keil自带的fromelf.exe工具生成bin文件。

注意我们使用的是bin文件,不是hex文件。生成命令根据自己电脑的目录改下:D:/Keil_v5/ARM/ARMCC/bin/fromelf.exe --bin -o ./beep.bin ./Obj/Template.axf

最后将生成的bin文件放到U盘里面去,看到App程序自动更新并且运行起来了。

 

七、调试问题

本来调到这里已经大功告成了,突然发现一个问题,使用FatFs格式化后看到U盘容量为768KB,用电脑(win10)fat格式化后容量是0.97MB,相差了200KB,如果这个U盘有几百MB我可能就不管这个问题了,但是对于单片机来说200KB的空间太宝贵了,这个U盘总共才1024KB,格式化后200多KB的空间没了也就是五分之一,网上查了下看博客看论坛也没看到谁问过这个问题可以参考,没办法了,只能自己详细研究下FatFs文件系统了,用Bushound检测到导致200KB差距的原因是单片机FatFs格式化和PC的Fat格式化两边写入的文件系统数据不一致造成的。接下来用winHex这个工具抓文件系统的数据来分析。

单片机内部FatFs格式化后文件系统为Fat16。

 

电脑端格式化后文件系统为Fat12,所以导致两者格式化后容量不同的主要原因就是文件系统有差距,虽然都是Fat但是一个是“Fat16”,一个是“Fat12”,他们的“Sectors per FAT”参数有很大区别,Fat16的“Sectors per FAT”为416,有1个Fat表;Fat12的“Sectors per FAT”为6,有两个Fat表。所以上的Fat16的Fat区开销比Fat12多了416*1-6*2=404个Sector的空间,即404*512(Byte)=202KB的空间,所以少容量的原因查明白了。

关于FatFs文件系统原理网上很多资料在这里就不说了,我也收集了一些资料附在文后的资料里供参考学习。FAT12的1个Fat区域占用6个Sector即 3KB空间,1个簇需要12位来表示,所以3KB可以表示2048个簇,1个簇为512字节,那么2048个簇可以指示1024KB即1MB的空间,这么看对我们这个1MB的U盘来说格式化成Fat12就OK了,那么为什么单片机内部格式化成Fat16了?格式化在f_mkfs这个函数实现的,本来只是纯粹想用用API而已,现在没办法,于是只能研究下源码了。

/* Align data start sector to erase block boundary (for flash memory media) */ if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &n) != RES_OK || !n || n > 32768) n = 1; n = (b_data + n - 1) & ~(n - 1); /* Next nearest erase block from current data start */ n = (n - b_data) / N_FATS; if (fmt == FS_FAT32) { /* FAT32: Move FAT offset */ n_rsv += n; b_fat += n; } else { /* FAT12/16: Expand FAT size */ n_fat += n; }

f_mkfs函数里的这一段代码是确定Fat区的大小的(n_fat),这个FatFs版本是R0.11,我没看过其他版本怎么写的,这段代码将Fat区域对其到Block_Size的边界,但是这个n_fat的单位是Sector。

DRESULT SRAMDISK_ioctl(BYTE lun, BYTE cmd, void *buff) { DRESULT res = RES_ERROR; if (Stat & STA_NOINIT) return RES_NOTRDY; switch (cmd) { /* Make sure that no pending write process */ case CTRL_SYNC : res = RES_OK; break; /* Get number of sectors on the disk (DWORD) */ case GET_SECTOR_COUNT : *(DWORD*)buff = SRAM_DEVICE_SIZE / BLOCK_SIZE; res = RES_OK; break; /* Get R/W sector size (WORD) */ case GET_SECTOR_SIZE : *(WORD*)buff = BLOCK_SIZE; res = RES_OK; break; /* Get erase block size in unit of sector (DWORD) */ case GET_BLOCK_SIZE : *(DWORD*)buff = BLOCK_SIZE; res = RES_OK; break; default: res = RES_PARERR; } return res; }

从以上函数知道调用disk_ioctl去GET_BLOCK_SIZE的时候给n返回了BLOCK_SIZE这个宏为512,那么Fat区大小需要对齐到512个Sector的大小。其中b_data = 102,N_FATFS = 1,n_fat进这段代码前是6(已经计算好了要用Fat12的),但是经过对齐变成了416,于是当成Fat16格式化了,当然Fat16用起来也没问题。其实我们的本意是Block大小和Sector大小一样占用512字节的,但是disk_ioctl函数这里返回的BLOCK_SIZE不是字节的意思,单位是Sector,所以我把case GET_BLOCK_SIZE这里改了改让它返回1表示1个Sector,让Fat区对齐到Sector边界。这个case只影响格式化不影响其他地方。重新编译代码上盘后容量回来了,如下图。

 

八、参考资料

《简单实现stm32f103芯片usb模拟U盘进行IAP更新用户程序》

《FatFs 之三 FAT文件系统基础、FAT 数据格式、引导、编码》

《FatFs官方文档》

《FAT32文件系统研究.pdf》

《浅析FAT32文件系统.pdf》

[1]龚勇. Windows下数据恢复的研究[D].电子科技大学,2008.

 书籍 《微控制器USB的信号和协议实现》

mscIAPDemo.rar

beep(App).rar

资料及代码下载链接:链接:https://pan.baidu.com/s/1Lch1REE8r_QqrqXLZe_EKg 提取码:khtf 

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有