单片机IAP固件升级

认识IAP

单片机的 IAP(In-Application Programming)指的是“在应用中编程”,它是一种通过程序控制单片机的固件更新或修改的技术。在IAP模式下,单片机可以在不外接编程器或脱离主系统的情况下,通过自身的程序来实现对其内部存储器(如Flash)的重新编程。

IAP的工作原理:

  1. 分区存储:单片机通常将程序分为两个区域:应用程序区和用于编程的存储区(如Flash存储)。通常,主程序运行在Flash的一部分区域,而另一部分区域保留用于存储新的固件代码。

  2. 固件更新:在IAP模式下,单片机的固件可以通过串口、USB、I2C、SPI等外部接口接收新的固件文件。接收到固件文件后,单片机会将这些文件存储在Flash的备用区域。

  3. 自我更新:一旦新的固件文件存储到指定区域后,单片机的程序可以在运行过程中触发IAP操作,将新的程序代码写入到Flash中,然后在下次启动时执行新的程序。

IAP的应用场景:

  1. 远程升级:IAP可以支持单片机通过外部接口进行远程固件升级,适用于不便于现场人工更换芯片的应用场景,如智能家居、嵌入式设备、物联网设备等。

  2. 应急修复:在设备出现问题或固件有bug时,IAP可以用来快速修复程序,而无需人工干预,极大提高了设备的可维护性。

  3. 增加系统的灵活性:通过IAP技术,系统可以实现定期的固件更新,使得设备在使用过程中能够不断优化、增强功能或修复漏洞。

IAP的关键技术:

  • Flash存储编程:IAP技术依赖单片机的Flash存储,通常需要通过程序控制对Flash的写入、擦除等操作。

  • 引导程序:IAP操作通常需要一个小型的引导程序(Bootloader),这个程序驻留在单片机的启动区,负责检查是否有新的固件更新请求,并执行相应的固件编程操作。

  • 通信接口:IAP需要通过某种通信协议(如UART、SPI、CAN等)来接收新的固件数据,并将其写入Flash中。

IAP实现的步骤:

  1. 引导程序启动:系统启动时,首先运行引导程序(Bootloader),它检查是否有固件更新请求。

  2. 接收新的固件:如果有更新,Bootloader通过通信接口接收新的固件数据,并将其存储在Flash的备用区域。

  3. 数据校验:引导程序会对接收到的数据进行校验,确保固件数据完整性。

  4. 擦除与编程:然后,程序会擦除原Flash区域的数据,并将新固件写入。

  5. 跳转执行:最后,引导程序会跳转到新的固件地址,开始执行更新后的应用程序。

注意事项:

  • Flash写入限制:Flash存储器通常有一定的擦写次数限制,因此IAP操作时需要注意避免过度擦写。
  • 固件兼容性:进行IAP时,更新的固件需要确保与硬件和其他软件部分兼容,以防止引导程序执行失败或设备崩溃。
  • 安全性:IAP过程需要采取一定的安全措施,如加密传输和校验,防止固件被恶意篡改。

总结:

IAP技术能够让单片机在应用运行时自行更新固件,无需外部编程器或人工干预,极大提高了设备的维护性和灵活性。它在嵌入式系统、物联网设备、智能硬件等领域具有重要的应用价值。

Bootloader(引导程序)是嵌入式系统和单片机中用于启动操作系统或应用程序的基础软件。它的主要作用是在系统上电或重启时,初始化硬件并加载并执行主程序或操作系统。简而言之,Bootloader是设备开机时首先运行的程序,它帮助将设备从一个无操作系统的状态启动到一个有操作系统或应用程序的状态。

Bootloader的功能:

  1. 硬件初始化:Bootloader在系统启动时,首先会对硬件进行必要的初始化操作,比如设置时钟、配置I/O引脚、初始化存储设备等。这是为了确保硬件能够正确工作,以便后续的软件可以正常执行。

  2. 加载主程序:Bootloader的最主要任务是从某个存储介质(如Flash、EEPROM、SD卡等)中加载主程序或操作系统,并将其转交给CPU执行。在单片机和嵌入式系统中,主程序通常存储在闪存(Flash)或其他非易失性存储器中。

  3. 固件更新:在一些系统中,Bootloader还支持通过外部接口(如串口、USB、网络等)进行固件更新。它可以接收新的固件文件并将其写入系统的存储器中,然后重新启动系统以运行更新后的固件。

  4. 系统恢复:如果系统的主程序损坏或者不可用,Bootloader通常也提供一种恢复机制,允许设备恢复到一个已知的工作状态,通常是从备份或默认的固件进行恢复。

  5. 安全功能:在某些情况下,Bootloader也负责检查系统固件的完整性,防止加载被篡改的程序。它可以进行签名验证、加密解密等安全性操作,确保只有合法的固件能够被加载。

