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

基于STM32在线升级OTA经验分享

[复制链接]
攻城狮Melo 发布时间:2024-7-29 14:16
简介9 D! {7 k0 z7 M( S" [
本文主要讲解在线升级(OTA)的基础知识, 主要是针对IAP OTA从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对OTA的认识.8 h: Z  i& K/ F1 f

/ r' E9 d9 c) |* Z# q
7 L9 A, j* W* }2 Y- V% A: I2 T
1. OTA基础知识' u( N0 D2 N" r' u4 E# l4 J3 q6 r
什么是BootLoader?
2 b; D$ y; {) U' T. p' dBootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.8 s- g7 y8 r) |+ c
; e8 v3 {  ?# E" B
STM32中的程序在哪儿?( R% X% |' Z. a3 U  F
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码5 i( ]$ A! b- G) R, f) q

  q5 l; t- E7 q) ~( e: C
1.png

" n, k, z! ~0 I" G, s
3 m  n  Z0 ~: u3 K3 s9 v% {' `
接下来就可以进入正题了.  x! i. K3 V) V0 E9 a6 s) K; ]' s
2 K  ?4 Z4 t+ t5 P
进行分区
  p: Y# F( a- @- W# }既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
, N' ]4 b, L; |& e! k
4 E: _* ?9 E7 i  C( A
2.png
8 a- a$ E+ S7 X4 i
& G/ K# I1 G2 ?) j

0 M$ S6 y) C" \. A- d0 M以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:
$ w8 b% N8 o, P7 r; YBootLoader区存放启动代码. U( N( E# E4 g2 K: F
App1区存放应用代码
& B. b% Q% W& K% RApp2区存放暂存的升级代码
1 B" v6 K$ z3 ^, J
3 R. X" H5 ^9 U- a2 C* _0 l' r. I: [
3.png
! t4 J8 Q1 R! C- Z+ v
" K+ E- Q3 G4 m
总体流程图( R) W7 T" e+ n
先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
: }9 D7 [$ e7 t/ l4 x然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.. D6 V9 {: h7 {' t7 r

+ c$ I0 b) _$ Y' n, ?
在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
7 `) m4 G1 r. ]" N/ |' `8 J/ T0 Y, V% P& q" T5 L* U4 Z6 w
4.png ! W* R& W! g8 L# F6 R( k. _
4 S4 R: K9 R: L6 @: I

# p$ |, ~3 n0 P) e" k! k
, n, V+ Z+ _  {
2. BootLoader的编写
, E: z, N! ^4 V/ C7 m本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。0 H3 O. c( Z* B* Q8 h

$ J' H- |6 V+ Y- H* ^/ V流程图分析
/ ~: p) F# k9 p: w以我例程的BootLoader为例:
4 q/ p/ ?* n; D5 H( M! C5 @我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示- Q, [$ w8 ~2 X! C2 _4 A8 b

* V, i! U9 H. p1 i3 _6 R
5.png

5 p/ D6 M1 l) x* |7 x$ _) N
% ~$ d7 \! V5 Z& Y# B
程序编写和分析
; X  e- L6 E% U- i, d" m' V9 E
所需STM32的资源有:* J4 a1 R) Z/ m! S1 f& {, k+ H8 |. j
发送USART数据和printf重定向0 k- o2 Y  R5 E; ~- j) v& u2 J
Flash的读写* l& [1 K( q( {- h, e* W5 E
程序跳转指令,可以参考如下代码:
# |1 Z" W1 B& j2 s, q) Y; L$ v8 r0 X/ Q( p
5 s: M6 R: u7 A0 }, K$ m$ A9 k
  1. /* 采用汇编设置栈的值 */
    1 ]4 m4 M8 M8 _0 Q) b* l) n
  2. __asm void MSR_MSP (uint32_t ulAddr)  o+ V8 {! C. ?) A. u
  3. {
    . W$ W/ X0 {% q! Y
  4.     MSR MSP, r0   //设置Main Stack的值
    . y  u/ w0 {7 [0 P  u
  5.     BX r14
    ( H; W5 f9 A* k: Z* u
  6. }8 z* D9 A% p: K

  7. 4 M: `& w  g- J- Z# P* C8 r

  8. 1 A  d- Y) U! }* x$ t7 X9 D9 @- ?/ I4 o+ L8 i
  9. /* 程序跳转函数 */
    : y2 H- O! ~7 p
  10. typedef void (*Jump_Fun)(void);
    " W0 x3 _+ T0 g( c9 Y* |
  11. void IAP_ExecuteApp (uint32_t App_Addr)
    - Q. i4 f6 e* R9 i  Z( X
  12. {
    0 `1 g4 e, j3 V  Y
  13.   Jump_Fun JumpToApp;; z4 R) y% H' {0 o

  14. 4 ]/ d7 B3 `/ a( O- @) K8 ?- [
  15.   if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.5 _& H+ x9 n7 |* L7 |( U, L6 k
  16.   {0 R9 B% u2 M7 X
  17.     JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)
    ) l! O3 U7 k& E* ~+ ^& B3 D
  18.     MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    . e) ]5 F$ J% T' F7 U
  19.     JumpToApp();                                                //跳转到APP.
    8 z# Z$ C+ a* _' Z$ o  t/ Z5 M
  20.   }
    9 }+ W( P. g$ ]: n) K8 s# c! t
  21. }
复制代码

* H" X: }# {$ f# L4 Q在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
, N3 g7 N' l! N其他的代码请参考BootLoader源代码
6 Q" [  m8 t0 Z6 g( N3 |9 g
, W+ \8 z) G; p6 k) T; V% h4 n
3. APP的编写
! T( D; |5 c$ h本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。  f2 t- v; p. l

  |" A' B4 B5 ]* p" N+ t( d

" p+ v' u- H! r1 A6 I流程图分析

' W3 d& M% |! m7 t; H# \. C以我例程的App1为例:" i/ M0 t- l5 q" Z+ \' H
先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;7 l- e" v" ~* P% ]) S+ m0 L. q
打印版本信息, 方便查看不同的App版本;, {" @$ |6 e) X5 P! t% g* k
本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:% c$ v+ m) C6 W2 h* N8 \

6 ^4 z6 g, Y" e# A/ S) d0 M
6.png

  V+ p- P( ]6 F; y: @. t6 s6 s% I/ Z  X
程序编写和分析

; ]$ k7 }' X- p) E. h所需STM32的资源有:( s. `) x, w* W# t
发送USART数据和printf重定向8 X/ ~0 V0 X( o& y. J! D
Flash的读写8 d2 G8 r" o+ h! h3 I, ~: u) M
串口的DMA收发* C6 [9 \# l% K0 L2 i+ o
YModem协议相关$ h$ N  ?) R' U8 D. u$ W
Ymodem协议
3 _: p# V  I% ~+ {; O/ s. o; k) K百度百科[Ymodem协议]
7 Q! Z$ C: ^( B6 p- v0 z0 M( y5 l具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).1 Q4 b7 [: h' s; H* K
Ymodem协议相关介绍可参考我的这篇教程 YModem介绍0 c7 b4 K2 M$ W! y9 m
6 s6 c0 d& |3 ^* C, M( t* N3 ?0 H" B
代码分析
5 ^4 ?$ z/ Q* }4 a) d4 Z+ Y/ L$ g代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
/ r/ y; O1 I& x' C后面放了我的源代码, 详情请参考我的源代码.( Y0 p3 N% \5 P7 ?" _2 Z/ t  N! ^
主函数添加修改向量表的指令
  @/ z/ T( N' @7 c' Y0 n, c- ^" K) y% [9 C* m6 x% a' k
7.png

# t' e2 _+ Q' g7 {. Q
& x: @- m3 Y1 j2 O' ]# K打印版本信息以及跳转指令
7 a; s4 J! h+ o% H, ^2 z, _9 ]0 W8 n! @. u5 q2 E  n- ?- J2 K" g
8.png
0 [" j- a% D' [( n8 o

. f9 L4 E' r  o; nYModem相关的文件接收部分, Z+ S7 D6 b: ^6 M: j2 i( ]( Z4 f( a

  1. 5 |+ h9 }( M$ X
  2. /*** J5 Q5 y) u2 @
  3. * @bieaf YModem升级1 l! k  `; L; s1 I% L5 U0 ~
  4. *
    , N; y4 {9 M2 ~
  5. * @param none
    # U) G% N! i: u7 V. B- U; M  X
  6. * @return none
    . r& ~- R; A" w7 o9 u/ l; g, K
  7. */
    % b/ f5 @5 I. t. C
  8. void ymodem_fun(void)0 m' ?) c8 }5 A7 `6 U; Y9 v* \1 D
  9. {
    8 i  Q* _& F. k+ H* f6 C8 k8 d
  10.         int i;" O- o: A6 Z. C* c
  11.         if(Get_state()==TO_START)
    # q0 _# _8 a' X2 U8 T
  12.         {+ x( b, [1 x/ T6 l
  13.                 send_command(CCC);
    * |+ M; \& l: Y1 W$ ]4 v
  14.                 HAL_Delay(1000);
    " z: ~3 d! ^- a
  15.         }. A: A* ^( ~4 p* l+ Y/ t9 k) S
  16.         if(Rx_Flag)            // Receive flag
    * M8 \* F" [" n. G
  17.         {
    + Z- L: \; F2 r* s" G+ l, z
  18.                 Rx_Flag=0;        // clean flag
    $ m# l, A/ ^3 ^$ n
  19.                                 # z0 `  i3 I% E9 }* _
  20.                 /* 拷贝 */
    ) }6 q# z4 Z* r$ U2 U
  21.                 temp_len = Rx_Len;7 f$ E) q1 c. K0 m. U  W" y/ F1 l
  22.                 for(i = 0; i < temp_len; i++)/ K& ]  P/ x0 J
  23.                 {
    $ \% \% V* i. a8 p
  24.                         temp_buf[i] = Rx_Buf[i];' n/ b) }" u# w1 v4 b# Y8 i
  25.                 }' \: c$ [( D/ l3 v
  26.                
    5 j) {; v% U5 I- B
  27.                 switch(temp_buf[0])0 P5 _0 Z' Y& T' O3 {* u9 x
  28.                 {* f8 O( Q% \7 @3 u" z  I& g
  29.                         case SOH:///<数据包开始5 s' m7 a) X' E0 U' o6 m5 T
  30.                         {7 {4 _- K( b- Z2 ~- r
  31.                                 static unsigned char data_state = 0;! X# }; `! `: x: C$ j
  32.                                 static unsigned int app2_size = 0;
    9 n- ^1 w* R, w
  33.                                 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验1 U/ F1 B7 e/ }( N, Y/ ?2 p& `1 _
  34.                                 {                                       
    7 l' y4 R9 j# _: q: D: e
  35.                                         if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
    : S* f" n3 I: y, g; z
  36.                                         {
    ; h/ t1 A$ S3 h0 I! T0 a
  37.                                                 printf("> Receive start...\r\n");& a9 j+ G) j5 E; ^7 ^& ?: U+ y
  38. 5 X6 Z& |, V* n2 i# F5 \* y) W
  39.                                                 Set_state(TO_RECEIVE_DATA);
    6 S- t- o6 T- d3 D
  40.                                                 data_state = 0x01;                                                
    : [8 [. j. m9 d9 h) ?: V4 f
  41.                                                 send_command(ACK);7 K( ^  z3 O! ], c/ t
  42.                                                 send_command(CCC);
    : S4 U; k3 ^2 S0 |. |% J5 H
  43. 8 E0 T1 ?) h2 i' _0 Z
  44.                                                 /* 擦除App2 */                                                        7 P, u( R0 n# m+ Z- R% e
  45.                                                 Erase_page(Application_2_Addr, 40);
    5 }) z1 g% R- P- L  O0 ~
  46.                                         }
    " ]9 n$ A" S9 E* j/ ?
  47.                                         else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
    ) y: X  Z" C& m2 y) {3 K( x" B
  48.                                         {" p8 @( c) `+ R* e
  49.                                                 printf("> Receive end...\r\n");% Z) Q0 u, n) p! H, M1 q

  50. 3 {) L; _" O4 ~: {' w" F& B1 a# R4 d
  51.                                                 Set_Update_Down();                                                1 k8 e/ v! A# Z9 L! t3 m
  52.                                                 Set_state(TO_START);; s) y% D+ O% u2 |* ]: {+ O: s+ \: o
  53.                                                 send_command(ACK);8 M* z' k. ?$ f  T. {6 O4 V3 `2 v
  54.                                                 HAL_NVIC_SystemReset();
    3 l8 C+ i- k3 [7 ^, ]3 T/ Q! q
  55.                                         }                                        $ h  H9 A/ ?, P; x8 ^
  56.                                         else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
    & |( ^9 h- ^) D3 h5 Q  j: d3 j
  57.                                         {7 o. v; Q! C" E; ^
  58.                                                 printf("> Receive data bag:%d byte\r\n",data_state * 128);/ s- \- p# g* [% Q8 c! i9 I
  59.                                                 7 F' W+ `& J/ Y
  60.                                                 /* 烧录程序 */
    . t6 T. Q: w( }9 ]  U
  61.                                                 WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);/ p  F: r2 H2 U9 Z8 u- j
  62.                                                 data_state++;, d3 {9 k: W- R7 P( u" ^
  63.                                                 # s2 L9 E1 O9 Q" F
  64.                                                 send_command(ACK);                8 y2 M5 H$ q& j8 \
  65.                                         }
    , K2 |; i2 r) z3 i$ Y7 Q
  66.                                 }8 X8 |' C9 v3 R) M+ O% L1 t' I4 W% u
  67.                                 else. B& w2 w" I) n' {) T. F% R
  68.                                 {, z7 v+ s6 W. l. F- \% \
  69.                                         printf("> Notpass crc\r\n");8 i* m6 A$ p: U0 b5 b5 u" w
  70.                                 }% O9 W4 ]7 M7 s
  71.                                 
    6 n4 E& J3 @, U! U
  72.                         }break;
    ) B0 o' C2 c; r, j* _% C2 q7 p
  73.                         case EOT://数据包开始9 ]8 c' F: W' v2 ]
  74.                         {6 Z9 g: t) x/ m3 z: G
  75.                                 if(Get_state()==TO_RECEIVE_DATA)& U6 E) V$ {( [4 o4 H
  76.                                 {5 ]6 w- j# Q  _% ~9 j
  77.                                         printf("> Receive EOT1...\r\n");
    + `/ l9 X  H& G/ t+ k
  78.                                         $ m2 Z3 c" n& d' X
  79.                                         Set_state(TO_RECEIVE_EOT2);                                        1 J+ d% m: n% r7 G' f, [! l
  80.                                         send_command(NACK);/ V( m. ~, {5 x* r# Y
  81.                                 }
    & ?& y* o3 }1 R
  82.                                 else if(Get_state()==TO_RECEIVE_EOT2)
    : h+ k7 _+ J/ @# L9 a) q! A
  83.                                 {! J  ^1 n+ m( l5 X8 `; S: O( I' X
  84.                                         printf("> Receive EOT2...\r\n");
    6 c3 l0 e, k+ d: m/ _3 y7 c
  85.                                        
    . O1 H1 T+ r' b8 y$ l* q- y" m
  86.                                         Set_state(TO_RECEIVE_END);                                       
    . s% E' c9 b4 u" d% ~% M) y
  87.                                         send_command(ACK);
    # E; H1 V# S  z: z* V# N/ H
  88.                                         send_command(CCC);: s' z$ P. F$ g( W# v
  89.                                 }
    7 [+ P3 K. ~( C
  90.                                 else3 B/ M" V& y: J- [  T: h
  91.                                 {* S: B7 `( S) N" C" E
  92.                                         printf("> Receive EOT, But error...\r\n");( h# f8 K2 m- `  j$ C6 m) ^. |
  93.                                 }
    : }& @& o& m% ]2 m  t
  94.                         }break;        
    # A/ H; z$ Q7 d) A; ^
  95.                 }
    6 _, l1 U3 S- J  p" F* Y; H
  96.         }  h* O# y+ A* s( g4 p! F
  97. }
