本帖最后由 WoodData 于 2023-3-31 18:06 编辑
#申请原创#@安小芯
写在前面的: 感谢21IC平台和国名技术举办的这次基于N32G45x的SD卡IAP升级开发活动。看到这个活动就申请了,主要是对IAP升级感兴趣,非常幸运成功入选。 IAP应用升级主要是为了不使用下载调试器,而是通过MCU的外设通信接口实现更新应用固件的方法。在这之前我也做过通过串口UART和USB的U盘模式更新固件。这样可以方便产品有BUG时,在用户本地更新。再加上对固件进行加密和MCU升级更新时解密,就可以直接给用户自己更新,还不怕泄露固件。 第一、目标规划 本次目标是实现基于国名的N32G45X单片机实现SD卡的IAP升级。我将实现目标分为以下步骤。 首先就要实现的功能: 1、MCU的flash擦除,读和写功能。 2、SD卡的驱动移植,包含SD卡的读写数据。 3、文件系统的移植,这里移植最常用的FATFS文件系统。这样对SD内文件操作起来更方便。 4、主要是设计触发进入升级更新的方法,以及更新完之后的跳转到应用APP. 第二、按照功能步骤逐个调试。 1、首先是FLASH读写、擦除 关于MCU的FLASH操作,参考国名技术的例子资料。整理代码如下: //FLASH单字写入 void APPFLASH_ByteWrite (uint32_t _addr, uint32_t _data) { // *** Device-Specific *** // TODO: Add this based on MCU. /* Unlocks the FLASH Program Erase Controller */ FLASH_Unlock(); do{ if (FLASH_COMPL == FLASH_ProgramWord(_addr, _data)) break; }while(1); /* Locks the FLASH Program Erase Controller */ FLASH_Lock(); } //FLASH单字读 uint8_t APPFLASH_ByteRead (uint32_t addr) { // *** Device-Specific *** // TODO: Add this based on MCU. __IO uint32_t * pdat; pdat = (uint32_t *)addr; return *pdat; } //FLASH擦除 void APPFLASH_PageErase (uint32_t _addr) { // *** Device-Specific *** // TODO: Add this based on MCU. //page flash = 2K=0x800 __IO uint32_t addr0; addr0 = _addr & (~0x7ff); FLASH_Unlock(); do{ if (FLASH_COMPL == FLASH_EraseOnePage(_addr)) break; }while(1); FLASH_Lock(); } //FLASH多字节写入,这里要求写入起始地址为512整数倍。 void APPFLASH_PageWrite (uint32_t _addr,uint8_t *buff,uint32_t len) { __IO uint32_t i,addr0; __IO uint32_t * pdat; addr0 = _addr & (~0x000001ff); FLASH_Unlock(); for(i = 0;i < len;i += 4) //page=2K=0x800/4=0x200=512 { pdat = (uint32_t *)(buff + i); do{ if (FLASH_COMPL == FLASH_ProgramWord(addr0,*pdat)) break; }while(1); addr0 += 4; } FLASH_Lock(); } 2、实现SD卡驱动,参考我自己之前的SD卡驱动,使用的是SPI接口。因为买的SD模块是SPI接口的,就直接用上了。 使用的是MCU的SPI1外设,端口如下: * PA4 <===========> CS * PA5 <===========> SCK * PA6 <===========> MISO * PA7 <===========> MOSI 下面是SPI的初始化代码 /** * @param None */ void drv_spi_init(void) { GPIO_InitType GPIO_InitStructure; SPI_InitType SPI_InitStructure; RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_SPI1 | RCC_APB2_PERIPH_GPIOA, ENABLE); /*!< Configure SPI pins: SCK */ GPIO_InitStructure.Pin = GPIO_PIN_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure); /*!< Configure SPI pins: MOSI */ GPIO_InitStructure.Pin = GPIO_PIN_7; GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure); /*!< Configure SPI pins: MISO */ GPIO_InitStructure.Pin = GPIO_PIN_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure); /*!< Configure CS_PIN pin: CS pin */ GPIO_InitStructure.Pin = GPIO_PIN_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure); /*!< SPI configuration */ SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX; SPI_InitStructure.SpiMode = SPI_MODE_MASTER; SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS; SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH; SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE; SPI_InitStructure.NSS = SPI_NSS_SOFT; SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_16; SPI_InitStructure.FirstBit = SPI_FB_MSB; SPI_InitStructure.CRCPoly = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Enable(SPI1, ENABLE); } //SPI通信读写数据 uint8_t spi_read_write(uint8_t _data) { /*!< Loop while DAT register in not emplty */ while (SPI_I2S_GetStatus(SPI1, SPI_I2S_TE_FLAG) == RESET) ; /*!< Send byte through the SPI1 peripheral */ SPI_I2S_TransmitData(SPI1, _data); /*!< Wait to receive a byte */ while (SPI_I2S_GetStatus(SPI1, SPI_I2S_RNE_FLAG) == RESET) ; /*!< Return the byte read from the SPI bus */ return SPI_I2S_ReceiveData(SPI1); } 接着就是SD卡驱动调试,代码太多这里就不列出来了,具体代码在工程中查看。主要是实现SD卡初始化,获取SD状态,然后是读写PAGE页。 3、接着就是FATFS文件系统移植了。 主要实现文件系统需要的几个函数。 代码实现如下: /** * @param lun : not used * @retval DSTATUS: Operation status */ DSTATUS SD_initialize(BYTE lun) { if(MSD_Init() == 0) { MSD_GetCardInfo(&CardInfo); return 0; }else return STA_NOINIT; } /** * @brief Gets Disk Status * @param lun : not used * @retval DSTATUS: Operation status */ DSTATUS SD_status(BYTE lun) { return 0; } /** * @brief Reads Sector(s) * @param lun : not used * @param *buff: Data buffer to store read data * @param sector: Sector address (LBA) * @param count: Number of sectors to read (1..128) * @retval DRESULT: Operation result */ DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count) { int res; if( !count ) { return RES_PARERR; /* count不能等于0,否则返回参数错误 */ } if(count==1) /* 1个sector的读操作 */ { res = MSD_ReadSingleBlock(sector ,buff ); }else /* 多个sector的读操作 */ { res = MSD_ReadMultiBlock(sector , buff ,count); } if(res == 0) { return RES_OK; }else { return RES_ERROR; } } /** * @brief Writes Sector(s) * @param lun : not used * @param *buff: Data to be written * @param sector: Sector address (LBA) * @param count: Number of sectors to write (1..128) * @retval DRESULT: Operation result */ #if _USE_WRITE == 1 DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count) { int res; if( !count ) { return RES_PARERR; /* count不能等于0,否则返回参数错误 */ } if(count==1) /* 1个sector的写操作 */ { res = MSD_WriteSingleBlock(sector , (uint8_t *)(&buff[0]) ); }else /* 多个sector的写操作 */ { res = MSD_WriteMultiBlock(sector , (uint8_t *)(&buff[0]) , count ); } if(res == 0) { return RES_OK; }else { return RES_ERROR; } } #endif /* _USE_WRITE == 1 */ /** * @brief I/O control operation * @param lun : not used * @param cmd: Control code * @param *buff: Buffer to send/receive control data * @retval DRESULT: Operation result */ #if _USE_IOCTL == 1 DRESULT SD_ioctl(BYTE lun, BYTE cmd, void *buff) { DRESULT res = RES_ERROR; 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 = CardInfo.Capacity/CardInfo.BlockSize; res = RES_OK; break; /* Get R/W sector size (WORD) */ case GET_SECTOR_SIZE : *(WORD*)buff = CardInfo.BlockSize; res = RES_OK; break; /* Get erase block size in unit of sector (DWORD) */ case GET_BLOCK_SIZE : *(WORD*)buff = CardInfo.BlockSize; res = RES_OK; break; default: res = RES_PARERR; } return res; } #endif /* _USE_IOCTL == 1 */ SD驱动接口定义如下: const Diskio_drvTypeDef SD_Driver = { SD_initialize, SD_status, SD_read, #if _USE_WRITE == 1 SD_write, #endif /* _USE_WRITE == 1 */ #if _USE_IOCTL == 1 SD_ioctl, #endif /* _USE_IOCTL == 1 */ }; 文件系统使用前要初始化,通过f_mount()的API挂载系统。挂载之后就可以通过文件操作函数API f_open(),f_read(),f_write()操作文件。 int FATFS_Init(FATFS *fs,char * _path) { FRESULT res; FATFS_LinkDriver(&SD_Driver,_path, 0); res = f_mount(fs,_path,1); printf("f_mount return =%d\r\n",res); return res; } 文件系统配置在ffconf.h内,看自己需求配置了。基本很少改动,默认就可以。文件系统移植完成后,可以测试一下文件的读写功能是否正常。方便后面读取文件更新固件。 4、前面的驱动都完成后就可以着手IAP的应用流程设计了。 本次应用流程如下: 4.1、检查MCU复位原因,通过检测RST复位按键或者软复位强制进入bootloader。这时不 管APP应用是否有效存在。 4.2、检查app应用有效标识是否存在。标识是app更新完后在FLASH指定地址写入数据标 识。如果APP有效,而且不是RST按键复位或者软复位就直接跳转到APP应用。否则 就继续进入bootloader继续更新。 4.3、初始化bootloader的外设。 4.4、查找读取SD卡内bin文件固件,读取文件并写入到FLASH对应地址空间。 4.5、更新完成APP后等待10s跳转到APP。如果更新APP失败,这时如果旧的APP还是有 效的话,那就等待10s跳转到APP;否则停止在bootloader内,这时可以按复位键再次 尝试APP更新。 BOOTLOADER和APP应用的FLASH空间分配。给bootloader分配了前20K空间。后面的给APP空间。其中后面的空间的前512字节用作存储APP的一些信息。所以APP应用固件从0x08005200地址开始。 #define APP_FW_START_ADDR (uint32_t)(0x08005200) #define APP_FW_FLASH_SIZE (uint32_t)(1024*100) //100K #define APP_FW_PAGE_SIZE (uint32_t)(0x00000800) //2048 #define APP_FW_INFO_ADDR (APP_FW_START_ADDR - 0x200) #define APP_FW_END_ADDR (APP_FW_START_ADDR + APP_FW_FLASH_SIZE) 下面是实现代码: uint32_t UpdateFlag,RstTimeTick; // pFun Jump_to_app; uint32_t Jump_addr; //这里检查复位标识。 void IAP_CheckFlag(void) { uint32_t Rstflag; uint32_t *pdat; UpdateFlag = 0; AppUpdateFlag = 0; //软件复位或者rst引脚复位进入Bootloader。 Rstflag = RCC->CTRLSTS & 0xFF000000; RCC->CTRLSTS |= (0x00000001 << 24); //清复位标志 if((Rstflag == 0x04000000)||(Rstflag == 0x10000000)||(Rstflag == 0x14000000)) { UpdateFlag |= 0x00000001; //需要更新 } //===================================================== pdat = (uint32_t *)(APP_FW_INFO_ADDR+508); if(*pdat == 0xAA55A55A) //有app代码 { UpdateFlag |= 0x00000002; }else { UpdateFlag |= 0x00000001; //需要更新 } if(UpdateFlag == 2) { if(((*(__IO uint32_t *)APP_FW_START_ADDR)&0x2FFE0000)==0x20000000) { Jump_addr = *(__IO uint32_t *)(APP_FW_START_ADDR + 4); Jump_to_app = (pFun)Jump_addr; __set_MSP(*(__IO uint32_t *)(APP_FW_START_ADDR)); Jump_to_app(); } UpdateFlag = 1; } RstTimeTick = 10000; } //这里是等待10s之后跳转到APP应用。 void IAP_UpdateRestart(void) { if((AppUpdateFlag == 3)|| ((AppUpdateFlag == 0) && (UpdateFlag & 0x2))) { //更新完成后10秒自动跳到APP。 if(RstTimeTick == 0) { RstTimeTick = 10000; if(((*(__IO uint32_t *)APP_FW_START_ADDR)&0x2FFE0000)==0x20000000) { __disable_irq(); //禁止中断 RCC->APB2PRST = 0xFFFFFFFF; //所有外设复位 RCC->APB1PRST = 0xFFFFFFFF; RCC->AHBPRST = 0xFFFFFFFF; RCC->APB2PRST = 0x0; RCC->APB1PRST = 0x0; RCC->AHBPRST = 0x0; __enable_irq(); // Jump_addr = *(__IO uint32_t *)(APP_FW_START_ADDR + 4); Jump_to_app = (pFun)Jump_addr; __set_MSP(*(__IO uint32_t *)(APP_FW_START_ADDR)); Jump_to_app(); } UpdateFlag &= ~0x00000002; } } } 这里是读取SD文件,并更新FLASH. int read_file_update (char* file_path) { uint32_t buff[512]; FIL fsrc; /* file objects */ FRESULT res; UINT br; int page_index; res = f_open( &fsrc , file_path , FA_READ); if ( res == FR_OK ) { if(UpdateAppCodeStart((uint8_t *)file_path,f_size(&fsrc))) goto _exit1; if(UpdateAppCodeInfo((uint8_t *)buff)) goto _exit1; page_index = 0; while(1) { res = f_read(&fsrc, (uint8_t *)buff, 512, &br); if(res == FR_OK) { if(br == 512) { if(UpdateAppCode(page_index,(uint8_t *)buff)) goto _exit1; }else { if(UpdateAppCodeEnd(page_index,(uint8_t *)buff,br)) goto _exit1; break; } page_index ++; }else{ goto _exit1;} } f_close(&fsrc); if(UpdateAppCodeCheckALL()) goto _exit1; return 0; } printf("%s file open error.\r\n",file_path); _exit1: f_close(&fsrc); return 1; } 下面是MAIN函数 /** * @brief Main program. */ int main(void) { IAP_CheckFlag(); //检查复位标识,是否跳转到APP. SysTick_Config(SystemCoreClock/1000); init_cycle_counter(true); RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO , ENABLE); GPIO_ConfigPinRemap(GPIO_RMP_SW_JTAG_NO_NJTRST, ENABLE); /* Initialize Led1~Led5 as output pushpull mode*/ GPIOInit(GPIOA, GPIO_PIN_8, 1); GPIOInit(GPIOB, GPIO_PIN_4, 1); GPIOInit(GPIOB, GPIO_PIN_5, 1); GPIOInit(GPIOC, GPIO_PIN_7, 1); GPIO_ResetBits(GPIOB, GPIO_PIN_4); GPIO_ResetBits(GPIOB, GPIO_PIN_5); GPIO_ResetBits(GPIOA, GPIO_PIN_8); GPIO_ResetBits(GPIOC, GPIO_PIN_7); /* USART Init */ USART_Config(); //串口初始化 printf("Flash Program IAP from TF Card.\r\n"); printf("sysclk=%u\r\n",SystemCoreClock); //文件系统初始化 if(FATFS_Init(&fs,path) == 0) { //读取SD卡内的bin固件 if(read_file_update("0:/DEMO.BIN") == 0) { RstTimeTick = 10000; //10s printf("Update OK.\r\n"); GPIO_SetBits(GPIOB, GPIO_PIN_5); //更新成功板上绿灯亮 }else GPIO_SetBits(GPIOA, GPIO_PIN_8); //失败红灯亮 }else GPIO_SetBits(GPIOA, GPIO_PIN_8);//失败红灯亮 while (1) { IAP_UpdateRestart(); //等待跳转到APP. GPIO_SetBits(GPIOB, GPIO_PIN_4); GPIO_SetBits(GPIOC, GPIO_PIN_7); delay_ms(100); GPIO_ResetBits(GPIOB, GPIO_PIN_4); GPIO_ResetBits(GPIOC, GPIO_PIN_7); delay_ms(100); } } 好了以上就是实现bootloader的步骤。 第三、app应用的编写测试。 首先在KEIL工程的Target设置页设置FLASH ROM起始地址为0x08005200。 然后添加编译后的axf固件转bin文件。 添加命令: D:\Keil_v5\ARM\ARMCLANG\bin\fromelf.exe --bin -o "$L@L.bin" "#L" 下一步在system_n32g45x.c中找到VECT_TAB_OFFSET定义,修改为0x5200。这个是设置中断向量表首地址。 /* #define VECT_TAB_SRAM */ #define VECT_TAB_OFFSET 0x5200 /*!< Vector Table base offset field. This value must be a multiple of 0x200. */ 修改以上内容后就可以正常写APP应用了。编译之后将bin文件复制到SD卡中。 下面是APP测试代码: int main(void) { RCC->APB2PRST = 0x0; RCC->APB1PRST = 0x0; RCC->AHBPRST = 0x0; SysTick_Config(SystemCoreClock/1000); init_cycle_counter(true); RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO , ENABLE); GPIO_ConfigPinRemap(GPIO_RMP_SW_JTAG_NO_NJTRST, ENABLE); /* Initialize Led1~Led5 as output pushpull mode*/ GPIOInit(GPIOA, GPIO_PIN_8, 1); GPIOInit(GPIOB, GPIO_PIN_4, 1); GPIOInit(GPIOB, GPIO_PIN_5, 1); GPIOInit(GPIOC, GPIO_PIN_7, 1); /* USART Init */ USART_Config(); printf("app code running.\r\n"); printf("sysclk=%u\r\n",SystemCoreClock); if(FATFS_Init(&fs,path) == 0) { } while (1) { GPIO_SetBits(GPIOB, GPIO_PIN_4); GPIO_SetBits(GPIOB, GPIO_PIN_5); GPIO_SetBits(GPIOA, GPIO_PIN_8); GPIO_SetBits(GPIOC, GPIO_PIN_7); delay_ms(1000); GPIO_ResetBits(GPIOB, GPIO_PIN_4); GPIO_ResetBits(GPIOB, GPIO_PIN_5); GPIO_ResetBits(GPIOA, GPIO_PIN_8); GPIO_ResetBits(GPIOC, GPIO_PIN_7); delay_ms(1000); printf("app code running.\r\n"); } } 通过串口打印数据,可以看到写FLASH的APP更新过程,然后跳转到APP运行。
程序代码: |