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

【经验分享】STM32接口篇之SPI接口和W25Q16初始化详解

[复制链接]
STMCU小助手 发布时间:2022-2-10 21:34
文档目的:
学习STM32F4的SPI接口和FLASH存储芯片W25Q16,通过对初始化代码分析,更好的了解STM32中SPI接口编程中的一些关键知识点。
7 P- M' n$ c1 w3 @7 U( G
学习素材:
l  W25Q16数据手册
l  电路原理图
l  STM32F4应用手册
针对W25Q16的SPI接口初始化代码* I% n0 b% `+ b8 F

. H# i2 g: S9 @% e/ l8 d) Q2 O" M1 z' R8 C- v
1 硬件电路说明
(1)=关键原理图
' d, N, `8 ?+ m. J- y! I
FLASH芯片接口图

  W# d! V5 l0 U8 f, ?' D1 y
I5E69S_9V@F7BQ@0H]IXX`T.png

% V- D3 G; M) c" a5 @4 _( C
GPIO接口图
{3LFE}L%ES[N3L0$B89D(BC.png
% [0 [5 s5 `+ L0 k/ p5 J
(2)   原理图说明
首先我们看到上面第一个图,第一个图展示了W25Q16的原理图信息,我们配合W25Q16的数据手册(第六页 见下图),我们分析具体IO的连接和功能,分析结果见下表
`P}TBKW{T%LWDLCHN0`W_MJ.png

) F$ ]+ r: U* A
# T& E4 i1 m% L! m
  
管脚号
  
名称
连接的io或者信号
功能
  
1
  
CS
PB0(不复用)
片选信号 该信号低有效  即当该信号为低电平的时候对W25Q16的操作是有效的
  
2
  
