随着物联网的普及和设备互联需求的要求,产品具备本地/远程连接的能力成为一个关键的需求;
当设备具备更强的网络连接能力时,开发/产品/用户产生对设备更广泛控制的能力的需求,而且需求也在不断变化当中;同时产品上线发布之后会有产品缺陷的问题暴露;这时候对嵌入式设备具备升级能力成为产品功能设计中一个必要的功能,所以在产品开发当中要加入对升级功能的设计;
以下介绍一种stm32f1xx的升级方案设计,并对设计要点和细节进行详述,外设函数库使用标准函数库;
1、OTA
空中下载技术(Over-the-Air Technology)是通过移动通信的空中接口实现对移动终端设备及SIM卡数据进行远程管理的技术,实际的应用场景中即便是通过无线通信的方式如蓝牙 、zigbee、wifi等或者有线方式进行对设备的升级都可以称作OTA升级;
2、IAP
IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级(百度百科),在stm32中是指下载到mcu中的一个固件主要具备一种可以将内外部flash中的一个固件写到内部flash另一区域并跳转到另一个区域运行自己写入到此区域的固件的能力;
具备操作另一个区域的这部分这部分称作boot,被操作写到另一个区域的固件称作app,但是相对于stm32来说它们都是应用;
3、示意图
boot分区代码功能
修改升级标志,具体为接收到上位机升级请求后,修改存储标志区域中存储的标志;
boot区域内的代码主要功能是通过串口(这里以串口传输为例)根据和上位机定义的传输协议接收传输过来的固件的数据并将数据写入app的flash分区内;
升级完之后需要进行跳转;
分区的大小根据boot的大小进行划分,一般为10k字节左右;
app分区代码功能
产品本身功能的代码,但与升级没有关系;
从boot调转到app,app运行后要检查升级区域的标志,修改标志后向上位机报告升级是否成功;
分区的大小按实际项目需要进行划分;
固件升级标志分区
主要定义正在升级标志值、升级成功与否的标志值;
大小分几个字节即可;
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
协议结构
上位机到板卡 :方向(1byte) 命令(1byte) 编程地址(4byte低位在前) 长度(2字节低位在前) 数据(n字节由长度域决定) 校验和(前面数据的和)
方向:两个方向从上位机到板卡,从板卡到上位机;
命令:包括开始升级、升级状态查询、升级命令、开始运行跳转;
数据:可以表示固件数据,也可以表示固件总长度,与具体命令相关;
板卡到上位机:方向(1byte) 命令(1byte) 数据(1byte) 校验和(前面数据的和)
方向:从板卡到上位机;
命令:上位机发送的命令;
数据:表示成功或失败;
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中断,对于数据的处理更方便;
1、对于上位机发送的开始升级命令,进行解析,并对自身进行复位;
2、从boot跳转到app后对升级成功的标志进行清除,并回复上位机自己已经起来表示升级成功;
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()