STM32 IAP在线升级在项目中的应用

  • IAP即在线应用编程,平时我们写好的程序都是通过下载器去下载的,但是对于组装好的产品在想更新底层硬件代码是很麻烦的事情,如果在公司情况还没那么糟糕,要是发出去的产品出现bug,你不可能要用户给你下载程序的。IAP这种技术,我们就可以像软件一样,可以实现远程更新了。我们需要做的就是,写FLASH读写接口,程序可以通过串口,网口等进行下发,然后内部调用FLASH写函数,把代码写到对于区域即可。
  • 当然这只是一个大概思路,具体实现还是要注意很多细节的东西。网上也有好多关于这方面的教程,但是能用到项目中的却很少,我写这边文章就是想和大家分享我在项目中实际应用。
  • 想了想,就以我实际开发过程来写吧,这里对新人来说也可以当作一篇教程来学习。

一、FLASH读写接口的实现

  • 这里大家可以参考原子哥的FLASH模拟EEPROM实验来写。因为我们做写的是程序,数据流很大,需要做一些改动,这样写入速度会快很多。
  • 首先我们来了解一下STM32F1的FLASH,如下图,我们要看的只有主存储区,可以看到单片机内部FLASH是按2K一页来区分的,而且对其读写是有如下几点要求:
  1. 每次写入必须为2个字节。
  2. 写入地址为2的倍数。
  3. 写入之前必须是被擦除的(即其值为0xFFFF),也可以理解为,写入数据只能把位写0,不能置1。
  4. 写入速度≤24MHz。
  5. 擦除方式:页擦除和正片擦除(这个要注意,如果你是做数据保存,就必须先把这一页的数据读取到缓存中,然后修改缓存里的值,再整页写入)。

  • FLASH写入过程如下:
  1. 解锁
  2. 读页数据到缓存
  3. 页擦除
  4. 修改缓存数据
  5. 把缓存数据页写入
  6. 上锁
  • 首先我们都有一下基本的读写函数,写函数官方库已经为我们提供,我们要写的就是读函数,代码如下:
//读1个字节
uint8_t FLASH_ReadByte(uint32_t Addr)
{
    return *(vu8 *)Addr;
}
//读2个字节
uint16_t FLASH_ReadHalfWord(uint32_t Addr)
{
    return *(vu16 *)Addr;
}
//读N个字节
void FLASH_ReadNByte(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
{
    uint32_t i;

    for(i = 0;i < Len;i++)
    {
        pBuff[i] = FLASH_ReadByte(Addr);
        Addr += 1;
    }
}
  • 然后就是在基本函数的基础上面扩展我们需要的函数,因为升级过程中,我们需要保存一些标志,需要用到读某一页的函数。
#define STM32_SECTOR_SIZE   2048    //页大小
#define STM32_SECTOR_NUM    255     //页数

//STM32 FLASH的起始地址
#define STM32_FLASH_BASE 0x08000000

void FLASH_ReadPage(uint8_t Page_Num,uint8_t *pBuff)
{
    uint16_t i;
    uint32_t Buff;
    uint32_t Addr;

    //是否超出范围
    if(Page_Num > STM32_SECTOR_NUM)
        return;
    //先计算页首地址
    Addr = Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE;

    for(i = 0;i < STM32_SECTOR_SIZE;i += 4)
    {
        Buff = FLASH_ReadWord(Addr);

        pBuff[i]   = Buff;
        pBuff[i+1] = Buff >> 8;
        pBuff[i+2] = Buff >> 16;
        pBuff[i+3] = Buff >> 24;

        Addr += 4;
    }
}
  • 需要读写就需要写页,再来写一个写页函数,由于一次只能写2字节,所有我们调用的是官方库函数FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)。
void FLASH_WritePage(uint8_t Page_Num,uint8_t *pBuff)
{
    uint16_t i;
    uint16_t Buff;
    uint32_t Addr;

    //是否超出范围
    if(Page_Num > STM32_SECTOR_NUM)
        return;
    //解锁
    FLASH_Unlock();
    //先计算页首地址
    Addr = Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE;

    for(i = 0;i < STM32_SECTOR_SIZE ;i += 2)
    {
        Buff = ((uint16_t)pBuff[i+1] << 8) | pBuff[i];
        FLASH_ProgramHalfWord(Addr,Buff);
        Addr += 2;
    }
    //上锁
    FLASH_Lock();
}
  • 然后我们还要写两个重要的函数,他们都是写N字节函数,区别是一个要先把页数据读到缓存中,再写入,这个函数用来保存一些标志等等,另一个函数我们不负责扇区数据擦除保存等处理,我们只管往某个地址写入数据,这个函数用来做升级用,这样速度会快一些。下来就来实现这两个函数。
