STM32
直播中

徐磊

7年用户 903经验值
私信 关注
[问答]

STM32是怎样通过CubeMX配置去进行IAP的呢

IAP的原理是什么?

STM32是怎样通过CubeMX配置去进行IAP的呢?

回帖(1)

李思路

2021-10-27 10:04:44
  MCU:STM32F107
  开发环境:CubeMX V6.1.1+Keil MDK V5.26
  一。使用CubeMX配置STM32
  1.FreeRTOS配置,如果只是做一些验证或者测试,基本上使用默认配置即可:
  
  2.USB外设配置:
  USB_OTG配置为USB_HOST:
  
  开启USB中断,注意中断优先级设为5,有时默认的中断优先级为0,由于引入RTOS处理USB且RTOS管理的中断优先级为5级以下,0优先级的USB中断会导致程序卡死:
  
  配置USB_HOST所支持的设备类型。由于我的程序内USB还做通讯使用,所以配置支持所有类设备,如果只做U盘读取,配置支持MSC类即可,需要注意的是USB任务堆栈别太小,否则读写U盘时容易卡死:
  
  3.FATFS配置:
  有了USB接口、有了U盘,那么剩下的就是使用文件系统来读写U盘了,所以我们对U盘的读写都是通过FATFS文件系统的API来完成的。
  
  同样,几乎使用默认配置即可,不过要想支持中文,CODE_PAGE一定要设为GBK简体中文。
  其他的一些外设配置、时钟配置不多说,最后生成代码前,要把系统的堆栈空间设大一些,否则同样容易卡死,或者大容量的U盘可能会无法正常识别:
  
  点击生成代码,剩下的就得啃代码了。
  二.U盘IAP原理
  IAP就是让我们能够脱离下载器(例如常用的ST-Link,J-Link等),在MCU的Flash里写入新的程序。常见的iap方式有串口iap、网口iap、U盘iap等,今天我们要实现的的就是U盘iap方式。这几种iap方式,都是通过一个加载程序,将要更新的程序文件(一般为BIN文件)写入到Flash中,我们称这个加载程序为Bootloader(简称BOOT)程序,要更新的程序称为Application(简称APP)程序,BOOT只负责加载,APP实现我们的应用功能。所以,带有IAP功能的单片机其实运行的是有两个程序,BOOT+APP,这两个程序都在Flash中,BOOT程序放在Flash的起始地址,APP程序放到BOOT程序地址后面,上电后单片机先运行的是BOOT程序,在BOOT程序里判断是否需要更新APP程序,如果需要更新就把更新文件写入到存放APP程序地址的Flash中,如果不需要更新就直接跳转至APP程序。
  
  例如手上用到的这个单片机Flash内存大小为128KB,Flash地址为0x8000000~0x8020000。BOOT占用内存大小为32KB,起始地址0X8000000,也就是MCU的Flash起始地址;APP程序占用94KB的内存,起始地址为0x8008800,也就是BOOT内存区域的终点地址。上电后程序先从0x8000000开始运行BOOT程序,如果不要更新程序就跳转至0x8008800继续运行APP程序,所以从BOOT程序切换至APP程序就是一个PC指针跳转的过程,至于如何跳转,后面介绍。
  U盘iap的方式,U盘就是充当扩展的外部内存的角色,存放需要更新的BIN文件,程序更新过程中MCU先将U盘里的BIN文件读取到MCU内部的Ram中,然后再将Ram中的数据写入到Flash中,过程如下:
  
  如果更新文件比较大,超过MCU的RAM大小,可以将更新文件分割成若干部分,然后从前往后依次读写即可。
  三.U盘IAP过程
  1.对MCU内存进行分区。前面讲到要想实现IAP那么MCU就得运行两个程序,要想运行两个程序就得对MCU内存分区来存放这两个程序。使用MDK很容易实现MCU内存分区,如下图:
  
  这个Start地址就是当前程序要下载到Flash中的起始地址,前面讲到APP的起始地址为0x808800,所以这儿就设置为0x808800.
  2.设置APP程序的向量表偏移地址。
  
  由于我们程序的起始地址改了,那么中断向量的地址当然也要跟着改变,否则程序无法正常进入中断,而中断地址的改变就是通过向量偏移量来实现的,程序的起始地址从0x8000000变为0x8008800,地址偏移了0x8800,那么中断向量同样偏移了0x8800,所以定义VECT_TAB_OFFSET为0x8800即可。新版的CubeMX重定义偏移量是需要先定义USER_VECT_TAB_ADDRESS。BOOT程序的地址还是从0x8000000开始的,无需设置中断向量偏移。
  3.编写BOOT程序里的Flash读写函数。
  MCU内部Flash的读写不像读写Ram那样简单,需要对Flash进行相关配置,Flash的擦写函数以及初始化函数参照了官方BSP里的IAP代码:
  /*-----------------------------------------------------------------------------
  * 函 数 名 : FLASH_If_Init()
  * 函数功能 : Flash初始化
  * 输 入 : 无
  * 输 出 : 无
  -----------------------------------------------------------------------------*/
  void FLASH_If_Init(void)
  {
  HAL_FLASH_Unlock();
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
  HAL_FLASH_Lock();
  }
  /*-----------------------------------------------------------------------------
  * 函 数 名 : FLASH_If_Erase()
  * 函数功能 : Flash擦除函数
  * 输 入 : 无
  * 输 出 : 无
  -----------------------------------------------------------------------------*/
  uint32_t FLASH_If_Erase(uint32_t start)
  {
  uint32_t NbrOfPages = 0;
  uint32_t PageError = 0;
  FLASH_EraseInitTypeDef pEraseInit;
  HAL_StatusTypeDef status = HAL_OK;
  HAL_FLASH_Unlock();
  NbrOfPages = (USER_FLASH_END_ADDRESS - start)/PAGE_SIZE;
  pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
  pEraseInit.PageAddress = start;
  pEraseInit.Banks = FLASH_BANK_1;
  pEraseInit.NbPages = NbrOfPages;
  status = HAL_FLASHEx_Erase(&pEraseInit, &PageError);
  HAL_FLASH_Lock();
  if (status != HAL_OK)
  {
  return FLASHIF_ERASEKO;
  }
  return FLASHIF_OK;
  }
  /*-----------------------------------------------------------------------------
  * 函 数 名 : FLASH_If_Write()
  * 函数功能 : Flash写函数
  * 输 入 : destination:当前要写入的Flash起始地址
  p_source:指向储存的待更新数据
  length:本次写入Flash的数据长度
  * 输 出 : 无
  -----------------------------------------------------------------------------*/
  uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length)
  {
  uint32_t i = 0;
  HAL_FLASH_Unlock();
  for (i = 0; (i 《 length) && (destination 《= (USER_FLASH_END_ADDRESS-4)); i++)
  {
  if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, destination, *(uint32_t*)(p_source+i)) == HAL_OK)
  {
  if (*(uint32_t*)destination != *(uint32_t*)(p_source+i))
  {
  return(FLASHIF_WRITINGCTRL_ERROR);
  }
  destination += 4;
  }
  else
  {
  return (FLASHIF_WRITING_ERROR);
  }
  }
  AppAddresssCurren = destination;
  HAL_FLASH_Lock();
  return (FLASHIF_OK);
  }
  这里用到AppAddresssCurren这个变量来记录本次Flash写入数据后的地址,以便下次写Flash时就从这个地址开始继续往后面写。
  4.读取U盘内的更新文件,写入MCU的Flash中。
  有了Flash的操作函数后,剩下的就是读取U盘里的更新文件,然后写入到Flash中,函数如下:
  #define PAGE_SIZE ((uint16_t)0x400) /*1 Kbytes Size of page*/
  #define FLASH_SIZE ((uint32_t)0x20000) /*128 KBytes Flash*/
  #define APPLICATION_ADDRESS ((uint32_t)0X08008800)
  #define USER_FLASH_END_ADDRESS ((uint32_t)0x08020000)
  #define USER_FLASH_SIZE ((uint32_t)0x00017800) /*94 KBytes User flash size*/
  #define RAM_BUFFER_SIZE ((uint32_t)50*1024) /*KBytes*/
  /*-----------------------------------------------------------------------------
  * 函 数 名 : AppWrite()
  * 函数功能 : U盘IAP函数
  * 输 入 : 无
  * 输 出 : 无
  -----------------------------------------------------------------------------*/
  uint8_t RAM_Buffer[RAM_BUFFER_SIZE] ={0x00};
  void AppWrite(void)
  {
  FRESULT res;
  retUSBH = FATFS_LinkDriver(&USBH_Driver, USBHPath);
  AppAddresssCurren = APPLICATION_ADDRESS;
  res = f_mount(&USBHFatFS, (TCHAR const*)USBHPath, 0);
  if(res != FR_OK)
  {
  Error_Handler();
  }
  res = f_open(&USBHFile,“TestApp1.BIN”, FA_READ);
  if(res != FR_OK)
  {
  Error_Handler();
  }
  res = f_read(&USBHFile, RAM_Buffer, sizeof(RAM_Buffer), (void *)&APP_Size);
  if(res != FR_OK)
  {
  Error_Handler();
  }
  if(((10*1024)《APP_Size) && (APP_Size《RAM_BUFFER_SIZE))
  {
  FLASH_If_Erase(APPLICATION_ADDRESS);
  FLASH_If_Write(AppAddresssCurren, (uint32_t*) RAM_Buffer, APP_Size/4);
  }
  f_close(&USBHFile);
  FATFS_UnLinkDriver(USBHPath);
  }
  这里我们定义了一个RAM_Buffer[RAM_BUFFER_SIZE]数组来存放读取的BIN文件,数组的大小根据时间的Ram大小决定,我这里是64KB的所以定义了50KB的Ram来读取BIN文件,然后将读取的BIN文件通过FLASH_If_Write()函数写入Flash中,完成外部新的程序的更新至Flash中。
  5.跳转。
  前面说了从BOOT切换至APP程序其实就是PC指针的一次跳转,这个跳转通过下面这个函数实现的,参照官方BSP里的IAP例程:
  typedef void (*pFunction)(void);
  pFunction JumpToApplication;
  /*-----------------------------------------------------------------------------
  * 函 数 名 : JumpToAPP()
  * 函数功能 : 程序跳转函数
  * 输 入 : 无
  * 输 出 : 无
  -----------------------------------------------------------------------------*/
  void JumpToAPP(void)
  {
  __disable_irq();
  if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
  {
  JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
  JumpToApplication = (pFunction) JumpAddress;
  __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
  JumpToApplication();
  }
  }
  在跳转前需要调用__disable_irq()函数来关闭中断,否则如果BOOT里开的有中断(比如说我这里开的有定时中断和串口中断),跳转至APP后会卡死。
  /*****************************************************************************/
  到这里我们整个IAP流程就讲完了,自己动手做一做还是蛮简单的。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分