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

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

- Y8 `% E* w& }
微信图片_20230701185542.png

6 T  z/ `1 g8 A1 s
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
    7 {' ^, h; d" U: [
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生

    , }- K5 m% |8 I1 ~/ W6 v
  • CS:从设备片选信号,由主设备控制
    5 S; P1 Q0 h( M+ G2 ?5 j

    ( \/ G# ?& T% [1 P4 n) n! T* ^7 K$ j
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。" Z5 N) \: }* C; ^
当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。+ r$ e6 u/ k3 l2 w, c* K
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:

    , S. V" s% P7 C
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样

      f% t8 L& o' g" ]5 x
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    / k3 ^2 Y! l0 z( Z

    % ^! V$ d, R7 S7 T, ~( l! m. }
微信图片_20230701185539.png
- F4 k8 _$ k# |6 i
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的常用命令表
& z6 G. U4 t- p& F4 q/ \/ t) j
微信图片_20230701185536.png

/ E3 b! h) T( P8 d4 m
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    / z4 R. x1 ]9 i4 O- q( r
  • 指示灯D1
  • USART1串口
  • W25Q64
    5 ^' P/ v7 a& m7 r
  • K_UP和K_DOWN按键) t. S! i+ O+ c- p- c
    " m5 \( i) J' Z9 U# ~0 u! ^
    ; k$ s5 A' Q7 p6 I, ~3 {
微信图片_20230701185531.png
5 a, i0 L; c# Y

+ O) a' D& ^' [; u, }" K2 S5 O4 m
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

( N- ~0 o, l5 Z0 a7 `4 x. S0 @% w- d; T
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
: K$ H! |0 b4 D+ q8 F, _
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
+ V% Z+ x+ \$ E4 J
  1. void MX_SPI2_Init(void){" T( u4 s1 q6 [
  2.   hspi2.Instance = SPI2;
    $ w$ \5 M8 _; w3 w0 V
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式6 m; }3 n7 z+ ~6 W! Q6 Q3 }
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式* o: B/ s. X# G6 O( b
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度
    # m3 @; p2 k: Y8 k/ F. C
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平& j  k/ z2 V: B3 i  a* a
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    " E. ~5 [5 e- N! _9 D& D& H, K
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制  {) R! S1 |  o/ p
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256! ]- T4 C3 r* l& H4 r/ q! T
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行
    " L, R0 L2 N/ z9 d
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式
    ) G9 E0 _: q: |% E% R1 r; Y$ v
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
    , T* {/ H( q& C4 u$ D9 Z! b
  13.   hspi2.Init.CRCPolynomial = 10;
    / G2 _5 A5 ]& c+ A/ {6 S4 T
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){% p# R- w$ N$ i, v! O
  15.     Error_Handler();1 R" e1 K& s6 z- W; p
  16.   }
    , Z3 N( u; x' ]/ G) G  S' G
  17. }
    % C; {3 f7 e) c$ u
  18. + }& D+ j% P# ~# I
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){- u5 ?; C* B+ v# \3 f
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    1 g0 ^6 b3 X1 y  R, e# J1 H" O" P0 f
  21.   if(spiHandle->Instance==SPI2){4 d: s) r* M" E8 H
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  # c! c" v' u- N9 r1 W  q
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();
    ) W$ t0 o1 s/ @  w8 P
  24.   /**SPI2 GPIO Configuration    - H1 l6 [, |, ~
  25.   PB13     ------> SPI2_SCK
    ( G; T& B6 }) _" T0 B
  26.   PB14     ------> SPI2_MISO  K; E# d) z5 U& R$ U
  27.   PB15     ------> SPI2_MOSI */
    3 V. i6 U3 v" d4 \/ G: x" T
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;9 {7 z7 e5 u8 F# {9 U/ c. @
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;3 z) x+ P3 s- }& z
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    / k6 A. v9 ~% X, P# J
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);$ [& x* Z0 o& Q0 _) V
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;( r0 X/ k0 I6 G4 a7 d9 H7 a1 ~, S
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    # }: T; ^% V/ G; y. d1 t" E% J6 y
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;+ e; X* ^; m; _# y4 a3 p5 A' R
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    0 b6 U' [3 _: y- D% q
  36.   }4 f2 N1 O. v1 @' `; Y/ z" X; i+ I
  37. }