复制代码
' y+ l! r$ ?; C$ g& @
其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.2 l, Z0 W. ?* s

2 ^" j! [1 r7 h0 U
/ |! Q# o( ^3 u2 D
4. 整体测试: A( e0 c- _6 y% Z3 D% q
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。+ y/ k" m; M% p7 l) N

# k- w4 l+ B- X9 t% d2 Z2 J' \源代码4 K- v! s/ ~! t) w8 m8 s
BootLoader源代码和App1源代码可以在原作者的gitee获取& R- a  u7 E# N- Y$ y" N
4 U& \2 e4 M" x+ K; s' Y: @0 @$ _
代码的下载" q' B, L# N/ ]: b. t# g; C( Y1 [
由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。+ {; G9 W' z  \% s/ t

( [) W5 Z" m& A. C4 S3 j* m( |/ a
9.png ) H  O  n' O: i# @$ h* q: X* i3 ?7 ~

, ~/ {) L5 _- d( _# j! a- d
; c2 O* {( P) pBootLoader的下载
4 k, k& |, G' [2 X2 T6 {BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
9 P: U0 A8 O3 u2 T& L按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
$ ^, C+ b+ D) l$ D  V6 W2 d
* F/ g1 G  W( U" ~
10.png
" H/ v2 v6 ^1 E0 M. j% ~  D
4 K! ^  E+ q2 n, L
( x# R% |4 `5 A4 v6 b" w7 I0 X
烧录代码: x1 ~0 c4 K, m! }3 Z$ v
运行, 通过串口1打印输出, 会看到以下打印消息
8 I) Y. j* C2 m/ J0 g. M说明BootLoader已经成功运行7 j5 J0 ~  b7 |. f! Z
) F4 l2 t# K( N
11.png , r$ L! F% L6 p

5 j: _- ^! f( v5 W* u
) V: l2 t1 g4 f  U0 x; T& z; QApp1的下载
5 F- r( H! v6 J. q3 K- RApp1稍微复杂一点, 需要将代码的起始位置设置为0x08005000$ ]6 H/ _# l& i$ X* K2 O$ |. N
同时也要修改擦除方式为Erase Sectors, 见下图
/ c: }% g. D1 h4 ^; m5 \" D/ Z' w7 s2 `, R: M! {; ~
12.png
" V' Z3 e) R& \8 H! U( s) ]( x
" \. v2 T. @* f5 g  P
& `! G% O$ T' _" ]! J8 u! |8 C5 ]
13.png
! Q  @" G# ]8 f1 o) T* T

1 T# ^! V& v8 x* ~( z! D. C
+ y& w3 D  x" }$ Y8 Y烧录代码
+ G/ [9 h6 O7 S运行, 通过串口1打印输出, 会看到以下打印消息) [4 Z2 M. ?+ H+ D" l. N9 x9 h, A  q
说明BootLoader已经成功跳转到版本号为0.0.1的App1
/ b( M! d  @  G. y: L2 K; H+ X* b0 O8 r9 f2 {# B
14.png

; K1 _# K1 v1 E
' ?  T- Z. C  t+ j7 \- n生成App2的.bin文件: ^2 F. y1 f( D# s& m
Keil生成.bin文件
9 x; |# ]% W0 \3 x% F1 g
3 n& H" [( z0 u9 G
修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
: c4 d) A( {7 c3 H- ~( p: P
; w' ?5 o) a5 s4 h8 Y8 d/ Z生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件
5 X2 Y: H* }' {% |# D1 g1 G
! V5 D2 l6 _  _3 @$ i  W$ E
15.png
1 ]* u# j! z" K
! r4 f: e% E9 x

8 _# h9 U* }0 G! O( C使用Xshell进行文件传输0 s# T/ w. `: B- m) {2 k0 y5 s
打开Xshell
2 h, Q( j- J( W' \, p" {) {代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的; V/ d& y( x9 D7 m& S
所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
( ~/ w( ~- ]( K7 _4 T" d. M- {+ W你会看到App的版本成功升级到0.0.2了.7 n# t# I' U' V( P
如果你到了这一步.! k: K! h* W/ n! L
那么恭喜你! 你已经能够使用在线升级了!
/ ]* m+ _3 `, B1 E0 m' V% o: u4 d( u, f  K' T8 n
5. 总结
4 [; K, }5 |! W7 J; J3 e5 P通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多.
" u" \% D9 s9 B; o$ H, j7 }" N# z3 ?: _1 w
转载自: IOT全栈技术
4 _. g1 {4 `$ Y9 w如有侵权请联系删除. i1 |* @9 V$ \# @
0 T8 B9 N8 j7 }1 q8 ~: H# w# [; c
赞 收藏 评论1 发布时间:2024-7-29 14:16

举报

1个回答
向日葵, 回答时间:2025-10-29 10:00:48

你好,有程序源码或者链接吗

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版