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

基于STM32CubeMX的SPI总线经验分享

[复制链接]
攻城狮Melo 发布时间:2023-7-1 18:56
1.SPI总线及W25QXX芯片
1.1 SPI总线简介
SPI全称Serial Peripheral Interface,即串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的、全双工、同步通讯总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间提供了方便,正是这种简单易用的特性,如今越来越多的芯片集成了这种通讯协议。下图是SPI内部结构简易图
% T3 E3 P- S, I- ^7 T
微信图片_20230701185542.png

* @$ P. y2 n2 v$ |' \' h9 p
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
    . X; P3 u: q0 \
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生
      F& ^* v) l9 k" v0 X
  • CS:从设备片选信号,由主设备控制

    " Y2 Q( [  |8 a7 y9 C% ?
    5 A! G8 C4 s* F, K' U9 x
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。5 m, E* n* L/ M7 x' L
当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。
, t7 f$ y0 V7 s+ sSPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
    " d4 z2 w) i, ^" \
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样
    ' Z2 \4 z+ b: w! M* q
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样

    - ?; V4 e, V/ Q4 C, C; Q
    7 E* T5 H. K1 ^3 u' v
微信图片_20230701185539.png

- B0 h) A, o& l/ K4 t
1.2 W25QXX芯片简介
W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。下表是W25QXX的常用命令表

0 f! b/ n+ K9 @
微信图片_20230701185536.png
! F6 r9 h  x( o; K$ k3 \
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    2 R6 \3 Q+ p! j, G9 o2 e
  • 指示灯D1
  • USART1串口
  • W25Q64

    6 H6 D0 A' ]- o  D
  • K_UP和K_DOWN按键+ a) D( P3 v# B* J  a/ ~

    0 H. K* |' A& p, |" M- G1 ~  K. r

    9 }, b8 O! o  p3 [2 P4 ]
微信图片_20230701185531.png

+ b; T' t4 n. ^* g" `" E

