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

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

6 K- x0 P0 Y$ {  [4 N0 i$ D$ Q
微信图片_20230701185542.png
3 ?; s4 |2 V2 Q
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:

    : z& j8 ?( b! D9 g; \. M0 w8 {2 V; r
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生

    ) e$ I/ B9 n: e3 c' l/ F$ O2 M
  • CS:从设备片选信号,由主设备控制
    ! f; T, [+ |# s) |6 r
    3 X- R1 F" \* @& X
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。- H  O0 p2 Q. z) b6 Q. `) V
当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。
5 J! F5 A; B+ ySPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
    1 x7 Y6 G9 M- K) B; }
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样

    # I/ f% j: H. R5 C) D) W& ]5 r0 l
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样

    2 w: R3 j; k6 ~+ O4 J6 h; D

    ' w5 p9 J, x) b9 E! B0 k
微信图片_20230701185539.png

, N) O7 X) a0 V  A7 t  G
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的常用命令表
7 d: A+ o) R' `$ a7 E8 F
微信图片_20230701185536.png
  n) }6 m- M8 g8 a! `- b# H
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    % ^9 R3 g- S- ~. Q2 j$ H
  • 指示灯D1
  • USART1串口
  • W25Q64

    4 i+ l( W, g, [0 `1 M+ x
  • K_UP和K_DOWN按键7 W% S4 x" k" T+ F! J* ~6 _

    5 C" O) b& e: ~0 |! e- n
    1 T9 F0 t2 e2 G9 ]- d
微信图片_20230701185531.png

7 X% u4 `) X8 \( ]- Y

( S, H9 i3 c- 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
8 X4 O" {6 \8 e$ s3 [% Z# X
% O: W6 |1 I: C6 }; T
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
( w' Z: {! ~$ q
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
3 F) ]+ |6 \- c7 Y
  1. void MX_SPI2_Init(void){
    ! O: N9 ^% v% L
  2.   hspi2.Instance = SPI2;7 x. I; V; O- I* m1 d
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式4 o2 V4 `- }: E+ |$ N
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式
    0 y9 }' Y- `6 g/ Z
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
    . H: G. e" O& a$ t: |- v& {* ]1 ~
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平
    / A- k, q' b! R2 O  i
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样: ?. s, q4 _) o7 `! q
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
    5 D; g* q5 v  j: S- t
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
    6 m* @- c. x! c; Q  N; q
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行5 l5 ?5 O& U4 I6 c" ~$ U
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式# M$ ]' @+ a: \- [% E2 J/ b5 ?* T) A
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验( y- F9 n+ @3 ?9 }, a
  13.   hspi2.Init.CRCPolynomial = 10;
    , ?: b6 B& H- J% J5 z
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){
    - W# o+ {3 W' c( _- |4 G5 D
  15.     Error_Handler();3 V; ]5 o. f1 @7 K& O; R
  16.   }
    # v$ x( l/ j+ x% D: b! w) q, r
  17. }% h, }9 U/ t8 |1 w0 b  Y
  18. + Z0 A3 F$ ]1 \% v; B3 H" t
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){" x, [7 M! [& P' o
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    0 |+ Y8 m* z1 n) @
  21.   if(spiHandle->Instance==SPI2){
    ( A# \$ K1 }- `9 h5 T0 N6 \: q
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  
    + k0 j/ i* N& m+ V# o
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();- d: N& `/ e/ R, a3 P
  24.   /**SPI2 GPIO Configuration    6 A' G) M1 b: ^+ b$ E/ M
  25.   PB13     ------> SPI2_SCK
    1 }! [) ]& H; ?& _
  26.   PB14     ------> SPI2_MISO& q2 @7 t, u6 [6 \9 Z/ @- n% v5 D
  27.   PB15     ------> SPI2_MOSI */
    , E2 p' t) p6 e  v) L8 w6 h
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;' @% e" L9 u& l1 R7 X
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;# b) l5 W( J; U% C9 ~2 M
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;: d8 {0 a+ R7 z, p# k' n. \& U
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);0 M+ R) Y$ v$ `8 z" H
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;
    3 x4 D& U" I5 l; s5 U- Q; X8 k
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    5 a$ B. a2 M( t, O
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;( L( |" ~: l/ f0 p  ?! H
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    . W2 ?" ~) Y7 J* d
  36.   }
    ) S" e$ T( t; G  O( X2 r  h. ?
  37. }
