stm32的OTA(IAP)设计

前言

随着物联网的普及和设备互联需求的要求,产品具备本地/远程连接的能力成为一个关键的需求;
当设备具备更强的网络连接能力时,开发/产品/用户产生对设备更广泛控制的能力的需求,而且需求也在不断变化当中;同时产品上线发布之后会有产品缺陷的问题暴露;这时候对嵌入式设备具备升级能力成为产品功能设计中一个必要的功能,所以在产品开发当中要加入对升级功能的设计;
以下介绍一种stm32f1xx的升级方案设计,并对设计要点和细节进行详述,外设函数库使用标准函数库;

一、概念

1、OTA

空中下载技术(Over-the-Air Technology)是通过移动通信的空中接口实现对移动终端设备及SIM卡数据进行远程管理的技术,实际的应用场景中即便是通过无线通信的方式如蓝牙 、zigbee、wifi等或者有线方式进行对设备的升级都可以称作OTA升级;

2、IAP

IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级(百度百科),在stm32中是指下载到mcu中的一个固件主要具备一种可以将内外部flash中的一个固件写到内部flash另一区域并跳转到另一个区域运行自己写入到此区域的固件的能力;
具备操作另一个区域的这部分这部分称作boot,被操作写到另一个区域的固件称作app,但是相对于stm32来说它们都是应用;

3、示意图

在这里插入图片描述

二、分区规划

1、分区功能介绍

boot分区代码功能

修改升级标志,具体为接收到上位机升级请求后,修改存储标志区域中存储的标志;
boot区域内的代码主要功能是通过串口(这里以串口传输为例)根据和上位机定义的传输协议接收传输过来的固件的数据并将数据写入app的flash分区内;
升级完之后需要进行跳转;
分区的大小根据boot的大小进行划分,一般为10k字节左右;

app分区代码功能

产品本身功能的代码,但与升级没有关系;
从boot调转到app,app运行后要检查升级区域的标志,修改标志后向上位机报告升级是否成功;
分区的大小按实际项目需要进行划分;

固件升级标志分区

主要定义正在升级标志值、升级成功与否的标志值;
大小分几个字节即可;

2、分区规划在代码上的配置

boot代码起始地址:
使用的keil5开发的话,需要在keil5软件的工程配置代码处配置ROM的起始地址为0x08000000,并根据固件大小配置长度,最终体现在工程编译后工程目录的 xxxx.ict文件中
如下

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00001000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00001000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00005000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

boot代码向量表地址:
文件system_stm32f10x.c

void SystemInit (void)
{
  
  
  
  #ifdef VECT_TAB_SRAM
  SCB->VTOR=SRAM_BASE|VECT_TAB_OFFSET; /* VectorTableRelocation in Internal SRAM. */
  #else
	#ifdef  VECT_TAB_BOOT
	SCB->VTOR = FLASH_BASE | 0x2000;
	#else
	SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //Vector Table Relocation in Internal FLASH. 从主闪存启动时,在此处设置向量表偏移 VECT_TAB_OFFSET为0
	#endif
  #endif
}

