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

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

7 e; C% g# K) l' n. u
微信图片_20230701185542.png

2 z0 t" W8 X* L- J) i
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
    9 P# o0 C: K& R! k4 n
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生
    # P* e1 d1 H7 z
  • CS:从设备片选信号,由主设备控制
      {# V! v  Y$ A5 K
    ( b% T: a- S1 K1 T% K
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。+ l9 m' x6 T: E" t' v/ z( J
当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。/ k( F) o9 F* R
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:

    2 R0 t- \% _+ ]5 Q) o
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样
    ; j  L) Q3 S3 \5 t
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    & t6 ]9 T, c; r/ @& l
    & w* M5 {& D7 a: j' |3 G
微信图片_20230701185539.png

, C! C: x8 Z- @1 K
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的常用命令表

8 t$ p' S5 W) r( o0 \( C4 k
微信图片_20230701185536.png
* Q( f  S' n" J2 _
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    ; a, j5 X% I/ J+ Y' E! M
  • 指示灯D1
  • USART1串口
  • W25Q64

    - |9 q# U' q- m
  • K_UP和K_DOWN按键
    4 H/ X; R; I5 H' M' u
    1 I# q8 x5 \6 k& P3 M, J

    ) H2 Y) d* T% c0 S: F
微信图片_20230701185531.png
6 W" u- X0 I! i4 N# f3 ?5 Y* D2 a

0 y5 {% N8 x. N% k
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

$ Q' L! h3 h: r* Q) |8 E, ~) H2 v# }+ x, z; O, q- G
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

  Y( s4 E. Y7 p; P7 i
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中

, Y6 F  [( Q: N1 T6 x" d
  1. void MX_SPI2_Init(void){
    / m0 I7 N, A0 H
  2.   hspi2.Instance = SPI2;: o3 X- |6 {4 J6 d- X6 @
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
    " c, ^! n4 s' ^' j
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式
    " T; R0 T+ i- b" B( ~
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
    & B) i2 ]( v  b- {" g# @& N1 ~
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平
    # |1 O# d  a4 `/ c6 t3 M. K6 z6 e
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    # x- b  o6 [) N# K, u
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
    5 Q9 ]1 \) \0 E& x4 @
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
    9 X- y& u( K- Z. I2 L
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行- f( @- K& j$ r( D. O
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式5 S7 p5 \9 m3 G& K2 V; R; ?
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验+ D- E; _% G" X8 M* D9 L9 O6 l6 L. q
  13.   hspi2.Init.CRCPolynomial = 10;, }/ X0 ^. o1 T' b" Q
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){
    ' u6 z/ U0 j! i9 M0 m: |9 U
  15.     Error_Handler();) \0 I  ]# E$ g. y0 s1 F
  16.   }, j) |. a- F: N+ ]
  17. }. S5 n0 c. X1 P5 K. m# Y( @, l
  18. $ U% D8 A% f" C; ~) H
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){+ f/ J8 {% v  B9 s* @( t' ~
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};( g+ z6 w! ^+ N- H1 N& W+ n5 Y& y) }
  21.   if(spiHandle->Instance==SPI2){
    8 {3 O, v; ]8 A# }- _# _* y
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  3 v" g) ?5 w; B) k! c" R; t
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();
    0 Y$ v# i4 q( @2 k  Y/ S
  24.   /**SPI2 GPIO Configuration    6 |1 }3 f7 P, r( S$ o; e* H
  25.   PB13     ------> SPI2_SCK0 q) C- Q; R9 B
  26.   PB14     ------> SPI2_MISO
    . h6 I! Z' c! A! ]1 R" s
  27.   PB15     ------> SPI2_MOSI */8 u/ S$ z; S6 B6 y$ Y8 E
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;+ S! J) h/ g% q; D$ ?$ [2 M
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    0 s6 u9 K- i* i6 O
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;- Q+ ?" _5 i5 T7 M: \" ?
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    3 u) P; w+ \9 l) @9 e$ }# C) l
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;
    , O) |+ e: H0 W
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;" i5 L+ j7 L; \# x  t4 [, S
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;
    1 O) v4 W3 M6 B4 u5 X4 X
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    " X3 I7 ]# Y+ ~" C) S7 a
  36.   }) f0 J  j, n/ X, n" t
  37. }
