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

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

: `' b* ]# U' u5 e* s
微信图片_20230701185542.png

0 A" @. s( j/ {  r7 W1 y
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
    " f( I2 i) J: Q2 Q& |# j: a
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生
    ! S9 z  {5 O: W/ A1 }6 [
  • CS:从设备片选信号,由主设备控制
    ) B8 }( v- B9 T2 J& j5 J6 c% B

    . @3 t5 M% B% `5 B7 p
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
# q; p" ~) c* d7 l4 s当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。- w3 O7 ~0 G& b+ K
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
    & y$ N# j, P6 q0 ]. M! U
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样

    ( l5 I  Q* b5 \  Z
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    6 V. W8 V/ Y, C7 E9 J

    & X) U0 B/ ?+ k% ?6 E9 Z5 @; `" b; C
微信图片_20230701185539.png

1 N% V, f; z) s6 z8 t/ x4 d3 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的常用命令表
9 c( x' X6 w# o3 S, u
微信图片_20230701185536.png

: }% C2 Y, I+ X9 y
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息

      ?3 x+ Q# z" g! y% x3 w( I. ]
  • 指示灯D1
  • USART1串口
  • W25Q64
    6 g* ^% u; z# s5 X4 b
  • K_UP和K_DOWN按键+ K: P6 q5 L* S# y! I; j  W1 j1 R
    ! l: g. X( w/ k' V  h8 e" C+ M

    + D3 u: |) [$ M+ M6 E! m
微信图片_20230701185531.png

8 a; n1 y/ E+ f$ Q' M

# Q5 `! V* x' g3 q6 X
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
  E) N3 S5 h* U9 d
1 K( u: @" v* j0 a7 Y; f1 U3 D  V% c
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

, }0 ?" B0 n. Z. d8 B9 Z9 r9 \7 W
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
" y8 j4 e) m( T" |0 y/ W/ r
  1. void MX_SPI2_Init(void){
    . \  j. t; V& C% a% A
  2.   hspi2.Instance = SPI2;
    1 e, C7 n$ J" R) K* _/ |6 r
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
    + y1 B# S7 f4 t' B0 T% J- m
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式. n% r! Z- N* S: @* @0 C! F
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度# N. b& l" J* N) T3 {, _* |
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平4 U% f" q: i& N. j* a  a
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    0 H/ k3 f; }" t* }5 Q! H3 p
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
    " k. U% G% v' M& a- T! k
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256: c0 f5 i# s/ p8 y% g. e! p5 h0 }
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行8 l- h  `# o+ P0 j
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
    9 Q8 O+ e7 N1 Q& M7 U7 }. M
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
    5 n& G7 E# }$ ~7 C
  13.   hspi2.Init.CRCPolynomial = 10;; I; x* j0 |4 a! y/ Y
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){
    ! y2 N  d& k* E5 o4 Z3 x. v
  15.     Error_Handler();
    5 B6 M) P, A1 t1 C
  16.   }: m8 V' W# J; K7 q- o% t# K- E1 U
  17. }
    + f' B& \0 Y6 Z4 b; C
  18. ) v; X2 t2 m$ J
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){
    ( D" ^* [- @: u' J: K
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    ; c% S  H4 E* _& ^4 D+ c
  21.   if(spiHandle->Instance==SPI2){  T! m# h* Q$ V' X' P
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  
    - r+ [" i* E# v% m  z
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();* A: Q/ @9 L6 H# L4 W
  24.   /**SPI2 GPIO Configuration   
    0 f) p1 p5 P2 g+ q8 P
  25.   PB13     ------> SPI2_SCK
    5 w! x% x8 s6 [
  26.   PB14     ------> SPI2_MISO
    4 N$ q3 p; Q5 J' s6 c7 y
  27.   PB15     ------> SPI2_MOSI */
    + U+ g5 l; A3 X6 N0 m4 s* |
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
    + H& z' g" ?2 H
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;( {) \( {9 \- X" {7 I) ?
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;' q# i" l" m& i7 i
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    5 D" r5 [9 I6 N6 K7 |$ Z$ O
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;
    2 W+ M0 W7 _3 a! u4 W
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    , H$ a5 Y0 K5 ^; h5 N
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;( d1 ^- L  Q: V% C3 ]
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    3 \8 J& n9 `" D6 t0 J
  36.   }2 Y- \7 Z, _# [9 n
  37. }
