你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32的BootLoader经验分享

[复制链接]
攻城狮Melo 发布时间:2023-3-30 18:13
之前很想做一个属于STM32的BootLoader,但是想想没什么实际用处就没有下手,但是当前的项目遇到的麻烦事情要我改变了这种看法。比如说我开发了一个产品需要测试,把一个样品送到客户手里去测试使用,但是客户在使用的过程中提出了一些修改建议,那我就在做一个样品改好程序之后发出去给客户,不久之后客户又有了新的修改要求,如此往复,大部分时间都耽误在做样品、寄快递上了,极大地耽误了产品的研发周期。但是如果我们能够远程的将修改好的程序下载到产品上去的话会减少很多研发时间。当然前提是产品本身有接出来通信接口,我们使用这个接口才能进行程序升级,或者产品本身能够连接到网络通过网络更新固件。0 ~6 J2 `1 Y' j% ^4 z# K; h: v
0 E  w4 E6 g0 e# Y
好的,既然BootLoader是有实际价值的,那就能动手。首先要理解BootLoader是什么东西,这个是源自linux上的BootLoader的概念,在linux上,BootLoader是首先执行的程序,BootLoader启动之后初始化CPU、RAM、Flash等设备,然后从Flash中读取Linux程序数据到RAM中去,最后跳转到RAM中Linux的起始地址中去启动Linux系统。除了从Flash中读取系统启动之外,BootLoader还能通过网络NFS协议从服务器上读取Linux并启动。BootLoader还能够更新Linux内核、配置Linux启动信息、测试系统等等。我们要做的STM32的BootLoader也是类似的工作原理,但是没有Linux系统的BootLoader功能那么强大。我们要做的STM32的BootLoader只有两个主要目的:
& \# @2 n0 `; n% }
1 x. B- X( _  d0 ~1、跳转到应用程序并执行。' S8 t. H# |' i
2、更新应用程序。* A0 l# ~  _; d0 Y5 y3 w: [( j* U

' L: A' L, O) b$ N" d6 _2 y) v为了实现上面的两个基础功能我们创建两个工程一个叫probooter一个叫proapp,前者是BootLoader程序工程,后者是测试用的应用程序。- u1 S" t  u' v5 g& Z7 K2 B" R" v
+ X$ C! O, j9 ]9 s3 Z, Z
我们需要规定好BootLoader和Application程序再Flash中的存储结构,我们使用的是64KB Flash的STM32进行测试,BootLoader程序占用前20KB的空间,Application占用后面的43KB的空间,最后1KB的空间不能使用,好像是用来标志Flash锁定状态的,忘了是我之前用的其他MCU上规定的的还是STM32上的规定,反正也不差这个1KB。Flash存储结构如下:4 k6 W  H% x0 b7 X0 T; g8 N
  1. 0x0800 0000        page0         BootLoader程序起始地址
    " c4 b  h4 e3 Q* @* H# J9 z
  2. .                page1 % f) O: z/ d% ^$ x" J
  3. .                page2 . ]- U; C. s* n
  4.                 ......
    5 ?( E: u- F' d) P# h3 x: b* z
  5. .                page18
    % f- X! G7 R5 C; l
  6. .                page19
    8 g( s3 x. f( [4 Q
  7. 0x0800 5000        page20        Application程序起始地址& X% M1 m/ @) A0 ]7 ^" T
  8. .                page21. p: M3 G( X- E- N4 O7 i  }
  9. .                page225 B, m, _* W0 d5 v) v  q
  10.                 ......6 u1 V* `6 h) G
  11. .                page60
    ' p% Z1 A$ p" K3 w; ~* t' }1 g
  12. .                page618 |. W* A% Q+ d, K
  13. .                page62        Application程序终止地址9 e9 f" |  ~( D+ p5 ~1 P
  14. 0x0801 0000        page63        Flash存储器终止地址(根据型号不同有变) 同时用于存储APP程序的CRC数据
复制代码
- A" h4 n& T5 }+ u, I7 o& J' M5 I
我们需要根据上面的存储结构配置BootLoader工程和Application工程。& ^6 U7 X7 G: Q, m
% F' F+ i6 }7 Q$ o2 H. p
BootLoader工程配置如下:
1 h" h' {1 f7 T( [$ S7 i- T9 s7 z  V% h( y1 J: V6 s% I9 Z% E
20180713110726953.png 1 q4 l) q5 ^$ \; n9 y; C

, V8 m: g( {! r- n' w7 HROM起始地址为0x08000000,大小为0x5000。# w! p* X: D; M
: x' ~/ u9 [. O5 Q; g- G
20180713110754288.png
5 n: q$ m. H0 M5 |& |% g$ k

$ B/ n, w8 Q  R5 d; o程序存储基地址为0x08000000,RAM存储基地址为0x20000000。
- C* }& ]& r7 g* S8 ]/ i# C* U7 d: q8 z3 l' W
20180713110821948.png
, e7 f4 |" e- v& J, L1 P

; `0 G6 A) s2 U/ S2 X0 oJlink下载配置器件的Start地址为0x08000000,size为0x5000。5 s9 F% T/ |: M3 P4 R6 _+ t- }4 V
Application工程配置如下:
. u8 h5 x- W. R2 |' r
/ W7 R/ Q! z$ C; z! M. x
20180713111615812.png
3 l! W6 f! W& p6 F8 ~( x, V9 ]
7 x0 f( T2 s. J+ R- J7 vROM起始地址为0x08005000,大小为0x10000,这个大小大点没关系,但是BootLoader的大小必须严谨。% j+ U8 ]" w# n. {# b