复制代码

: i2 P: [" G4 c4 s  u9 Y- u; Q
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数
    , j' u+ ]2 g# k# b1 V3 p2 ]* q
  2. void W25QXX_Init(void){
    3 e8 k  E9 o: o( x. i
  3.   W25Qx_Disable();" o$ a& {6 x7 r
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID1 f5 k$ y# |7 v$ p% \8 C6 l/ h
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);: [3 E7 h2 t2 c8 V+ g
  6.   if(W25QXX_TYPE == 0xc816)7 T' M! x$ S. d5 q$ ~
  7.     printf("FLASH TYPE:W25Q64\r\n");
    2 W  n  g3 K4 Z3 d1 b- u
  8. }
    " [5 {# w6 y- w: M; Q9 _
  9. ; X, Z! O6 l, P- `
  10. uint16_t W25QXX_ReadID(void){5 B" W3 z  m1 g1 e/ l9 S) a% j2 }$ i
  11.   uint16_t ID;. F" @' ?. Y" C5 L# M8 |! b# V& z
  12.   uint8_t id[2]={0};0 h6 b" u+ Y0 K& j! F) H
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令        3 I9 d! T% {: V( v
  14.   W25Qx_Enable();//使能器件
    4 s( C/ b6 b6 u) ?& j: n
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);
    3 C( F* z$ G) m. s  P$ _+ {; x% D2 S
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);8 C9 c. X0 g) W& M2 {6 [
  17.   W25Qx_Disable();//取消片选        0 f1 l( ]7 t6 \, m
  18.   ID = (((uint16_t)id[0])<<8)|id[1];4 {7 {* s+ K# G
  19.   return ID;
    ) s9 u1 q0 A0 U
  20. }
    3 R, M0 d) `6 Q5 D8 b
  21. 8 f0 B+ c/ W  J6 U5 X
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
    4 J/ g7 h) f3 |
  23.   uint8_t cmd[4] = {0};/ r! I* L  Y- R, V5 o! C
  24.   cmd[0] = W25X_ReadData;//读取命令% L4 f$ Z! F/ I" e
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));
    $ d' j# q( t1 z
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));
      A; K1 X  X# F; y+ X1 m/ ?4 [
  27.   cmd[3] = ((uint8_t)ReadAddr);
    # c/ M/ {( v  C2 N

  28. ( D9 |/ c- H. j) E3 K8 O1 m
  29.   W25Qx_Enable();//使能器件/ X' d; x) g- L+ i1 J' r- T, u4 w: b
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);, J; H+ `# ?  e4 G- E! r1 Z
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
    6 c; b0 K! s( k
  32.   W25Qx_Disable();//取消片选       
    % H" Q: D+ @$ ]# h# R9 p
  33. }
      h! a! R* j  G7 V
  34. 1 x  L* z2 D5 f' t, D6 Z
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
    + W0 E& y- x* ]/ @8 m2 D. B
  36.   uint32_t secpos;$ g. _- ^2 o9 M' ~
  37.   uint16_t secoff;3 s; }( x" L# T: X8 T( R6 \1 m8 s
  38.   uint16_t secremain;* _2 o7 {+ u+ b7 \
  39.   uint16_t i;, F& ~4 s0 e( d! b  m5 W+ S
  40.   uint8_t *W25QXX_BUF;
    + d1 m8 a! t6 Y9 w
  41.         3 P2 L- c* U, ^2 N4 s7 q
  42.   W25QXX_BUF = W25QXX_BUFFER;" w! l1 K; r* z
  43.   secpos = WriteAddr/4096;        //扇区地址  l" O$ K! M& ?( y7 S5 Y
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移# V/ R' z. u  P. A* I5 \6 ]8 ]
  45.   secremain = 4096-secoff;        //扇区剩余空间大小2 t( m# H3 [. B
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
    ! I* d  A0 r/ k- ?$ u" T
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节
    7 g9 A0 Q6 s; @; d" l
  48.     secremain = NumByteToWrite;# N4 f: \, e2 s. `2 V. X
  49.   while(1){. w% h$ I6 i# X6 G$ v7 F8 r
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
    " L: b1 `' T1 L0 k5 D5 F# [0 w$ c
  51.     for(i=0;i<secremain;i++){//校验数据$ [# `! c! j3 a# v4 X8 ^
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除
    1 B9 b6 f6 P" s7 ]8 {7 [
  53.         break;+ Z4 c1 _+ [* T; M
  54.     }9 i' T! {: k- X* h
  55.                 + ^& r/ u( b6 o% i4 g$ h) }
  56.     if(i < secremain){//需要擦除# @7 ~  j$ ^" x6 w+ |
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区& Y8 l1 P) ]7 S9 T' u" J
  58.       for(i=0;i<secremain;i++){
    7 X- s  O; Y4 j8 G& p3 s
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];
    ' ^  s' |4 O- k% ]& \! q3 q. p
  60.       }
    9 w. P, N  Y2 m. K8 I. o
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
    / O3 k2 i- A6 t  v0 H
  62.     }
    8 b9 N6 b/ n& r2 _
  63.     else{- R$ j6 {& u) c& F  {) X" O, u
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
    + Q. O! T7 E: ^
  65.     }6 C2 f' B  D+ j  L9 K8 r0 [6 d, U: ?
  66.                 , A) G5 {! {! i+ v2 L
  67.     if(NumByteToWrite == secremain){//写入结束了7 a2 L7 G+ \8 O; o  N* T. q
  68.       break;
    8 @6 P7 R0 {7 _, ]" \0 N" k* H1 }) @
  69.     }! g8 V. d+ ^8 ^. C; o
  70.     else{        //写入未结束9 F7 P* W/ ]0 T' f. A/ a/ j
  71.       secpos++;        //扇区地址增1- d: [0 F( L, E7 P0 k8 A
  72.       secoff = 0;                //偏移位置为0                + U, H; ~+ R+ g9 Q* L7 [0 N1 }
  73.       pBuffer += secremain;        //指针偏移
    8 N% h2 M' @" ?) Y+ s+ A+ V
  74.       WriteAddr += secremain;        //写地址偏移
    6 d7 e) {1 M  a/ ~' h; O! _" K* N: S
  75.       NumByteToWrite -= secremain;//字节数递减
    : U5 ^2 O" S0 `- U+ U
  76.       if(NumByteToWrite > 4096)               
    . V7 g, s7 I9 N
  77.         secremain = 4096;        //下个扇区还没是写不完
    " E: P0 a7 V8 T( R" `9 Q
  78.       else
    . ^, i# P( S: `9 I
  79.         secremain = NumByteToWrite;//下个扇区可以写完了
    $ |$ M+ |  N/ v( m8 j# G: L
  80.     }3 q6 h7 s9 Z( V# ~/ A3 F# V
  81.   }4 \) U. d7 j' m) c7 o( t
  82. }