复制代码
* a- r( j  U/ }) y& \8 i0 l% L
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    $ ?& U+ [& k3 D2 L
  2. void W25QXX_Init(void){
    # k$ l; ^$ H  N) ^4 C
  3.   W25Qx_Disable();1 U  Y1 r0 x1 h5 q3 K
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID& X( [2 [* A0 W: ~- T( D  y6 B3 i
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);+ ]* B5 y; s$ l
  6.   if(W25QXX_TYPE == 0xc816)" ]  d! ]5 P6 b6 n" D/ F
  7.     printf("FLASH TYPE:W25Q64\r\n");- p/ m* g+ n+ U- J: ~% W" q' C
  8. }
    3 X+ U/ `7 A: D, @$ t
  9.   M8 d; I1 Z6 j) \# \% Z3 Q# T
  10. uint16_t W25QXX_ReadID(void){
    ( K' _# f8 G: H( B
  11.   uint16_t ID;
    * w0 a2 ?4 S! C1 B9 T) t
  12.   uint8_t id[2]={0};
    " o/ s$ m6 x2 L' R2 j+ {" i
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令       
    7 q7 n3 w% g, o* l" e" b: Y- @
  14.   W25Qx_Enable();//使能器件
    % j9 M! U0 S7 p; x
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);7 P) L. l( K( K3 j6 Q, j1 b  B2 b1 k
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);& a. m9 I; u  t4 ~6 k4 `  p- X
  17.   W25Qx_Disable();//取消片选       
    , u  I7 N/ `# E& x
  18.   ID = (((uint16_t)id[0])<<8)|id[1];) ~( O/ r5 j2 T# s. T. y  Z- v! q0 n
  19.   return ID;$ f6 P4 Z) i, r! f; L2 T- j- _
  20. }
    ( A* b% k5 G; i% |. m) n+ f# H; D5 z
  21. + @: l4 s9 x) u8 B5 p
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
    ' H/ {, I: `. {: c; c
  23.   uint8_t cmd[4] = {0};
    & v7 w& k# \: g# F: j4 S
  24.   cmd[0] = W25X_ReadData;//读取命令: I  b  P" w% _' C9 B, d! q
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));" ~) K. s* z! g  H6 P- h6 P, n- ?
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));
    . n, m' V- ?$ `' e4 c2 n5 p
  27.   cmd[3] = ((uint8_t)ReadAddr);* }6 j: d0 i/ [9 X# v) ?8 J: V
  28. 9 }4 G* c+ @& m
  29.   W25Qx_Enable();//使能器件
    : C, i5 u2 Y  }# Z
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    & h- h  F; o1 Y& w
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
    ' u3 {6 P. N/ s* a3 ]% S( d. @- l
  32.   W25Qx_Disable();//取消片选       
    2 I5 x/ E5 G/ o4 }2 J3 m2 o$ @
  33. }
    4 `% G) ?5 @8 R) d! ^& Z% ~% ^

  34. ; H' q% C) I# A/ T
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
      R8 |% ~0 n4 r" G! C# Q) `
  36.   uint32_t secpos;
    # E, J* c% D1 ]  J( H
  37.   uint16_t secoff;
    . g/ P7 w  E) e& {
  38.   uint16_t secremain;' Y; S; a5 o+ H8 G$ c2 i9 V
  39.   uint16_t i;% y- K2 E: q2 h, o$ Y
  40.   uint8_t *W25QXX_BUF;
    ( F& g9 S; q7 R( d
  41.        
    " D" N) o/ e; z* ]( C# f1 c
  42.   W25QXX_BUF = W25QXX_BUFFER;7 \$ s0 D$ b# n* R& L
  43.   secpos = WriteAddr/4096;        //扇区地址. f% J! P/ U$ y; W
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移
    3 O& W# M- V. `) Q+ w7 M. J
  45.   secremain = 4096-secoff;        //扇区剩余空间大小
    ; s0 o# M1 u% g. {5 {- T
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
    ) u2 q) k) r/ a6 M
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节( ~2 }3 p. D  }3 k0 w: U
  48.     secremain = NumByteToWrite;$ _4 ]* U+ M& q9 T! p
  49.   while(1){7 W/ I0 M. q9 P  G8 P4 ?) s
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容, [- u6 ?; s' ~. |8 I
  51.     for(i=0;i<secremain;i++){//校验数据
    . }" j7 o; ]0 G- Z: o% L
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除
    7 I% S0 k+ k6 ?- x1 v3 E% V
  53.         break;- P+ C+ i3 c' \
  54.     }
    1 A# H$ ]9 X2 M% @, t" q
  55.                
    : @( G  g2 y6 i6 u2 R& D6 @- E0 d
  56.     if(i < secremain){//需要擦除+ r6 S& x! I$ P$ P' Y
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区  ~- v2 x( W% \# ]
  58.       for(i=0;i<secremain;i++){7 T5 }  z7 z0 @. |  ]
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];
    ' V: |5 p1 D4 m
  60.       }
    - G- h/ Q  _# z  Y) W# S. D$ o' F
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  L% M8 B6 N) t
  62.     }
    1 _0 e. U& q5 K4 @( o* w: q
  63.     else{
    ( D" c7 h- v4 ^4 N
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
    . v* m. D! D, _% y3 y
  65.     }+ U: g! J' K8 o- ]7 S- e3 l. @
  66.                   ^6 \4 [; `6 G' l4 k  W" r
  67.     if(NumByteToWrite == secremain){//写入结束了
    + _% Z7 o4 x5 k
  68.       break;0 L9 A5 K1 E: s* D
  69.     }
    1 j& W2 C  P# P* e- \
  70.     else{        //写入未结束
    . T  j: l8 G( D) O% q9 b' |
  71.       secpos++;        //扇区地址增1$ }' G, A+ y9 [2 C: w3 T
  72.       secoff = 0;                //偏移位置为0               
      V/ k* f! J' _# t
  73.       pBuffer += secremain;        //指针偏移
    " R( y& U* h$ Y3 `) e6 |0 I- P8 X. t
  74.       WriteAddr += secremain;        //写地址偏移* z; x* v5 {9 o2 ^) {
  75.       NumByteToWrite -= secremain;//字节数递减
    7 Y0 ]* a! t' |6 ?( F* I
  76.       if(NumByteToWrite > 4096)               
    ! h" \: V# x. A
  77.         secremain = 4096;        //下个扇区还没是写不完6 L5 a& I4 ~, ~: j# P
  78.       else
    4 c9 O8 C" @6 K  Y  F7 g1 G: h
  79.         secremain = NumByteToWrite;//下个扇区可以写完了' i- \' \4 M1 G* o
  80.     }
    7 |1 X, x: _" H: _! F9 f8 u
  81.   }
    6 U! P$ i- H' T* ?7 e0 U) I9 g- r
  82. }