- z: K# N2 t* Y6 q
3.软件设计
3.1 STM32CubeMX设置
➡️ RCC设置外接HSE,时钟设置为72M
➡️ PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
➡️ USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
➡️ PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式
➡️ PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚)
➡️ 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制
微信图片_20230701185526.png
9 q" |. V+ b* a: t( q: f
: o7 O- j, ~2 D) [8 d: t% L
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
6 ~/ Z% B, ~: o( v) n9 |4 [% a
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中

, D) I+ d5 r) ~8 X. r
  1. void MX_SPI2_Init(void){
    0 B3 f, d4 I# f. g; J
  2.   hspi2.Instance = SPI2;
    4 D# `9 k% l# D! N) S! }% p
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
    / n8 }. ]" h+ e" W& U# d2 u
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式% s; X* O( ?+ b& w
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
    0 T1 I7 C9 x. J  ^
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平9 q/ U5 T/ ~0 [6 p' A* R+ \
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    ; `: }: d3 _7 i% w) p& Z; J5 L
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
    : k  z2 ~+ q0 e4 S
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
    2 J( J2 ~- F+ O% d/ U1 W, [
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行- v  W# ^3 u1 Q0 Q# f! ?. b& X# Y
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式7 K9 I# T/ j/ J* Z
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
    / W/ p# i( N+ E6 D
  13.   hspi2.Init.CRCPolynomial = 10;
    2 u6 O2 x% f  _$ V
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){5 y5 h* a7 i7 I: d  g7 k
  15.     Error_Handler();
    ( g7 x$ [6 K5 [% @
  16.   }4 Q. X/ n8 k. B8 y- ?( l9 s
  17. }. ?6 Q# X- _3 w! ~% g$ ]
  18. , M& X8 a/ q% a& [; J- ^1 M2 I
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){# {: p% [, |1 r8 [: z% j
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    1 P3 y4 z1 @3 u3 {% k2 p
  21.   if(spiHandle->Instance==SPI2){. y" S* q- D8 p; `) S4 a
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  ' ?( U" d( d6 }
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();
    * t! f* q: T+ l; l" d0 k
  24.   /**SPI2 GPIO Configuration    ) J: Q7 j) L, y6 a( Z' n
  25.   PB13     ------> SPI2_SCK
    4 l5 t$ H: F) H7 t# V2 q' x, l+ V
  26.   PB14     ------> SPI2_MISO5 P" w: H+ p. M  c% p: F7 A
  27.   PB15     ------> SPI2_MOSI */7 z+ A0 h& n: b# ?1 Q/ W
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
    " ^# C$ F6 @+ d1 D. z* Z% B
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    0 d2 [1 `  `) w, F  P
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    , B; X  z2 D0 L& F: n* b
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    ! }4 k8 d) v! Q$ N
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;2 Q# d' _2 z  o- y* R! H
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    ( o" `1 T" s( v) a) |* m$ ]! O6 _
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;
    & W* t( K1 l1 J7 B- j+ Q% L
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);( \* ~5 a2 ^; a
  36.   }( x6 I+ }; z; g# b& `
  37. }
复制代码

  C  Y# n4 ~* `
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    # Z2 A9 P9 Z% i6 k
  2. void W25QXX_Init(void){+ }" G: T7 M, ~
  3.   W25Qx_Disable();- d- H" f+ D: S% D, [
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID+ @" `+ m3 e1 C& M% m# O
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);
    & V9 n% N' t" T2 |2 o/ s
  6.   if(W25QXX_TYPE == 0xc816)
    $ _9 e9 g6 s. Y, q/ r! k0 }
  7.     printf("FLASH TYPE:W25Q64\r\n");' m: @. f/ O/ \/ h9 ~& M* N! ]
  8. }
    & y& g! V1 @+ Q2 l% O
  9. & l6 W% |' {- f
  10. uint16_t W25QXX_ReadID(void){4 E* C7 q' `. @
  11.   uint16_t ID;5 n+ t/ H( n# ^5 W5 s- w
  12.   uint8_t id[2]={0};
    9 A& d% }( ?" t/ _: M
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令        ) a6 Z  s9 L! G% M/ _0 k8 u
  14.   W25Qx_Enable();//使能器件
    2 |: t6 \4 ?& O3 p: h
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    # \& \) P& [  h
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);' K3 C8 m: R  ~
  17.   W25Qx_Disable();//取消片选        ; [5 A! Y6 Y6 T/ q, d$ s5 V' m) W; E
  18.   ID = (((uint16_t)id[0])<<8)|id[1];5 K  l4 K. u0 A9 p1 d  W4 D1 s
  19.   return ID;
    & z- {9 W+ c8 @9 a
  20. }4 w# q& r$ C9 |) C% n4 {+ k

  21. : K' R! G  c/ t& K2 A
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
    # H- E) @+ U8 q' \  g) ^
  23.   uint8_t cmd[4] = {0};
    - C' i. W9 Y% h8 p
  24.   cmd[0] = W25X_ReadData;//读取命令
    9 I' b2 b  F$ N( W- {
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));
      w; [2 J5 N& d" U2 ]' q$ b
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));6 z3 ~: [! a6 j$ k2 ~
  27.   cmd[3] = ((uint8_t)ReadAddr);: N2 o/ R3 i1 w1 c

  28. - O" ~: @% D7 ?0 L0 }
  29.   W25Qx_Enable();//使能器件0 I  w) B( d( _
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    4 [/ }% W% s1 L3 I9 R
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
    3 ]3 c4 o6 @( M) X1 Z7 `$ v
  32.   W25Qx_Disable();//取消片选       
    5 v  D: K. W" a
  33. }% V( r" ~- t! S/ O. Z0 u4 }

  34. 7 P- r# {$ [: {: i! G! v& d, }
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){# z1 w4 S/ P: [( P% u: C5 f6 f! K% x/ K
  36.   uint32_t secpos;
    , k" q% v6 o0 M# O/ w* F
  37.   uint16_t secoff;
    7 ?  p$ d  O- ~' U) [& r* I- j
  38.   uint16_t secremain;( |; m# r1 k8 B9 {% m2 ^: s
  39.   uint16_t i;
    / C( o! o9 L9 C
  40.   uint8_t *W25QXX_BUF;
    * R( _7 b& [/ Q8 ]4 f
  41.         * W! X  C$ ~% _* `& I+ o
  42.   W25QXX_BUF = W25QXX_BUFFER;
    , [8 D( B" r; E) o
  43.   secpos = WriteAddr/4096;        //扇区地址
    & n9 w5 D$ M" v4 q& Q& w& V1 ~
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移
    - k# r1 k1 ]4 n4 P, Y) f- a
  45.   secremain = 4096-secoff;        //扇区剩余空间大小8 ~0 j! f( Z4 n, a2 J+ {
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);) Y/ Y, p% i8 }1 C, B$ Y* A
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节
    % [) V+ ?$ d/ Y- i
  48.     secremain = NumByteToWrite;1 r& A* G- v5 ^0 r, D
  49.   while(1){; H4 y. x- o; U7 |' R* }8 \
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
    1 o, c9 o8 F# e2 R% D: w" M* [1 p
  51.     for(i=0;i<secremain;i++){//校验数据
    ' g4 c/ N5 Q- b8 W0 _
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除; q' q2 u' h, Y, ^+ _( v' v4 x
  53.         break;! l9 s- r% C  B. h; z9 |
  54.     }
    , C8 s0 W6 K% ]3 z3 x5 }* J! S
  55.                 - o( }/ a5 ~$ j6 d# d" [; f
  56.     if(i < secremain){//需要擦除$ R2 j) V! y5 ^* G0 [! ^
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区
    2 I" n1 @* o. h7 E: @8 ?0 A
  58.       for(i=0;i<secremain;i++){3 n( h3 ^3 ?* f- ^& ^/ x
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];" G: J/ \( m0 x( x$ t, J! q# v) r( `
  60.       }4 J7 e& T+ Y3 Y3 y* @; F4 M
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区; C* B6 {+ H7 \6 ?, ^( W
  62.     }- y+ \, s+ H; R& M) d) Z3 m
  63.     else{
    " N  v( f! z  N/ K* w
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
    " \5 M; z3 U- }8 g
  65.     }1 C: W7 w% E9 z* ?3 h2 i1 c) C
  66.                 : f/ z# ~2 E0 v/ w9 y
  67.     if(NumByteToWrite == secremain){//写入结束了
    2 I8 C$ ~( R( U, t
  68.       break;
    4 c. ^3 C/ t' t& o! o
  69.     }" H) q: L1 ]% v: z! F! E
  70.     else{        //写入未结束
    * l  }" P" ?, d% U( P" x2 }
  71.       secpos++;        //扇区地址增17 k) M, h- s, ]2 q
  72.       secoff = 0;                //偏移位置为0                / c+ Y, L3 G: c4 J; q4 |
  73.       pBuffer += secremain;        //指针偏移
    9 T" [, P7 ^" A, w+ j; {0 r
  74.       WriteAddr += secremain;        //写地址偏移6 q1 r0 L: V' g
  75.       NumByteToWrite -= secremain;//字节数递减$ D; O& @7 t( S9 s
  76.       if(NumByteToWrite > 4096)               
    / m% E, _( {; t) b) X* |( _" J+ C
  77.         secremain = 4096;        //下个扇区还没是写不完
    + d3 s% k7 E: \% w- A) f# w1 o  C
  78.       else
    : J5 ?/ Z5 |' |, x* s2 ?- v7 \+ \
  79.         secremain = NumByteToWrite;//下个扇区可以写完了
    ( _  g% N8 g" ^
  80.     }
    5 x1 H% E& ^# j6 X0 W6 g+ n; `; [' G( O
  81.   }/ A6 A0 C% B6 {
  82. }
复制代码

6 s) P/ l# O4 e
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */
    4 ?: p9 _& E7 t' e. k; }1 A; c
  2. uint8_t wData[0x100];
    . o7 z  \( I! b
  3. uint8_t rData[0x100];
    ! v! m" B( U' ^5 u% w& T  s
  4. uint32_t i;
    . {1 T4 @7 h7 Q! w# ~* J) l$ y4 N
  5. /* USER CODE END PV */
      |9 U0 K5 e  v+ r1 C/ G
  6. int main(void){" J- F. H# G' U& A2 M
  7.   /* USER CODE BEGIN 1 */
    8 ]" k3 H! c3 A+ s) q3 z
  8.   uint8_t key;
    4 G8 D- E7 \- q: J; h/ L8 {' Z
  9.   /* USER CODE END 1 */4 m7 x2 J* M  z; ~5 X8 A
  10.   HAL_Init();+ B8 p0 |" [) r# \4 X8 F
  11.   SystemClock_Config();1 k# p8 z1 g0 f3 |( \8 a. L& E
  12.   MX_GPIO_Init();
    " U% i( E7 y: ]
  13.   MX_SPI2_Init();1 U3 l; }6 |7 H  p8 e+ d3 ^
  14.   MX_USART1_UART_Init();
    , X: x2 u9 p# e- e
  15.   /* USER CODE BEGIN 2 */
    $ B( W- v# w2 t5 M" U
  16.   W25QXX_Init();  k( X9 [8 t# s9 V9 ?% v
  17.   for(i=0;i<0x100;i++){) i3 F# z' t- S7 A/ {
  18.     wData[i] = i;
    8 z# H! f0 v. G3 E, ^/ T9 s  v
  19.     rData[i] = 0;/ o* ?5 g5 d8 J2 ^5 n4 h  j
  20.   }       
    - y* `5 P; l) j% v
  21.   /* USER CODE END 2 */
    4 V/ l: l% U' h/ L1 W: F
  22.   while (1){6 _: f, n& {' p* @  U- ?$ N" t
  23.     key = KEY_Scan(0);- Y/ e7 `; J; F8 }
  24.     if(key == KEY_UP_PRES){, g! x* k; X* [" p7 X5 f8 m% P/ {7 z
  25.       printf("KEY_UP_PRES write data...\r\n");$ \8 U# R0 [/ C, B1 t; D
  26.       W25QXX_Erase_Sector(0);
    6 K2 Y, O5 \) c: N1 K
  27.       W25QXX_Write(wData,0,256);               
    7 W3 \; d& U! C' S$ y7 c& v
  28.     }1 V3 A- \8 g+ J4 a1 r2 A
  29.                         $ T7 n% x# t6 Q9 ]
  30.     if(key == KEY_DOWN_PRES){
    $ O, g9 t2 T4 V1 K5 h4 ^
  31.       printf("KEY_DOWN_PRES read data...\r\n");0 @2 R, I5 s' C& F1 S4 N' C
  32.       W25QXX_Read(rData,0,256);* l1 b$ Z( {+ F8 E; n1 Z7 |9 ]7 I% a# E
  33.       for(i=0;i<256;i++){% N9 {) X5 u- Z7 w& [6 B% w
  34.         printf("0x%02X ",rData[i]);
    9 {% f/ m% B( @: P
  35.       }3 E! U4 t! q, e8 x
  36.     }& X) g& ?7 X+ s: ^) {4 G# ~; K. T
  37.                 ; O" R$ l. `: M6 H6 N0 {
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);/ [+ `& G$ S7 B+ a1 z$ ^0 F2 P
  39.     HAL_Delay(200);
    ( {% Y4 Q/ F/ @6 c
  40.   }
    1 ^. s' Q! {0 C2 u' Y+ O' z
  41. }
复制代码

9 _0 R, V9 C& p1 f7 A( }4 N
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息

$ o, ^9 D/ y' Y' A 微信图片_20230701185521.png # f1 D% n: i. f9 d1 ^

+ e/ }: H8 E' I9 B' X4 Q转载自: 嵌入式攻城狮
% m( |& Q' W0 S6 J如有侵权请联系删除) h7 ~5 w- D0 Z8 D! k6 b. f
% d; h: d0 ~! q; j. d! c

- K8 F; Z5 I" [- R1 d+ h# b: V2 `0 W5 |' r: O8 m# p  w" N  @
收藏 评论0 发布时间:2023-7-1 18:56

举报

0个回答

所属标签

相似分享

官网相关资源

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