Bootloader的工作过程:

  1. 上电或复位:当设备上电或复位时,CPU首先执行位于特殊地址(通常是0x0000或其他特定位置)的一段代码,这段代码就是Bootloader。

  2. 硬件初始化:Bootloader首先会初始化必需的硬件资源,例如时钟、内存、存储设备、通信接口等,确保系统能正常工作。

  3. 固件选择:在一些设备中,Bootloader会检查某个存储介质(如Flash)中是否存在新的固件或操作系统。如果存在,Bootloader会加载新的固件或操作系统。如果没有,它将执行默认的主程序。

  4. 加载并启动主程序:Bootloader将主程序或操作系统加载到内存中,然后将控制权交给主程序,系统开始正常运行。

  5. 等待用户命令或输入(可选):一些Bootloader还可以提供用户交互界面,允许用户通过按键、串口命令等方式选择执行特定操作,如恢复出厂设置、启动调试模式或进行固件升级。

Bootloader的类型:

  1. 简单Bootloader:只负责从存储设备加载并执行主程序,没有太多附加功能。它通常小而简洁,占用的存储空间少,适用于一些资源有限的设备。

  2. 功能丰富的Bootloader:除了加载主程序外,还提供固件更新、调试支持、安全性验证等功能。比如U-Boot、RedBoot、ARM的CMSIS-DAP等。

  3. 引导程序的安全版本:有些Bootloader还支持安全功能,比如签名验证、加密解密等,防止恶意软件或病毒通过篡改固件来攻击设备。这类Bootloader通常用于需要高安全性的场合,如金融设备、医疗设备等。

Bootloader的应用场景:

  • 嵌入式设备:大多数嵌入式系统和单片机(如Arduino、STM32、ESP32等)都有Bootloader,用于启动应用程序或操作系统。

  • 固件升级:在一些设备中,Bootloader可以支持固件的在线或离线升级,通常是通过外部接口(如串口、USB、网络)下载并写入新的固件。

  • 恢复功能:如果系统主程序出现问题,Bootloader可以提供恢复机制,通过备份或默认固件来恢复系统。

  • 安全设备:在高安全性的设备中,Bootloader会包含固件的完整性验证机制,防止设备运行被篡改的恶意代码。

示例:STM32的Bootloader

对于STM32单片机来说,Bootloader是预先存储在芯片中的一段小程序,通常在系统的起始地址运行。它的任务包括:

  • 初始化系统时钟、外设等。
  • 支持通过串口(USART)、USB等接口接收固件,进行固件更新。
  • 在没有外部编程器的情况下,可以通过外部接口实现固件的恢复或升级。

总结:

Bootloader是一个非常重要的程序,它为嵌入式系统和单片机提供了启动、加载和更新主程序的能力。它通常在设备上电时首先执行,确保设备能够从存储设备加载并运行主应用程序。通过支持固件更新、恢复机制和安全功能,Bootloader在现代嵌入式设备中扮演着至关重要的角色。

知识点补充

flash相关

1、32位单片机flash地址一般从0x80000000开始,SRAM地址从0x20000000

2、在操作flash时需要知道对应MCU Flash扇区划分大小,flash擦除只能一个扇区一个扇区的擦除,并且Flash编程最小单位为半个字,对于32为单片机来说半个字就是两个字节。注意在对flash操作前要先解锁flash,清除flash标志,操作完成后对flash进行上锁。

3、keil可以对单片机flash编程起始地址进行划分,如果Flash起始地址不是0x80000000,在应用程序main要对单片机Flash地址进行偏移,偏移量相对0x80000000的大小。

注意:

单片机支持最小擦除是扇区擦除还是叶擦除,查看对应mcu库函数进行查看解释。

我用的是串口接收,接收数据大小为一个字节所以需要将两个字节拼成一个字在进行编程。

跳转相关

/**
 * @brief       跳转到应用程序段(执行APP)
 * @param       appxaddr : 应用程序的起始地址
 * @retval      无
 */