复制代码
6 P$ X- Z8 h$ D' ^0 n2 w' ?" ~
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */: r5 l4 K( J" s! F2 k: E1 N
  2. uint8_t wData[0x100];
      V/ Q0 n5 x' _; j' Y. z# s6 t
  3. uint8_t rData[0x100];: y. w( X$ L/ Z: M
  4. uint32_t i;+ r$ @. l& G2 f6 J
  5. /* USER CODE END PV */1 o4 n' ~- ~; P" `  A
  6. int main(void){
    : a+ i$ [6 N4 ^' A& [  {1 l7 D1 k
  7.   /* USER CODE BEGIN 1 */
    # _) u- t) k: A- H, |. t
  8.   uint8_t key;
    6 @, V* f4 W* Z9 h4 E" I
  9.   /* USER CODE END 1 */
    . S( ~# x  F8 Z" Z0 d; e5 V, o
  10.   HAL_Init();
    4 b" P2 m# G. |6 X$ @* ^  R
  11.   SystemClock_Config();
    8 O1 \) \( A* s% m! M/ w6 E
  12.   MX_GPIO_Init();
    ; n0 C4 w- p/ l
  13.   MX_SPI2_Init();" T; D! k0 V6 J
  14.   MX_USART1_UART_Init();
    ; \6 x7 d" D0 G- r7 i
  15.   /* USER CODE BEGIN 2 */: I1 k- Q, ~6 ~# o+ ^8 Y' W
  16.   W25QXX_Init();
    6 D9 P/ z2 n0 S: V! ]! W9 S
  17.   for(i=0;i<0x100;i++){: ?9 P  {2 T6 b$ S
  18.     wData[i] = i;6 Z0 X1 p3 k3 F1 i/ ?
  19.     rData[i] = 0;# Q& X$ |5 `1 q1 K5 w4 T
  20.   }       
    ) u% g; ^7 ^0 D) @
  21.   /* USER CODE END 2 */% j, Q; K* M, l+ Q
  22.   while (1){0 ^4 A$ {; o( l- s' `9 F& f" F
  23.     key = KEY_Scan(0);3 [3 K" X0 ?! v1 p+ s# z
  24.     if(key == KEY_UP_PRES){
    - G4 i  K5 h7 ]8 f  c* x! U
  25.       printf("KEY_UP_PRES write data...\r\n");0 C) L. _* i, F
  26.       W25QXX_Erase_Sector(0);/ L8 p% X& T6 T6 q
  27.       W25QXX_Write(wData,0,256);                3 W9 o- A/ g& b3 j! D
  28.     }3 L, k. J2 s  a0 E7 a8 O: Z* M1 O
  29.                         ; m. r7 d  {6 V+ g3 W: a( H
  30.     if(key == KEY_DOWN_PRES){
    + ]7 G  x4 G! N3 l8 Q- g
  31.       printf("KEY_DOWN_PRES read data...\r\n");
    0 v" m9 @! S+ o
  32.       W25QXX_Read(rData,0,256);' S, g. f$ N5 X6 \
  33.       for(i=0;i<256;i++){  y0 s6 B& B  q$ }3 c( R( M! o. t
  34.         printf("0x%02X ",rData[i]);0 q# n. w- _( w2 i
  35.       }' G5 M- Z( O! K. G
  36.     }
    * m, Z. l* ]3 X4 r6 n  }
  37.                 0 l, J) X8 F: [( D
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
      [4 I0 m, v% h1 {5 v
  39.     HAL_Delay(200);: T$ D  I( K% p1 Q6 S0 r
  40.   }
    5 N9 n7 N! M$ E7 K. q8 _3 H7 ^% ?
  41. }
复制代码

! t) ^" F6 X( I$ {, c7 c5 T2 s3 F
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息

8 I# r9 F+ K' ^2 N 微信图片_20230701185521.png 6 }+ m9 X* x& H$ e  ~  B1 k

7 Y5 a3 o8 g5 |3 g' X" W4 ~- T转载自: 嵌入式攻城狮
* @* ]6 P+ V! F5 t如有侵权请联系删除
3 p; J4 g7 T: X, p
6 l& n8 W+ J0 y+ |& }" A0 X
+ o* y% u* E( J1 p2 z% J- J
% O+ T# N; `3 [6 ]& d+ D# [7 k8 A
收藏 评论0 发布时间:2023-7-1 18:56

举报

0个回答

所属标签

相似技术帖

官网相关资源

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