请选择 进入手机版 | 继续访问电脑版

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

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

, Y2 I1 Q  ^  @6 i
微信图片_20230701185542.png
. I0 a7 f, H  i& A
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
    1 e, F5 m( c8 r2 o
  • MISO:主设备数据输入,从设备数据输出
  • MOSI:主设备数据输出,从设备数据输入
  • SCLK:时钟信号,由主设备产生
    7 k1 S# O/ }9 H( ^. E
  • CS:从设备片选信号,由主设备控制

    / N0 h. w) f' t( \6 A6 t' ^
    0 w0 l7 k6 q  P6 @+ p: [3 G
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
: A& O. n" S+ E! |3 y当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。. f' P! i% m6 @- x& w1 L
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
    $ M, Z4 I5 f7 O( P" m8 m
  • CPOL=0:串行同步时钟的空闲状态为低电平
  • CPOL=1:串行同步时钟的空闲状态为高电平
  • CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样

    ! O3 Y* s4 h6 x4 H, ?8 D& w. H& C
  • CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样

    " ]# G* O8 _9 @7 _

    . |  l8 H1 G' ?
微信图片_20230701185539.png

3 `7 |* J$ y+ a1 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的常用命令表
7 c- b! I* }4 c0 A' z5 N
微信图片_20230701185536.png
; }9 @2 r' s- l* M
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
    3 x. r3 F, D6 @5 |6 X
  • 指示灯D1
  • USART1串口
  • W25Q64

    ; ?. d" z$ l$ c9 L. _, u
  • K_UP和K_DOWN按键
    9 m. ~% }0 t+ V" Q7 }1 Y
    8 p4 e/ B( T/ c7 q
    4 ?0 C# ~" w! g" X* Z) ~! i
微信图片_20230701185531.png

% e; ]% J! W2 T& S3 f
+ L/ E  d+ Q( l" J3 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
& a3 P3 }! x' u3 L' t

& B+ I8 s% y) z6 l: D7 z* I
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

- u" f2 C$ J2 O. b& y- R- x9 A
3.2 MDK-ARM软件编程
➡️ 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中

+ K. E( y  I& y' r
  1. void MX_SPI2_Init(void){
    . I! P, O; I8 o) S/ T
  2.   hspi2.Instance = SPI2;- G/ S& N  x. C6 h! v% B
  3.   hspi2.Init.Mode = SPI_MODE_MASTER;//设置为主模式
    . t; m, l% x6 S9 o: I
  4.   hspi2.Init.Direction = SPI_DIRECTION_2LINES;//双线模式
    2 D' d9 A5 @& Y9 G$ C, G
  5.   hspi2.Init.DataSize = SPI_DATASIZE_8BIT;//8位数据长度8 I0 _+ K% M; j2 K
  6.   hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;//串行同步时钟空闲状态为高电平
    9 r: S( @  J/ o
  7.   hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;//第二个跳变沿采样
    2 m, N( l# \9 W' S5 D
  8.   hspi2.Init.NSS = SPI_NSS_SOFT;//NSS软件控制6 u: D+ h3 J. @4 R
  9.   hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;//分配因子256
    5 o7 s0 f3 s: Q/ E+ a$ j+ g, l  A
  10.   hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;//MSB先行
    9 r* b: @) R0 u7 j8 q+ y
  11.   hspi2.Init.TIMode = SPI_TIMODE_DISABLE;//关闭TI模式0 w6 @4 Q) ]" `
  12.   hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验' S) L: k; z& t. H
  13.   hspi2.Init.CRCPolynomial = 10;
    0 G: |% i& \) H# c$ o: d2 p) W
  14.   if (HAL_SPI_Init(&hspi2) != HAL_OK){
    ! U% O8 a* u3 @" w+ k0 d
  15.     Error_Handler();
    ' `' ]8 _8 C9 S" v3 f
  16.   }
    / p, a" l# H# g6 r0 i( Y
  17. }
    $ K- D. p; X  `* S, A$ @6 a. a
  18. 0 @) n/ a/ Z- ~" `
  19. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){% T' g" s6 ?: U/ V; M" X3 i* p1 }
  20.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    ' e8 Z. y9 O# s$ O" H/ T) ^5 s
  21.   if(spiHandle->Instance==SPI2){0 n$ g( J, ?3 i6 N  T8 Y, u7 s/ @
  22.   __HAL_RCC_SPI2_CLK_ENABLE();  7 b6 r& P7 Y( [
  23.   __HAL_RCC_GPIOB_CLK_ENABLE();( T; |6 P) R8 m/ v* ]  ?
  24.   /**SPI2 GPIO Configuration    5 ?, s, ~- t; b. v& e
  25.   PB13     ------> SPI2_SCK
    4 r+ g/ {5 A8 P. D; x, G
  26.   PB14     ------> SPI2_MISO
    ) B6 n+ A8 a) L' B
  27.   PB15     ------> SPI2_MOSI */- C0 g$ V: l+ h' O* m
  28.   GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;" c' z8 v& ^( x# V( ]0 H$ p8 U
  29.   GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;9 P# c1 s( b2 J) m( v, E3 z7 b
  30.   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    + K% y# D; E$ u9 T; Y
  31.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);& |" ^# y2 B# k6 _
  32.   GPIO_InitStruct.Pin = GPIO_PIN_14;
    * u: K. A2 c7 [/ R" C7 {
  33.   GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    8 f) e+ K4 T) X2 A  ]" j- l. W
  34.   GPIO_InitStruct.Pull = GPIO_NOPULL;
    3 }% t. D3 w7 o% A4 i$ x7 l8 A
  35.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);" v: i, g0 j& C
  36.   }
    ( d; l2 `6 x% i4 ^8 A+ Y* S
  37. }
复制代码
. v- u3 K( W) c: P- R1 N
➡️ 创建按键驱动文件key.c 和相关头文件key.h
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
  1. //这里仅介绍几个重要的函数, L& C0 C3 j) I
  2. void W25QXX_Init(void){
    4 O" N0 y5 P5 h: w  D7 p
  3.   W25Qx_Disable();
    : {1 G  q' R# @
  4.   W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID1 a2 X6 ?; m9 _1 y; {$ D2 c
  5.   printf("FLASH ID:%X\r\n",W25QXX_TYPE);! `3 S3 \' r* x) Y/ X4 e) _8 u
  6.   if(W25QXX_TYPE == 0xc816)
    8 F2 T) e! P/ L3 I
  7.     printf("FLASH TYPE:W25Q64\r\n");- s4 e; G$ t; P& T6 @
  8. }
    5 l) T6 Y# C! m& i
  9. + j, P( G# z$ d( S- S4 m0 s. `
  10. uint16_t W25QXX_ReadID(void){- _  y5 I9 F) _1 ]5 l* s
  11.   uint16_t ID;
    : m" E& D+ }  `3 l1 x5 P
  12.   uint8_t id[2]={0};
    2 F, i- I& w3 o$ ?* |# h
  13.   uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令       
    * N# W" I9 f2 F% `; c
  14.   W25Qx_Enable();//使能器件' t# n9 j4 u; W1 ^
  15.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);. \( z0 C% P% C. r. k  U3 U2 [
  16.   HAL_SPI_Receive(&hspi2,id,2,1000);
    2 ^7 E; {) Q* D  `6 S) U# w- l
  17.   W25Qx_Disable();//取消片选       
    4 C4 B2 z7 m* W1 k* h
  18.   ID = (((uint16_t)id[0])<<8)|id[1];. S( Q' D+ N/ }8 q# v9 j6 E
  19.   return ID;
    + {$ R1 w2 T# D! T* m/ o
  20. }
    : N, j# @/ @2 j% U1 v( b
  21. % b! R! H" R8 _. P7 T( B( ~
  22. void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){
    & g! |9 ^4 r8 X* r
  23.   uint8_t cmd[4] = {0};
    , f# d  O3 |& }. H" L/ i! Y
  24.   cmd[0] = W25X_ReadData;//读取命令+ J+ t/ v8 o7 H
  25.   cmd[1] = ((uint8_t)(ReadAddr>>16));2 ]: o5 N) p5 }. s" v' B' C  N
  26.   cmd[2] = ((uint8_t)(ReadAddr>>8));
    / u" E4 N6 B3 D
  27.   cmd[3] = ((uint8_t)ReadAddr);6 }7 |# U9 A$ B
  28. ' {# W. p: P  p$ d5 X, c
  29.   W25Qx_Enable();//使能器件& w  K( y1 i! u! O
  30.   HAL_SPI_Transmit(&hspi2,cmd,4,1000);2 K# ^6 U( u9 W. e  ]$ P4 x. t. e( `
  31.   HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);8 }, X( V: B5 Y4 l
  32.   W25Qx_Disable();//取消片选        3 _3 ~) s' X4 K5 r5 \
  33. }# @, w# I. ]! k& j: [3 E- }9 {, y" H
  34. 7 D# [3 b/ ]' ~% M2 m1 d& a8 l8 d/ y
  35. void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){
    1 B* u- V% ]2 K5 _9 Z* }
  36.   uint32_t secpos;. _3 S+ I7 j- K
  37.   uint16_t secoff;
    # A" [2 G7 U5 f. Y( w- f; R# r! p' s
  38.   uint16_t secremain;
    # y0 d% q  l2 g& V
  39.   uint16_t i;
    6 c7 p% }, z/ v1 z: I$ W
  40.   uint8_t *W25QXX_BUF;7 X; G9 |) }, L
  41.         0 |/ b+ J7 N7 e3 h6 U# e/ k
  42.   W25QXX_BUF = W25QXX_BUFFER;& }1 r8 Y+ v4 H' X2 O* m! S' f) l+ l
  43.   secpos = WriteAddr/4096;        //扇区地址7 l) W1 }- y  q
  44.   secpos = WriteAddr%4096;        //在扇区里的偏移* f; o' c! f' O* A& T
  45.   secremain = 4096-secoff;        //扇区剩余空间大小
    $ i$ g- z! i% U: H0 m1 \" B& ^
  46.   printf("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);
    - U7 v2 v3 t$ l7 [6 u2 p
  47.   if(NumByteToWrite <= secremain)  //不大于4K字节8 N) n- s2 d6 _9 s9 ?
  48.     secremain = NumByteToWrite;( M2 L* v. ~) Z/ {% l3 p
  49.   while(1){
    # f' m6 A: n! M' S* ]$ e8 N
  50.     W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
    5 B0 d+ S( s7 g4 X
  51.     for(i=0;i<secremain;i++){//校验数据
    4 N7 Z% G  ~9 t. R- H* O: Z8 E4 {
  52.       if(W25QXX_BUF[secoff+i] != 0xff)//需要擦除* G% u% y* M4 r( g
  53.         break;  y- v. a4 {# i- U+ \
  54.     }
    2 X6 h" P3 E5 \2 N+ z
  55.                 % E+ V& I! n2 a1 G! i
  56.     if(i < secremain){//需要擦除4 S% \. w/ W+ E, H" a7 [
  57.       W25QXX_Erase_Sector(secpos);//擦除扇区- Q, R7 _, q: ^
  58.       for(i=0;i<secremain;i++){  L! p  G4 E$ C: K# c& @
  59.         W25QXX_BUF[i+secoff] = pBuffer[i];
    : F. ~% W% r1 p; G6 M2 U
  60.       }
    ' A6 W, K' }8 ?2 G
  61.       W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区
    6 c9 X0 q6 T. E" Y
  62.     }
    $ P: V8 Y, J# v3 s  ~) H1 m
  63.     else{  ]- l7 ^/ ?" k( d% |+ S
  64.       W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间
    + f8 E) r2 C3 j- O* ~# z9 P( X
  65.     }. W4 r$ d: J* Y% R, y, {& _5 j
  66.                   G+ F' U8 T- @7 Y* q
  67.     if(NumByteToWrite == secremain){//写入结束了
    5 u" e; d# D  k" b% f% p- N* f* z. `/ _
  68.       break;
    - b. F' h4 t: ~% C! k& i" U7 X
  69.     }
    . D  E! A* u2 T% O. L% j% \
  70.     else{        //写入未结束
    1 }& B; W2 C# H" L% T$ ^- Y
  71.       secpos++;        //扇区地址增1
    + @8 Y3 i, k! Z1 h+ h+ s/ q; e" _
  72.       secoff = 0;                //偏移位置为0                : N5 ?4 P% X( f$ t1 Y3 c
  73.       pBuffer += secremain;        //指针偏移
      r- l- ?- g  j2 F6 ]# s$ d
  74.       WriteAddr += secremain;        //写地址偏移- T3 a6 [8 W( I8 y
  75.       NumByteToWrite -= secremain;//字节数递减
    ( Z) B9 @( Q1 S( u' q
  76.       if(NumByteToWrite > 4096)               
    5 W8 y' R- L" E# e
  77.         secremain = 4096;        //下个扇区还没是写不完# M" g" o; l% f: r0 e
  78.       else
    , |# h* S( ?8 u; h
  79.         secremain = NumByteToWrite;//下个扇区可以写完了* }9 T, A, I8 i3 k4 m4 G
  80.     }8 c% A3 @# \( e% P( G. l
  81.   }
    6 X4 S% w# J) W6 X3 w
  82. }
复制代码

$ g6 c! g4 C- R  e1 p; R
➡️ 在main.c文件下编写SPI测试代码
  1. /* USER CODE BEGIN PV */
    % Z/ e* r# b$ ]+ r, W  ~
  2. uint8_t wData[0x100];
    ! m' n1 o- K) G
  3. uint8_t rData[0x100];
    2 K6 H5 }- n6 S% i( M* Q
  4. uint32_t i;
    ' o7 R4 _& U& |8 k
  5. /* USER CODE END PV */
    & `! U) L2 Z1 B8 h6 i: q
  6. int main(void){
    $ Y  l5 G/ l- t
  7.   /* USER CODE BEGIN 1 */
    3 G+ c. P' M+ Y3 f9 }& B8 d9 {# y  W
  8.   uint8_t key;2 p" ^" p9 \! U2 O
  9.   /* USER CODE END 1 */
    " ^" J$ q$ G9 z) W/ K0 y
  10.   HAL_Init();
    3 s7 ?  s9 Q2 N" @/ t
  11.   SystemClock_Config();
    , G+ t9 _, _* B
  12.   MX_GPIO_Init();
    : Q* m3 L+ ]) g( R7 H1 J
  13.   MX_SPI2_Init();9 v0 m& w* m4 ]* e$ _
  14.   MX_USART1_UART_Init();5 @  `7 c2 |- e9 D
  15.   /* USER CODE BEGIN 2 */
    ) D7 P' L0 ?# u$ y" z
  16.   W25QXX_Init();
    0 }% |; J: T5 s! |; `+ T$ _
  17.   for(i=0;i<0x100;i++){( m9 n! y- @5 a9 ~
  18.     wData[i] = i;
    " e0 W8 @: l8 H8 Y& ~5 _; Q/ B5 |2 E
  19.     rData[i] = 0;
    ; T+ Q8 j# a2 g
  20.   }        + Q7 F1 [! ?( b
  21.   /* USER CODE END 2 */$ y3 r: z7 }2 D. F& N# g
  22.   while (1){
    7 _4 j4 ~7 V" A& N8 \
  23.     key = KEY_Scan(0);
    ) k5 R: O. D' x& v
  24.     if(key == KEY_UP_PRES){
    # U+ l+ x5 \: [' S1 u
  25.       printf("KEY_UP_PRES write data...\r\n");
    ! z6 p/ t* l1 l4 t5 y% f* j/ ~! r
  26.       W25QXX_Erase_Sector(0);6 A8 u6 v3 Y" J: z
  27.       W25QXX_Write(wData,0,256);                6 q6 }5 l, u& S. J4 H4 h7 M
  28.     }# N2 @" r9 Y* c: K8 R7 D$ x/ }% Z
  29.                         + x' _8 z9 z7 |- S
  30.     if(key == KEY_DOWN_PRES){9 T5 r; M& U8 z: y0 t
  31.       printf("KEY_DOWN_PRES read data...\r\n");
    # p# {6 E4 Q! I' ~- u
  32.       W25QXX_Read(rData,0,256);
    2 a" y; \! o* V
  33.       for(i=0;i<256;i++){9 I& g% w3 ~' A- E" y5 P4 J: _
  34.         printf("0x%02X ",rData[i]);
    % m! @* K8 s  g. O2 e" u5 B
  35.       }
    ( Q! M% i5 N; x
  36.     }9 z- J8 Y; w1 ~. Y% l4 x/ K- x
  37.                 2 [5 L2 U' e( @# B5 {6 J: z, o( {
  38.     HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);; Q  a& Q* {: n$ I% E
  39.     HAL_Delay(200);  p( x- }$ K4 ?6 e! [
  40.   }
    $ I4 p0 T; N" h4 T. m  b0 O) |& l% h
  41. }
复制代码

& c! h4 A% q# S. g: ~! C0 D6 h
4.下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息
. Q5 J/ d8 {! ^! x! F" {
微信图片_20230701185521.png
2 f$ x& e, X- m/ Y4 f8 |
* h2 {. W: s/ r) t/ S3 }! H) n. K5 q转载自: 嵌入式攻城狮
% n& y3 @7 M6 y( K( o如有侵权请联系删除  Z2 T( X. S8 {) g+ y# o7 U( C; p

$ `& w3 X# P; [
9 F% G- c" q" _1 f& k
, i( m4 B2 r: {  o: J
收藏 评论0 发布时间:2023-7-1 18:56

举报

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