void iap_load_app(uint32_t appxaddr)
{
    uint8_t i  = 0;
    if (((*(volatile  uint32_t *)appxaddr) & 0x2FFE0000) == 0x20000000)     /* 检查栈顶地址是否合法.可以放在内部SRAM共64KB(0x20000000) */
    {
        
         /* 用户代码区第二个字为程序开始地址(复位地址) */
        jump2app = (iapfun) * (volatile uint32_t *)(appxaddr + 4); 


        
        /* 关闭全局中断 */
        __set_PRIMASK(1); 
        
        
                 
        /* 关闭滴答定时器,复位到默认值 */
        SysTick->CTRL = 0;
        SysTick->LOAD = 0;
        SysTick->VAL = 0;
        
        //复位时钟
        rcu_deinit();

        
                /* 关闭所有中断,清除所有中断挂起标志 */  
        for (i = 0; i < 8; i++)
        {
            NVIC->ICER[i]=0xFFFFFFFF;
            NVIC->ICPR[i]=0xFFFFFFFF;
        }
        
        /* 使能全局中断 */ 
        __set_PRIMASK(0);
        
        /* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
        __set_CONTROL(0);
        

        
        /* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
        sys_msr_msp(*(volatile uint32_t *)appxaddr);
        
        /* 跳转到APP */
        jump2app();
        
        /* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
        while (1)
        {
            delay_1ms(1000);
            printf("iap_load_app函数跳转失败");
        }
    }
}

整体代码

IAP.c

#include "iap.h"



iapfun jump2app;//APP函数指针

uint8_t IAPReceiveBuffer[IAPReceiveBufferLength] = {0};//串口接收APPbin文件Buffer

uint8_t IAPReceiveBufferFlag = 0;//0还没有接收到,1正在接收,2接收完成

void sys_msr_msp(uint32_t addr)
{
    __set_MSP(addr);    /* 设置栈顶地址 */
}


/*
获取下一片扇区号
输入:当前扇区号
输出:下一片扇区号
*/
uint32_t GetNextSectorNumberFun(uint32_t NowectorNumber)
{
    //printf("switch:%x",NowectorNumber);
    switch(NowectorNumber)
    {
         case CTL_SECTOR_NUMBER_0:return CTL_SECTOR_NUMBER_1;
         case CTL_SECTOR_NUMBER_1:return CTL_SECTOR_NUMBER_2;
         case CTL_SECTOR_NUMBER_2:return CTL_SECTOR_NUMBER_3;
         case CTL_SECTOR_NUMBER_3:return CTL_SECTOR_NUMBER_4;
         case CTL_SECTOR_NUMBER_4:return CTL_SECTOR_NUMBER_5;
         case CTL_SECTOR_NUMBER_5:return CTL_SECTOR_NUMBER_6;
         case CTL_SECTOR_NUMBER_6:return CTL_SECTOR_NUMBER_7;
         case CTL_SECTOR_NUMBER_7:return CTL_SECTOR_NUMBER_8;
    }
    //return  NowectorNumber+8;
}


/*
输入扇区号返回该扇区Flash大小
输入:扇区号
输出:flash扇区大小,单位字节
*/
static uint32_t GetNowFlashSectorSize(uint32_t NowectorNumber)
{
    switch(NowectorNumber)
    {
         case CTL_SECTOR_NUMBER_0:return ADDR_FMC_SECTOR_0_3_SIZE;
         case CTL_SECTOR_NUMBER_1:return ADDR_FMC_SECTOR_0_3_SIZE;
         case CTL_SECTOR_NUMBER_2:return ADDR_FMC_SECTOR_0_3_SIZE;
         case CTL_SECTOR_NUMBER_3:return ADDR_FMC_SECTOR_0_3_SIZE;
         case CTL_SECTOR_NUMBER_4:return ADDR_FMC_SECTOR_4_SIZE;
         case CTL_SECTOR_NUMBER_5:return ADDR_FMC_SECTOR_5_11_SIZE;
         case CTL_SECTOR_NUMBER_6:return ADDR_FMC_SECTOR_5_11_SIZE;
         case CTL_SECTOR_NUMBER_7:return ADDR_FMC_SECTOR_5_11_SIZE;
         case CTL_SECTOR_NUMBER_8:return ADDR_FMC_SECTOR_5_11_SIZE;
    }
    return 0;
}


/*!
根据输入地址返回对应的扇区号
*/
static uint32_t fmc_sector_get(uint32_t address)
{
    uint32_t sector = 0;
    if((address < ADDR_FMC_SECTOR_1) && (address >= ADDR_FMC_SECTOR_0)) {
        sector = CTL_SECTOR_NUMBER_0;
    } else if((address < ADDR_FMC_SECTOR_2) && (address >= ADDR_FMC_SECTOR_1)) {
        sector = CTL_SECTOR_NUMBER_1;
    } else if((address < ADDR_FMC_SECTOR_3) && (address >= ADDR_FMC_SECTOR_2)) {
        sector = CTL_SECTOR_NUMBER_2;
    } else if((address < ADDR_FMC_SECTOR_4) && (address >= ADDR_FMC_SECTOR_3)) {
        sector = CTL_SECTOR_NUMBER_3;
    } else if((address < ADDR_FMC_SECTOR_5) && (address >= ADDR_FMC_SECTOR_4)) {
        sector = CTL_SECTOR_NUMBER_4;
    } else if((address < ADDR_FMC_SECTOR_6) && (address >= ADDR_FMC_SECTOR_5)) {
        sector = CTL_SECTOR_NUMBER_5;
    } else if((address < ADDR_FMC_SECTOR_7) && (address >= ADDR_FMC_SECTOR_6)) {
        sector = CTL_SECTOR_NUMBER_6;
    } else if((address < ADDR_FMC_SECTOR_8) && (address >= ADDR_FMC_SECTOR_7)) {
        sector = CTL_SECTOR_NUMBER_7;
    } else if((address < ADDR_FMC_SECTOR_9) && (address >= ADDR_FMC_SECTOR_8)) {
        sector = CTL_SECTOR_NUMBER_8;
    } else if((address < ADDR_FMC_SECTOR_10) && (address >= ADDR_FMC_SECTOR_9)) {
        sector = CTL_SECTOR_NUMBER_9;
    } else if((address < ADDR_FMC_SECTOR_11) && (address >= ADDR_FMC_SECTOR_10)) {
        sector = CTL_SECTOR_NUMBER_10;
    } else {
        sector = CTL_SECTOR_NUMBER_11;
    }
    return sector;
}

/*
擦除要使用的Flash扇区
输入:APPStarAddr      flash扇区起始地址
        NumToWrite,APP文件总大小,字节
*/
uint8_t EraseSectorsApp(uint32_t APPStarAddr,uint32_t NumToWrite)
{
    uint32_t NowFlashSectorNumber;//当前flash扇区号
    uint32_t RemainderToWriteNum = NumToWrite;
    uint32_t  CurrentSectorSize ; 
    fmc_state_enum fmc_sector_erase_flag;
    
    delay_1ms(1000);//调试防止过度擦除
    //fmc_unlock();//解锁
    NowFlashSectorNumber = fmc_sector_get(APPStarAddr);//先根据写入起始地址得到该扇区号
    
    while(1)
    {

        delay_1ms(1000);//调试防止过度擦除
        CurrentSectorSize = GetNowFlashSectorSize(NowFlashSectorNumber);//获取当前扇区大小
        //剩余大小与片扇区作比较,判断是否需要继续
        if(RemainderToWriteNum > CurrentSectorSize)
        {
            fmc_sector_erase_flag = fmc_sector_erase(NowFlashSectorNumber);//擦除扇区
            switch(fmc_sector_erase_flag)
            {
                case FMC_READY:printf("EraseSectorsApp_相关flash区擦除完成\r\n");break;
                case FMC_BUSY:printf("EraseSectorsApp_相关flash区忙\r\n");break;  
                case FMC_RDDERR:printf("EraseSectorsApp_读取D总线保护错误\r\n");break;  
                case FMC_PGSERR:printf("EraseSectorsApp_程序序列错误\r\n");break; 
                case FMC_PGMERR:printf("EraseSectorsApp_序大小不匹配错误\r\n");break; 
                case FMC_WPERR:printf("EraseSectorsApp_擦除/编程保护错误\r\n");break;   
                case FMC_OPERR:printf("EraseSectorsApp_操作错误\r\n");break; 
                case FMC_TOERR:printf("EraseSectorsApp_超时错误\r\n");break;
            }
            if(fmc_sector_erase_flag != FMC_READY)
            {
                printf("flash擦除失败\r\n");
                printf("擦除失败扇区地址:0x%x\r\n",NowFlashSectorNumber);
                return FunReturnDefeat;
            }
            else
            {
                printf("flash擦除成功\r\n");
                printf("擦除成功扇区号:0x%x\r\n",NowFlashSectorNumber);
                
            }
            RemainderToWriteNum  = RemainderToWriteNum - CurrentSectorSize;//更新剩余传输大小
            NowFlashSectorNumber =  GetNextSectorNumberFun(NowFlashSectorNumber);//更新当前扇区号
            
        }
        else
        {
            fmc_sector_erase_flag = fmc_sector_erase(NowFlashSectorNumber);//擦除扇区
            switch(fmc_sector_erase_flag)
            {
                case FMC_READY:printf("EraseSectorsApp_相关flash区擦除完成\r\n");break;
                case FMC_BUSY:printf("EraseSectorsApp_相关flash区忙\r\n");break;  
                case FMC_RDDERR:printf("EraseSectorsApp_读取D总线保护错误\r\n");break;  
                case FMC_PGSERR:printf("EraseSectorsApp_程序序列错误\r\n");break; 
                case FMC_PGMERR:printf("EraseSectorsApp_序大小不匹配错误\r\n");break; 
                case FMC_WPERR:printf("EraseSectorsApp_擦除/编程保护错误\r\n");break;   
                case FMC_OPERR:printf("EraseSectorsApp_操作错误\r\n");break; 
                case FMC_TOERR:printf("EraseSectorsApp_超时错误\r\n");break;
            }
            if(fmc_sector_erase_flag != FMC_READY)
            {
                printf("flash擦除失败返回\r\n");
                printf("擦除失败扇区地址:0x%x\r\n",NowFlashSectorNumber);
                return  FunReturnDefeat;
                //fmc_lock(); //上锁
                
            }
            else
            {
                printf("flash擦除成功返回\r\n");
                printf("擦除扇区地址:0x%x\r\n",NowFlashSectorNumber);
                return  FunReturnSuccess;
                //fmc_lock(); //上锁

            }            
        }

    }
}


/*
向Flash中写入数据
输入:WriteAddrAPP起始扇区地址
        pBuffer,数据缓冲地址
        NumToWrite,写入数据大小,最大为1个扇区大小
*/
static uint8_t WriteDataToFlash(uint32_t WriteAddr,uint8_t *pBuffer,uint32_t NumToWrite)
{
    uint32_t i;
    uint16_t temp;
    uint8_t FlashWritFlag = 1;
    for(i = 0; i < NumToWrite; i += 2)
    {
        temp  = (uint16_t)pBuffer[i + 1] << 8;
        temp |= (uint16_t)pBuffer[i];
        FlashWritFlag = fmc_halfword_program(WriteAddr, temp);
        if(FlashWritFlag != 0)
        {
            printf("Flash写入失败,失败号:%d\r\n",FlashWritFlag);
            return FunReturnDefeat;
        }
        WriteAddr += 2;//写了半个字,地址偏移两个
    }
    printf("写入成功\r\n");
    return FunReturnSuccess;
}




/*
将IAP书写到Flash中



*/
uint8_t iap_write_appbin(uint32_t WriteAddr,uint8_t *pBuffer,uint32_t NumToWrite)
{
    uint8_t Funflag;
    uint32_t NowFlashSectorNumber;//当前flash扇区号
    uint32_t RemainderToWriteNum = NumToWrite;//剩余写入大小    
    uint32_t CurrentSectorSize ;//当前应扇区的大小
    uint8_t* SourceDataAddress = pBuffer;//源数据地址
    uint32_t DestinationDataAddress = WriteAddr;//目标数据地址
    
    fmc_unlock();//解锁
    
    /* clear pending flags 清除待处理标志*/
    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_OPERR | FMC_FLAG_WPERR | FMC_FLAG_PGMERR | FMC_FLAG_PGSERR);
    
    //擦除相关flash区
    Funflag = EraseSectorsApp(WriteAddr,NumToWrite);
    if(Funflag != FunReturnSuccess)
    {
        printf("iap_write_appbin函数擦除失败\r\n");
        fmc_lock(); //上锁
        return FunReturnDefeat;
    }
    
    while(1)
    {
         //写数据到flash    
        NowFlashSectorNumber = fmc_sector_get(DestinationDataAddress);//先根据写入起始地址得到该扇区号 
        
        CurrentSectorSize = GetNowFlashSectorSize(NowFlashSectorNumber);//获取当前扇区大小
        
        if(RemainderToWriteNum > CurrentSectorSize)
        {
            //
            Funflag = WriteDataToFlash(DestinationDataAddress,SourceDataAddress,CurrentSectorSize);
            if(Funflag != FunReturnSuccess)
            {
                printf("iap_write_appbin函数写入失败\r\n");
                fmc_lock(); //上锁
                return FunReturnDefeat;
            }
            DestinationDataAddress = DestinationDataAddress + CurrentSectorSize;
            RemainderToWriteNum = RemainderToWriteNum - CurrentSectorSize;
            SourceDataAddress = SourceDataAddress + CurrentSectorSize;
        }
        else
        {
            Funflag = WriteDataToFlash(DestinationDataAddress,SourceDataAddress,RemainderToWriteNum);
            if(Funflag != FunReturnSuccess)
            {
                printf("iap_write_appbin函数写入失败\r\n");
                fmc_lock(); //上锁
                return FunReturnDefeat;
            }
            else
            {
                printf("写入结束\r\n");
                fmc_lock(); //上锁
                return FunReturnSuccess;
            }
        
        }
    }



}



