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

基于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内部结构简易图

* M6 g: f) m/ m
微信图片_20230701185542.png
0 s" w$ c' W0 ]7 b2 T/ f
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:

    ) ]& l" B+ y5 i; I6 e
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生

      s( |, e- m! T$ A  }
  • CS:从设备片选信号,由主设备控制
    ; m% @% y: P. r5 c- x

    % k/ D8 [- i5 P2 \+ N, S6 M
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
- ?3 `$ f( L" d* ~9 n% i当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。
& R0 K$ I- ~! x- YSPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
    7 H' R' C* v  b$ F" Z, f6 U
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样

    & a8 h" g3 K  ~' c5 C
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    ( I$ @1 W% Y, x$ \1 Y( m5 e

    3 m/ u$ ^+ K2 r' K4 w  ~/ X
微信图片_20230701185539.png
& ]" [+ e$ g2 J  x
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的常用命令表

: \5 G( N( D- y6 h
微信图片_20230701185536.png
, @4 B6 ^8 B+ m" L
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    5 h+ _/ ?# A5 v6 Z! T. B6 A8 d
  • 指示灯D1
  • USART1串口
  • W25Q64

    & m1 n2 b$ n6 U
  • K_UP和K_DOWN按键9 T5 }2 R; u2 w2 U2 r' r  v

    6 q2 ?( B% l; e5 }
    6 |! x! s1 S: r
微信图片_20230701185531.png
- P5 O. h: S& J  k; p0 u

: t2 U* \; u6 f; c2 `6 h6 T2 @* A
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

: H) ^, y8 V: e# t
/ ~: Y* J' n# O+ p$ N* ^2 \7 }
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
  c  o" l0 j) B3 ]3 V
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中