复制代码

. ?+ L! q. ^- D  q' I
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */
    7 R4 F# o- t* |6 s2 g
  2. uint8_t wData[0x100];
    7 t1 _0 {. n' P$ F* ?* I
  3. uint8_t rData[0x100];
    : }# k( h: ]( p+ I9 p
  4. uint32_t i;
    4 `4 j; Y) v( y' G: _
  5. /* USER CODE END PV */! i" l2 p, J# P- f0 N
  6. int main(void){4 ~! `, V. v  }2 `6 f
  7.   /* USER CODE BEGIN 1 */
    3 G1 ]# y9 ~+ W' J+ h2 n, d
  8.   uint8_t key;
    6 ?' S1 _* X) S
  9.   /* USER CODE END 1 */
    : K' h2 w9 J/ s$ Q7 x$ q
  10.   HAL_Init();
    $ O& F# Y7 H( P2 c, a1 i& j
  11.   SystemClock_Config();, J+ l* N- e. E( _
  12.   MX_GPIO_Init();2 Y' N8 Y; B1 N% s  d
  13.   MX_SPI2_Init();$ K: z. k1 i& p: _
  14.   MX_USART1_UART_Init();: [4 O3 r8 _7 p
  15.   /* USER CODE BEGIN 2 */; Q" e1 i8 T: b' \
  16.   W25QXX_Init();
    % v, C4 N9 Y% `/ Z1 A" e& r
  17.   for(i=0;i<0x100;i++){, D" s% }9 A  B7 {
  18.     wData[i] = i;4 H' E# E+ }3 C7 ~: `3 D
  19.     rData[i] = 0;
    % w5 }7 d% }* e* |. c* O; o
  20.   }        3 o% @2 k  ~2 O3 B" Y) c  @! J; a* b
  21.   /* USER CODE END 2 */( |) y9 S# k, s9 D& j
  22.   while (1){8 z% k# t/ h, I/ f0 `
  23.     key = KEY_Scan(0);. a3 z! z: U+ ~! Q; ^' s' e# [( M
  24.     if(key == KEY_UP_PRES){. M9 {+ j; d6 _) N: {1 ]+ W( @
  25.       printf("KEY_UP_PRES write data...\r\n");
      B" N5 c" B: e( v2 X! p
  26.       W25QXX_Erase_Sector(0);
    2 F4 E7 [& {; ?- ~* [
  27.       W25QXX_Write(wData,0,256);                $ N0 M5 B( p* J; g5 K4 g/ R# p
  28.     }2 F* \) a; w% G0 l& E
  29.                         + L" {  k. }  C3 ]0 A
  30.     if(key == KEY_DOWN_PRES){3 l- ?8 \6 b+ ~8 g# r
  31.       printf("KEY_DOWN_PRES read data...\r\n");( T" ?4 K4 s0 P. w$ p" Q
  32.       W25QXX_Read(rData,0,256);4 m; N7 z, e) K- ~5 ?
  33.       for(i=0;i<256;i++){) @* r8 R0 U: R# H: Y
  34.         printf("0x%02X ",rData[i]);  X6 \4 b5 M) e+ m: M
  35.       }, y3 [  J9 X* ~7 c) o
  36.     }+ F! e4 r4 c; ?6 e8 U0 ?
  37.                 7 X1 y3 g$ U* u+ z/ \0 D0 w5 c
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);- _) |* I9 z- ]7 @0 S0 p8 i
  39.     HAL_Delay(200);$ e* c  g% D" G6 K( w
  40.   }
    - D6 K9 j7 c4 x. |
  41. }
复制代码

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

6 l2 \5 M- s: Q7 R$ U5 e1 g! c: P 微信图片_20230701185521.png
- b6 }1 M1 b( X- t+ K5 [4 f
( r2 Y& y" t) g; S* u/ j7 J8 h; d转载自: 嵌入式攻城狮% m' B# J$ m* z3 p3 T, n- X# ]
如有侵权请联系删除8 z! ]4 m; e  ]9 r6 y' F; ^

) u4 W9 \  e$ o8 d0 m
% i! F# }, k5 H! u$ x4 e* v7 m
, U$ A4 G/ E2 i$ w
收藏 评论0 发布时间:2023-7-1 18:56

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版