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

详解 stm32 在线 IAP 升级

[复制链接]
STMCU小助手 发布时间:2022-12-14 14:24
本文主要讲解在线升级IAP的基础知识, 主要是针对IAP从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识。
5 p% R$ e0 s1 o) u+ T! Y1 a3 D1 y# F% Q6 _5 h( O
1.在线升级知识
' ]# E) t- B9 F0 r什么是BootLoader?' F9 e$ C# f) _7 G3 s& D( m
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序,App也是一个程序,BootLoader程序是用于启动App程序的.) S! o6 {7 E! O6 v8 ?& @2 V. m
& d. [9 a9 Q# S2 K
STM32中的程序在哪儿?
9 W8 P$ W9 s* \$ R2 ~9 I正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码) t' p5 N+ I# h5 h: c( r. p

8 k  T  t0 N% H, B+ m 微信图片_20221214142442.png
7 r0 m1 S! Z( E' d5 S0 Y& y* w; k9 I' y# L2 D
接下来就可以进入正题了.
! x  A! L5 W  f. e( c, t

! J, J2 f9 {, c: d7 q5 @进行分区

) H& ]  s* C. ^* c7 m: Z9 J既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
7 R$ ]& ]! f" E8 P% e  q7 a
$ l) }$ u% C) g: L 微信图片_20221214142437.png
# z# `( t8 z* s4 O7 J) U

' t. s' `2 U) x; b0 x+ |以它为例, 我将它分为三个区.BootLoader区、 App1区、App2区(备份区)具体划分如下图:
$ l; z! i1 |0 c6 ?2 s: FBootLoader区存放启动代码
& Q  R3 O2 f$ z+ Z0 AApp1区存放应用代码* H) x: Y4 j1 P+ G1 D/ p5 T
App2区存放暂存的升级代码
: @( P/ u5 z# J& e
9 b+ N/ b* R' a% h+ _! _
微信图片_20221214142434.png . @' ]( f, Z4 w* F1 \

6 r( g: z2 r& o' c% B总体流程图
- w) Q# d* x7 W' @2 e- L
先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.3 |9 P* v2 W$ [; v5 V7 q: I) A# `
然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.: z, o- }) L1 Z6 P0 Z* y
在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:8 g& b  k' G& Q" U. ]; g
) X  S! S( R+ a6 ^% [* T
微信图片_20221214142430.png
+ o, Q% L7 ~% B" @2 t' K$ y

0 j5 I1 E+ d8 f6 M8 N
4 e, m, D  v& X1 @
2. BootLoader的编写" x% K3 f' r) B$ f: f' ]
本节主要讲解在线升级的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。# U- j; {1 s+ ]$ f' B; a

  J6 I0 {. V3 |! j  E7 T流程图分析+ ~. g" F4 W/ B8 V1 T1 d0 n
以我例程的BootLoader为例:
0 w- Z0 s8 l: `' F6 {

; y0 i9 Q" G, e) w& @我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示
) [. H  `9 l0 B7 ~  u
# B( n8 n: @. T( \
微信图片_20221214142425.png / C$ t/ ^% b& O' _0 j

" e" \7 ~& o3 [3 b3 R6 _- w
. M( K! n) @7 ]+ o4 B6 Q
程序编写和分析
5 [$ v% E8 _& ?- }. |4 `. C所需STM32的资源有:: f$ D% L2 @2 E; T/ ~
发送USART数据和printf重定向
0 e1 `. `* m% i8 K& n/ |5 M: R9 `3 r7 U* dFlash的读写
0 r1 ~1 n0 l% N2 ]8 o程序跳转指令,可以参考如下代码:
& h% i$ x, L& u. D& \+ V9 O" `5 h, ~
  1. /* 采用汇编设置栈的值 */" j) ?7 |5 t, |+ J/ \9 n2 @1 ^
  2. __asm void MSR_MSP (uint32_t ulAddr)7 D1 d+ k) |+ H- d* U. J4 g! n
  3. {+ B* N4 n, X* x" s) D" R- D6 o8 L
  4.     MSR MSP, r0   //设置Main Stack的值1 d# I2 p- X- I7 z" f5 v
  5.     BX r14
    ; K8 h9 b8 [" }0 v2 s4 s8 M  X
  6. }
    6 i. ^/ Y/ T- A9 U# j
  7. 9 A; a$ ]+ m0 \" B# w
  8. /* 程序跳转函数 */
    * F1 h. F5 U; L8 ~) d( p, _2 ~' K
  9. typedef void (*Jump_Fun)(void);9 _# o( g  E6 a4 C
  10. void IAP_ExecuteApp (uint32_t App_Addr)
    . k8 o$ _# V. s& R. _. Z
  11. {' S! W) Q; r1 F- u! X
  12.     Jump_Fun JumpToApp;# i6 U3 l) R9 w0 j
  13. ; Z" |  K% Z+ {  \0 V
  14.     if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.8 v5 D& r' S1 Z" a# B) Y
  15.     {
    3 O& z, P% v: }% G0 Y6 M9 H  K
  16.         JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)* N. }( g1 ?$ v" N
  17.         MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)/ d2 |( f# U5 e4 U4 q
  18.         JumpToApp();                                                //跳转到APP.
    6 y+ `+ E, o9 F# ^  H  N' u
  19.     }
    9 o6 f/ H! l" n0 D0 |; _
  20. }
复制代码

9 `& W  I) L* q9 f+ A3 z在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);- a: n' ^( {2 [3 G) Y7 C
其他的代码请参考BootLoader源代码
. G  ~- A" i) U% R2 g$ n
3. APP的编写
8 ]$ T/ j+ H7 k( _/ k: X2 v
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
7 _; D" o: e) ?/ a: M
: @" o9 }5 d$ |( Q+ [流程图分析( e" h: C! \: Z* w* u/ M
以我例程的App1为例:
% a0 R- W' n: h7 I3 }; v
0 ]0 P" k6 I# ?, q" ]
微信图片_20221214142419.png * ~9 _: p+ t  q$ A0 d