" D; |/ [+ i" B- h3 r" g# ^( n
  1. void MX_SPI2_Init(void){- x9 W6 e6 p7 M$ b- G7 v
  2.   hspi2.Instance = SPI2;
    1 H- w) G) i8 N6 J/ ?2 H
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
    2 I& Y- i$ \  e0 ~
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式/ l! P2 H2 o7 W7 p2 X
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度1 [, O/ f! F- B+ n# Q9 X
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平
    % x$ Y# T; F3 w
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    ) @3 y& T. i$ d" ~$ _" _
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制9 R+ l# O4 t# f# y, B
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256, p' M1 n8 h, t0 V# \
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行. i( j/ n0 V  ]2 W; C- s
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
    ' ]3 f6 p* g. ?2 ]
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
    1 `  k; \- o4 E& b& s
  13.   hspi2.Init.CRCPolynomial = 10;
    # N; ^! k$ T, G
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){
    ; t" v- Z" E3 Z" D/ a
  15.     Error_Handler();) l$ [5 h; z0 q
  16.   }
    3 E3 a$ ~! k+ P8 Y' f. a! I/ o8 ]
  17. }
    ! u; _+ T5 R* t2 a8 d/ V. w7 b

  18. * J  g/ H" Q7 q3 x; U0 A! m& n
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){
    ' h- `$ U  m) {- g3 ?( v3 z
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    ! q7 M9 ], K% I8 v% m
  21.   if(spiHandle->Instance==SPI2){, V- R1 N) c  q' f1 B) e- Q0 P: ~1 Y
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  
    ( ?7 `4 z) f# X
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();4 \6 G% R3 F+ ~$ P/ I5 c
  24.   /**SPI2 GPIO Configuration   
    . ~$ V$ }8 r( r
  25.   PB13     ------> SPI2_SCK/ {$ c! ?% V, Q. L3 |! F
  26.   PB14     ------> SPI2_MISO
    # ]3 f6 c3 [8 L
  27.   PB15     ------> SPI2_MOSI */
    4 u* Y: t" f& \# `+ _
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
    8 ^* v+ [5 ?2 n/ J2 v$ `
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;; i2 j1 g( t; C, P3 E
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;4 R2 q, B$ J$ {. s# B+ J, m
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);% r! r1 l  X) d! k8 c+ T1 y
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;* D9 w2 U; W4 ]& L
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;8 z. E6 k' X' x0 m8 V
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;
    ( E. c% m3 B: B* r3 W( H
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);5 H  e7 e+ v2 O  m& B+ E/ c: m* O
  36.   }1 Q" Y5 I( M( M; @5 M/ I9 O0 u  o8 J" D
  37. }
复制代码

6 V+ j0 H& @& ~2 p- Z
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    . C9 a- R' f- [3 o. k0 I1 u
  2. void W25QXX_Init(void){5 p4 |& C! X# N  e( S& h% g0 q
  3.   W25Qx_Disable();
    * l4 F" R) `) F) B1 H$ r! g
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID1 N; l  K* ]6 A  \9 E) l
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);
    1 l' B6 H% \( H
  6.   if(W25QXX_TYPE == 0xc816): t; Q$ D, R3 F) `# R# h) Z
  7.     printf("FLASH TYPE:W25Q64\r\n");
    2 Q; z8 U& }: K# R; P, M
  8. }8 g# o5 w; f5 a+ }9 C2 j4 B; D

  9. . C  H4 a; ?/ P' Y0 u
  10. uint16_t W25QXX_ReadID(void){- t0 g$ _, ~8 y
  11.   uint16_t ID;* D7 m( z$ h3 ^& W  t2 w( n  X
  12.   uint8_t id[2]={0};+ F$ k4 h' x8 l$ F0 A9 o
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令       
    . c! O& d1 Y5 ]3 x' [( Q  p% y5 A
  14.   W25Qx_Enable();//使能器件+ C. f3 W$ A  l6 d5 z) R
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    3 D. ~$ t% t# ~- h% K; e* i' a# K
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);; q, j# V9 A4 }7 ]+ `
  17.   W25Qx_Disable();//取消片选       
    ! Y! M" E% K, N* L& V  o" o
  18.   ID = (((uint16_t)id[0])<<8)|id[1];
    ' \% R) y8 ^+ T
  19.   return ID;+ X3 Z. ]; S$ e  ~) B& \
  20. }# K& g% ^1 Y$ |+ \) g
  21. + T3 g- R/ s$ {! [" `0 A8 E; B  J
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){; a* r% |8 h1 K  W9 y* ~% |5 E" a
  23.   uint8_t cmd[4] = {0};
    ( W( D. r) R' ]% a9 G
  24.   cmd[0] = W25X_ReadData;//读取命令: {. l  A- M- O8 {
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));
    % d# {: |! o9 V  v: |0 Y
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));
    : S  o, z7 z, ?3 ]9 Y! G" H
  27.   cmd[3] = ((uint8_t)ReadAddr);2 j. M) ]4 u3 V% X
  28. 7 b- {& c* e( M: \3 _( Z
  29.   W25Qx_Enable();//使能器件" g: e; P" G" k! e
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);1 a: X) _+ l. e: b
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);/ z9 r3 t4 ]  S' d8 A+ h- T
  32.   W25Qx_Disable();//取消片选       
    + U) O+ {) P0 Y( E6 T9 B# F
  33. }  |. W  x$ R: g' N
  34. 4 I+ ?; g) i8 n. E5 v- ^
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){1 c: G6 q/ ~! _, j9 y$ Y0 i' z' [
  36.   uint32_t secpos;$ T6 x& \  O$ d$ ~
  37.   uint16_t secoff;6 U8 j- L% x& J/ y
  38.   uint16_t secremain;3 l% @$ L& k' w% C; Q
  39.   uint16_t i;6 n7 l3 L+ u) S; y. u  r! r* \
  40.   uint8_t *W25QXX_BUF;
      d0 i% `0 s% A5 o
  41.        
    / {$ G' n( e" r& l
  42.   W25QXX_BUF = W25QXX_BUFFER;
    / U& t( n2 L9 d& H
  43.   secpos = WriteAddr/4096;        //扇区地址5 Q  ?4 k0 |0 B+ V# k
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移9 E2 G% A. v$ m" m  w, S
  45.   secremain = 4096-secoff;        //扇区剩余空间大小: c& n# R/ s/ {& C
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
    3 @$ Y" `7 S1 o% u7 T3 t
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节
    6 K# {6 C% K4 p* R! n
  48.     secremain = NumByteToWrite;& e( f( j2 J, M- t- T" O* N
  49.   while(1){/ B2 j% l* v" l* S- Y/ B# o1 u
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
    ! M8 i$ E& a, S, k- X9 _2 j7 B4 ^
  51.     for(i=0;i<secremain;i++){//校验数据8 i8 U$ \/ M' g/ j1 D1 R- K7 V8 O
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除3 z2 d  Y$ H1 M, d! }) y+ U
  53.         break;  }; x& c1 C  n$ @3 y% Y
  54.     }; r. F+ _% p4 i5 s0 g0 ?
  55.                 . x4 _: V: b9 U" _/ O) h
  56.     if(i < secremain){//需要擦除
    3 n: Y0 a: D3 ~
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区
    4 G0 J) f; H, i& [# |3 I1 |. h
  58.       for(i=0;i<secremain;i++){) P6 t- g* U) R* j3 v- |/ t
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];
    1 C3 o+ O/ \9 C! y1 Q! Q
  60.       }4 z: D+ ]+ z6 l$ w# s% ]8 _
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区% W  J$ ~, ?+ {" s% u( S) G8 H
  62.     }/ b0 D4 A9 {/ a% A
  63.     else{6 G& c8 {3 i0 V' t* K  E  V  w: J
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间+ |! @; }. a' n  C3 ?! g8 Y! t
  65.     }5 i/ o  ~. l* q8 V1 n5 a* P
  66.                
    ' ~9 X7 P/ N" }0 z/ i5 @' w
  67.     if(NumByteToWrite == secremain){//写入结束了
    1 E+ ]& P) t% d( d/ n8 c
  68.       break;
      Q3 c5 s% \  E6 Z4 r
  69.     }
    2 {' J& E( \+ q/ P
  70.     else{        //写入未结束2 X4 B8 m, C# ?0 H
  71.       secpos++;        //扇区地址增19 _, ?5 R* D( r( L' Y
  72.       secoff = 0;                //偏移位置为0               
    8 M/ O: }( j( k% N4 ^: v* s& D
  73.       pBuffer += secremain;        //指针偏移3 q# F' p. B! g" [3 F
  74.       WriteAddr += secremain;        //写地址偏移/ N2 ^# y' v5 e3 @. J
  75.       NumByteToWrite -= secremain;//字节数递减
    & W0 k8 q" X5 E: A- ~; S; K) Y
  76.       if(NumByteToWrite > 4096)               
    : w( W1 _2 Z. S- o: ?+ W
  77.         secremain = 4096;        //下个扇区还没是写不完
    " X9 ]5 [1 {: ^7 \
  78.       else
    " h7 o7 s" j+ f, S% `% _! }# A
  79.         secremain = NumByteToWrite;//下个扇区可以写完了
    6 L, g2 G, w/ ^" [2 S4 M/ ~
  80.     }6 q, ?- W8 ]% u, J2 J
  81.   }* j$ k% W" y+ I2 V% h- ]* X$ k
  82. }
复制代码

6 E3 G! b! g, z
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */  O9 q7 H4 i1 F( P. F2 z4 M4 y% S* I
  2. uint8_t wData[0x100];
    ( V$ d% m+ L( o" ^6 K' n
  3. uint8_t rData[0x100];
    & }/ l6 x3 w8 P. h
  4. uint32_t i;! C% ^  b5 u) g( V2 d' ]
  5. /* USER CODE END PV */4 d: `! o3 p0 \" w8 \
  6. int main(void){
    " C# G* i5 Y' i7 P. t* \7 ]
  7.   /* USER CODE BEGIN 1 */4 \" H, c  m# H$ F, ^9 D- o0 \
  8.   uint8_t key;
    6 [5 ^% q0 ?( Z
  9.   /* USER CODE END 1 */: \3 L: K6 e2 b3 f; e4 l
  10.   HAL_Init();9 x2 ?5 d, c$ @% f2 `
  11.   SystemClock_Config();1 O6 s4 T4 p% Y. [
  12.   MX_GPIO_Init();
    * L4 o4 j+ j7 f6 h; }+ F
  13.   MX_SPI2_Init();0 M1 P( q+ Z5 Z! k1 y
  14.   MX_USART1_UART_Init();
    ' d1 Y& s# D* M/ ?
  15.   /* USER CODE BEGIN 2 */
    $ V6 i( l8 L( ?: l8 ?
  16.   W25QXX_Init();+ l3 ^' Z9 P& k  z$ c$ M  m
  17.   for(i=0;i<0x100;i++){
    : J: f& S" y+ m! z" V
  18.     wData[i] = i;7 T* J/ h! h. w  b% E; D" g
  19.     rData[i] = 0;
    ( _& [4 w4 a) k
  20.   }        & n# {2 x. d! Y+ ]5 D$ J
  21.   /* USER CODE END 2 */6 }" R+ H* T' g% d+ ]5 c! i
  22.   while (1){
    & a5 P! q' J" h: W, w: a
  23.     key = KEY_Scan(0);
    * u; m  n4 b' c
  24.     if(key == KEY_UP_PRES){9 Y  M3 i: b% _/ s
  25.       printf("KEY_UP_PRES write data...\r\n");' s$ J( b& \1 z5 @. {2 d
  26.       W25QXX_Erase_Sector(0);
    4 c8 D; z2 y( `+ o: c/ _0 H7 u
  27.       W25QXX_Write(wData,0,256);                $ l2 x, A  {* z: U
  28.     }" q, A( @* ]# k1 P
  29.                         7 z$ Z/ g8 C" j* }
  30.     if(key == KEY_DOWN_PRES){. z2 }, e$ Q6 s' b0 N; U
  31.       printf("KEY_DOWN_PRES read data...\r\n");
    , ^9 H5 k! R4 A8 E3 z% H
  32.       W25QXX_Read(rData,0,256);$ ^( }" L! N, ]* {
  33.       for(i=0;i<256;i++){
    # Q' E+ L* D! i6 C# Q  ?4 w# o
  34.         printf("0x%02X ",rData[i]);# O* i- o, i! e& Q5 Z1 @$ y) m
  35.       }
      d! I4 l3 s2 m/ T+ H* Q2 Y
  36.     }* `8 k6 C5 O9 |3 @! G
  37.                
    ' n1 r- [( Q5 ~: L4 i7 o9 x* z
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);) v& H) l5 ?& u) ~: X, D
  39.     HAL_Delay(200);
    : @2 \8 m% [* g, Y* D3 T% k
  40.   }$ A' G, I! c+ M9 N7 w3 l/ K% a
  41. }
复制代码
' p5 k2 E/ H$ H6 |$ R" o6 \! c9 G
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息
8 O5 D: E) }) P+ u1 L
微信图片_20230701185521.png % T5 r, Q3 e7 Q4 Y8 p( T$ `& E

1 W( {+ ?% J' Q8 ^7 J% k' Z9 F+ J2 Y( t转载自: 嵌入式攻城狮  _5 {4 X3 g2 z. c
如有侵权请联系删除
- n4 @$ [, H# T" Q. \, A
: V  H( M; r; o1 C1 s* j2 \
8 r$ {! }( i( G; V2 ]4 E7 Q$ ?+ Z( }# ~5 U; B! O% m, D
收藏 评论0 发布时间:2023-7-1 18:56

举报

0个回答

所属标签

相似分享

官网相关资源

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