复制代码

7 x# O1 e# B8 a* n- h
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    2 e8 t7 g6 c' @& w+ H/ [
  2. void W25QXX_Init(void){
    ! {" B. `  U' R
  3.   W25Qx_Disable();( o: O+ C! ]: G8 d+ V/ W3 [) o' _4 j
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID
    0 f( i2 C7 |  J
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);$ j  o( v% j6 G; s7 V: t
  6.   if(W25QXX_TYPE == 0xc816)7 Q2 _! E7 N7 X, l; ?
  7.     printf("FLASH TYPE:W25Q64\r\n");8 F* n. p: k. y0 g4 x
  8. }
    & q  S9 ^1 ]) o- m% k* N

  9. $ Q) U! Q) H' j
  10. uint16_t W25QXX_ReadID(void){
    6 ?: r" s: s& ~
  11.   uint16_t ID;, |) p* d5 `4 @7 f; f1 h5 R0 J1 ~. M
  12.   uint8_t id[2]={0};
    ) p) A4 I. N; ?. g
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令       
    $ g9 Q; v+ a: B8 r. O0 }  t
  14.   W25Qx_Enable();//使能器件
    " I/ o: I% N% K
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    / b8 a( Y2 E( P% `& S
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);% w9 ^" z9 b, e+ G5 R: ^+ S* e
  17.   W25Qx_Disable();//取消片选       
      ]4 b: x/ i" x& w2 `" F
  18.   ID = (((uint16_t)id[0])<<8)|id[1];7 V/ N) w3 J/ v; D
  19.   return ID;
    * H& ~+ @% A$ T$ E- L
  20. }, V! f+ l$ J5 K/ E- K
  21. 0 K2 A/ X7 T$ g- |% }* F
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){/ z. _  @& K9 t$ [! [% i7 \% j1 x) O
  23.   uint8_t cmd[4] = {0};/ Y( v* n' o7 B. ]/ Z
  24.   cmd[0] = W25X_ReadData;//读取命令. [: {1 ?0 }9 V' I0 ]6 y: S, x
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));+ \7 |! {. x. ~, ~  n
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));: l& @& `( i/ v0 j( T# P
  27.   cmd[3] = ((uint8_t)ReadAddr);
    3 t7 n3 M2 k% r' c
  28. % Y% s. b" ]/ z+ |
  29.   W25Qx_Enable();//使能器件
    $ A4 U- V! O9 M% C- e
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);$ a" n7 @! Y" @: E8 h( ?* X
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
    1 f# Z- `# c9 x# ~0 c
  32.   W25Qx_Disable();//取消片选        " ?* ?$ u1 u6 E  `
  33. }
    : |6 l1 ^7 \& c. \
  34. & E& c1 |: J3 @. R, m5 d
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){6 @+ t" X& Y/ _9 d$ @3 M
  36.   uint32_t secpos;2 ^6 ]0 m& l& F4 U4 `
  37.   uint16_t secoff;! C4 C1 u% X: E- t0 X, M8 ~
  38.   uint16_t secremain;  c2 i4 M: j7 _
  39.   uint16_t i;  X0 Q6 h- k: G2 ^
  40.   uint8_t *W25QXX_BUF;6 L, H3 J$ P8 y8 J
  41.        
    ! p. ]6 J  d/ q5 J, s) h2 y3 F. Y
  42.   W25QXX_BUF = W25QXX_BUFFER;% n: M& c" s" h$ K  S% D2 e
  43.   secpos = WriteAddr/4096;        //扇区地址
    2 @! y7 ^  v; r' P
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移( p2 a: A2 N9 X
  45.   secremain = 4096-secoff;        //扇区剩余空间大小
    0 B, Y/ {* O4 @+ P! ?! M
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);: d0 C) x* d. r3 H" p
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节5 }: X- w7 Z. T* [& E' o/ j1 F1 a; a
  48.     secremain = NumByteToWrite;
    : }9 O: D; }% w) u
  49.   while(1){- y: Q: q4 C) ?. r. s( B8 ]- r
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
    ( c  j: G# u4 j# z& _+ l
  51.     for(i=0;i<secremain;i++){//校验数据
    ' r7 o7 b5 m( f/ I+ \
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除
    ( I0 s6 H- y' N. B0 k
  53.         break;# V* g, P9 J7 t/ P, ^- `) s1 K% I
  54.     }
    " j% O8 u2 x& |: A" z7 G
  55.                 8 c! Q: X, K! B0 z7 H+ [4 N( d* f
  56.     if(i < secremain){//需要擦除
    2 a0 N2 T2 M/ Z5 m# B8 o' R
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区! M) E9 c4 p+ ?$ ~' @
  58.       for(i=0;i<secremain;i++){
    : v% X9 W4 d' Z- g3 A
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];2 B% B1 N+ R1 l0 E8 [# }  N
  60.       }* n0 ~$ L! D) G- U. ?$ G2 f/ O! k
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区" M2 Q0 T' P: }6 c  W
  62.     }% L0 [. J: ]  G3 p2 l/ s
  63.     else{& q7 Z1 |, X2 @) M; X
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
    0 P5 n' y8 z, e" n5 B5 M
  65.     }6 @1 \4 v& ?2 [% m" Y
  66.                
    ) \2 Z- \5 p; F0 }; M1 v
  67.     if(NumByteToWrite == secremain){//写入结束了
    6 {2 n4 f( Z4 ?5 Q; p# K
  68.       break;' n: s. k2 q6 I
  69.     }8 Q- U$ Q, L1 L$ N8 ^) n  [
  70.     else{        //写入未结束8 S5 u0 F. d) A+ s/ q
  71.       secpos++;        //扇区地址增1
    9 S/ X  `9 G( r3 T
  72.       secoff = 0;                //偏移位置为0               
    ) P. s2 F6 B' g
  73.       pBuffer += secremain;        //指针偏移# V  `& X  |  L: f% [2 D+ ?( A
  74.       WriteAddr += secremain;        //写地址偏移
    2 w9 g7 j2 f" p4 C# H( }5 s
  75.       NumByteToWrite -= secremain;//字节数递减2 Y) G/ O" q  }* J5 V" t4 W
  76.       if(NumByteToWrite > 4096)               
    # I! Z2 p/ ?9 Y9 }. s
  77.         secremain = 4096;        //下个扇区还没是写不完
    ' ]# r& g+ m# A
  78.       else2 ~( r. r8 I$ v3 \. N: ~0 g" v
  79.         secremain = NumByteToWrite;//下个扇区可以写完了0 ?0 M, r2 P) S: u$ |
  80.     }& v5 ~. K, D/ t7 s9 Y
  81.   }
    ! w5 ?5 G$ G/ B; a
  82. }
复制代码
# z6 w8 U! g/ k& H, W6 o& b# V
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */4 l3 R$ h+ w0 Z% K
  2. uint8_t wData[0x100];
    5 s6 C+ W  [; t; Z5 O3 G3 I: q# z) l
  3. uint8_t rData[0x100];
    - {  G% _0 `+ u0 T2 o: f! N+ |8 q
  4. uint32_t i;) |% |5 g( f2 z' Q; Q
  5. /* USER CODE END PV */
    ! V- w7 j* J5 Q' b/ O; y
  6. int main(void){
    8 ]( S3 H) `/ }' ]& f, q" O8 R  e5 _
  7.   /* USER CODE BEGIN 1 */
    % e1 ]2 {7 F9 r
  8.   uint8_t key;( c9 W; D! F8 `" `+ |) c0 G0 d
  9.   /* USER CODE END 1 */
    2 @9 N9 J/ s, L' I* @) ?. Z
  10.   HAL_Init();) ]7 p3 k  y$ D1 M* t; @" o0 V
  11.   SystemClock_Config();
    . A9 e& A: s( x7 {4 J% t
  12.   MX_GPIO_Init();2 ?/ |' s! \$ f& H3 K- j0 ^( Q
  13.   MX_SPI2_Init();
    % h6 g) y" C* ]
  14.   MX_USART1_UART_Init();. v. @  S6 Y& R  i- A3 e1 y# {
  15.   /* USER CODE BEGIN 2 */  y) M3 `9 ~% ?. Y4 r9 B
  16.   W25QXX_Init();8 F: V$ R- a3 u: X% m: q
  17.   for(i=0;i<0x100;i++){, S4 g! n6 |; ^& [/ m
  18.     wData[i] = i;8 B- x2 \; }& b- ?# d- ?, T& H
  19.     rData[i] = 0;
    : R- \- B/ z  a3 F8 Y
  20.   }       
    5 `1 M, [$ G4 q
  21.   /* USER CODE END 2 */+ t- m: S: z+ d9 _$ A  B( M% J
  22.   while (1){: X, R+ E, l( p
  23.     key = KEY_Scan(0);& ]. b/ m9 m' [; h. P
  24.     if(key == KEY_UP_PRES){
    / Z3 [" a: G& D5 E, Q+ a1 B& |
  25.       printf("KEY_UP_PRES write data...\r\n");) ^$ d0 {. g: |9 d! G
  26.       W25QXX_Erase_Sector(0);! x- m/ v) n$ T' M5 g9 s
  27.       W25QXX_Write(wData,0,256);                6 k* q: Z( `7 \" D' s5 @/ ~( i5 r5 r+ P
  28.     }
    1 M0 X" L+ w. m; F& w
  29.                         - ?# c/ y7 S; q3 R& S& ^
  30.     if(key == KEY_DOWN_PRES){
    4 R) c* W1 R5 N2 d, p
  31.       printf("KEY_DOWN_PRES read data...\r\n");
      E" I7 K6 I& G) E
  32.       W25QXX_Read(rData,0,256);: I9 J' ~$ ^2 L. g
  33.       for(i=0;i<256;i++){5 ~1 W* c" f+ h, ]
  34.         printf("0x%02X ",rData[i]);
    % t; k8 O' q: z0 k
  35.       }) w8 T5 {' y! h; K& E* T
  36.     }
    , |9 n/ ^/ i7 y
  37.                
    5 E9 ]9 f0 S4 a5 i/ V2 ]
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
    / T( D5 n% |' K* k& L  k
  39.     HAL_Delay(200);) O. K1 T" f0 M! |- a2 `
  40.   }
    " t: ]  {3 Y* q1 X5 I
  41. }
复制代码

& \2 a/ u$ T+ F' P( U& r" @
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息

: W* r) j3 y7 F; L2 _ 微信图片_20230701185521.png & W$ X1 m, _5 D

7 Z: H6 v" ^$ i$ ^3 Q3 V3 f- X转载自: 嵌入式攻城狮
0 Z3 J: f4 q  F6 Y! d; D! A6 @如有侵权请联系删除
& m/ Q9 t7 B0 V% S: J4 D" A) w5 A3 _" M2 c
$ y2 i- c4 J9 |

) z7 F; c% |& a( H" p1 d
收藏 评论0 发布时间:2023-7-1 18:56

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版