复制代码
% {/ m) m3 w7 \2 _/ t8 q) u, K
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    8 j' u+ u0 n2 x$ }9 W
  2. void W25QXX_Init(void){- O5 h' {3 B, _; K7 ?, _
  3.   W25Qx_Disable();6 _1 l5 x6 O0 j$ Y6 C
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID
    / a: \' l9 y' `: }% w/ \/ ^5 u' x4 p
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);4 \5 c; k; k/ |/ {! z
  6.   if(W25QXX_TYPE == 0xc816)
    ' J3 h' k! n- @4 c
  7.     printf("FLASH TYPE:W25Q64\r\n");2 J% p3 d5 ?$ p, E/ x
  8. }
    7 x" Y* i6 y2 J
  9. ( n/ d1 L* h* V+ k& e7 |& f
  10. uint16_t W25QXX_ReadID(void){
    0 M6 k( j5 ?* D1 D$ [3 j9 |
  11.   uint16_t ID;7 d( |- Q" ]# x" T
  12.   uint8_t id[2]={0};) Q# i  @7 g  T4 X! N. Z9 @
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令       
    % l1 I* E' Q5 y. T% P" Y
  14.   W25Qx_Enable();//使能器件
    ' ^: S5 m# o2 ?5 p$ h
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
      D9 d; `/ q( t! F
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);8 B7 d  `! d  D- a  A3 [) ^3 V2 [
  17.   W25Qx_Disable();//取消片选        * t8 p& E$ G1 {& }% U
  18.   ID = (((uint16_t)id[0])<<8)|id[1];7 @1 A7 a2 W# k8 a  y
  19.   return ID;
    ) H% b' J0 u6 f( b2 u9 ^
  20. }( }; K, u6 \( S% l3 J' P3 M
  21. 9 Y& C! R& G: [' g: L6 u4 ^6 {
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){* Z" G- F: O. ^# G2 }. _
  23.   uint8_t cmd[4] = {0};# j- D: f' g, ~& f/ i% _
  24.   cmd[0] = W25X_ReadData;//读取命令  }( b2 u" `3 @* y5 |8 v6 q3 z& T6 z
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));
    # e1 T# f& {2 h9 g! x
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));
    & d1 T2 f" V4 ~) Q
  27.   cmd[3] = ((uint8_t)ReadAddr);; k1 B' U7 e: V$ c# N: R* Y
  28. * w1 J0 v7 G1 ?: Z) o' t; D% H
  29.   W25Qx_Enable();//使能器件2 C' l, i+ p+ p/ Y7 h7 X5 J( [
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    0 Y. D9 E4 Z% W! t8 f: N9 M
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
    7 k' F% [: h+ \0 `
  32.   W25Qx_Disable();//取消片选       
    " O6 R6 A9 T! s  e& M
  33. }
    ( m, o# ~* e" [/ Z
  34. ' U  B& {4 ]# k8 f% @5 b! d
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){. u6 t+ S* X2 l& Y8 o& u
  36.   uint32_t secpos;$ b: t( T/ D: k* k, ~' E
  37.   uint16_t secoff;, @8 q* T9 L$ o' g
  38.   uint16_t secremain;
    2 p# T5 E; y( w' |3 P
  39.   uint16_t i;$ q/ t6 A  c- [, E
  40.   uint8_t *W25QXX_BUF;! \2 @9 y9 r3 V' ~5 i
  41.         ) m) ^+ N7 x; M: @  z
  42.   W25QXX_BUF = W25QXX_BUFFER;/ W) E5 o1 U) d+ I; t) X$ O$ i2 M
  43.   secpos = WriteAddr/4096;        //扇区地址
    0 E  w2 n2 b6 R7 P8 v( G* B6 f# e
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移8 f) b+ t/ R" w* X6 w& Y+ B% _
  45.   secremain = 4096-secoff;        //扇区剩余空间大小
    3 {9 @+ F+ ^6 H
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
    ( V$ D8 @0 G- `& B
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节" h9 x' b, |0 M& Y
  48.     secremain = NumByteToWrite;2 @6 K# P5 E0 Y
  49.   while(1){* z2 T0 v" l/ K. S: N
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
    2 Y! L& C0 b2 |: _- w, s
  51.     for(i=0;i<secremain;i++){//校验数据
    , I) J. K2 A6 E: `6 e
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除
    + l! F, N  q. x7 S$ y
  53.         break;
    9 M- `7 ^5 I+ L9 g' E6 s; g
  54.     }* l. I! L2 {% m$ q+ d' H
  55.                
    # y  k$ d! Q( W$ @2 N+ f) A
  56.     if(i < secremain){//需要擦除
    & N( U! w, Y' g+ L; ~
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区/ J, J4 k- u/ d' g4 x5 N; A
  58.       for(i=0;i<secremain;i++){
      I. r+ ^% W3 d
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];
    0 j( [6 X& M# F9 v0 x
  60.       }
    & d4 J: @& U3 s1 |, C: d( p! H
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区5 |8 \" K$ U: i. N
  62.     }) X; {) V- T3 E
  63.     else{
    , w+ W2 x6 q9 c7 h7 J
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
    4 c& j- j' x, T% r
  65.     }
    % j' z, l% a. ?# S
  66.                
    * @# G% A2 s' Q( b* y
  67.     if(NumByteToWrite == secremain){//写入结束了1 A5 q( t2 `, B& m/ W  z
  68.       break;
    8 j( u, ?- n1 I  a) H" m# p
  69.     }) d1 j1 ^7 Y4 c- I: f5 P
  70.     else{        //写入未结束" M, m1 V& d; M( @! n6 ~. G. L
  71.       secpos++;        //扇区地址增1# O4 x: k  [' Y) V
  72.       secoff = 0;                //偏移位置为0                3 i) e! \6 g* U3 f
  73.       pBuffer += secremain;        //指针偏移
    2 s. t+ P" k5 I  p# j
  74.       WriteAddr += secremain;        //写地址偏移
    # \9 |2 F5 v7 a! C- s* x2 ^& c9 ]& N
  75.       NumByteToWrite -= secremain;//字节数递减' g" C; q1 ~7 ?* B. |' E0 X- o
  76.       if(NumByteToWrite > 4096)               
      M2 @- t5 |, Z. k; |; \; T3 ?
  77.         secremain = 4096;        //下个扇区还没是写不完4 P' y2 @/ l' f- R$ R; U
  78.       else
    - k6 }: O/ Y: I; s5 ~/ J4 F! h5 m
  79.         secremain = NumByteToWrite;//下个扇区可以写完了
    - b7 x7 x3 v, F% T5 N
  80.     }
      T+ L5 W3 I/ e. e9 }- r
  81.   }0 ?" l7 g' e+ h2 C4 c, Z. i
  82. }
