本帖最后由 aimejia 于 2018-5-22 11:19 编辑 1 r# P9 \' G& ?. b
1 q6 |, Y2 V8 s最近一周一直在基于STM32F429项目的IAP工程,耗时4天才完成,得空记录下来。' q7 s" N% j4 S3 B
9 b; D' W" Y6 ?+ F* n3 |, n, A文章主要涵盖了以下几点:
8 u. Z5 D9 B& \( Q1. IAP是什么?$ q8 j$ S* q7 K7 V" V9 j. R
2. bin文件和hex文件的差别
0 Y4 u9 i, b9 ]7 Q& O3. ymodem协议介绍及其缺陷
: Q( u& k# {3 Z! k4. RS485通讯; r# }* ~* B* D
5. IAP的main()函数代码片段 # d! S1 e8 J: b; g" s. h
2 C2 ?- S1 L5 b) X9 A项目的框架如下:
4 d. r9 `7 l: N& w' d r* z* v0 W+ p( N
- ymodem协议+ X; A8 R; B6 V4 v" f0 u
- PC机_超级终端 -----------------> STM32产品的串口! A a# _& S/ j/ y) ~8 r
- RS485通讯线
复制代码 / g5 y) f. H: X. T3 y t1 R( ]5 }
从ST官网下载的IAP的SDK,其中包含了经典的ymodem协议和基于STM32F429的HAL库,工程就是基于该SDK开发的。
5 d9 X2 Y' f$ B' V1 T( p* `7 w- i
, F7 S6 V2 L' _1. IAP的概念
' R5 E/ ~; K8 E! P# o" K+ C9 r0 @+ ?# N8 t7 ]! P
IAP的具体概念解析网上搜索一大堆,在这里简单描述。IAP(In Application Programming)即在应用编程,常听说的还有ISP(In System Programming)即在系统编程。ISP指的是将通过JTAG等接口将单片机程序烧录进单片机的FLASH(当然也可以是其他存储介质,如SRAM),而IAP指的是采用引导程序(Boot) + 应用程序(App)的方式烧写单片机程序,App是真正实现业务逻辑功能的代码。 c( O. N: k2 ]1 x9 s' o
/ E1 ~8 [* v" {: |
一般产品的调试口,也就是JTAG口是被置于机壳里面的,烧写需要打开机壳,也需要专业工具和电脑桌面软件。IAP的Boot程序通过ISP的方式烧录到单片机的低地址的FLASH处,每次单片机复位后会先执行Boot程序,在Boot程序中进行判断,用户是否要升级,若是则从串口(或者网口/CAN通信口)读取App程序写到高地址的FLASH,读写完毕后再跳转到FLASH上App的起始地址,执行业务逻辑功能代码,若否则直接跳转到App的代码处理:
2 {: H% ]4 E4 t! Q3 z; n- |-- 要升级 --> 读取读取串口发来的APP程序,写入FLASAH目标地址 --|
, K( y/ x* a. h+ q! ^ - | |; Q) A& h* E( z+ P4 P
- Boot: 判断是否要升级App -| |! D: }# I. s5 z. R7 D* I4 A9 [3 F
- |-- 不升级 ---------------------------------------------------------> 跳转到APP程序起始地址处理
复制代码
' @. o3 m2 F2 T; x" C) `Boot和App都是单片机程序,只是实现的功能不同,前者是为了引导App,后者是为了实现业务逻辑功能。这里有一个关键的动作,就是跳转,即从Boot跳转到App起始地址处。
1 k/ E M5 P% d7 S4 B3 L5 j0 S5 {/ w2 q* c6 j
需要清晰2个概念:
- y n x+ w' V4 s+ [2 ~ (1) 程序的起始地址 $ G5 h) Z7 A8 k! i7 y
程序的起始地址默认是被放在FLASH的起始地址处,即0x08000000: , u. `1 h) z2 z1 E1 ~+ `2 R
/ H# r/ ~. }( V
4 E7 Y W* Y3 ?0 uBoot是放在这个默认起始地址的,App则要往后移动,这里设置为0x08008000:
\+ C% W) m# u N/ P' ]1 H
3 F1 H* R9 [# ]7 P
1 g' V- U3 e+ F需要注意,这是在App没有采用分散加载时设置的程序存放起始地址,若采用了分散加载,则需要修改工程中的.sct文件。详细内容可参照杜春雷的《ARM体系结构及编程》。7 m4 M( V" b# L9 _+ M% g, a, V2 n
$ n5 {' l8 w5 b' t3 {. K0 ^ (2) 中断向量表的地址
6 [: ?/ S8 R# u+ y. t+ _; e 对于STM32来说,每个单片机程序都有一张中断向量表,也就是说,在存有Boot和App的FLASH上就有两张中断向量表,Boot根据Boot程序中的中断向量表发生中断跳转,同理,App就要根据App程序的中断向量表发生中断跳转。中断向量表的摆放位置正是程序的开始地址。所以需要将App的中断向量表的摆放位置放在0x08004000。在system_stm32f4xx.c中:
& x) c- i! J. E0 y0 ?- h4 l( U$ o3 m9 I6 J$ o5 D7 [% G
- #ifdef VECT_TAB_SRAM
1 `# ], W5 W& T - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */2 r' U) O6 p( n. W% T
- #else/ v( [8 e0 V( `% z6 {
- SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */4 V$ U+ a& Z( A; o. U$ t2 t/ d
- #endif
复制代码
. \' x8 Y2 Z5 ~程序运行时候发生中断/异常/系统调度时就会去读取SCB->VTOR以获取中断向量表的地址;FLASH_BASE即STM32F429的(内置)FLASH的起始地址:
6 L* `" G# O" V( {* I9 Q8 T5 U3 H3 r% ?
- #define FLASH_BASE ((uint32_t)0x08000000)* [( \, p7 ?; o
- VECT_TAB_OFFSET是偏移量,所以我们可以通过设置此值指定中断向量表的存放地址。8 O3 F) T$ v& `4 M. c3 G
) g9 @, g8 f0 ]2 O( u# m: l- 讲到这里,顺便介绍STM32的中断向量表。表的形态如下:(摘自startup_stm32f429xx.s)+ u7 {0 |+ e: y7 H) p" X& m+ Y
+ I) B1 @$ ?# a- __Vectors DCD __initial_sp ; Top of Stack
0 W% m* M) V" X9 \% Z+ W - DCD Reset_Handler ; Reset Handler& `( _0 m9 n1 Z z/ V
- DCD NMI_Handler ; NMI Handler; _: ~& I- _9 d) p! @5 y
- ; ...5 R( C- s( ?) K) j3 z% ]
- ; External Interrupts
8 N. V) p& z6 X" B l) z! c - DCD WWDG_IRQHandler ; Window WatchDog
- X$ C, t7 k8 s3 R7 l: I+ } - DCD PVD_IRQHandler ; PVD through EXTI Line detection 8 |/ Q0 A9 u/ \* x7 h
- ; ...
; D- `1 c1 f' J' [ - DCD LTDC_ER_IRQHandler ; LTDC error6 h) \/ q; [$ ]8 G c, @
- DCD DMA2D_IRQHandler ; DMA2D+ W' a, O; z% a% N1 |, L o
8 ^1 n. Q7 U( A R- __Vectors_End
! D6 s+ N: ?' P9 U) v - __Vectors_Size EQU __Vectors_End - __Vectors
复制代码
( Z( ^4 w. J& K显然,Boot程序结束后程序运行指针跳转到App的__initial_sp处,__initial_sp处存放的是App的栈的起始地址。中断向量表的作用是当程序发生异常/中断的时候会根据表中的标号而跳转到具体对应的中断处理函数(ISR)。: f4 D% Q2 d- \; r
2 \& R2 w9 {) L
2. ymodem协议介绍
H }" E; }& x" o' |
. N0 d8 ]6 M# O, n* ~ 关于ymodem协议的接收网上资料甚多,不做过多的介绍了。简单分析ymodem的数据包格式:
, e- g! \. a* j
5 }9 _8 k: b! j5 \ P y6 L# K) K- /* /-------- Packet in IAP memory ------------------------------------------\
2 L1 W+ `" K' U8 J* @4 E/ p5 X - * | 0 | 1 | 2 | 3 | 4 | ... | n+4 | n+5 | n+6 | 4 w w/ s' E. H" I
- * |------------------------------------------------------------------------|2 x" K+ g5 B6 }" M. O
- * | unused | start | number | !num | data[0] | ... | data[n] | crc0 | crc1 |
9 @- ^7 M& g: w5 a/ R+ o$ c - * \------------------------------------------------------------------------/: t3 h5 P) ?" ~) f% A. j
- * the first byte is left unused for memory alignment reasons */
复制代码
$ \' A0 m4 |' @: E- ]; U (1) unused:无意义,用于字节对齐 & M; `6 N4 O( H# f
(2) start:起始信号,”SOH”表本数据包的数据区有128字节,”STX”则表示有1024字节
7 f( y' Y8 n: k, O, x. a (3) number:数据包的编号,编号为0x00-0xFF。到数据包编号到达255后,将会从0开始计数。
! S4 X: k2 K7 r# H# A5 C5 {+ d) m4 @ (4) !num:数据包编号的反码 / b# H4 @4 J X, V; P/ J/ `" M1 j
(5) data[0]…data[n]:数据区。对于第一个数据包(编号为0),存放的是文件名 0 e; c( c9 U/ M$ w) z5 h
(6) CRC0、CRC1:校验码(只有数据区参与校验)
2 ?9 \: j5 T7 L3 W- e 下面是通讯流程: ' B- s! Y8 p. c# O
(1) 接收方发送一个字符’C’,也就是十六进制0x43。代表接收方已经处于接收数据的状态
- h2 P# T6 I- C8 d$ A% Z (2) 发送方接收到’C’之后,发送头帧数据包,数据包格式如上所述,此时数据区的数据是文件名和文件大小 7 a& U( U# i$ O2 G
(3) 接收方收到数据包后发送ACK应答正确,然后发送一个字符’C’,发送方收到’C’后开始发送第二帧数据,第二帧数据即使第一个数据包 % g# F% g/ Y" p5 b- Y
(4) 接收方收好数据包后,发送ACK正确应答,然后等待下一包数据传送完毕,继续ACK应答,如此循环
" V# B; @# C1 S (5) 数据传输完毕后,发送方第一次发EOT,第一次接收方以NAK应答,进行二次确认
. T5 T, p" ^0 S$ y1 N6 z; ~, L5 Z (6) 发送方收到NAK后,第二次发EOT。接收方第二次收到结束符,依次以ACK和C做应答 . I5 v) k- ?1 k! k
(7) 发送方收到ACK和C之后,发送结束符
6 K4 O$ |8 [, x$ d ` (8) 接收方收到结束符之后,以ACK做应答,然后通信正式结束
, d5 ]: u, k; d# b, Q% N+ U5 g9 B% J8 {7 Q5 {
ymodem协议除了接收数据包外还会将数据包按照指定地址写入FLASH。本SDK使用的是HAL库。
# a3 A1 |9 {& _ 这部分的实现在ymodem.c的Ymodem_Receive()函数中:1 f5 }3 [& a; w& P
. B: j$ r( z' w% ~% h- }6 f- #define PACKET_HEADER_SIZE ((uint32_t)3): _' q% i, ` @; n# F6 D
- #define PACKET_DATA_INDEX ((uint32_t)4): C- ?: k$ q; \
- #define PACKET_START_INDEX ((uint32_t)1)& n& N" w$ [$ t. v4 ^) t5 d
- #define PACKET_NUMBER_INDEX ((uint32_t)2)7 p$ x- H; Y3 `6 C6 R4 R
- #define PACKET_CNUMBER_INDEX ((uint32_t)3): m& U2 x1 N! [8 ~3 Q
- #define PACKET_TRAILER_SIZE ((uint32_t)2): S1 c0 M3 e! ]# g8 h
- #define PACKET_OVERHEAD_SIZE (PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE - 1)* P; B( ^$ | m `3 k
- #define PACKET_SIZE ((uint32_t)128)# Z' w1 {4 I3 x1 }. W1 U$ I
- #define PACKET_1K_SIZE ((uint32_t)1024)
7 m/ ?0 H) _. |9 L - ' \: q$ e3 n9 }6 ^& W. V
- uint32_t flashdestination;
3 M1 I, _: B6 B/ T' s - uint8_t aPacketData[PACKET_1K_SIZE + PACKET_DATA_INDEX + PACKET_TRAILER_SIZE];
- t; g/ M1 P$ e, l; V( l7 S
6 J- l( m* k1 @- static HAL_StatusTypeDef ReceivePacket(uint8_t *p_data, uint32_t *p_length, uint32_t timeout)) g6 [+ }# Z) i/ A; X. |6 U" ~
- {
' E7 E6 N9 z( d- ~ - uint32_t crc;
7 z6 Q$ M4 x5 K( [ - uint32_t packet_size = 0;: s$ j' \, d: _/ a9 i4 ?$ N
- HAL_StatusTypeDef status;1 ~; \1 E8 N8 H& O
- uint8_t char1;
. O' x& ]: q0 I5 ?9 W; O7 V' Y
5 O9 w$ {% L5 m8 P2 E3 H- *p_length = 0;
) A! a+ a$ t& S- N( T9 V( g - //调用HAL库函数接收字符,UartHandle是数据包的句柄
8 U1 {/ d& }9 P6 S) z - status = HAL_UART_Receive(&UartHandle, &char1, 1, timeout);5 L6 _6 l) B: z# D& t- n
- % X' X- [8 ^5 f- D3 `% k
- if (status == HAL_OK)$ e7 N; I( j \4 q$ F# X
- {
1 C1 S' [2 {, o/ P3 Z% q - switch (char1)0 e( c7 O" U$ @+ h% g. r
- {1 Y7 `3 c" N2 p; J/ V
- case SOH:# R! j8 r6 M9 ]
- packet_size = PACKET_SIZE; //PACKET_SIZE为128* @, [8 [8 Y) \2 f8 p
- break;) ]% a) d9 c" m9 r* j
- case STX:/ ^+ R6 }, V& v+ J
- packet_size = PACKET_1K_SIZE; //PACKET_1K_SIZE为1024
% U$ K8 S9 M1 z" f' R - break;
+ Z4 v" D! }9 L8 x" U! X; a! S9 E - case EOT: //数据传输完毕5 B% {2 R/ P! |1 r
- break;0 F* C& L+ Q7 `% Q3 V8 _8 g) Z
- case CA:" U. h+ D5 {! w- B4 U- b9 Y4 Z9 S, O6 E
- if ((HAL_UART_Receive(&UartHandle, &char1, 1, timeout) == HAL_OK) && (char1 == CA))
' s) I: z) G- u& J( e# v - {
3 F5 [& ~( s. I- r j - packet_size = 2;2 s3 c% D/ V" T0 p3 K
- }) F1 X* _3 L$ r1 ?) ~7 s$ b4 q
- else
5 L% f, E% V2 N% R - {
# L0 U! E5 E: c8 d2 E - status = HAL_ERROR;* o6 k6 r' {" Q
- }
8 c, z- I* S4 l, ?% F8 O - break;, Y0 ]! f2 R2 p8 p, V' l& }$ x, D
- case ABORT1:
8 x, w/ C, R! a4 c - case ABORT2:0 O2 j: r: _; Y* M/ g- F
- status = HAL_BUSY;
6 O# [. h% U# ^3 u6 ~ - break;
+ B' F# s% w7 Y- D. Q# K; d0 d3 k - default:3 B1 B8 i1 ^% N9 a
- status = HAL_ERROR;
: e7 r) K" B5 c; s+ \4 r7 \2 Q3 x2 J - break;
: |5 d d# v6 B" O% g, Z - }
) }- c/ x6 n$ x( C: a1 Q. ^# W - *p_data = char1;" i# l; M' A9 f+ w: z0 p2 a: L
- - V* k8 j$ g0 |7 ~, d
- if (packet_size >= PACKET_SIZE )
/ i+ @' X0 a( ]6 W' `: N2 m - {4 P/ r' `. j* ]
- //接收真正的数据包" R* @, r1 h; P) u& e+ ~. t, `
- status = HAL_UART_Receive(&UartHandle, &p_data[PACKET_NUMBER_INDEX], packet_size + PACKET_OVERHEAD_SIZE, timeout);5 I: v. h. J) @
- 8 H& x" A( H7 X Z4 e
- //检验数据包 x+ R t& z; l2 L# p8 B
- if (status == HAL_OK )
6 d* j- R/ `4 H0 {6 P- c" k - {0 L0 S5 r# R- t3 z; D4 B; T. P
- if (p_data[PACKET_NUMBER_INDEX] != ((p_data[PACKET_CNUMBER_INDEX]) ^ NEGATIVE_BYTE))
( w" R$ p% s [3 } - {
, r9 k" Q7 e7 U# a - packet_size = 0;, f) T& e% o# q g
- status = HAL_ERROR;! F+ I. F" o {9 z t5 q
- }
4 x; G& s+ B- Z8 \: b+ f - else. ~. ~& j* m% d5 g; o; @$ T/ r* w
- {
% |9 J0 ~6 P# e: k0 ~/ v - /* Check packet CRC */+ g3 i6 ]" r5 [
- crc = p_data[ packet_size + PACKET_DATA_INDEX ] << 8;
! ^8 f# l& C) n - crc += p_data[ packet_size + PACKET_DATA_INDEX + 1 ];
1 z1 B1 {" @* C/ Z; }: f" c7 z - if (Cal_CRC16(&p_data[PACKET_DATA_INDEX], packet_size) != crc )6 E1 q; b7 Y: \8 Z/ P) _
- {
# Q; v. c% C' }' C - packet_size = 0;
9 o7 ?- f& r. ]0 P/ A - status = HAL_ERROR;. t" o$ C/ j) H
- }
& g w2 p% |9 @. P - }
* o5 t' t- J. D5 q. c) o0 I2 A! h* A - }
' r# f* c# k" G' N - else
. g4 _+ N) _. @7 O: \" j: q" W - {
* ?/ A7 p# N1 A8 ? - packet_size = 0;
% z" p' T# y6 `( K - }9 P* u8 y- b% W1 A3 e# `
- }
* t4 Q% y( z# p7 O( r/ x! I - }
( m$ N% h7 ? X7 `0 n - *p_length = packet_size;5 n( c5 ~1 s8 d/ i: i
- return status;0 d; ~. R% T: W2 R% q1 [7 M
- }0 c) v% i8 C: b. f- w" N, H
- % ^ e( \1 ^8 t* ^7 ?! V
- COM_StatusTypeDef Ymodem_Receive ( uint32_t *p_size ) //p_size为输出型参数,用于存放从PC端发来的文件的大小& ^. }0 ]8 S; o1 a4 i, G2 Y
- {
: a) c- o# s4 j0 R$ U6 f - uint32_t i, packet_length, session_done = 0, file_done, errors = 0, session_begin = 0;/ B5 G. |' t0 @) [! `
- # \( P$ b" N$ D, `! B" Y
- uint32_t ramsource, filesize;
8 S0 i6 ^, l) n; Q - uint8_t *file_ptr;; x4 T$ g' l. I5 x( r
- uint8_t file_size[FILE_SIZE_LENGTH], tmp, packets_received;
" F5 Y. o6 J* t3 |- k) y1 d$ W - COM_StatusTypeDef result = COM_OK;6 r& |9 `+ ? B; O& K& `! g
- . X5 Y) P m0 B+ d' D4 E+ j+ ?
- /* APPLICATION_ADDRESS是App的起始地址 */
) ^3 T4 N5 U- h( o$ p - flashdestination = APPLICATION_ADDRESS;
. u: ]$ v' U4 t4 { - 9 M) ?2 s. Y; ?1 a! o$ r3 |- O
- while ((session_done == 0) && (result == COM_OK))
: x/ @3 _. e: j- i2 O# m' `1 F" c - {! k. \2 m. G: o' `( @- K& p
- packets_received = 0; //记录接收到的数据包的个数. L: B" \: E7 p7 Y2 |# U
- file_done = 0;, {, Q9 K+ X$ h0 E
- while ((file_done == 0) && (result == COM_OK))
. o7 ]# l8 |8 a' i - {' a6 T8 o$ o5 D e# Q
- switch (ReceivePacket(aPacketData, &packet_length, DOWNLOAD_TIMEOUT))* Q u' c9 }5 ]& g6 w, j/ ]
- {
/ b; _7 ~' w1 p/ H Q, n - case HAL_OK:
" y' R( G1 [& K* k' Y' n$ u - errors = 0;
! W! a* C4 \ N1 s5 V - switch (packet_length)2 \; \. r* O& k% w7 \( K9 W
- { C* L/ M8 Z8 h$ ?$ K
- case 2:4 }3 |0 e D8 A0 ^0 b8 [
- /* 发送方终止发送 */( E" a* y4 c) ]* f' B3 u: W
- Serial_PutByte(ACK);& ], t' K3 k, O R, i
- result = COM_ABORT;. S( x3 C. [ \/ e" X( }4 ?8 o, K
- break; l, ?+ p- v$ k. }7 o
- case 0:$ ]* U6 l2 I0 j: K
- /* 正常结束传输 */
' e. [5 A, C: P# E - Serial_PutByte(ACK);
0 Z* F% M; T8 a% k% { - file_done = 1;$ M* Z4 v" {) O
- break;+ f0 L& U) P7 u/ Y
- default:$ Q# }6 E0 R+ ^) q
- /* 数据包编号出错 */
1 _$ b$ M7 }2 v- H - if (aPacketData[PACKET_NUMBER_INDEX] != packets_received)0 O& L& ^4 }& E& [/ Z- s1 f
- {
; K( c( | p# K% W: E: a - Serial_PutByte(NAK);
" `+ a; C' y& {7 o, g% r - }4 S( @; M5 @( g8 J0 [# M# @
- else2 q# w) q5 |' E5 s8 J) y
- {
% A; l3 c, j5 i# ~& X" v - if (packets_received == 0): n& @& ?1 _0 b2 B2 }
- {
. d6 i; ?' i0 A - /* 数据包编号为0,证明这是数据区存放文件名的帧数据,事实上这个判断是有误的 */3 w+ O! d4 H6 m: |. ]
- if (aPacketData[PACKET_DATA_INDEX] != 0)
1 Q/ r6 \1 _1 a9 i7 ^+ ^ - {* M3 V$ p5 r/ Y6 N5 ~
- //读取文件名
/ r" m7 T( }5 V; ?' L1 e3 L - i = 0;
' A) E4 y5 T" `% \ - file_ptr = aPacketData + PACKET_DATA_INDEX;
! Q a. S, x& N( W! L' o% W/ ~$ m - while ( (*file_ptr != 0) && (i < FILE_NAME_LENGTH))
0 u7 x% |$ d, w6 ^% X& z& P) U e4 s' F9 T - {
; m% s8 O: Q1 j - aFileName[i++] = *file_ptr++;
7 { M6 _: q" k9 |5 \* S - }+ T: J! D) K- ^. E+ U' \" X
- aFileName[i++] = '\0';$ ~. U* \9 O& B+ T$ j
/ @9 o: W1 ]- E, m- //读取文件大小
: C4 c* }, v" g; f7 ]5 l+ ` - i = 0;
" E+ H; I: |# h: E - file_ptr ++;* ~ j0 o3 {4 ^1 C- v8 ~( p+ `
- while ( (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH)), n2 X/ T7 L6 r/ v- Z M4 n1 m* L1 j
- {# Z3 A" E4 b9 P) `% A
- file_size[i++] = *file_ptr++;" v" \* e/ a- K1 K- D2 I
- }
1 L# V" j. N8 W2 m) o4 o - file_size[i++] = '\0';
$ r+ X+ ]7 ?7 k: D - Str2Int(file_size, &filesize);+ c" U: x' S# Y
- 6 X8 f& E) f; N9 n
- if (*p_size > (USER_FLASH_SIZE + 1)) //文件大过于可供存储的FLASH的空间% M+ F' I( s" N) S/ H9 F
- {
+ A9 z. w7 u6 z: m - tmp = CA;8 d; x. m; E* S7 I5 W
- HAL_UART_Transmit(&UartHandle, &tmp, 1, NAK_TIMEOUT);2 f4 ~7 _: T Q( ?4 ?3 a+ `
- HAL_UART_Transmit(&UartHandle, &tmp, 1, NAK_TIMEOUT);& \# \ P. o5 D# B7 ^+ F
- result = COM_LIMIT;
2 C8 u; T: u9 n) C$ l - }6 Q& j- {( E& i" \; a) B
8 n% o! p& {* \* i# ^- /* 擦除扇区 */9 [) Z0 `. S; i9 Y9 T
- FLASH_If_Erase(APPLICATION_ADDRESS);) n) S4 ~9 {" m3 |7 a# [! H
- *p_size = filesize;/ w: n4 o7 e& {" j8 Z- O( b0 X
- / n0 v+ V B# f% c9 d2 G
- Serial_PutByte(ACK);& T2 H, H9 Q6 R
- Serial_PutByte(CRC16);- ]& F/ b8 b! x( F
- }: A/ y7 S2 l: ?6 C) \
- /* 文件头为空,传输结束 */6 u: h! E0 W5 W% G; f
- else
1 s9 ~ J- G& }, o9 u- w - {
9 ?- ^0 B0 K6 O, N* ]0 Z0 T - Serial_PutByte(ACK);; p5 k9 I2 m6 O9 w) h. B
- file_done = 1;
+ j g& K! b. @4 a - session_done = 1;) m( x7 M0 y( O
- break;
! k) r. w, {- B& [$ t: G7 P - }
: w6 v7 Q4 {4 M7 Z- _ - }
& R% ^* Q6 H# |8 u8 ]4 ~ - else /* 真正的数据包 */
8 q+ P9 |: {1 r+ w& U: }, _ - {0 L. Z) G5 l- d6 Y, d8 l( ^
- //将收到的数据存放到FLASH! e+ d* f# c- U& _
- ramsource = (uint32_t) & aPacketData[PACKET_DATA_INDEX];5 _! B+ k/ M' L! d: f
- if (FLASH_If_Write(flashdestination, (uint32_t*) ramsource, packet_length/4) == FLASHIF_OK)8 q& l# H8 I8 }( ~! e% r
- {$ g0 L1 @6 b( L, s0 |- t E% A
- flashdestination += packet_length;
1 m) A* t: y" m* m2 S6 E0 J) ~ - Serial_PutByte(ACK);* K% c0 u/ C) [( o& S
- } S: r+ H* N) ?
- else //写入失败+ y$ @# J3 n' ?9 g! C1 S C8 M& t
- {
% |+ i ]; m5 j* o - /* End session */
6 u+ P5 Z# k1 Q" u! p - Serial_PutByte(CA);
8 |# d1 X" @! ~) D% }9 V - Serial_PutByte(CA);) p0 \& T' J6 Z$ t5 L" A7 M7 U2 u
- result = COM_DATA;
9 |! p/ ?1 \1 v E4 f) X! j - }
. I' i; }5 ^& s$ w* B, P - }
0 @) r) o7 A+ j1 `, [ - packets_received ++;
; f! X; i( ]1 M! J! L - session_begin = 1;3 _; _+ D# Z$ h4 p' V9 n
- }" b( l; U/ D- ^9 P, S1 t0 m/ j( B
- break;
6 x6 H8 U6 N6 K' u - }
% }6 Y1 w, r" ]8 j - break;
, t$ C, a* Z4 o g- e3 Y, p - case HAL_BUSY: q1 b8 s! @ A" W# B
- Serial_PutByte(CA);
z* v. S4 ]. p- F0 S, N - Serial_PutByte(CA);: @& C3 Z, c& ^" @& a/ g
- result = COM_ABORT;
# `6 b% d; H$ h7 R& W - break;' [* v9 F* J; l$ o6 {
- default:
: e. y4 l8 L- v: Z - if (session_begin > 0)* Z+ C& |$ T2 D8 v. q4 m' T
- {2 G* N Q: [& `3 b1 ~: S
- errors ++;
2 |' z6 _: s' J. i- s9 j, ]$ x - }
2 m |, ]" C! |$ F- F+ }2 }1 g - if (errors > MAX_ERRORS) errors大于MAX_ERRORS,PC端将收到"接收端未响应的提示",终止传输
5 g8 n- n: U! f - {" Q* S- f+ y& @, P0 [! q
- /* Abort communication */0 l3 u; W7 A4 U8 O
- Serial_PutByte(CA);7 u5 I0 t" ?7 l5 k+ ^" ?
- Serial_PutByte(CA);
1 O7 F* \% P; D5 m6 U( f0 l - }
, x- y* t8 l9 L7 K' {# P - else
2 T/ _& l1 q& `! g1 C - {
; u0 h1 P G7 V6 c' B7 S - Serial_PutByte(CRC16); //返回'C'字符,PC端提示接收端未响应并记录次数,超过次数PC端也将提出
$ q" s* r7 n6 e# @4 C - }
* K4 X; h7 Z1 o/ L5 X - break;
0 y4 ]$ L- I- n6 }( ^2 v - }
6 p$ q6 l- a! a0 } - }
4 {1 x' T5 \. e$ P2 X - }
& o# i. P! [# L5 z6 `* S, w3 D$ b& N - return result;
) v4 \0 k# f+ _ - }
( b, u: V' X* d6 [3 O* Y6 n6 R8 W' t
复制代码
3 n$ A% h5 P+ i2 \事实上,对数据包的接收处理操作是有问题的:packets_received用于记录数据包的个数,它是uint8_t类型,取值是0-255这确实是符合ymodem协议的,但是超过255的数据包呢,观察上面代码可以发现并没有对第超过255个数据包,也就是第256个数据包的处理。第256个数据包的编号也是为0,会进入:
) D7 B5 B& t6 z; N, M# K8 n8 O& m7 }* `4 K, o
- if (packets_received == 0)& T& b% F, y& ~( _& d# Z; Z9 W
- {
$ A/ m9 v0 Q5 ]1 \2 u. U, ]* j - if (aPacketData[PACKET_DATA_INDEX] != 0)8 h( H" `8 U% X% k6 d1 C& v5 a
- {. T2 O% W5 L7 |( K
- //进行读取文件名、文件大小、擦除FLASH操作
) I3 E) J8 B7 q9 G) e' U - }
' ^1 T4 k3 g3 [8 s - }
. @5 b; Z1 ]4 u
复制代码 & v: m& q/ r" D: S; x
但是编号为0的第256个数据包的数据区事实上是数据,收到该数据包还是要以真正的数据包的写FLASH等操作。' [) o( P' ?; u3 |* P* B! I
% t+ O1 e* N3 o( J5 y! }* m
一开始我利用IAP传输小于256K的APP的时候是正常运行的,后来传输400+k的APP就会出现问题:无法跳转至APP。经过多番调试才定位于此,所以简单修改上面的代码,代码片段为:0 M% n+ K; |; y* b, _* ]
+ h/ Z/ P- `5 V+ z% I$ U0 d- A! s- volatile int8_t is_first_pack = 1;
8 w) q% R0 I- a& n9 s+ G - switch (RPreturn)
" P( \7 G r8 g- o6 w; \+ s7 K - {+ \4 R/ U. J& X. g8 h# c
- case HAL_OK:" U0 c1 B+ w1 H; Q" F) l' I
- errors = 0;' c1 [# B! v3 Q" M4 w) Y
- after_isp = 1;
6 `8 _3 _5 ^, e* g; F. f - switch (packet_length)8 ^9 f. v" S9 O3 I, Y
- {& G8 ^, ~$ l+ u
- case 2:
3 f" ~: h7 A5 z9 f8 l! a, _9 i - Serial_PutByte(ACK);: M8 Q+ o0 \/ p8 Q" D! Q
- result = COM_ABORT;
4 p$ a; j/ H7 L& F: T% C9 [ - break;/ _3 M/ {) v- k+ w
- case 0:& v q( M# |) G9 b& @3 Y
- Serial_PutByte(ACK);
5 O4 }+ a: [" v6 ~: Q - file_done = 1;9 u" e8 X( t5 ~' T% I0 A
- break;0 _4 E+ t, I; A- ]$ ]! c4 c/ [
4 h1 I8 \. {) f$ j- default:
# U: S0 {) E k. N; p, z5 H' p - if (aPacketData[PACKET_NUMBER_INDEX] != packets_received) //ÅжÏÊý¾Ý°üµÄ±àºÅÊÇ·ñÕýÈ·
6 p& H3 R4 b6 y+ B% Z) z8 { - {
+ H/ B. Y6 A) G6 g% b3 Y - Serial_PutByte(NAK);' F* M3 @( B# R2 F
- } O; r9 P& i$ x8 L
- else3 {5 s9 f( j! ?8 Z
- {
" a G1 r% R4 ]" i& C- @% l1 e2 c n - if (packets_received == 0 )
4 q6 n# A T, E2 r! | - { ) V- i- g5 M5 q1 G7 j# ^
- if (aPacketData[PACKET_DATA_INDEX] != 0 )
0 M5 @0 r7 G6 D# a$ [ - {) y: Z# C" y. v) C6 }- S) U1 _4 Y
- if (is_first_pack) //第一个编号为0的数据包
+ W7 d p1 C$ m/ Z - {
! C4 B5 m' X W8 C - //...
$ M( p2 u) |7 ` - is_first_pack = 0; " v1 v; ]2 W9 H7 K2 p
- }# R) d4 `; L/ B
- else //即使数据编号为0但不是第一个数据包,采取存储操作 & c8 G. z7 e i# H5 K c( Q: D
- {( c( c( O3 n! u ^4 `
- ramsource = (uint32_t) & aPacketData[PACKET_DATA_INDEX]; % ?3 Q# O) A& L, L7 [# N9 }
- if (FLASH_If_Write(flashdestination, (uint32_t*) ramsource, packet_length/4) == FLASHIF_OK)% q3 t0 {5 U1 f* F$ A
- {
* C0 G$ ]0 e. d: i& @6 `& [" [4 x - flashdestination += packet_length;+ L; _) |7 H, }+ z3 D4 c3 g
- Serial_PutByte(ACK);
+ b6 k6 y/ h/ ?) g# [2 _0 p - }
6 z2 ]! Q6 c, Q! ?" y% S' j - else
3 P T/ f, _9 w+ H - {
# ^7 d) k+ I' ~. w% k7 l! \* i7 L - Serial_PutByte(CA);4 g7 e! q# ]! g. [7 V' h+ d
- Serial_PutByte(CA);* c- o U5 |6 a+ x8 a
- result = COM_DATA;5 j/ L* T- x+ q$ r; {7 C
- } 1 H g$ A$ t' O- p, K
- } V- Y6 y7 g1 s% h4 a
- }8 b0 [6 |: D% l/ |1 M5 I
- else
9 @0 g7 C7 N$ g2 [1 K - {, u: X7 k+ T" H4 U! K
- Serial_PutByte(ACK);0 d: D8 [& G* A" C s" L$ [
- file_done = 1;
4 d3 p' K: g2 V/ v& { - session_done = 1;
9 {5 a# z4 m+ e - break;4 i1 A5 _. G \6 w2 Z4 s0 I( V
- }3 C4 G8 | }3 B5 ^9 l" S' X
- }
! e7 i; ?# |2 J! G5 d9 v - else
+ `; Y' z( z* u% t - {
' U& m" a ]1 }) U
6 L2 ?1 B ?- |- ramsource = (uint32_t) & aPacketData[PACKET_DATA_INDEX];
% {8 v% c( i) \ - if (FLASH_If_Write(flashdestination, (uint32_t*) ramsource, packet_length/4) == FLASHIF_OK)$ `; X( n2 v" g( D5 E- y
- {0 x8 I9 Q1 {: X- N7 q$ \
- flashdestination += packet_length;
1 x7 t$ `) E; h - Serial_PutByte(ACK);
( }& s- c" R- I3 A - }
" a8 a+ P3 H0 Y( y* B/ v6 P - else8 p* u+ ^8 ]8 ~2 ~$ @
- {- W+ k6 y K5 y& Z! p3 i
- Serial_PutByte(CA);' `6 Z- ~( D$ ]2 |
- Serial_PutByte(CA);
" ]1 s4 ?0 l T1 y) A - result = COM_DATA;) t- q% `; P( x5 C& w1 a
- } * M$ \3 q" ^2 T. n t' L, \0 k0 M
- }/ v7 F& t$ F& N! U. a
- packets_received ++;
2 U: ]4 I2 l3 s v- h - packets_received = packets_received % 256;
9 H8 |/ @4 G- M1 ^# E. l" O, Y" C5 F - session_begin = 1;3 }- A% A) f$ V* J8 w. i( _: }
- }0 O9 m3 C& z3 _ K7 G
- break;
3 Z* @6 N: @+ D2 ~ - }
; G, r! _- y: L" W5 a - break;
! c1 X/ @& e/ l - //..! q3 o0 l2 @! L0 [3 J# N; V$ ^7 F
- break;7 }; N8 u& ^4 F9 t+ N# Y
- }
: q5 Q' g9 ~* n" R3 i# Z- m
复制代码
7 w4 S( g' I$ J3. Bin和Hex文件的差别
4 k$ v [) y2 i9 @. \3 r4 Z
$ N* O: v) H3 k8 f: b; Z( f 第一次接触Hex文件一般是在学习51内核单片机的时候,通过烧录器烧录到开发板上使用的就是Hex格式的文件。Hex文件是以ASCII文本形式保存编译后的二进制文件信息,注意这里强调的是文本文件(而非数据文件,即二进制文件)。文本文件是人们可以看得懂的文件,但是计算机/MCU只认识二进制数据文件(Bin文件),Bin文件才是MCU固件烧写的最终形式,也就是说MCU的ROM中烧写的内容完全是Bin文件。由此可得,我们通过烧录器烧录Hex文件到单片机的ROM时,烧录器其实会将Hex文件的数据转为Bin文件的数据,最后才烧录到ROM。# U; U7 P# C# ~: M) F8 ~
! _3 Q" e) k" e
其实明白这一点我们就知道在IAP工程中,APP文件需要是Bin格式的文件而不能是Hex文件。既然Bin文件是MCU/计算机最终想要的,为什么我们不直接生成Bin文件,而却要生成Hex文件?其实Hex文件保存的不仅是Bin文件的内容,还有一些附属配置信息,随便拿个项目Hex文件分析:
+ ~6 O; W0 h, b9 k6 U* `
8 {: \' [* c7 R" N3 G; I& I' U( g- ' c& _* ~! W r5 h: {2 l. u
- : 10 9AB0 00 6841298459D0420F9AC4641EFBC10408 2E
. W% P! S. e5 o2 B0 t - : 10 9AC0 00 E7604BC5CC64011029B85A6980413C55 08
& h9 ` g. C! W* y - : 08 9AD0 00 55557C2964291C81 15- N: X) M- Z3 m) z8 S* O8 i3 j
- : 04 0000 05 080081AD C1
8 @9 H1 g5 n# Q; x( Z& D. a c - : 00 0000 01 FF
复制代码 9 ?) Z' Z9 V( M# W9 L
Hex文件中的数据是ASCII编码,所以是人们能看懂的。上面3行内容,每行都是以’:’开始的,之后是数据长度、地址域、数据类型、数据域、校验和。
! {0 V5 u, t( d* W4 M& F) j
- @! C% D/ Z: ~$ ]- /* /-------- Hex Data format -------------------------------------------------------------\% L" R: t: {- F. W- X% m% @3 @- |
- * | 0 | 1 | 3、4 | 5 | 6 | 7 | ... | n | n + 1 |
! d0 t8 r( g5 q1 T! R/ \! o - * |--------------------------------------------------------------------------------------|7 t/ y; i" u% i w
- * | : 开始 | 数据长度 |地址域 | 记录类型 | 数据域 | 数据域 | 数据域 | 数据域n | 校验和 |1 R5 _& w" g* t7 u% h, z I' p
- * \--------------------------------------------------------------------------------------/: O2 K' z) r P* v# k/ I* ]
- * 每行数据都是以冒号开始的 */
复制代码 % P6 d2 P- x( o" R! {# H7 a' {
注:记录类型的意义 ! t$ R+ L% O/ W1 O. j! u
(1) 00: 数据记录
; _0 H9 h4 X6 {8 G9 L" p2 v9 } (2) 01: 文件结束记录 ' V( {2 D1 A% c* T
(3) 02: 扩展段地址记录 9 d# K9 n; Z- @; T9 F
(4) 03: 段开始地址记录 1 A$ b: G' }6 m. |; X0 \
(5) 04: 扩展线性地址记录 " F' p4 h w) m$ |: t
(6) 05: 线性地址开始记录
; c; v' m5 r3 L2 X7 N; z- h& a+ Y7 l$ i8 R8 q7 U" m f
由此可见,生成Hex文件的意义在于:
2 @: x- b7 j: h! c1 G (1) Hex文件使用ASCII文本保存固件信息,方便查看固件内容 % q _* R# j; s
(2) 文件内容每行的校验和与最后一行的文件结束标志,在文件的传输与保存过程中能够检验固件是否完整+ B* a6 t1 q" {* Y
6 @! d( O4 k. g+ h/ B
因此hex文件更适用于保存与传输。相比之下,Bin文件纯二进制文件,内部只包含程序编译后的机器码和变量数据。当文件损坏时,我们也无法知道文件已损坏。不过在IAP中,Bin文件仍旧是不可替代的。
* {+ ]/ r" g( ^' U% ~- v. K2 O. L( }, V& a2 v# f# q
4. RS485通讯) R# S/ h; Q( R& H+ A7 m
9 k8 C# v; ]! ?& A" f 从ST官网下载的的IAP SDK是基于RS232通讯的,即ymodem默认是从串口接收在APP数据的。但是我这个实际项目中用到的是采用RS485的通讯。RS232转为RS485,在软件上只是多了一步方向控制操作。因为RS485是半双工通讯,所以在发送单片机需要发送数据时需要将RS485总线设置为发送状态,接收数据则需要设置为接受状态。关于收发控制,软件上实现只是拉高/拉低对应控制RS485控制芯片的接收状态的GPIO即可。我的做法是默认是接收状态,当要发送时在发送函数内切换为发送状态,函数退出之前又切回接收状态。; B v& }! ]* l8 V# J* X
4 x. |* v+ `8 e j) }8 ]* q5. IAP的main代码片段分析# p b7 ]- W. |1 i p
4 R$ L7 o) A' y* X+ J7 C. s! W; U: ? IAP升级代码工程,一般需要基于一个能跑起来的通讯(这里是指RS485)和LED灯(用于指示状态,当然也可以用LCD等其他提示状态)运行正常的工程代码。下面是main()函数:
6 E: I/ c; o# H& A8 m- B) O% G( J8 o* b1 J9 R3 A0 Z. w
- void SerialDownload(void);7 [$ H: i2 f# S8 H
* a4 t( p0 r! l- //定义函数指针,用于跳转到APP
+ B- U" D. O+ V9 n+ X! y b - typedef void (*pFunction)(void);
9 q p$ d- p' U - extern pFunction JumpToApplication;
4 V) }0 A% t1 @" o - extern uint32_t JumpAddress;
; ^( Y5 B7 H* Q$ I3 Q7 g - UART_HandleTypeDef UartHandle;1 V& L w# N$ W* ~- A& X8 r7 n& d
7 x* n* R0 N1 Y. |' S- int main(void)
$ D; C* }! s( ~) [' d8 r+ ~ - {! z8 j4 K7 {, [$ ~7 d+ F+ c2 V% ]
- HAL_Init();
7 m& \( J2 ?5 B, h- v3 C7 A0 p6 [$ L/ T - SystemClock_Config();
' _5 W/ y% n* W% \6 u3 n
' T6 l0 l; g# R, o' Y8 g7 ]3 ?0 k- LED_BSP_Init();
5 m; @2 S! w4 x( Z - FLASH_If_Init();, B' ?8 Y! @. C# f# V: K
- 8 b6 n( ~7 k' I. F5 F# f, m
- //初始化RS485" `3 I; c6 L% Z0 x( {% c5 ], \" X6 Y
- UART_Init();
* S2 ?" n9 z2 r% D - RS485_RX_ENABLE(); //默认设置为接收状态
9 P+ H# L% n7 Y% w# e& B' A - HAL_Delay(10);, _& ?% Z1 y5 v# }
* @, H8 H( Z' U3 o( B$ w6 P- IAP: + k E) b9 A1 d' ~ j
- Serial_PutString((uint8_t *)"\r\n====================================================================");
+ b( d, Y) ?0 S - Serial_PutString((uint8_t *)"\r\n= IAP For STM32F429xx =");* n/ e, u, z! A' y6 P' y
- Serial_PutString((uint8_t *)"\r\n====================================================================\r\n");
' ~$ g* }: b8 J/ v - ' [- o2 ?. z! }4 n+ i0 a/ ]- F
- SerialDownload(); - U; @: k! }( w) z9 p
- HAL_Delay(10);
4 Y, D$ l D- G8 C7 K* l) `) d - % Z* R! A+ d K+ A7 J q/ Q
- //APPLICATION_ADDRESS是在FLASH中存放APP的起始地址- z1 a7 B/ C- B4 `( _! V
- //此判断是为了保证APP的栈地址是在SRAM中。其实结果并不一定是0x20000000。有些APP可能定义的全局变量较多,那么栈的起始地址会偏移
* H' G1 A) x3 @% p) L) {+ C0 [& z - if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
/ o: z# _' M# W' h2 [( y/ Y3 }2 F - {' m* K* Q- F$ d) p9 H
5 w; I3 X6 Z3 N* l5 E4 k* U+ Z- Serial_PutString((uint8_t *)"\r\n======================= Run application ======================= \r\n\n");; y4 q$ q- u+ a$ @! |
- HAL_Delay(10);7 F# f5 P; A) V* s
- % z& M y6 j/ T/ L. F
- /* Jump to user application */# d" W* O H* z' C6 B) b
- JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);) _! m: i3 c- r! x
- JumpToApplication = (pFunction) JumpAddress;
! U! g3 Q6 A- l1 r - /* Initialize user application's Stack Pointer */
% q2 a8 E$ s# Z g0 ]( D0 _2 f7 Y( S - __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);) \8 j. ]/ P* W% B6 G1 I9 O
- JumpToApplication();: |/ C/ F/ T# D( c" X1 x
- }8 Z4 a# R# _$ `% V9 S5 `
- / ?* n( `2 y: B U
- Serial_PutString((uint8_t *)"\r\n ======================= Download error, once again ======================= \r\n\n");3 a3 B7 z0 ]1 D+ U5 v
- goto IAP;
# n K( {( b8 H- ~- _ - }9 D3 M0 ]7 ?( Q$ o K
- ) Y' ~9 F# o2 p
复制代码
) j h/ S" z$ jIAP的实现还是十分简单,但是中间走了N多弯路,特别是在调试ymodem接收大于256kb的APP上。关键要注意上述几点内容。当然,上述内容属于个人见解。
+ b4 R- l& x3 b: y9 u
" f. y1 N/ b; q4 C( ?9 w
# l1 s+ E9 w, Y, U! ]7 U4 ~# K' u3 S" b- S$ B+ a( _2 K* |$ r
转载自hsade- B9 D# d4 ^. p, t( D
. h, b" [ n5 Y7 Z9 H
; C( [) D: I3 t8 J- h3 T
7 b8 e9 I; p @9 @! o' r8 r- G' e3 ]2 \: Q1 d$ f# P
; X4 I4 M4 @6 ~; u% p# b; Z |