/**
 * @brief       跳转到应用程序段(执行APP)
 * @param       appxaddr : 应用程序的起始地址
 * @retval      无
 */
void iap_load_app(uint32_t appxaddr)
{
    uint8_t i  = 0;
    if (((*(volatile  uint32_t *)appxaddr) & 0x2FFE0000) == 0x20000000)     /* 检查栈顶地址是否合法.可以放在内部SRAM共64KB(0x20000000) */
    {
        
         /* 用户代码区第二个字为程序开始地址(复位地址) */
        jump2app = (iapfun) * (volatile uint32_t *)(appxaddr + 4); 


        
        /* 关闭全局中断 */
        __set_PRIMASK(1); 
        
        
                 
        /* 关闭滴答定时器,复位到默认值 */
        SysTick->CTRL = 0;
        SysTick->LOAD = 0;
        SysTick->VAL = 0;
        
        //复位时钟
        rcu_deinit();

        
                /* 关闭所有中断,清除所有中断挂起标志 */  
        for (i = 0; i < 8; i++)
        {
            NVIC->ICER[i]=0xFFFFFFFF;
            NVIC->ICPR[i]=0xFFFFFFFF;
        }
        
        /* 使能全局中断 */ 
        __set_PRIMASK(0);
        
        /* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
        __set_CONTROL(0);
        

        
        /* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
        sys_msr_msp(*(volatile uint32_t *)appxaddr);
        
        /* 跳转到APP */
        jump2app();
        
        /* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
        while (1)
        {
            delay_1ms(1000);
            printf("iap_load_app函数跳转失败");
        }
    }
}

 IAP.h

#ifndef __SYSIAP_H
#define __SYSIAP_H


#include "LYSys.h"

//扇区号
#define ADDR_FMC_SECTOR_0    ((uint32_t )0x08000000) 
#define ADDR_FMC_SECTOR_1    ((uint32_t )0x08004000) 
#define ADDR_FMC_SECTOR_2    ((uint32_t )0x08008000)
#define ADDR_FMC_SECTOR_3    ((uint32_t )0x0800C000) 
#define ADDR_FMC_SECTOR_4    ((uint32_t )0x08010000)
#define ADDR_FMC_SECTOR_5    ((uint32_t )0x08020000)
#define ADDR_FMC_SECTOR_6    ((uint32_t )0x08040000)
#define ADDR_FMC_SECTOR_7    ((uint32_t )0x08060000)
#define ADDR_FMC_SECTOR_8    ((uint32_t )0x08080000)//没有扩展的话最扇区地址最大就到0x0807FFFF
#define ADDR_FMC_SECTOR_9    ((uint32_t )0x080A0000)
#define ADDR_FMC_SECTOR_10   ((uint32_t )0x080C0000)
#define ADDR_FMC_SECTOR_11   ((uint32_t )0x080E0000)

//扇区号对应的扇区大小


#define ADDR_FMC_SECTOR_0_3_SIZE          0X4000
#define ADDR_FMC_SECTOR_4_SIZE            0X10000
#define ADDR_FMC_SECTOR_5_11_SIZE         0X20000


typedef void (*iapfun)(void);                   /* 定义一个函数类型的参数 */

#define FLASH_APP1_ADDR         0x08004000      /* 第一个应用程序起始地址(存放在内部FLASH)
                                                 * 保留 0x08000000~0x0800FFFF 的空间为 Bootloader 使用(共64KB)
                                                 */
#define APPStartAddFlashSectorSize 0x4000       //APP起始Flash对应的扇区大小
                                                 
#define IAPReceiveBufferLength 1024*100         //开辟100K字的空间,用于存储接收到的bin文件                                                 



void iap_load_app(uint32_t appxaddr);           /* 跳转到APP程序执行 */

uint8_t iap_write_appbin(uint32_t WriteAddr,uint8_t *pBuffer,uint32_t NumToWrite);//flash写入函数




#endif

main文件

/*全局变量声明*/
extern uint8_t IAPReceiveBuffer[IAPReceiveBufferLength];
extern uint8_t IAPReceiveBufferFlag;
extern uint32_t recv_len;
uint32_t lastrecv_len = 0;


int main()
{
    uint16_t WiteIAPTimes = 0;
    systick_config();  /* 设置时钟,168Mhz */
    drv_uart_init(115200);;					//初始化USART
	printf("2024-07-10--welcome to you ! \r\nnow start booting...\r\n\
    if you need update program, you can start transfer file data, wait for minutes...\r\n");

    while(1)
    {
        for(WiteIAPTimes =0;WiteIAPTimes<20;WiteIAPTimes++)
        {
            delay_1ms(1000);
            
            if(IAPReceiveBufferFlag == 1)
            {
                printf("开始接收APP程序\r\n");
                while(IAPReceiveBufferFlag != 2)//等待APP接受数据完成
                {
                    __NOP();
                    delay_1ms(100);
                    printf("正在接收中\r\n");
                    if(recv_len == lastrecv_len)
                    {
                        IAPReceiveBufferFlag = 2 ;
                    }
                    lastrecv_len = recv_len;
                }
                printf("接收完成\r\n");
                printf("Main一共接受%d字节\r\n",recv_len);
                recv_len = 0;//可以重新接收
                break;
            } 
            printf("等待接受APP文件\r\n");            
        }       
        if(IAPReceiveBufferFlag == 0)
        {
            printf("没有接收到APP开始跳转\r\n");
            iap_load_app(FLASH_APP1_ADDR);
            //开始跳转
        }
        //走到这就是写入
        while(1)
        {
        
            delay_1ms(1000);
            printf("开始写入Flash\r\n");
            iap_write_appbin(FLASH_APP1_ADDR,IAPReceiveBuffer,lastrecv_len);
            break;       
        }

        delay_1ms(1000);
        printf("写入结束Flash\r\n");
        iap_load_app(FLASH_APP1_ADDR);
        
            
    
    }
}

 串口.c

#include "MyUart1.h"
/********************************************************************************************
书写日期 2024.10.10   师浩杰  
说明: 
1、nvic_irq_enable(USART1_IRQn, 0, 0);//在中断优先级使能函数里配置了中断分组为2,在这个函数里进行了是否中断分组的判断,因此可以在main函数里直接统一进行中断分组
**********************************************************************************************/


/*********************************Gitee仓库代码*****************************************/


//#include "gd32f4xx.h"

/*
IAP工程总结
1、我们不知道bin文件到底有多大,可以采用将串口接收到的数据直接用DMA放在我们的APP——FLASH区域
起始地址就是串口缓冲地址
*/


/***************对Gitee的代码进行优化*******************************/


#define USART0_DATA_ADDRESS    ((uint32_t)&USART_DATA(USART0))


extern uint8_t IAPReceiveBuffer[IAPReceiveBufferLength];
extern uint8_t IAPReceiveBufferFlag;



 UART_INFO_T s_uart_info = {USART0, 115200 ,RCU_USART0, RCU_GPIOA, GPIOA, GPIO_AF_7, GPIO_PIN_9, GPIO_PIN_10, \
                                    USART0_IRQn, DMA1, RCU_DMA1, DMA_CH5, DMA_SUBPERI4};


/**
***********************************************************
* @brief 串口引脚初始化
* @param
* @return 
***********************************************************
*/
static void drv_uart_gpio_init(void)
{
	rcu_periph_clock_enable(s_uart_info.rcu_gpio);
    /* connect port to USARTx_Tx */
    gpio_af_set(s_uart_info.gpio, s_uart_info.af_num, s_uart_info.tx_pin);
    /* connect port to USARTx_Rx */
    gpio_af_set(s_uart_info.gpio, s_uart_info.af_num, s_uart_info.rx_pin);   
    
    /* configure USART Tx as alternate function push-pull */
    gpio_mode_set(s_uart_info.gpio, GPIO_MODE_AF, GPIO_PUPD_PULLUP, s_uart_info.tx_pin);
    gpio_output_options_set(s_uart_info.gpio, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, s_uart_info.tx_pin);

    /* configure USART Rx as alternate function push-pull */
    gpio_mode_set(s_uart_info.gpio, GPIO_MODE_AF, GPIO_PUPD_PULLUP, s_uart_info.rx_pin);
    gpio_output_options_set(s_uart_info.gpio, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, s_uart_info.rx_pin);
}

/**
***********************************************************
* @brief 串口配置初始化
* @param
* @return 
***********************************************************
*/
static void dev_uart_config_init(uint32_t baudRate)
{
    /* USART configure */
    rcu_periph_clock_enable(s_uart_info.rcu_uart);
    usart_deinit(s_uart_info.uart_num);
    usart_baudrate_set(s_uart_info.uart_num,baudRate);
    usart_receive_config(s_uart_info.uart_num, USART_RECEIVE_ENABLE);
    usart_transmit_config(s_uart_info.uart_num, USART_TRANSMIT_ENABLE);
    
    /*DMA USART configure */
    usart_interrupt_enable(s_uart_info.uart_num, USART_INT_RBNE);   //使能空闲中断
    nvic_irq_enable(s_uart_info.irq, 0, 0);
    
    
    usart_enable(s_uart_info.uart_num);    
}

/**
***********************************************************
* @brief 串口DMA配置初始化
* @param
* @return 
***********************************************************
*/
static void dev_uart_dma_config_init(void)
{
    dma_single_data_parameter_struct dma_init_struct;
    /* enable DMA rcu*/
    rcu_periph_clock_enable(s_uart_info.rcu_dma);
    dma_single_data_para_struct_init(&dma_init_struct);
    dma_deinit(s_uart_info.dma_num, s_uart_info.dma_rx_ch);
    dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;               //配置传输方向
    dma_init_struct.memory0_addr = (uint32_t)IAPReceiveBuffer;        //配置数据目的地址
    dma_init_struct.periph_memory_width = DMA_MEMORY_WIDTH_8BIT;    //配置目的数据位宽
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;        //配置目的地址是固定还是增长
    dma_init_struct.periph_addr = USART0_DATA_ADDRESS;              //配置数据源地址
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;       //配置源地址是固定还是增长
    dma_init_struct.number = IAPReceiveBufferLength;                //配置数据传输最大个数
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;             //配置DMA数据通道优先级
    dma_single_data_mode_init(s_uart_info.dma_num, s_uart_info.dma_rx_ch, &dma_init_struct);
    /* configure DMA mode */
    dma_circulation_disable(s_uart_info.dma_num, s_uart_info.dma_rx_ch);
    dma_channel_subperipheral_select(s_uart_info.dma_num, s_uart_info.dma_rx_ch, s_uart_info.dma_sub_peri);    
    dma_interrupt_enable(s_uart_info.dma_num, s_uart_info.dma_rx_ch, DMA_CHXCTL_FTFIE);
    /* 使能UART接受数据使用DMA 使能DMA通道*/
    usart_dma_receive_config(s_uart_info.uart_num, USART_RECEIVE_DMA_ENABLE);
    dma_channel_enable(s_uart_info.dma_num, s_uart_info.dma_rx_ch);
}    


/**
***********************************************************
* @brief 串口初始化
* @param
* @return 
***********************************************************
*/
void drv_uart_init(uint32_t baudRate)
{
    drv_uart_gpio_init();
    dev_uart_config_init(baudRate);
    //dev_uart_dma_config_init();
}



/**
***********************************************************
* @brief 串口0中断服务函数
* @param
* @return 
***********************************************************
*/

volatile uint32_t recv_len = 0;
void USART0_IRQHandler(void)
{
    
    uint8_t receiveData = 0;
    
	if (RESET != usart_interrupt_flag_get(s_uart_info.uart_num, USART_INT_FLAG_RBNE))
	{
        usart_interrupt_flag_clear(s_uart_info.uart_num, USART_INT_FLAG_RBNE);
        receiveData = usart_data_receive(s_uart_info.uart_num);
        IAPReceiveBufferFlag = 1;
        IAPReceiveBuffer[recv_len++] = receiveData;
        
        
        /* DMA */
//        dma_channel_disable(s_uart_info.dma_num, s_uart_info.dma_rx_ch); 
//        recv_len = IAPReceiveBufferLength - dma_transfer_number_get(s_uart_info.dma_num, s_uart_info.dma_rx_ch);
//        //recv_len = dma_transfer_number_get(s_uart_info.dma_num, s_uart_info.dma_rx_ch);
//        printf("%.*s\n",recv_len,IAPReceiveBuffer);       
//        //memset(s_rcv_data_buf, 0, MAX_BUF_SIZE);
//        IAPReceiveBufferFlag = 2;
//        printf("一共接收到:%d 字节\r\n",recv_len);
//        dma_flag_clear(s_uart_info.dma_num, s_uart_info.dma_rx_ch, DMA_FLAG_FTF);
//        dma_transfer_number_config(s_uart_info.dma_num, s_uart_info.dma_rx_ch, IAPReceiveBufferLength);
//        dma_channel_enable(s_uart_info.dma_num, s_uart_info.dma_rx_ch);          
    }
}



/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
		 必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return 
***********************************************************
*/
int fputc(int ch, FILE *f)
{
	usart_data_transmit(s_uart_info.uart_num, (uint8_t)ch);
	while (RESET == usart_flag_get(s_uart_info.uart_num, USART_FLAG_TBE));
	return ch;
}


void DebugUSART0TransmitData(unsigned char *Data,unsigned int Lenth)
{
         uint8_t i = 0;
        for(;i< Lenth;i++)
        {
        	usart_data_transmit(s_uart_info.uart_num, Data[i]);
            while (RESET == usart_flag_get(s_uart_info.uart_num, USART_FLAG_TBE));
        
        }

}

串口.h

#ifndef __MYUART1_H
#define __MYUART1_H




#include "LYsys.h"

/*************************Gitee*****************************/




typedef struct
{
	uint32_t uart_num;//串口号
    uint32_t baudRate;//串口波特率
	rcu_periph_enum rcu_uart;//串口时钟
	rcu_periph_enum rcu_gpio;//串口Gpio时钟
	uint32_t gpio;//串口GPIOX
    uint32_t af_num;//引脚复用号
	uint32_t tx_pin;//发送引脚
	uint32_t rx_pin;//接收引脚
    uint8_t irq;//中断向量
    uint32_t dma_num;//DMA号
    rcu_periph_enum rcu_dma;//DMA时钟
    dma_channel_enum dma_rx_ch;//DMA接收通道
    dma_subperipheral_enum dma_sub_peri;
}UART_INFO_T;

/**
***********************************************************
* @brief 串口初始化
* @param
* @return 
***********************************************************
*/
void drv_uart_init(uint32_t  baudRate);



void drv_uartx_init(UART_INFO_T* StrUart);

#endif

参考资料:

GD32IAP差分升级:

AN034_CN_Rev1.0.pdfhttps://www.gd32mcu.com/data/documents/userManual/AN034_CN_Rev1.0.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值