打印
[活动专区]

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

[复制链接]
1246|43
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 WoodData 于 2023-3-31 18:06 编辑

#申请原创#
@安小芯

写在前面的:
感谢21IC平台和国名技术举办的这次基于N32G45xSDIAP升级开发活动。看到这个活动就申请了,主要是对IAP升级感兴趣,非常幸运成功入选。
IAP应用升级主要是为了不使用下载调试器,而是通过MCU的外设通信接口实现更新应用固件的方法。在这之前我也做过通过串口UARTUSBU盘模式更新固件。这样可以方便产品有BUG时,在用户本地更新。再加上对固件进行加密和MCU升级更新时解密,就可以直接给用户自己更新,还不怕泄露固件。
第一、目标规划
本次目标是实现基于国名的N32G45X单片机实现SD卡的IAP升级。我将实现目标分为以下步骤。
首先就要实现的功能:
1、MCUflash擦除,读和写功能。
2、SD卡的驱动移植,包含SD卡的读写数据。
3、文件系统的移植,这里移植最常用的FATFS文件系统。这样对SD内文件操作起来更方便。
4、主要是设计触发进入升级更新的方法,以及更新完之后的跳转到应用APP.
第二、按照功能步骤逐个调试。
1、首先是FLASH读写、擦除
关于MCUFLASH操作,参考国名技术的例子资料。整理代码如下:
//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接口的,就直接用上了。
使用的是MCUSPI1外设,端口如下:
*    PA4  <===========>  CS
*    PA5  <===========>  SCK
*    PA6  <===========>  MISO
*    PA7  <===========>  MOSI
下面是SPI的初始化代码
/**
* @name    drv_spi_init
* @brief   spi 外设初始化
* @param   None
* @return  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文件系统移植了。
主要实现文件系统需要的几个函数。
代码实现如下:
/**
  * @brief  Initializes a Drive
  * @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)    /* 1sector的读操作 */
    {      
        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)    /* 1sector的写操作 */
    {   
        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更新。
BOOTLOADERAPP应用的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");
    }
}
通过串口打印数据,可以看到写FLASHAPP更新过程,然后跳转到APP运行。

程序代码:
游客,如果您要查看本帖隐藏内容请回复
  

使用特权

评论回复
沙发
liszt99| | 2023-4-4 08:27 | 只看该作者
下载下来,学习学习

使用特权

评论回复
板凳
wifi99| | 2023-4-4 08:28 | 只看该作者
我也来学习学习

使用特权

评论回复
地板
18565779961| | 2023-5-5 11:40 | 只看该作者
下载下来,学习学习

使用特权

评论回复
5
jade0715| | 2023-5-25 15:01 | 只看该作者
厉害,学习学习

使用特权

评论回复
6
sunrui591027| | 2023-6-10 11:30 | 只看该作者
非常感谢楼主分享

使用特权

评论回复
7
chenjun89| | 2023-6-10 22:31 | 只看该作者
不错,下载学习学习。

使用特权

评论回复
8
chenqianqian| | 2023-6-10 22:56 | 只看该作者
感谢开源,立创免费打样YYDS。

使用特权

评论回复
9
星星心| | 2023-7-4 16:58 | 只看该作者
学习学习

使用特权

评论回复
10
牛牛ko| | 2023-7-5 17:39 | 只看该作者
下载学习

使用特权

评论回复
11
wwppd| | 2023-7-10 14:27 | 只看该作者
将APP程序从SD卡搬运到MCU中 首先从sd卡通过FAFTS文件操作系统打开程序文件,然后记录下复制开始地址和程序文件大小

使用特权

评论回复
12
ingramward| | 2023-7-10 14:51 | 只看该作者
SD卡IAP升级速度很快吗?              

使用特权

评论回复
13
wengh2016| | 2023-7-10 15:25 | 只看该作者
SD卡型号和使用的文件系统而有所差异吧

使用特权

评论回复
14
pixhw| | 2023-7-10 16:00 | 只看该作者
运行 SD 卡中的升级程序,并将升级程序的数据写入单片机系统中

使用特权

评论回复
15
iyoum| | 2023-7-10 16:36 | 只看该作者
单片机的固件升级方式通常有多种

使用特权

评论回复
16
jtracy3| | 2023-7-10 17:13 | 只看该作者
如何实现程序的远程OAT呢?              

使用特权

评论回复
17
wangdezhi| | 2023-7-10 17:50 | 只看该作者
在SD卡IAP升级过程中,需要保证SD卡的稳定性和可靠性

使用特权

评论回复
18
chejia12| | 2023-7-27 08:45 | 只看该作者
666666

使用特权

评论回复
19
X1aoBa| | 2023-7-28 10:23 | 只看该作者
学习学习

使用特权

评论回复
20
ehua| | 2023-9-23 17:16 | 只看该作者
如果您要查看本帖隐藏内容请回复

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

115

主题

4395

帖子

25

粉丝