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