: k# H. }: B- |/ d5 x( T先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;8 j2 h8 O% `! K% @7 J  j/ J
打印版本信息, 方便查看不同的App版本;
  `& u5 l5 f' S8 ^; c# u$ o: q& K本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
/ _: S/ _5 L: {  k$ W8 q- x7 a
, V* d: @' T9 E6 S8 a程序编写和分析
" K" B0 f( I! L5 C# |7 x所需STM32的资源有:
, a3 S& _3 M* ]6 ^发送USART数据和printf重定向
$ T' _4 h+ X4 N. {: W/ d5 {, _% yFlash的读写1 J3 y% ~/ a, A
串口的DMA收发
$ G+ c6 h$ G& {# s0 _YModem协议相关% w, x, }" G9 F' ]& w, ?
Ymodem协议8 Z2 ]- W$ f$ C& T! q) j
百度百科[Ymodem协议]
) ~- ]3 @, x) q2 z, Q具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
- |  v7 j+ Y& _( sYmodem协议相关介绍可参考我的这篇教程 YModem介绍
6 }! y( }4 w( k( V$ h8 z  }) ^2 D
+ x8 ]! p+ |3 @4 R! {8 d4 w
代码分析  x) c" z, }3 G5 D
代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
1 \( w/ u* E0 S; Q7 V' J7 `( o

- J2 T3 l5 v9 B' a. H后面放了我的源代码, 详情请参考我的源代码.( k! t7 K/ \7 s  y' Z9 A4 k- @
, f# W: H, I( [+ \& Q+ {2 T8 ?) V. {- K
主函数添加修改向量表的指令  y+ |4 R3 W, \. C" X
2 i4 T6 x! R- N5 l/ F
* L/ G) M" y  M0 d. k
微信图片_20221214142329.png 9 w- o0 P# \: c3 H+ d9 n5 k8 R1 B