void FLASH_WriteNData(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
{
    uint32_t Offset;
    uint8_t  Page_Num;
    uint16_t Page_Offset;
    uint16_t Free_Space;
    uint16_t i;

    if((Addr < STM32_FLASH_BASE) || (Addr > STM32_FLASH_END))
        return;

    Offset = Addr - STM32_FLASH_BASE;//偏移地址
    Page_Num = Offset / STM32_SECTOR_SIZE;//得到地址所在页
    Page_Offset = Offset % STM32_SECTOR_SIZE;//在页内的偏移地址
    Free_Space = STM32_SECTOR_SIZE -  Page_Offset;//页区剩余空间
    //要写入的数据是否大于剩余空间
    if(Len <= Free_Space)
        Free_Space = Len;

    FLASH_Unlock();//解锁

    while(1)
    {
        FLASH_ReadPage(Page_Num,STM32_FLASH_BUFF);//先把数据读到缓存中
        FLASH_ErasePage(Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE);//页擦除
        //修改缓存数据
        for(i = 0;i < Free_Space;i++)
        {
            STM32_FLASH_BUFF[i+Page_Offset] = pBuff[i];
        }
        FLASH_WritePage(Page_Num,STM32_FLASH_BUFF);//把缓存数据写入
        //判断是否超出当前页,超出进入下一页
        if(Len == Free_Space)
            break;
        else
        {
            Page_Num++;//下一页
            Page_Offset = 0;
            pBuff += Free_Space;

            Len -= Free_Space;
            if(Len > STM32_SECTOR_SIZE)
                Free_Space = STM32_SECTOR_SIZE;
            else
                Free_Space = Len;
        }
    }
    FLASH_Lock();
}
void FLASH_WriteNByte(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
{
    uint16_t i;
    uint16_t temp = 0;

    if((Addr < STM32_FLASH_BASE) || (Addr > STM32_FLASH_END))
        return;

    FLASH_Unlock();//解锁

    for(i = 0;i < Len;i += 2)
    {
        temp = pBuff[i];
        temp |= (uint16_t)pBuff[i+1] << 8;

        FLASH_ProgramHalfWord(Addr,temp);
        Addr += 2;
        if(Addr > STM32_FLASH_END)
        {
            FLASH_Lock();
            return;
        }
    }
    FLASH_Lock();
}
  • 因为我们程序可能会占用多页,所以我们需要写一个擦除指定页的函数,代码如下。
void Flash_EraseSector(uint8_t Start_Page,uint8_t End_Page)
{
    uint8_t i;
    uint8_t num = 0;

    if(Start_Page > End_Page)
        return;

    FLASH_Unlock();//解锁

    num = End_Page - Start_Page;//擦除页数

    for(i = 0;i <= num;i++)
    {
        FLASH_ErasePage((Start_Page + i) * STM32_SECTOR_SIZE + STM32_FLASH_BASE);//页擦除
    }

    FLASH_Lock();
}
  • 我们写了几个接口,我们要测试一下是否好用,开发就是要稳扎稳打,保证每个功能稳定。测试嘛,给它们搭一个小舞台,让它们上去表演一下,哈哈。我们要的就是往某页写入数据,再读出来,看看是否相同,注意你程序的大小不要把当前运行的代码覆盖咯。
void Test_Flash_WR(uint8_t Page_Num)
{
    uint16_t i = 0;
    uint8_t j = 0;

    //是否超出范围
    if(Page_Num > STM32_SECTOR_NUM)
        return;

    for(i = 0;i < STM32_SECTOR_SIZE;i++)
    {
        buff[i] = j++;
    }
    //页擦除
//  Flash_EraseSector(Page_Num,Page_Num);
    //写入
//  FLASH_WritePage(Page_Num,buff);
    //写入
//  FLASH_WriteNByte(Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE,buff,STM32_SECTOR_SIZE);
    //写入
    FLASH_WriteNData(Page_Num * STM32_SECTOR_SIZE + STM32_FLASH_BASE + 4,buff,10);
    //清零
    memset(buff,0,STM32_SECTOR_SIZE);
    //读出
    FLASH_ReadPage(Page_Num,buff);

    for(i = 0;i < STM32_SECTOR_SIZE;i++)
    {
        printf("%02X ",buff[i]);
    }
    printf("\r\n");
}

二、分区规划

  • 写完FLASH接口函数,下来就是进行对我们的FLASH进行分区了,这样才知道我们的数据到底应该写到哪里。下面是我自己使用的分区方式。
  • 首先是Bootloader分区,放置我们的引导程序,主要负责判断标志来决定是跳转到app分区运行,还是进行程序更新,又或者是需要恢复出厂程序。
  • 其次是APP分区,这里存放的是我们的主程序。
  • 下来是Download分区,负责存储我们下发的更新代码,这样做是保证代码完整再进行更新,保证更新成功率。
  • 最后是Flag分区,存放一些标志性数据。
分区 大小 扇区 备注
Bootloader 12K 0 - 5 引导程序
APP 100K 6 - 55 存储App
Download 100K 56 - 105 下载缓存
Flag 2K 255 升级标志

三、Bootloader程序实现

  • 说一下Bootloader程序设计思路吧,单片机上电进入Bootloader程序,先判断升级标志是否需要升级固件,需要就把Download分区拷贝到app分区,然后清空升级标志;下来判断是否右APP分区中断向量表是否正确,正确说明有app可以跑,直接跳转到app运行;如果没有在bootloader里循环等待接收app固件。下面是我程序的整体框架:
#define FLASH_APP_ADDR                STM32_SECTOR6_ADDR
#define FLASH_DOWNLOAD_ADDR     STM32_SECTOR56_ADDR
#define FLASH_APP_FLAG                 STM32_SECTOR255_ADDR
#define FLASH_UPDATA_FLAG           FLASH_APP_FLAG + 2

int main(void)
{
    SystemInit();
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    Delay_Init();

    LED_Init();
    KEY_Init();
    USART1_Init(115200);

    //判断是否需要升级固件
    if(FLASH_ReadHalfWord(FLASH_UPDATA_FLAG) == 0xAA55)
    {
        printf("Updata App...\r\n");

        IAP_Copy_App();//拷贝到app分区

        printf("Updata App Succeed...\r\n");
    }
    //判断是否有APP程序
    //中断向量表判断
    if(((*(vu32*)(FLASH_APP_ADDR + 4))&0xFF000000) == 0x08000000)
    {
        printf("Run App...\r\n\r\n");

        Delay_ms(10);
        IAP_Load_App(FLASH_APP_ADDR);//转到app
    }

    printf("No App\r\n");

    TIM3_Init(1000,72);//定时0.001s

    while(1)
    {
        Task_Process();

        if(USART1_RX_CNT > 0)
        {
            IAP_WriteBin(FLASH_DOWNLOAD_ADDR,USART1_RxBuff,USART1_RX_CNT);
            USART1_RX_CNT = 0;
        }
    }
}
  • 这里我们需要实现的函数有IAP_Copy_App()和IAP_Load_App(),代码如下:
typedef void (*IAP_Fun)(void);
IAP_Fun JumpApp;

uint8_t STM32_FLASH_BUFF[STM32_SECTOR_SIZE] = {0};

void IAP_Copy_App(void)
{
    uint8_t i;
    uint8_t buf[2] = {0x00,0x00};
    //擦除App扇区
    Flash_EraseSector(6,55);

    for(i = 0;i < 50;i++)
    {
        FLASH_ReadPage(56 + i,STM32_FLASH_BUFF);
        FLASH_WritePage(6 + i,STM32_FLASH_BUFF);
        LED3 = !LED3;
    }

    FLASH_WriteNData(FLASH_UPDATA_FLAG,buf,2);
}

void IAP_Load_App(uint32_t Addr)
{
    //检查栈顶地址是否合法
    if(((*(vu32*)Addr) & 0x2FFE0000) == 0x20000000)
    {
        __disable_irq();
        JumpApp = (IAP_Fun)*(vu32 *)(Addr + 4);
        MSR_MSP(*(vu32 *)Addr);
        JumpApp();
    }
}
  • 然后我们还要写一个关于下载程序的函数IAP_WriteBin(),一般我们数据会通过串口或网口下发过来,下发的数据要保存到下载分区,所以需要一个写数据到下载分区的函数。
void IAP_WriteBin(uint32_t Addr,uint8_t *pBuff,uint32_t Len)
{
    uint8_t buf[2] = {0xAA,0x55};
    //擦除App扇区
    Flash_EraseSector(6,55);
    //写入程序
    FLASH_WriteNByte(Addr,pBuff,Len);
    //更新标记
    FLASH_WriteNData(FLASH_UPDATA_FLAG,buf,2);
    //复位单片机
    NVIC_SystemReset();
}

原文地址:https://www.cnblogs.com/ZzJan/p/11564060.html

时间: 2024-08-28 02:57:27

STM32 IAP在线升级在项目中的应用的相关文章

STM32+IAP方案 实现网络升级应用固件

关注了这个概念有些日子了,这段时间总算有机会实战==网络升级应用固件,这里记录下遇到的问题,及解决方案. 原理与网上流传的串口作为传输手段 一致:不同之处,无非我这里使用了网络设备传输.==(lwip)TFTP客户端的应用. 参考: IAR环境下STM32+IAP方案的实现 STM32浅谈之IAP.pdf 基于IAP和Keil MDK的远程升级设计 keil MDK中如何生成*.bin格式的文件 概况: 什么是IAP,为什么要IAP 可实现的原理 实现过程 细节及实现 以上基本都可以从[IAR环

LBDP-Z支持STM32通过无线模块实现在线升级

LBDP-Z可支持STM32通过无线模块实现在线升级,支持单播或广播模式. LBDP-Z可变数据域报文 SD2 LE LEr SD2 GA SA FC DU FCS ED SD2 起始字节,ED结束字节,GA组地址,SA从站地址/源地址,DA目的地址,LE/LEr数据域长度,FC控制字节,FCS校验字节. GA为组地址,可表示0x02~0x7E共125个组:0x7F表示广播,0x00.0x01和0x80~0xFF保留.SA为从站地址,可表示0x02~0x7E共125个从站:0x7F表示广播,0x

LBDP-Z支持STM32通过无线模块实现在线升级(二)

LBDP-Z可支持STM32通过无线模块实现在线升级,FCm = 0x41/0x51仅支持广播模式(取消单播升级模式). 因广播模式可能发生丢包,因此需要多次发送,在代码校验后进行升级操作.增加FCn=0x39命令: FCn=0x39: 校验flash.设置升级标志并复位,后跟6字数据,开始地址(2B) ,长度(2B) ,CRC校验(2B) . 增加FCm = 0x43,支持单播升级模式. FCm = 0x43  后跟6+n字数据,开始地址(2B) ,长度(2B) ,CRC校验(2B),代码数据

javaWeb项目中如何实现在线查看pdf文件

最近有需求要实现在网页直接查看pdf,word,excel文件.但是实际当中并没有很好的开源插件供我们使用,确实有一些付费的插件不错,也很好用,但是对于我来说都不适合. 现在只是单纯的找到了围魏救赵的方法. 就是先实现显示pdf文件,其他文件用别的方式去转成pdf.虽然这个方法确实不好,但是也是没有办法的办法了,如果以后能有更好的,那就再发布别的吧. 这里我就直接介绍pdf的显示方法. 直接上干货. 首先在E:\tomcat8\webapps这个目录下面拷贝下面这个文件 http://yunpa

iOS项目中集成Flutter的最新适配升级

如果你在2019年8月之前将Flutter添加到现有iOS项目,本文值得你一看. 在2019年7月30日,合并合并请求flutter / flutter#36793之前Flutter 1.8.4-pre.21, 将Flutter添加到现有的iOS应用程序需要更改Podfile, 并在现有Xcode项目中添加运行脚本构建阶段. 要在此拉取请求之后更新到Flutter,您必须更改Podfile,删除“运行脚本”构建阶段,然后重新生成podhelper脚本. Podfile的改变 以前,需要将以下几行

Android在线升级相关笔记一(解析服务器版本与当前版本比较)

大概流程:Android客户端去访问服务器上的封装了版本号等信息的xml文件,对服务器上的版本和当前版本进行比较, 如果低于服务器的版本,则下载服务器上的新版软件,进行安装替换,完成升级. 一.首先用tomcat搭建服务器,用于开发测试. 下载tomcat请参考:http://blog.csdn.net/only_tan/article/details/25110625 1.在tomcat中新建自己的项目: \apache-tomcat-6.0.39\webapps 目录下新建自己的项目文件夹,

PIC32MZ 通过USB在线升级 -- USB CDC bootloader

了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 最近给我的开发板PIC32MZ EC starter kit写了个USB 在线升级程序--USB CDC bootloader.有了它,我可以很方便的升级我的应用程序.我大概是一个星期前开始决定写这个USB在线升级程序的,USB 有很两种类型,USB host和USB device. 由于USB host接触不多,所以我

项目中使用Quartz集群分享--转载

项目中使用Quartz集群分享--转载 在公司分享了Quartz,发布出来,希望大家讨论补充. CRM使用Quartz集群分享  一:CRM对定时任务的依赖与问题  二:什么是quartz,如何使用,集群,优化  三:CRM中quartz与Spring结合使用 1:CRM对定时任务的依赖与问题  1)依赖  (1)每天晚上的定时任务,通过sql脚本 + crontab方式执行 Xml代码   #crm 0 2 * * * /opt/***/javafiles/***/shell/***_dail

在ASP.NET项目中使用CKEditor +CKFinder实现图片上传功能

前言 之前的项目中一直使用的是FCKeditor,昨天突然有个想法:为什么不试一下新的CKEditor呢?于是花了大半天的时间去学习它的用法,现在把我的学习过程与大家分享一下. 谈起FCKeditor,相信没几个Web程序员不知道的吧.不过,官方已经停止了该产品的更新,其最新版是2.6.6,于2010年2月15日发布. 取代FCKeditor的产品叫CKEditor(Content And Knowledge Editor),与其说是对FCKeditor的升级,不如说是全新的一个产品.相比FCK