+ F( O- g* ~/ F9 J8 C" ]) v
20180713111620735.png * s1 F$ M. W( j9 ]8 _

' T( j9 Q# K- g% c/ L  r7 x0 ^# ]最重要的地方!!!这里的R/O Base一定要写0x08005000,表示程序基地址为0x08005000,所有函数、常量数据的地址以这个为基地址。R/W Base还是0x20000000,。9 b4 g/ O6 V7 s) C
/ H0 h2 w* n: y) g- l( S
20180713111626674.png
, X" {) B4 w+ ?: V8 U2 \6 ]$ F- Z
Jlink下载配置器件的Start地址为0x08005000,size为0x10000。+ F/ Y+ k* \% P6 P3 Y; b; g

; c0 p) N( S" e: X: @5 M2 M配置好了两个工程之后在Application工程中里写测试程序例如:
; C4 \2 G( ]5 m  B! C
  1. int main(void)
      g% \: D7 t9 G6 X3 W, r
  2. {
    $ P6 j* K; E5 B" `! R2 I. _- I( N
  3.         Initialization();6 @$ u0 \& E! d6 g  @
  4.         debug("123456789\r\n");2 K7 ?+ N" R5 |5 N2 K- L
  5.         int i = 0;
    - r3 Z8 v: u3 N4 R% E2 d- |( _/ C
  6.         while(1)
    * b+ n; N, h: _5 z
  7.         {
    4 P7 y: z) x6 i2 {+ n% U
  8.                 debug("%d ",i++);, H9 I, D, ?" S: B+ t  b0 Z0 q, e
  9.                 delay_ms(100);! Q1 G0 D" F* j9 Y# u
  10.         }$ o5 l' {: @) P6 |* ^: ^
  11. }
复制代码
  z( J3 _) Q2 }# j) V8 U
然后通过MDK使用Jlink下载程序,发现无法运行。这是对的,因为程序存放在了0x08005000处,单片机复位的时候从0x08000000(默认向量表存放位置)处获取复位向量,而0x08000000处是空的没有数据(假设一开始单片机被整片擦除了),这样程序就跑飞了。
- R/ X* ?, @+ Y9 `0 ]" @
: [# _3 v$ p9 Y9 f下面来写BootLoader程序,简单测试一下,将Application程序复制过来,编译下载之后程序可以运行,因为BootLoader程序的存放地址就是放在0x08000000处的,故可以正常运行。接下来可以测试一下从BootLoader程序跳转到Application程序运行。我本来以为可以使用汇编跳转指令“B”来实现跳转:
1 X* [/ A0 Z+ |3 e2 Y) {; T
1 Q8 X! U% o  ~# E7 E9 g! c; ]
  1.         __asm("B 0x08005004");
复制代码
& Q+ W! P# n; l0 `, B8 Q0 y3 q
但是发现这个指令不能使用,编译器直接忽略了这个指令了,我也忘了ARM汇编指令,而且这个是Thumb-2指令,算了换一个实现方法:使用函数指针跳转。: @' F8 D( O& ]( f$ S5 ?
" V  N) x( E" K2 N& H, a* c9 k" `
首先要获取Application程序的复位中断向量,查看Cortex M3权威指南中写道:
' n* w7 U) b9 |+ N: D% \/ G% n
) T% _/ e5 c  b: L9 O" w* J/ J
20180713113350494.png + `5 r8 n, a6 w& ~
: H$ P( t) _8 ]& J& ^: P
可以看出来复位向量在向量表的第二个字中,那我们读取0x08005004处的一个字的数据就是复位函数的入口地址。
! ]/ R5 v6 U9 s1 d* M3 I( b
- O2 N- v# A' ^4 e7 O
  1. void (*p)(void) = (void (*)(void))(*((int*)0x08005004));
复制代码

2 M2 }( Z  K4 I+ r$ E2 r上面这段程序就能将函数指针p指向Application程序的复位函数。
* L4 D" M0 o& z
8 o/ C5 a- k% N& p3 R1 w; u得到了Application程序的复位函数之后我们还不能急着跳转到Application程序中去,因为我们前面有提示,默认的中断向量表是在0x08000000处,而0x08000000处的中断向量表是BootLoader的,而不是Application的,我们需要重定位中断向量表,这个在权威指南中也有说明:
$ d  e3 x- r0 Y* w- w% z& B+ g  s: k
. M, U: y/ a5 A3 {' l
2018071311393586.png
* f' j: i% J, \; T
/ y, ]8 H7 X9 ~3 B中断向量表是可以设置的,STM32也实现了Cortex-M的这个功能,可以调用NVIC_SetVectorTable函数:+ u1 B$ _' s9 p, |+ C3 E
  1. NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x08005000);
复制代码

% T+ A- L' Z6 D4 a2 p8 o修改了中断向量表之后就可以跳转到Application程序的复位函数中去了:
+ E+ ?3 ~6 `, ]
  1.         p();