DO
PB4(复用SPI1_MOSI或者SPI3_MOSI
数据输出信号
  
3
  
WP
3.3V
写保护输入 当该信号为低电平是开启写保护  无法写入数据 从图中我们可以看出 这个管脚直接接在高电平3.3V上面 始终不开启写保护(不过做一个上拉应该会更好)
  
5
  
DI
PB5(复用SPI1_MISO或者SPI3_MISO
数据输入信号
  
6
  
CLK
PB3(复用SPI1_CLK或者SPI3_CLK)
时钟信号
注意:配合(1)中的GPIO接口图,我们可以得到具体IO的复用方法,在上表中已经给出。
# e9 u4 B* V( `
(3)   编程需求
通过上表的分析,我们可以得出如下编程需求:
l  PB3(CLK),PB4(MOSI),PB5(MISO)初始化为复用功能(可以使用复用SPI接口或者SPI3接口,因为SPI1和SPI3控制器的相关引脚都在这三个IO上,见GPIO接口图)
l  PB0初始化为普通IO,该IO的功能是在操作SPI接口的时候,拉低该IO,操作结束以后,拉高该IO。
l  初始化配置SPI控制器,该控制器的初始化要配合W25Q16芯片手册进行。

$ N! S5 Q: ?* |: Z# e0 _( f+ H! E% {6 M' J5 X6 q8 L4 B
2 初始化程序分析(1)   代码展示
6 [: Q, @: [- V8 t, v" j7 i0 gvoid SPI1_Init(void)* i* }8 ?7 k7 C" H
{                 * R  G1 D) F2 E. v
GPIO_InitTypeDef  GPIO_InitStructure;         ' L. I* T. Z- ~1 [0 r! y7 X
SPI_InitTypeDef  SPI_InitStructure;         
. p" f! ~- j* b, P! \; O' pRCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟        
: R; \- s/ I2 j/ q( ARCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟         9 ?9 P5 c) b; b
//GPIOFB3,4,5初始化设置         " k# ]& j8 V' z4 ?3 b
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;        6 E* R0 }1 M8 a# j' O0 y
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能        & v6 U% _& _% i
GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         9 v6 k3 `& Y9 }' a& y
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz        
7 s+ o7 p& z4 ]5 g* ZGPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉         
. M' a7 w1 B! F  kGPIO_Init(GPIOB,&GPIO_InitStructure);//初始化        
8 K2 y' J5 o7 R$ ?GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3复用为 SPI1         $ C- M+ h9 g2 U4 m) l  Y5 R
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为 SPI1        
& z. o, B( e7 M; z' D, r7 ?GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1              0 N' i5 P& i) u2 X) l6 l
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1        1 V7 Y, T$ ~3 X# {( o  [
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1         
9 [  k" Z8 @6 f" Y( Y. C2 bSPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;           : _9 q) z5 [7 o) l+ m" s2 K- i
SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI         . K) Z4 F0 y3 D
SPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          2 N6 V0 T2 K7 @/ j+ S( s7 ^& j
SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          
  R% U' I/ o& ~( [# ]& N6 LSPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;   //串行同步时钟的第二个跳变沿(上升或下降)数据被采样         % m" O. C3 D, d1 J# w3 z' ^9 m
SPI_InitStructure.SPI_NSS= SPI_NSS_Soft;               //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制         SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256;            //定义波特率预分频的值:波特率预分频值为256         SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;         //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始         SPI_InitStructure.SPI_CRCPolynomial= 7;      //CRC值计算的多项式         
6 K4 B1 v! P9 T8 ?  r* P  G6 nSPI_Init(SPI1,&SPI_InitStructure);        " w) Z* c5 I0 Q% n, D* e/ q) f
SPI_Cmd(SPI1, ENABLE); //使能SPI外设         4 s0 s' `6 c& l( L$ v# l8 W
SPI1_ReadWriteByte(0xff);//启动传输            7 h( C; t6 u6 G4 l# A& _/ k2 |. ~
}     
* Y- \: O$ p# O4 Q$ w7 W& Z# }* v: J
3 z) A' U! s) `3 r9 {4 Cvoid W25QXX_Init(void){     2 r0 A) o; X  g) V$ C6 S
GPIO_InitTypeDef  GPIO_InitStructure;   
) @" `. e" E4 Q/ K9 MRCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟
/ n3 a5 L, U. M//GPIOB0    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;//PB0   5 s! J" U* e5 D' j$ S
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//输出   1 O8 V  s7 P2 M8 S! P4 h3 m* q5 p& a4 ?
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽输出   ' l# p( \* \% x" H& p) l% Z
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz   
5 Z; c/ c: ~5 D5 `$ ~+ G1 BGPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉   
- p. @1 e2 ^' y; OGPIO_Init(GPIOB, &GPIO_InitStructure);//初始化         
1 p9 d# f0 O  q8 GW25QXX_CS=1;                           //SPI FLASH不选中        
8 Q) Q% ~0 {/ \# ~+ f9 @3 TSPI1_Init();                                         //初始化SPI         " h0 r  z, Y8 o4 o4 r
SPI1_SetSpeed(SPI_BaudRatePrescaler_2);             //设置为42M时钟,高速模式         2 |' N8 j" `2 \) L5 `6 p
W25QXX_TYPE=W25QXX_ReadID();        //读取FLASH ID.9 a) f3 b$ z# n) J% P6 t. K9 M
}  
' I0 K; k6 z0 |1 ?& p! o(2)   代码分析1.  初始化代码总体分析由展示的代码,我们可以看出,对W25Q16的初始化主要使用了两个函数,主要调用函数void W25QXX_Init(void)对W25Q16进行初始化,而该函数又调用了SPI1_Init()函数对SPI1接口进行初始化。代码主要流程图如下: HP}@27X9@QD5A7XM6KLU_A8.png
1 v. j9 [1 e& Q* M: r; @1 G9 g2 D. g- ?; F& P5 m
通过以上流程图,我们可以看出对W25Q16的初始化,主要是初始化SPI接口。下面对代码具体说明。
* d% x0 d" Q! L* Y( y! u& D2.  代码详细分析(我们主要分析SPI1接口的初始化函数)我们先分析函数voidSPI1_Init(void),对该函数里面的没一行代码进行详细说明l  GPIO_InitTypeDef GPIO_InitStructure;
% m! q, I! d' HSPI_InitTypeDef  SPI_InitStructure;, x6 w  N5 y7 t: z# B
代码分析:首先定义了两个结构体变量,这两个结构体的类型定义在GPIO和SPI的库函数中,主要作为GPIO和SPI初始化调用的参数,我们一会详细分析。/ V# k, Y. E# x2 \# l& M
l  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟3 P' I0 b( M' Z* T4 u
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟
; L! R' I1 @' N6 Y; A" ?! ]. h代码分析:STM32有着强大且细分的时钟系统,我们可以单独的对每一个控制器的时钟进行使能,这样可以更好的降低功耗。因此,这两句分别使能GPIOB和SPI1的时钟。关于时钟库函数的说明,请看我的《STM32库函数说明及示例-RCC篇》' f! \6 n5 W. \4 ~
l  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;; W* X' @* }1 W* u/ h2 x
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能         
4 S! \/ _& x9 E6 p' v  i! hGPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
# k. f) U9 p% ^3 PGPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz         
5 _5 V+ a9 \* @$ d) Z  tGPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB,&GPIO_InitStructure);
, u1 H- _. w. `( y0 g3 {3 y  E
//初始化代码分析:这几行代码主要是初始化结构体参数GPIO_InitStructure,该参数指定了GPIO的一些具体的配置,注意第二行代码,我们把PB3,PB4和PB5设置成复用功能。第三行我们采用推挽输出,可以得到更大的驱动能力。第四行我们把IO的最大翻转速度配置成100MHZ,因为考虑PB3作为SPI的时钟口,最高的频率可达42MHZ。其它关于GPIO库函数GPIO_Init(GPIOB, &GPIO_InitStructure)的详细解释,请见我的《STM32库函数说明及示例-GPIO篇》。

- Z" {) S+ ]1 Ql  GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1         GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为4 ^" H% F9 z" q+ K8 p- A
SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1

1 K& q5 s1 S9 l  _8 b代码分析:以上三行实现管脚复用功能的具体配置,我们分别把PB3,PB4和PB5配置成SPI1接口的复用功能。关于该函数的详细说明,请见我的《STM32库函数说明及示例-GPIO篇》。
3 ]" h3 A5 k/ c, Y, L5 }+ j! O
l  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);        $ D' z2 N4 i" a- y- c- |! x. r- \9 u
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);         对SPI1接口的时钟进行复位,以便进行SPI接口的初始化操作l  SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;           $ R4 M  I. N$ v4 Q$ W
SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI        
: P* d2 j! k0 j. X8 n( b. aSPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          . `7 ]: G1 T/ |5 u: ^( ]+ V
SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          
# y9 Z( D( L8 r- K4 }- `% NSPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;            
" p4 o9 Z6 z$ U0 xSPI_InitStructure.SPI_NSS= SPI_NSS_Soft;                       8 Y2 W4 f" B' z
SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256;                           8 d' I- d$ t* X9 k, S! n
SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;                  / n6 Y/ L+ \. [& l8 d& }  l- j
SPI_InitStructure.SPI_CRCPolynomial= 7;      //CRC值计算的多项式         
! [  r  b/ K% V5 g& q, G8 wSPI_Init(SPI1,&SPI_InitStructure);   
   ) m7 N) ?( j6 w# w4 n/ k4 h
代码分析:这一段代码是SPI接口初始化的关键代码,在该代码中,对结构体变量SPI_InitStructure里面的成员变量进行了初始化。以下分别进行说明:
2 n- y' H; d+ b" t( ?0 mØ  第一行使用宏SPI_Direction_2Lines_FullDuplex将SPI接口配置成双线全双工模式,因为硬件接口使用了输入和输出两个IO,所以应该是双线模式,而且W25Q16的数据是在收发两个方向同时传输,所以是全双工模式。
4 u- P) P$ e. Q$ t) DØ  第二行设置STM32的SPI接口工作在主模式(SPI_Mode_Master)。因为读写数据的请求都是由STM32主动发出,所以是主设备。这里,大家要搞清楚主从设备的区别,通信的发起方就是主设备。- F- T7 @1 J" P) |7 J% @
Ø  第三行设置数据长度为8位(SPI_DataSize_8b),因为W25Q16的最小传输数据单位(或者说每个地址对应的数据是1个字节)是1个字节。1个字节是8位,所以选择8位。
: e: ~4 ?) d+ X  Y5 x( n9 P" VØ  第四行和第五行分别设置极性和相位为SPI_CPOL_High和SPI_CPHA_2Edge。SPI_CPOL_High表示时钟的初始电平位高电平,SPI_CPHA_2Edge表示在第二个边沿进行数据采样(见下图1)。这样的组合是SPI数据传输模式中的模式3。根据W25Q16的数据手册,W25Q16支持MODE0(MODE0是SPI的初识电平为低电平,在第一个边沿进行数据采样配置组合是SPI_CPOL_Low,SPI_CPHA_1Edge)和MODE3。请见图2黄色部分。因此这里我们选择MODE3,当然选择MODE0也是可以的。
3JMMP)P46M%1OA8G)0WFC9T.png
图1:SPI MODE3时序图