复制代码
8 Z# c3 x+ F8 q9 s( o( w# d8 y
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */" ]$ h1 n: d" Y. V
  2. uint8_t wData[0x100];
    0 c! c/ i  b' Z
  3. uint8_t rData[0x100];) }4 H4 i6 H8 p9 w' W8 ^/ G9 h3 ]
  4. uint32_t i;3 \* f& M: s# b4 f8 k. z8 C
  5. /* USER CODE END PV *// y8 h& i; n0 l& [! c. A
  6. int main(void){( x( ]9 ^8 k& i
  7.   /* USER CODE BEGIN 1 */3 N. ~; A2 G% s6 o
  8.   uint8_t key;2 \! R3 h- B# b5 i: B, W# P
  9.   /* USER CODE END 1 */0 f4 ^* K6 N  W9 m( t- s$ f0 T
  10.   HAL_Init();
    1 T( z: f+ B: [$ {1 y/ Q! H% O
  11.   SystemClock_Config();5 h. H: u+ p; ]% L
  12.   MX_GPIO_Init();
    1 O. X2 ^3 s- t+ d) d; C+ i7 A  @1 K
  13.   MX_SPI2_Init();
    - i- @+ q" H; }" A3 `! K; y( B6 ^
  14.   MX_USART1_UART_Init();4 u/ L4 Y2 O$ P1 e" k" b/ t
  15.   /* USER CODE BEGIN 2 */
    % x; R; m7 t5 y& [/ P
  16.   W25QXX_Init();
    ' o- l& \: R0 f- i7 M) p" h
  17.   for(i=0;i<0x100;i++){
    6 [; h5 n8 X; ^0 j. u! s
  18.     wData[i] = i;+ a7 u% a+ B, ~
  19.     rData[i] = 0;
    9 d' c6 u! ]% R+ x6 C/ j
  20.   }       
    0 X* n% m- s0 P4 f# t
  21.   /* USER CODE END 2 */
    4 l4 H- ^1 J5 f) i0 B( E
  22.   while (1){& e" N2 b/ F" f, Q" T" S6 {
  23.     key = KEY_Scan(0);
    % q8 i1 T& b8 c$ V) N$ [; T
  24.     if(key == KEY_UP_PRES){! G7 R6 _6 p; }) b3 i
  25.       printf("KEY_UP_PRES write data...\r\n");
    ; _' C4 O) y9 C$ c3 a
  26.       W25QXX_Erase_Sector(0);
    # y- C4 H5 d- F- P' N+ [2 t1 e# n
  27.       W25QXX_Write(wData,0,256);                % R$ K  y& C' X
  28.     }8 D2 [4 Y7 e  Z8 C: m
  29.                         3 v8 {/ S( a& {+ W
  30.     if(key == KEY_DOWN_PRES){
    ' N$ T2 v+ N* w; u; {
  31.       printf("KEY_DOWN_PRES read data...\r\n");& S+ m" Z4 E% n
  32.       W25QXX_Read(rData,0,256);
    : O8 j9 I2 L. r, V- X
  33.       for(i=0;i<256;i++){
    0 F, D  Z$ U; k: `5 d2 ?0 P' b
  34.         printf("0x%02X ",rData[i]);! A0 h+ G1 p& D6 d. R; M, y
  35.       }7 k2 z3 ]; a, y
  36.     }/ _! h8 S5 B# H! A
  37.                
    . b- m& j- h  O7 u
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
    & A! e# P8 v% @8 T7 t
  39.     HAL_Delay(200);# o- `$ H* v- ^5 S) j( ]
  40.   }
    8 q+ D9 a; E- ?% o6 F  u: a5 b5 Y
  41. }
复制代码

! J6 f! \# E1 n- v% N" z6 b
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息

6 {& C: O3 f. y5 D+ z) s' ]9 H 微信图片_20230701185521.png
% ]% |  l  ^8 I+ M* f
  x& }2 E4 u) o# ?& T/ }. Z转载自: 嵌入式攻城狮- W8 G# m$ I* R4 B1 B5 }
如有侵权请联系删除
+ X, G" b( _9 [0 `( y) l/ `
' X) n. I; H0 ~6 f+ |* A% ?4 g6 Y: B! @& Z8 @$ w. O

, w- S4 I+ h( F/ C) h6 |+ _8 z- ~
收藏 评论0 发布时间:2023-7-1 18:56

举报

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