app代码起始地址:
同理,起始地址大于,boot的起始地址+boot的分区长度,最好能整除当前型号mcu的扇区大小,如下

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08002000 0x0001D000  {    ; load region size_region
  ER_IROM1 0x08002000 0x0001D000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00005000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

app代码向量表地址:
文件system_stm32f10x.c

void SystemInit (void)
{
  
  
  
  #ifdef VECT_TAB_SRAM
  SCB->VTOR=SRAM_BASE|VECT_TAB_OFFSET; /* VectorTableRelocation in Internal SRAM. */
  #else
	#ifdef  VECT_TAB_BOOT
	SCB->VTOR = FLASH_BASE | 0x2000;
	#else
	SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //Vector Table Relocation in Internal FLASH. 从主闪存启动时,在此处设置向量表偏移 VECT_TAB_OFFSET 为0x00002000为app起始地址-flash起始地址
	#endif
  #endif

三、功能设计

1、升级过程时序图

在这里插入图片描述

2、升级协议

协议结构
上位机到板卡 :方向(1byte) 命令(1byte) 编程地址(4byte低位在前) 长度(2字节低位在前) 数据(n字节由长度域决定) 校验和(前面数据的和)
方向:两个方向从上位机到板卡,从板卡到上位机;
命令:包括开始升级、升级状态查询、升级命令、开始运行跳转;
数据:可以表示固件数据,也可以表示固件总长度,与具体命令相关;

板卡到上位机:方向(1byte) 命令(1byte) 数据(1byte) 校验和(前面数据的和)
方向:从板卡到上位机;
命令:上位机发送的命令;
数据:表示成功或失败;

3、boot代码设计

boot代码设计主要包括三部分
1、主程序轮训采用状态机模式,根据不同命令进行不同状态的转移;
2、向app跳转的函数

            typedef  void (*pFunction)(void);
            pFunction Jump_To_Application;
            u32 JumpAddress;
            NVIC_DeInit(); RCC_DeInit();
            JumpAddress = *(u32*) (appAddress + 4);
            Jump_To_Application = (pFunction) JumpAddress;
            __MSR_MSP(*(u32*) appAddress ); //appAddress为app分区起始flash地址
            Jump_To_Application();

3、串口中断接收,中断接收中断后将数据放入环形buffer中,主程序轮询时从buffer获取数据进行解析,从而进行状态转移;可以将串口中断设计成IDLE中断,对于数据的处理更方便;

4、app应用代码设计

1、对于上位机发送的开始升级命令,进行解析,并对自身进行复位;
2、从boot跳转到app后对升级成功的标志进行清除,并回复上位机自己已经起来表示升级成功;

5、上位机程序代码设计

1、与boot的进行协议和流程交互;
2、类似采用状态机的方式,加入超时、校验处理;

四、固件出厂部署

1、出厂固件可以使用脚本将boot和app代码进行合并成一个固件,整体使用烧录器烧写;
2、合并boot和app的脚本merge.py
用法 merge.py --iap boot的bin文件 --app app的bin文件

# -*- coding: utf_8 -*-
#from __future__ import print_function
import os
import sys
import time
import argparse
import shutil
import struct


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="bin merge demo")
    parser.add_argument('--iap',    dest='iap',     type=str, default='',           action='store', help='iap bin file path')
    parser.add_argument('--app',    dest='app',     type=str, default='',           action='store', help='app bin file path')
    parser.add_argument('--target', dest='target',  type=str, default='./merge.bin',  action='store', help='merge bin file path')
    args = parser.parse_args()

    if args.iap == '' or args.app == '':
        print('Pls input the iap and app path')
        sys.exit()

    iap_path    = args.iap
    app_path    = args.app
    merge_path  = args.target

    offset1 = 0x00000000
    offset2 = 0x00007800 #app相对于flash基地址的偏移

    shutil.copyfile(iap_path, merge_path)

    iap_bin = open(iap_path,'rb')
    app_bin = open(app_path,'rb')
    bin_merge = open(merge_path, 'ab')

    app_size        = os.path.getsize(app_path)
    bin_merge_size = os.path.getsize(merge_path)

    final_size = offset2 + app_size

    offset = bin_merge_size
    value_default = struct.pack('B', 0xff)

    while offset < final_size:
        if offset == offset2:
            data = app_bin.read()
            bin_merge.write(data)
            offset = bin_merge.tell()
        else:
            bin_merge.write(value_default)
            offset = bin_merge.tell()

    iap_bin.close()
    app_bin.close()
    bin_merge.close()
    print('iap and app merge finish!\n')
    sys.exit()

### STM32 OTA IAP 实现教程及代码示例 #### 1. 基本概念介绍 STM32IAP(In Application Programming)允许设备在不依赖外部编程器的情况下更新其固件。这通常涉及将闪存划分为两个主要部分:引导加载程序(Bootloader)和用户应用程序(User Application)。当需要更新时,新的应用程序可以通过无线或其他方式传输到指定位置并执行相应操作完成替换旧版本的应用程序[^1]。 对于OTA(Over-The-Air),它指的是通过网络远程发送新固件给目标设备的过程。结合IAP技术,在接收到完整的数据包之后可以直接写入内部Flash存储区内的特定地址空间内,从而实现了真正的“空中下载”。 #### 2. 开发环境准备 为了实现上述功能,开发者应先准备好如下资源: - 支持SWD/JTAG接口的调试工具; - 安装好Keil MDK或者其他IDE用于编写C/C++源文件; - 配置串口通信库以便接收来自PC端的数据流; - 设置合适的启动模式使MCU能够识别是从主控还是从备份区域运行当前镜像副本。 #### 3. BootLoader设计要点 创建一个可靠的BootLoader至关重要,因为它负责管理整个过程的安全性和稳定性。以下是几个关键点: - **分区规划**:合理安排各段内存区间大小及其起始偏移量。 - **校验机制**:确保每次读取/写入动作都经过严格验证防止意外损坏原有资料结构。 - **异常处理**:针对可能出现的各种错误情况提供相应的解决方案以保障系统的健壮性。 ```c // 示例:定义不同工作状态下的入口函数指针类型 typedef void (*pFunction)(void); extern uint32_t _estart; // 获取链接脚本中的_estart变量值作为重定位后的向量表基址 ``` #### 4. 用户APP开发指南 除了遵循常规嵌入式项目流程外还需注意以下几点特殊事项: - 应用层逻辑需兼容多实例共存场景下各自独立运作互不影响; - 对于某些敏感参数建议采用加密算法保护以防篡改泄露风险; - 尽可能减少对外设访问频率以免干扰其他正在使用的进程。 #### 5. 数据交换协议制定 考虑到实际应用场景复杂多样,因此有必要事先协商一套通用的标准来指导双方交互行为。比如规定消息格式、命令集以及响应时间窗口等细节内容,这样有助于提高效率降低误判概率。 ```c // 发送指令样例 uint8_t cmd[] = {0xAA, 0xBB}; // 自定义控制字节序列 HAL_UART_Transmit(&huart1, cmd, sizeof(cmd), HAL_MAX_DELAY); // 接收反馈结果 if(HAL_UART_Receive(&huart1, rx_buffer, BUFFER_SIZE, TIMEOUT) != HAL_OK){ Error_Handler(); } ``` #### 6. Flash擦除与写入操作 最后一步就是把获取的新版软件片段按照既定规则依次保存至非易失性介质之中去了。这里涉及到一些底层API调用,具体取决于所选用型号支持哪些特性选项。 ```c // 清空指定范围内的扇区单元格 static FLASH_Status Erase_Sector(uint32_t Sector_Address) { /* ... */ } // 向某一页里追加若干个连续字节 static FLASH_Status Program_Page(uint32_t Page_Address,uint8_t *data ,uint16_t length ) { /* ... */ } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值