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

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

" N( Y3 T# s8 I1 H  e7 v1 M
微信图片_20230701185542.png
  F( D- \, @0 m
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
    * h1 s6 `- T& L: U! W# K
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生

    0 K8 E0 S. D7 d9 O
  • CS:从设备片选信号,由主设备控制
    ! x! w) z1 P) ^# \0 p

    3 r) c; q, i) x
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
7 Q% a$ W% k' f) o2 N) g+ q" O: n当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。3 K" x5 I. W; N8 c
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
    ) Z  B" G9 y* U
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样
      v. ]) B) V' w  j4 b: h+ v* D5 s  b
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样

    7 T9 x; |( k) O5 p: H$ a
    , y, Y& R' F% Q
微信图片_20230701185539.png

' i  z  v* Y* Y( E6 q& Q6 v  S/ Q
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的常用命令表
) d% f1 E% A2 T2 P8 r% p' ^
微信图片_20230701185536.png

8 e. q% g3 L0 T$ I
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    1 n$ H( c  T; ~& w0 c
  • 指示灯D1
  • USART1串口
  • W25Q64
    7 Y6 |& d" U$ d/ w9 ^
  • K_UP和K_DOWN按键
    5 b) b! [2 k9 ~  c5 c

    5 d  s* y% ~% t: A, H( |' K1 @4 l

    , ]% p) O4 {2 @# T5 g9 w! a
微信图片_20230701185531.png
: j8 K2 V4 ]8 P5 r" y, c# x
0 |9 R* A7 n! G5 w. c! ^
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' i6 }8 O- E: D, S
1 q! U! _! T9 F
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
5 t4 B4 o8 l; |: r2 {+ n
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中

$ o; f" p& |; x
  1. void MX_SPI2_Init(void){
    ) [9 V' S* P, z( g8 Z& P! i/ o4 n
  2.   hspi2.Instance = SPI2;0 z- ]3 E5 m# F3 s) g# F
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
    5 O/ D) L7 m3 _4 {; D& B
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式7 Q# ~: Q8 X  [- j  n8 [0 a, s
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
    ' y$ X5 q9 g, S: X: R+ S+ Y
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平
    1 D8 {$ f5 w  S% B+ t7 A
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    ' [  _6 `% @8 @! B
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制
    . x0 x7 q3 R6 @% q/ U5 D4 P6 j* \
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256  G- c. Z6 L, H' }
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行: |- D, ~+ a3 u5 X& W
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
    3 i; F9 b) G( T% {
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验2 p. \' F, [7 w; ]( X. ^
  13.   hspi2.Init.CRCPolynomial = 10;$ q3 e4 O9 _' E! u; a
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){! B' [- N, T5 E/ A3 g' M2 ^
  15.     Error_Handler();" e( X$ x, J% F/ M9 M6 \! G
  16.   }
    # W/ t3 Y+ [) [6 F" J
  17. }
    . Q5 ^/ |0 ~' Z5 A
  18. ' p! U! f1 D+ a, w3 x+ s! }8 H
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){
    3 t# B( X) g! X: i
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};* j4 V. k1 [# Z( d, d7 l: Q
  21.   if(spiHandle->Instance==SPI2){
    0 b3 c4 `, M7 p% i% O3 p
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  , H& H1 I/ i4 w1 n2 U; L! }1 v, w
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();
    $ ?. c  P7 R: H
  24.   /**SPI2 GPIO Configuration    8 c+ x, B6 A' I: B* L( H
  25.   PB13     ------> SPI2_SCK
    ( U1 T: J: n( {2 c
  26.   PB14     ------> SPI2_MISO% B) b7 [2 n1 M% C
  27.   PB15     ------> SPI2_MOSI */$ x7 _$ W# ~5 C4 Z" }8 V
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
    ( {7 W8 ^1 I# [0 ~  S
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;5 M, E. l0 o' ]; I) P! T
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;7 z/ B7 _! i, {2 }
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);' o2 T3 G; K9 v1 D" ^( n
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;9 }+ J# m1 b+ K5 ]! d) [+ K/ w
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    # ]1 }3 D# D4 p+ [: A: T+ _
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;
    ' y2 B, {  m( d5 `
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);% \3 J# q/ t4 M8 N9 C% f
  36.   }
    % B( J- }9 F- p% E# c
  37. }