' F" \8 l) [  R! p7 S' ~0 V+ W
EFS4H%C)ZCAY~S5GEM)6B@V.png
, D4 {9 L/ @/ l$ W  ?) z0 ^图2

. @9 O7 \1 y0 }; ~2 y9 mØ  第六行设置SPI_NSS信号,由软件产生。因为我们没有使用硬件NSS信号,所以选择由软件产生NSS信号。大家注意我们的CS管脚是连接到普通IO上面的,因此没有使用硬件NSS。( }0 D8 V, C' b1 \' |
Ø  第七行配置SPI时钟的分频值为256(SPI_BaudRatePrescaler_256),因此实际的时钟值是APB2的时钟/256 Ø  第八行配置SPI的第一位是最高有效位(SPI_FirstBit_MSB)。这个也是和W25Q16相关的。请见下图红色箭头部分。/ L( @" @$ N; ]6 X+ v& O  ~
%IH74PDFQ}Z7`EPZE0Q6XT5.png " N; |' n  g# s
      
/ A  a$ j8 o( T! {. oØ  第九行配置SPI的CRC校验多项式为7
0 {" {+ H3 A+ d. Y: h; ^6 @Ø  第十行调用SPI_Init(SPI1, &SPI_InitStructure);对SPI1接口进行初始化。
! {& S9 {% s0 Y8 yl  SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输  9 r' d- c# ?: S$ w  n! O
以上第一行使能SPI接口,第二行使用SPI接口写入一个0XFF,以启动数据的传输。相当于启动数据发送器。

9 c7 {! a' L$ s$ h0 r% E
收藏 评论0 发布时间:2022-2-10 21:34

举报

0个回答

所属标签

相似分享

官网相关资源

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