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

2 篇文章 1 订阅

目录

一、前言

二、将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 <stdio.h>
/* 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 

 

  • 15
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值