基于HAL库的STM32 联网更新程序的功能实现

综述

        想要实现stm32的联网更新程序的功能,分为三个步骤:接收更新的程序数据擦除并写入相应的flash区域跳转至app并在满足条件后可以跳转回来

        其中,若要实现联网更新程序,更新的程序数据一般由联网模块通过串口,spi或者iic等方式传输给stm32。接收数据部分暂且不谈,该功能的重难点在 flash的读写 iap与app的相互跳转

Flash擦写

        在对flash进行读写前,要先了解stm32的flash扇区的分配。其中,常用的F1系列的stm32的flash大小为 64k,128k的的型号,单个扇区大小为 1kb(0x400)。flash大小为256k,512k的型号,单个扇区大小为2kb(0x800)。

        stm32的程序默认的上电启动地址为 0x8000000 。因此,IAP程序的起始地址就是 0x8000000。按照需求在魔术棒中对IAP程序进行地址分配,我这里随便打开了一个工程,因此没有对其进行地址分配,默认size大小为总flash空间。

        IAP程序所占用的空间可以在工程目录中的.map文件的最下方查看。该文件夹的命名每个人都不一样,按照自己的来。注意分配空间的时候最好留一定的余量,防止之后有所改动。比如我这里的程序大小是16.64k,我可以给他分配24k的空间以防之后增加功能。可以程序写的差不多了再回头更改size大小。

        在向Flash写入新程序数据之前,先要将对应的扇区擦除。

//该函数功能是,清除从 addr 地址开始的第 num 个扇区(从1开始计数)
void Flash_Clear(uint32_t addr, uint16_t num){
    uint32_t sectorError;    

    FLASH_EraseInitTypeDef eraseInitStruct;
    earseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; //选择清除模式:按页清除。
    earseInitStruct.PageAddress = addr + (num - 1)*1024; //该项配置从哪里开始清除。addr为app程序的起始地址,num为起始地址开始的第几个扇区,从1开始计数。
    earseInitStruct.NbPages = 1; //该项配置清除的页数。这里设置为1页。

    HAL_FLASH_Unlock(); //对flash操作前需要先解锁,操作完毕后要上锁。
    HAL_FLASHEx_Erase(&eraseInitStruct, &sectorError);
    HAL_FLASH_Lock(); 
}

        擦除完成之后需要对flash进行写入

//该函数的作用是在 WriteAddr 地址,写入 NumToWrite 长度的数据,数据内容为 *pBuffer 数组
//写入模式为 半字写入 ,因此传入的数组需为 uint16_t 类型,若数据处理部分得到的是 uint8_t 类型数组,需自行处理成 uint16_t 类型
void FLASH_Write(uint32_t WriteAddr, uint16_t *pBuffer, uint16_t NumToWrite)
{
    uint16_t i;
    HAL_FLASH_Unlock();
    for(i = 0; i < NumToWrite; i ++)
    {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);//半字写入模式
        WriteAddr += 2;
    }
    HAL_FLASH_Lock();

}

        写入之后,可以在keil的debug中的memory窗口查看是否写入成功。

        也可以选择将其读出后串口输出出来查看是否写入成功。

//这两个函数一个是一次读取半个字,一个是一次读取一个字节。可以自行通过while循环配合这两个函数选择读取方式
uint16_t FLASH_ReadHalfWord(uint32_t addr)
{
    return *(volatile uint16_t*)addr;
}

uint8_t FLASH_ReadByte(uint32_t addr)
{
    return *(volatile uint8_t *)addr;
}

//如下函数是从 ReadAddr地址读取 num 个字节,一次读取一个字节,存入 uint8_t 类型数组。可以自行修改以一次读取半个字
void FLASH_Read(uint32_t ReadAddr, uint8_t * ptr, uint16_t num)
{
    for(int i = 0; i < num; i ++)
    {
        ptr[i] = FLASH_ReadByte(ReadAddr + i);
    }
}

IAP与APP的相互跳转

        当flash成功写入之后,就要从IAP跳转到APP了。过程简单来说就是找到APP程序的重启函数然后调用,这个重启函数的地址可以在.map中找到。

        但是APP的程序肯定是每次都不一样的,重启函数的地址也不一定一样。因此肯定不可能在跳转的时候写死这个地址(但调试的时候可以写死看看效果),这样就要在程序里面动态获取这个函数的地址了。这个函数的地址存放在 app起始地址+4 的地方,我们要做的就是用一个函数指针指向这个地址,然后调用这个函数。当然,在跳转之前别忘了将iap中用到的外设都deinit,以防产生冲突。

起始值+4的地方,低位在前。拼出来就是0x 08 00 01 89

.map中记录的重启函数的地址

        这里假设我给IAP程序分配了24k即0x6000的空间,APP程序从0x8006000开始,size为0x1A000

#define APP_ADDRESS 0x8006000

typedef void(*pFunction)(void);//定义一个函数指针类型

int8_t IAP2APP()
{
    if(((*(__IO uint32_t *)APP_ADDRESS) & 0x2FFE0000) == 0x20000000)//检查地址是否合法
    {
        pFunction Jump2App;//声明一个函数指针类型的变量
        uint32_t _jumpAddress = *(__IO uint32_t*)(APP_ADDRESS + 4);//获取重启函数的地址
        Jump2App = (pFunction)_jumpAddress;//指向重启函数
        
        //接下来进行一系列时钟等外设的复位。其他自己调用的外设也可以在下面的位置复位。
        __set_PRIMASK(1);
        SysTick -> CTRL = 0;
        SysTick -> LOAD = 0;
        SysTick -> VAL = 0;
        HAL_RCC_DeInit();
        for(int i = 0; i < 8; i ++)
        {
            NVIC -> ICER[i] = 0xFFFFFFFF;
            NVIC -> ICPR[i] = 0xFFFFFFFF;
        }

        __set_PRIMASK(0);
        __set_CONTROL(0);
        __set_MSP(*(__IO uint32_t * )APP_ADDRESS);
        __disable_irq();
        //最后,万事俱备,跳转至app
        Jump2App();
        return 0;
    }else return -1;

}

最后的最后,要在APP程序 main 函数的最开头,开启总中断重设中断向量表

#define NVIC_VecTab_FLASH    (uint32_t)0x8000000 //stm32的flash起始地址
#define BOOT_SIZE            0x6000              //app程序起始地址的偏移量

//重设中断向量表
void NVIC_SetVectorTable(uint32_t NVIC_VecTab, uint32_t OffSet)
{
    assert_param(IS_NVIC_VECTAB(NVIC_VecTab));
    assert_param(IS_NVIC_OFFSET(OffSet));

    SCB -> VTOR = NVIC_VecTab | (OffSet & (uint32_t)0x1FFFFF80);

}

int main(void)
{
    __enable_irq(); //开启总中断
    NVIC_SetVectorTable(NVIC_VecTab_FLASH, BOOT_SIZE);


    //下面是其他代码
    ......


}

        至此,iap跳转至app结束。app跳转至iap只要在此基础上将跳转地址改为 0x8000000 即可。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值