0 i! k1 O1 N6 O9 p4 ^  [4 I打印版本信息以及跳转指令5 N8 w2 o5 A8 H$ t4 a8 P

7 q  t/ v# b2 p 微信图片_20221214142322.png , q- l& V* |: ], \" G" T  j
7 {1 T. r1 ?1 @: X) A
YModem相关的文件接收部分. t1 `( ]0 b! }! ]- ^
  1. /**# @# i1 U% e- c+ w
  2. * @bieaf YModem升级
    2 p' W" S2 A' d& c' y' T2 M
  3. *
    8 \! o- C- i' a4 I
  4. * @param none
    3 N/ D2 A* ~7 ?5 v* k! R
  5. * @return none
    * g' k! t# i4 F
  6. */) y" i; Q3 `, Q# x- ]
  7. void ymodem_fun(void)
    & T! x8 s3 l8 e: s+ U. y
  8. {+ b+ ]) o+ G: Z
  9.     int i;
    9 ?% K- X% P3 Q% ^- c7 b, S, o
  10.     if(Get_state()==TO_START)0 e8 c$ E' ~: R4 N. F5 E! ]
  11.     {
    % b. n7 A5 I% I: Q& [. h$ |
  12.         send_command(CCC);
    6 V4 X4 o& n4 e6 X5 k4 q
  13.         HAL_Delay(1000);# Q$ I3 X" H& A; I6 B+ k) U
  14.     }- m% C" B% k, Z3 j$ T" r
  15.     if(Rx_Flag)        // Receive flag
    5 j, e% o5 Y3 X6 r+ z8 |; a
  16.     {, ^& k8 C! b* b( p+ @. O; W5 E
  17.         Rx_Flag=0;    // clean flag/ n& o- Y* I% b% ]

  18. 4 R( g( C. g  k
  19.         /* 拷贝 */) {2 W1 C! u' m& }. U5 H" C9 d
  20.         temp_len = Rx_Len;- Y% q& y9 R' ?( g
  21.         for(i = 0; i < temp_len; i++)
    % v. ~  `& h5 v5 X2 u4 `
  22.         {! M1 ?" h9 |/ b! `7 U/ Y
  23.             temp_buf[i] = Rx_Buf[i];
    0 n  O# s( V9 _/ W* T3 Z8 o+ Z( x
  24.         }
    * h" R. V) l/ |/ p' `$ }' B) Z
  25. $ a% i4 Q. @6 ]# ]. i
  26.         switch(temp_buf[0]); d3 E: y8 X( b
  27.         {8 v( w( V4 ^$ U2 q& k* x
  28.             case SOH:///<数据包开始+ C" c% d/ C/ D' G- y- R7 A3 }
  29.             {! B5 K7 v+ n7 I" F+ m& _
  30.                 static unsigned char data_state = 0;6 s( W6 N7 K" ^! ^  D1 V- z
  31.                 static unsigned int app2_size = 0;
    + S% Q% G8 S1 I7 d
  32.                 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
    8 Y: N5 w; l6 {; T& X
  33.                 {  k! \: I  N. M7 e
  34.                     if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
    1 F$ q  G6 w" o4 K+ C
  35.                     {
    ' y$ N2 n8 A& C
  36.                         printf("> Receive start...\r\n");- i9 c' b* H/ d3 T- D$ J

  37. ) i# B1 e/ S; Y1 M" }8 \
  38.                         Set_state(TO_RECEIVE_DATA);
    0 `: X9 c. d. Q. Y. H, q
  39.                         data_state = 0x01;& g" w! z; l7 j, s* v" X7 j+ j
  40.                         send_command(ACK);( {0 V2 v, q( z9 L
  41.                         send_command(CCC);
    ; s2 \& a3 Y5 Q7 `* i
  42. 2 Q7 C* Y, A3 }. X6 y0 Q/ C$ h" f
  43.                         /* 擦除App2 */% s  r7 |9 e! |. F2 y
  44.                         Erase_page(Application_2_Addr, 40);% j" N" x, H% c% i; X2 y
  45.                     }
    2 W, S6 r# ]/ d1 l0 s" D
  46.                     else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
    % X/ q% C/ ?2 W
  47.                     {: `, P+ ~+ x& E" v8 u: X
  48.                         printf("> Receive end...\r\n");* K, X/ x/ A& p

  49. ) {* p4 u8 s; U
  50.                         Set_Update_Down();' M& o. Z/ |: }2 c* J3 F; P
  51.                         Set_state(TO_START);8 I1 y: S4 Q' s! c# _
  52.                         send_command(ACK);5 t' C  Y7 p- R0 y& b
  53.                         HAL_NVIC_SystemReset();
    3 e# X% M" L, B$ a3 Z* Y( y& m/ e
  54.                     }
    " b$ O& Q1 l. Q
  55.                     else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
    : ?0 a- l9 k2 e0 z% }3 @8 d  G2 d
  56.                     {* r9 V8 @* N0 E: H
  57.                         printf("> Receive data bag:%d byte\r\n",data_state * 128);
    . c, K4 X# Q7 V* D3 U# G

  58. 2 A. ^* G' y" f( Z" X
  59.                         /* 烧录程序 */' G# L2 C. i3 f' d7 F" Z; y
  60.                         WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
    / g3 K; n6 p" z; _0 O% _$ `
  61.                         data_state++;
    ( Y2 k( c, L9 h8 c6 Y5 p

  62. 0 z  H$ X  L4 j6 J7 Z$ G$ h
  63.                         send_command(ACK);
    % m3 X  g  N4 {
  64.                     }
    4 B1 r1 v/ T5 T* W9 f
  65.                 }# L7 ~9 g& @( H# s1 y8 K% x
  66.                 else
    * ~/ c" f1 j$ \4 e$ |  j3 n3 m- J8 x8 a
  67.                 {! b% E+ K$ I" o
  68.                     printf("> Notpass crc\r\n");! v+ f3 H; i7 H4 O# i. x
  69.                 }
    ! u; d  o. ]* w8 F: M

  70. " y+ t  i& s1 z  b4 K8 s0 S
  71.             }break;
    7 n! q+ H8 W2 k5 K- h: I; A" P
  72.             case EOT://数据包开始
    + R) m; z1 X9 c5 e1 s3 k0 @' _
  73.             {2 a  W; D6 l( u
  74.                 if(Get_state()==TO_RECEIVE_DATA). d1 G' W  {- b3 E5 L- q
  75.                 {( x& A, m9 L/ t4 _+ H
  76.                     printf("> Receive EOT1...\r\n");  Z4 C) y% c- g" u5 ]4 R

  77. " y$ p# b- c/ R5 M8 o* \6 q1 J
  78.                     Set_state(TO_RECEIVE_EOT2);
    6 o6 V& i/ S# K
  79.                     send_command(NACK);
    7 I- r$ ~" @+ e
  80.                 }
    0 N' Q7 }# h5 z( G* A
  81.                 else if(Get_state()==TO_RECEIVE_EOT2)
    & h/ j. _, s1 j- A: S2 X
  82.                 {+ R( k" l1 l% I
  83.                     printf("> Receive EOT2...\r\n");" D1 u# @% u2 z8 L

  84. 9 s; T1 }+ K* j; M( w9 q/ Y! \' L
  85.                     Set_state(TO_RECEIVE_END);
    " j. Z# u/ w* Q8 m" ?6 H/ e
  86.                     send_command(ACK);9 r1 c8 S% \- L/ X. z* k1 d  |
  87.                     send_command(CCC);
    8 ~$ S' H' [6 D8 ]. z4 l0 b. H
  88.                 }+ n' G! h& f/ y, `  M
  89.                 else  k& b: h  G" r  _7 \
  90.                 {) J+ J( P* i# }
  91.                     printf("> Receive EOT, But error...\r\n");4 k+ U9 O" [% D! L5 `5 ?  S& M
  92.                 }) C  j; f; z8 W7 X* o" h
  93.             }break;
    ( z% {* [; [' j7 r1 {
  94.         }; ^" R0 k4 E! F! u! f
  95.     }
    1 [! x( d6 D# G' u& ?+ w. A
  96. }
复制代码

. M& ~6 B( E. D+ c% I7 q其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接./ _$ i# L' E! Z) g# e
9 P! v) x1 u5 Z
4. 整体测试
! w+ R: g9 n9 h7 [本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。6 ~" ^  x9 e. z3 L  V. E, W
& G3 }, B/ U  M. G9 n
代码的下载  }9 N& ~1 |' n) c& G+ ?
由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。$ a# ~% G5 d1 i  J/ ~7 c

9 K( U/ C9 L* V1 L" p
微信图片_20221214142309.png 8 Z# e1 \& f6 t* s, e9 A7 E8 q, N

% I* K, T5 I6 |' PBootLoader的下载
; p1 K9 B; S  d# T7 fBootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置; Q3 n" f/ P; b- Y- }( g+ U
按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
/ k; K( s/ r+ M$ d0 a6 v6 m# [6 Y0 |, H2 G* G; Y4 k3 @
微信图片_20221214142249.png
# Z% i& E3 t" d# m0 K
  m1 X2 J4 v! \" A: `烧录代码) o3 E- n, {1 k0 X3 C4 q9 ]
运行, 通过串口1打印输出, 会看到以下打印消息
: @9 l3 J! Q- t说明BootLoader已经成功运行6 O1 j7 [! A' B1 r3 k/ f% n, r# e" n( y

# W( ~2 `% `& Z 微信图片_20221214142234.png " s+ s# A) O/ X, p0 W$ D
( F6 p% k2 p, T, S  }
App1的下载
" D! \, D) \- \. b) l+ jApp1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
# l: A! j. X0 U3 X* ~0 k同时也要修改擦除方式为Erase Sectors, 见下图
7 d5 t; Y. N, }: L0 N+ K0 S8 g% O+ |
微信图片_20221214142224.png
7 v+ A( X8 i4 o
1 `6 w' U: v# J- ~1 B) M
微信图片_20221214142220.png # x$ E! _& f0 `
8 P* }3 t0 L0 X  _( o+ P
烧录代码
" e0 M; D$ s% {2 u/ a+ u运行, 通过串口1打印输出, 会看到以下打印消息
) C; A6 y* L+ I! ~说明BootLoader已经成功跳转到版本号为0.0.1的App1  N0 f/ N) ]- b6 E

: B$ C/ w7 {0 \2 \- z3 M1 j7 T. L; ~ 微信图片_20221214142153.png
' P. _! F0 H/ R2 I% |
4 J# K) ]) ?: L9 X, O) m, d6 S
生成App2的.bin文件$ g! e' F( p6 H) n7 w7 X
修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
( r8 |8 ?! z4 O  H! d8 `
& K/ y  Q2 G& d; _; c3 t生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件6 R1 D! K5 f5 e
/ Q/ q2 y+ E5 t# W5 Z; O  J; b
微信图片_20221214142148.png
1 }5 O/ b: b& C7 Z# a
6 E  p' N3 c3 _, D! f! g使用Xshell进行文件传输
5 b8 \1 T! [% K/ y! N打开Xshell
/ `7 b' w# e6 u! N8 D$ h! a- t代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
6 `  Q0 d0 t3 T( U; `' ^所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
+ r( e( N" P7 Z+ X+ ?

! o- A- n% f  h! D 微信图片_20221214142114.png
8 j4 F$ K/ o" E9 f
5 ~% O/ F* P; H: M* J& [4 V7 x你会看到App的版本成功升级到0.0.2了.7 m0 Q2 g3 y: ]+ e
如果你到了这一步.
# c" H' A+ x+ x! {# J- f- V那么恭喜你! 你已经能够使用在线升级了!0 F' d$ v! C$ B( Y* ~% y

4 c9 a  C+ r! H* S4 t( g5. 总结
2 w0 X5 [4 g: R" T' ^通过本几节的教程,想必你已经会使用在线升级了,只要原理知道了其他的问题都可以迎刃而解了,除了使用YModem协议传输.bin文件,你还可以通过蓝牙、WIFI等其他协议传输,只要能够将.bin文件传输过去,那其他的部分原理都差不多。
/ R- G: l2 F0 q" l+ c. B: y5 ~
9 i' G  N( r0 t& b: o
/ H4 r/ ^; y, f+ F% ?
% t1 E/ ^9 N! N* I$ k( q9 z
转载自:混说Linux
5 P. O1 z. k6 E4 r. s* j/ Z- P
$ s' [, W) c+ S# T0 e) y

$ }# f4 h* h/ Z* V3 W; a
收藏 评论1 发布时间:2022-12-14 14:24

举报

1个回答
1+1=2 回答时间:2022-12-14 21:07:32
帖子分析的挺好的,但是有些地方好像没有说,比如从boot跳转到APP1的时候是否需要关中断?在APP1初始化的时候,除了修改栈地址,还需要做其他的吗?
- Z4 P# a4 s- ?0 V: J9 j

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版