复制代码

  o$ n7 ]% O+ ~4 O
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    ( U3 D9 y# b5 z3 Y! u
  2. void W25QXX_Init(void){
    3 d5 P( ]$ O9 _- o7 S
  3.   W25Qx_Disable();
    $ y4 D+ h, j2 {$ V1 c
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID& A. V5 ^" K, i  e
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);; Z3 [5 _/ a7 ]# Y' {7 i3 {
  6.   if(W25QXX_TYPE == 0xc816)
      P3 O4 q& d, {: x% {" S
  7.     printf("FLASH TYPE:W25Q64\r\n");
    ( H" k6 t6 p# B9 _; q" _; q
  8. }
    - [, J$ Y4 Y/ G; F
  9. $ \, Z3 [* l: K; r% t
  10. uint16_t W25QXX_ReadID(void){
    ; W! Q2 ^5 u8 u' L
  11.   uint16_t ID;
    + g9 {- N4 p% e0 m/ D" w  r
  12.   uint8_t id[2]={0};
    8 v+ H$ X3 x6 H( t1 G* L* N0 l/ ^
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令        * R% K/ {& R6 o& t6 t
  14.   W25Qx_Enable();//使能器件
    " H) u3 U0 s/ A% F0 E
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    $ {! S" z$ z9 \! s3 s! g' V
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);0 r' o" L" v* Q+ o& J2 X
  17.   W25Qx_Disable();//取消片选        . ?+ S% Q. F! F" m  y) Y
  18.   ID = (((uint16_t)id[0])<<8)|id[1];( |$ X* J8 w9 Z3 f
  19.   return ID;4 _2 D  r5 j# j7 i6 U- [! {
  20. }
    7 p. u" U: S9 U) H( i

  21. 0 n4 `7 x) k  c( t: J
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
    ! O! C: v) l, T
  23.   uint8_t cmd[4] = {0};
    5 d4 K: b+ @- h
  24.   cmd[0] = W25X_ReadData;//读取命令
    . ^. X. T7 R$ z
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));
    / s) g+ N! C3 n9 `( D$ i) c
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));' M4 c* l' k/ x& \
  27.   cmd[3] = ((uint8_t)ReadAddr);8 ~8 T" B" ^6 V3 U3 O4 d
  28. . T6 T$ x1 B- H
  29.   W25Qx_Enable();//使能器件
    2 N: P9 _5 `9 S- f- d2 ?
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    7 a# U# D3 a7 w# A+ `
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
    : Z7 ]! V$ `( d  f$ o2 _
  32.   W25Qx_Disable();//取消片选       
    # J% M% M9 p  @% _: U/ C5 D. a, z  p8 ~
  33. }4 M) r/ {1 T$ \  K1 J% F

  34.   h5 B+ A9 ?0 K
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
    " R: w7 a8 r- [; e8 G9 C+ }
  36.   uint32_t secpos;$ _( D0 {" p" e
  37.   uint16_t secoff;* z; `' a1 }. L) _. Q% Q
  38.   uint16_t secremain;
    6 D0 \6 b1 f# {8 U6 z
  39.   uint16_t i;- P/ _" v: `6 S9 @# T0 P
  40.   uint8_t *W25QXX_BUF;
    & e9 V: Y3 a  P
  41.        
    ) T# Z6 }" b# L$ E  R0 `" s% |
  42.   W25QXX_BUF = W25QXX_BUFFER;# S% q4 t' j* t0 k3 k
  43.   secpos = WriteAddr/4096;        //扇区地址) B9 ?' o" F. L5 Y/ H$ B1 E  }. q
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移$ R; f, ?" V: u, d
  45.   secremain = 4096-secoff;        //扇区剩余空间大小  F  c' u  L" _
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
    ) s7 {1 x7 g* \6 Z* s$ x* k' r. X- P  a
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节  I, c& O3 `/ ~6 p/ l$ l2 e
  48.     secremain = NumByteToWrite;
    5 R, f, e7 M0 g0 ?, O" V
  49.   while(1){
    & `- t9 g* u3 T. x. ^- n6 s8 F
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容5 C) Q# s7 I" J8 v7 j0 l; p2 ]; r
  51.     for(i=0;i<secremain;i++){//校验数据& e2 N( Y4 S& A% v& B
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除
    # A% @& b  e" D1 Y1 j6 S8 K$ k7 m& P
  53.         break;8 \$ c; {1 \1 O1 ^5 ?
  54.     }
    3 I3 G# V3 f0 d5 Q; c
  55.                 - ~; |3 D- S; o# H# i
  56.     if(i < secremain){//需要擦除# j: u9 N" Q5 X+ C- Q
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区' V4 S% B7 E% E( J! i
  58.       for(i=0;i<secremain;i++){# i0 ^5 D3 Y! ^7 R
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];* R4 r4 {( u: Q+ ]+ U$ U
  60.       }
    ) e% L2 V7 i6 L2 H4 a! V
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区, G; h/ ]0 D/ C8 H- [) |
  62.     }
    " h7 k6 R0 k, j, W
  63.     else{
    & k6 {4 \, w. Y- b7 n% i' j3 g
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间- U4 G( v/ B8 `5 J; b
  65.     }7 ~% k7 o4 I! ]. q6 Z/ \
  66.                 . d, D$ I! P' y
  67.     if(NumByteToWrite == secremain){//写入结束了2 e! |3 e9 V" s: @3 `- @
  68.       break;
    0 X) C0 ^# @7 h" k/ |& I( [- ]
  69.     }8 M8 C6 u7 p9 D! G
  70.     else{        //写入未结束% K  h1 U! R8 P* |+ c) O
  71.       secpos++;        //扇区地址增1+ D* P" R) b+ P9 x* V7 I
  72.       secoff = 0;                //偏移位置为0                : w! \5 M4 B# E: a8 M
  73.       pBuffer += secremain;        //指针偏移
    2 d: U5 a1 r; @# t
  74.       WriteAddr += secremain;        //写地址偏移# Q6 _8 c& B* @, C- j
  75.       NumByteToWrite -= secremain;//字节数递减
    - V' `" }8 }2 T2 O9 @$ Z
  76.       if(NumByteToWrite > 4096)                8 b( D% |+ i; }  n1 P8 z2 k- [
  77.         secremain = 4096;        //下个扇区还没是写不完: Y7 P) G% ~- k' G" }% N1 k. z. p
  78.       else
    , z- t5 t4 d- G9 U5 l
  79.         secremain = NumByteToWrite;//下个扇区可以写完了
    6 o( c: N) G% T8 X, ?1 f
  80.     }
    7 H" F4 H1 }! x( e3 h( a
  81.   }
    : x$ T# q2 v+ e7 N
  82. }
复制代码

* X' P; ]- E* H3 V* E: ?3 b* n. F
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */% y/ w" ~) U5 V5 b
  2. uint8_t wData[0x100];7 \, l: e# L$ A  O0 e' h
  3. uint8_t rData[0x100];' _- J$ e- d  g
  4. uint32_t i;
    " Y7 D' q, y) q; S( W' |
  5. /* USER CODE END PV */
      K) T: v: L5 o% Y7 G* s! ~/ f+ v
  6. int main(void){( i$ D& A4 ~8 s" n
  7.   /* USER CODE BEGIN 1 */
    # i! r4 A* N# p8 {8 i
  8.   uint8_t key;7 @( g0 b8 m. n5 |. Z, w
  9.   /* USER CODE END 1 */$ j. E% w8 O/ w1 {# J9 U
  10.   HAL_Init();
    + T, S. f" L. t$ W
  11.   SystemClock_Config();
    : H& r' T% ^7 b+ H* C5 W. w
  12.   MX_GPIO_Init();# Q- K. Q: P" i
  13.   MX_SPI2_Init();! J% h) ]& e( @3 B! K5 \  c& L
  14.   MX_USART1_UART_Init();
    2 o6 \0 M1 c7 @! h
  15.   /* USER CODE BEGIN 2 */
    , h) x) U1 I9 r% k) H+ U
  16.   W25QXX_Init();6 m0 Z& d, r* S# x/ d# Z% {# G
  17.   for(i=0;i<0x100;i++){- U/ [1 r' R% O3 I) p# l4 D# z6 {( [, o
  18.     wData[i] = i;
    " Z& v" E2 W* C* S& p/ s- p3 M
  19.     rData[i] = 0;2 j) N8 s# {0 L# I) q
  20.   }        3 k  d& [7 o; M
  21.   /* USER CODE END 2 */  s: ~2 U$ Y3 I4 a0 p! b0 z* w+ G
  22.   while (1){
    6 v: R2 c# X5 B' G1 H3 ^! B2 d
  23.     key = KEY_Scan(0);: u% O4 c- i: W9 V4 x+ P
  24.     if(key == KEY_UP_PRES){, _# [& C/ {+ B6 H
  25.       printf("KEY_UP_PRES write data...\r\n");/ `6 V9 v) b' N, H* s& K1 e- L# M
  26.       W25QXX_Erase_Sector(0);
    ( H# ?/ Q" ~4 s% R' X
  27.       W25QXX_Write(wData,0,256);               
    # c" \$ e1 Y  A
  28.     }
    ( J% ^/ O! j$ J4 ?. I# u  K
  29.                        
    % ?; ]3 Z5 p, a4 G
  30.     if(key == KEY_DOWN_PRES){. b; G$ Q& _; Q, G6 g- ?
  31.       printf("KEY_DOWN_PRES read data...\r\n");
    : k! Y& T* W) w* {; Q' H
  32.       W25QXX_Read(rData,0,256);4 z9 f3 W0 ?9 N2 J1 Z* i
  33.       for(i=0;i<256;i++){, ^, M" v3 h4 s# M( `
  34.         printf("0x%02X ",rData[i]);
    - ~6 r8 l# ?' E3 P% c; ^2 I
  35.       }. F$ C: _/ s6 i& {3 p& d
  36.     }  W" i* N+ q. e( G8 @4 E- Y1 c+ M" k
  37.                 $ r/ {, S+ \, p1 r
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);( Q" s8 R4 \3 T2 u4 ]
  39.     HAL_Delay(200);! J& {( z$ q- e+ `) u' E
  40.   }
    1 n6 e5 I) K9 x: G% y
  41. }
复制代码
/ i0 K' i; {7 f
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息

+ n: g& M* n, h; q* \ 微信图片_20230701185521.png 9 u1 r- a% i6 O
; r" t6 \' C) Y; ?$ g1 ]
转载自: 嵌入式攻城狮* I' n8 b2 T8 w: b
如有侵权请联系删除
+ t2 B  O/ C% G; A. v: c" J8 B# v9 ]0 b, |

, n  g; D5 p* _" l
, k9 d9 W1 X3 O  W8 u' D
收藏 评论0 发布时间:2023-7-1 18:56

举报

0个回答

所属标签

相似技术帖

官网相关资源

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