复制代码

! E7 S9 C5 N* q  l2 g( v  M实验结果的确是可以跳转到Application中去执行,我们已经完成了我们要开发的BootLoader的第一个功能:跳转到应用程序并执行。9 e  t9 g1 A+ P6 G+ J' ~9 f
& n9 i  {1 M' k( [1 y

2 P1 x' _# G( Y( x4 C* J下面还要实现BootLoader的第二个功能,就是更新Application程序。更新Application程序其实很简单,调用库函数中的Flash操作函数既可以。需要注意的是要先擦除Flash页之后才能向Flash中写数据。并且无论是擦除还是写入数据到Flash,都要先进行Flash解锁操作(FLASH_Unlock),擦除或者写入到Flash完成之后进行Flash锁定操作(FLASH_Lock)。同时在操作Flash之前要清除标志位(FLASH_ClearFlag),防止之前发生的标志位影响下面的操作。
8 ]. n" ?, H6 s0 S/ q2 D* M' e
' J/ W3 L) H. l  rFlash的擦写实现起来很简单,还有一个重点问题是如何获取Application程序数据,假设我们使用一个串口通信,这时候需要使用一个PC端的配套下载软件将程序文件下载到MCU中,BootLoader程序要接收到程序文件并且将程序文件写入到正确的Flash地址中去。5 k' U' G( _" l- V' z
2 F2 D  N4 I) K
为了方便起见,我使用X-Modem协议,串口通信中这个协议使用的比较多2 o+ ]  S" Q3 y( f$ f3 F
: \% H3 T. ^3 E, ~9 ~4 o
当可以正确接收到程序文件并且正确的存放到Flash中去的话就可以跳转到Application程序了,下面是我的实验结果:
1 n0 i5 w9 N1 \1 a, K0 _4 _* E( v3 I4 |/ [
20180713115423731.png 7 i$ ^# k7 k2 L: l7 J

& j0 }0 `8 V' ^, R8 E, h. s执行顺序是线擦除Application空间的Flash,然后调用Xmodem协议将程序文件下载并写入到Flash中去,最后跳转到Application程序的复位函数中执行。
+ M! K* E' X+ T3 ?3 K" x7 \0 N! I6 |4 \6 C: w. b
这里要注意的是MDK生成的HEX文件是不能下载的,需要转换成BIN文件,网上有很多HEX转BIN的工具,转换之后得到的BIN文件也不是能够直接下载的,因为生成的BIN文件是从0x08000000处开始的,为什么不是直接从0x08005000处开始呢,因为使用其他的串口下载工具下载的时候需要从头开始下载,而不是从0x08005000处开始下载,我这里是特殊应用,所以需要先删除0x08005000前面的数据,得到一个可以直接存放0x08005000处的文件。测试的时候可以自己使用winhex删除。
3 d/ Y3 R; p9 i+ c————————————————
& z1 u7 g% d* d) P版权声明:哐哐哐 Quan6 Y& Z4 r( E; |9 q# u" c) P
如有侵权请联系删除
2 i/ C6 l* b* U( n4 u: C
6 p/ N$ z! a4 }; d+ J5 Z0 G! |6 l' O9 V- X0 S! ~1 h

0 O  X9 f  a. V+ Z. i' ]
收藏 评论2 发布时间:2023-3-30 18:13

举报

2个回答
Sevenliu 回答时间:2024-1-28 19:55:59

跳转到APP之前不用加载栈顶指针地址?

Sevenliu 回答时间:2024-1-28 20:00:07

APP工程生成的HEX文件后,转成BIN文件格式,在BOOTLOADER的程序中,就直接从0x08005000地址写BIN文件的第1个字节吧?版主的特殊应用是啥场景?

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版