WoodData 发表于 2023-3-31 18:01

【开源活动】基于国民N32G45x的SD卡IAP升级

本帖最后由 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的初始化代码/** * @name    drv_spi_init * @brief   spi 外设初始化 * @param   None * @returnNone */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文件系统移植了。主要实现文件系统需要的几个函数。代码实现如下:/*** @briefInitializes a Drive* @paramlun : not used* @retval DSTATUS: Operation status*/DSTATUS SD_initialize(BYTE lun){    if(MSD_Init() == 0)    {      MSD_GetCardInfo(&CardInfo);      return 0;    }else    return STA_NOINIT;} /*** @briefGets Disk Status* @paramlun : not used* @retval DSTATUS: Operation status*/DSTATUS SD_status(BYTE lun){    return 0;} /*** @briefReads Sector(s)* @paramlun : not used* @param*buff: Data buffer to store read data* @paramsector: Sector address (LBA)* @paramcount: 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;    }} /*** @briefWrites Sector(s)* @paramlun : not used* @param*buff: Data to be written* @paramsector: Sector address (LBA)* @paramcount: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/#if _USE_WRITE == 1DRESULT 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) );    }else   /* 多个sector的写操作 */    {         res = MSD_WriteMultiBlock(sector , (uint8_t *)(&buff) , count );    }    if(res == 0)    {      return RES_OK;    }else    {       return RES_ERROR;    }}#endif /* _USE_WRITE == 1 */ /*** @briefI/O control operation* @paramlun : not used* @paramcmd: Control code* @param*buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/#if _USE_IOCTL == 1DRESULT 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_drvTypeDefSD_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);        returnres;}文件系统配置在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地址开始。#defineAPP_FW_START_ADDR   (uint32_t)(0x08005200)#defineAPP_FW_FLASH_SIZE      (uint32_t)(1024*100)      //100K#defineAPP_FW_PAGE_SIZE       (uint32_t)(0x00000800)    //2048#defineAPP_FW_INFO_ADDR      (APP_FW_START_ADDR - 0x200)#defineAPP_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;    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函数/** * @briefMain 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运行。
程序代码:**** Hidden Message *****

liszt99 发表于 2023-4-4 08:27

下载下来,学习学习

wifi99 发表于 2023-4-4 08:28

我也来学习学习

18565779961 发表于 2023-5-5 11:40

下载下来,学习学习

jade0715 发表于 2023-5-25 15:01

厉害,学习学习

sunrui591027 发表于 2023-6-10 11:30

非常感谢楼主分享

chenjun89 发表于 2023-6-10 22:31

不错,下载学习学习。

chenqianqian 发表于 2023-6-10 22:56

感谢开源,立创免费打样YYDS。

星星心 发表于 2023-7-4 16:58

学习学习

牛牛ko 发表于 2023-7-5 17:39

下载学习

wwppd 发表于 2023-7-10 14:27

将APP程序从SD卡搬运到MCU中 首先从sd卡通过FAFTS文件操作系统打开程序文件,然后记录下复制开始地址和程序文件大小

ingramward 发表于 2023-7-10 14:51

SD卡IAP升级速度很快吗?            

wengh2016 发表于 2023-7-10 15:25

SD卡型号和使用的文件系统而有所差异吧

pixhw 发表于 2023-7-10 16:00

运行 SD 卡中的升级程序,并将升级程序的数据写入单片机系统中

iyoum 发表于 2023-7-10 16:36

单片机的固件升级方式通常有多种

jtracy3 发表于 2023-7-10 17:13

如何实现程序的远程OAT呢?            

wangdezhi 发表于 2023-7-10 17:50

在SD卡IAP升级过程中,需要保证SD卡的稳定性和可靠性

chejia12 发表于 2023-7-27 08:45

666666

X1aoBa 发表于 2023-7-28 10:23

学习学习

ehua 发表于 2023-9-23 17:16

如果您要查看本帖隐藏内容请回复
页: [1] 2 3 4 5
查看完整版本: 【开源活动】基于国民N32G45x的SD卡IAP升级