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

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

[复制链接]
STMCU小助手 发布时间:2022-2-10 21:34
文档目的:
学习STM32F4的SPI接口和FLASH存储芯片W25Q16,通过对初始化代码分析,更好的了解STM32中SPI接口编程中的一些关键知识点。
6 _6 R$ m9 s0 w0 b  B
学习素材:
l  W25Q16数据手册
l  电路原理图
l  STM32F4应用手册
针对W25Q16的SPI接口初始化代码
+ Y$ E9 _. Z- `
) A9 ~7 ~+ k5 \/ Q3 x4 F8 c
4 C# T6 q) L' b2 y+ a
1 硬件电路说明
(1)=关键原理图
" ?: [6 F9 o3 x% m1 N: A# A7 d
FLASH芯片接口图
- o9 }! W. Q) j/ L% ]4 Z
I5E69S_9V@F7BQ@0H]IXX`T.png

" p4 Q4 \" @$ S' y3 Z% G7 b$ H
GPIO接口图
{3LFE}L%ES[N3L0$B89D(BC.png

& [+ O5 E6 g: K0 k% t- v
(2)   原理图说明
首先我们看到上面第一个图,第一个图展示了W25Q16的原理图信息,我们配合W25Q16的数据手册(第六页 见下图),我们分析具体IO的连接和功能,分析结果见下表
`P}TBKW{T%LWDLCHN0`W_MJ.png
% |4 m8 O9 y1 f$ {9 E+ R

( s/ n- @5 ^0 E( S) V
  
管脚号
  
名称
连接的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的复用方法,在上表中已经给出。

+ {' K1 q0 ]3 W
(3)   编程需求
通过上表的分析,我们可以得出如下编程需求:
l  PB3(CLK),PB4(MOSI),PB5(MISO)初始化为复用功能(可以使用复用SPI接口或者SPI3接口,因为SPI1和SPI3控制器的相关引脚都在这三个IO上,见GPIO接口图)
l  PB0初始化为普通IO,该IO的功能是在操作SPI接口的时候,拉低该IO,操作结束以后,拉高该IO。
l  初始化配置SPI控制器,该控制器的初始化要配合W25Q16芯片手册进行。
7 P% h: J) q# q5 M3 }5 K" v

! F" @4 m/ w4 x% ?, ]: q' q; `1 B" F( R
2 初始化程序分析(1)   代码展示
% w: ^; n0 D* w& d6 r6 cvoid SPI1_Init(void)$ Z  ^" k( o( T, J# b+ B7 s/ i4 z8 B! d
{                 
8 M7 J& S4 x7 m$ p/ M  \GPIO_InitTypeDef  GPIO_InitStructure;         
8 n# E4 }: W% w) ISPI_InitTypeDef  SPI_InitStructure;         
' H3 J7 K% m& `0 W. v, dRCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟        2 s8 |; \1 k4 R8 b8 q( X
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟         
& y) k2 i; @* B) I5 f6 B1 R5 C//GPIOFB3,4,5初始化设置         
0 R" D; G2 I+ M: G) F( K' S3 FGPIO_InitStructure.GPIO_Pin= GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;        
2 Q% e$ @, y9 JGPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能        7 y  W' c5 \* Q/ `9 L
GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         ' J8 d8 m, ~1 N- {
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz        % ^3 b7 [  Y2 Q4 t7 e
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉         
& u% v- i3 z8 i1 M9 g1 Y( BGPIO_Init(GPIOB,&GPIO_InitStructure);//初始化        ! p5 `- F7 Y# u6 U, e- I
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3复用为 SPI1         
; p# l. e8 l2 a8 |4 y, ]GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为 SPI1        
* G: b" l0 ~' ?2 S7 ^7 @& I9 AGPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1              
8 D- W' L! G. i7 U' ~" z! @RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1        
/ {# K) u7 W2 v" e$ {( T  PRCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1         4 W5 K& @" k3 |. d2 I0 k
SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;           ; w6 l& X* g* G7 x7 v2 N# p
SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI         9 c" [( a( ~4 z/ |
SPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          
* W8 H: ^! o+ {SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          
+ M5 \# F# s, m3 L: `  m( MSPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;   //串行同步时钟的第二个跳变沿(上升或下降)数据被采样         8 I2 H( F' A" [1 l* j$ v
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值计算的多项式         
7 `( T4 ~6 ~7 N( E9 nSPI_Init(SPI1,&SPI_InitStructure);        
4 X+ S( b( q6 L7 f! |& LSPI_Cmd(SPI1, ENABLE); //使能SPI外设         + O, m& M: \, _6 _' A. h
SPI1_ReadWriteByte(0xff);//启动传输            
! N+ ~4 a. x  M3 }; Q3 P( ^' {}     
- c- C! {+ R! m/ k  Z0 W/ G% n7 d2 l/ y0 W* N2 y
void W25QXX_Init(void){     6 J: K1 k* i* n6 j: ]7 h
GPIO_InitTypeDef  GPIO_InitStructure;   
9 |& j9 d, C2 Y* d+ }8 l2 p9 d, MRCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟
6 _, i7 v0 W! p" I- N//GPIOB0    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;//PB0   # T& w" u% ~" X7 P8 t
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//输出   
3 k9 C, W& _4 ^) K0 C$ MGPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽输出   
3 ^! ]  R  _6 A4 q' MGPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz   2 }4 Y2 R) `* |* m
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉   , T2 Y8 t+ ^- D) ~  M
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化         
: p3 B9 E; n9 rW25QXX_CS=1;                           //SPI FLASH不选中        $ d2 D3 L/ u7 b1 V1 j( d
SPI1_Init();                                         //初始化SPI         " ?, _( Q/ s4 V0 q. i
SPI1_SetSpeed(SPI_BaudRatePrescaler_2);             //设置为42M时钟,高速模式         8 W4 R; ^; g$ h5 F5 n( S, D
W25QXX_TYPE=W25QXX_ReadID();        //读取FLASH ID.
! _" j0 K1 s) u7 s; k; _$ ~% ?, v}  9 L( ?) m2 j/ i) d0 U1 r% S1 ], Y$ t7 w3 P
(2)   代码分析1.  初始化代码总体分析由展示的代码,我们可以看出,对W25Q16的初始化主要使用了两个函数,主要调用函数void W25QXX_Init(void)对W25Q16进行初始化,而该函数又调用了SPI1_Init()函数对SPI1接口进行初始化。代码主要流程图如下: HP}@27X9@QD5A7XM6KLU_A8.png
  ~2 }" _4 S( q/ n- o9 N2 U. V, `0 k3 z# Y% ]
通过以上流程图,我们可以看出对W25Q16的初始化,主要是初始化SPI接口。下面对代码具体说明。
5 X% y! _& z* F7 D# C2.  代码详细分析(我们主要分析SPI1接口的初始化函数)我们先分析函数voidSPI1_Init(void),对该函数里面的没一行代码进行详细说明l  GPIO_InitTypeDef GPIO_InitStructure;( O# L# F! K8 T4 _
SPI_InitTypeDef  SPI_InitStructure;
: ]2 x0 i: Y# Q6 s代码分析:首先定义了两个结构体变量,这两个结构体的类型定义在GPIO和SPI的库函数中,主要作为GPIO和SPI初始化调用的参数,我们一会详细分析。
0 m. D2 t/ T4 @l  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
" |; v+ A) C  jRCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟: ?4 O' L! g3 B( K' V3 A0 }  f
代码分析:STM32有着强大且细分的时钟系统,我们可以单独的对每一个控制器的时钟进行使能,这样可以更好的降低功耗。因此,这两句分别使能GPIOB和SPI1的时钟。关于时钟库函数的说明,请看我的《STM32库函数说明及示例-RCC篇》
+ u8 a) D# `$ U  E& q0 }( yl  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
+ U4 |' v# m1 F' nGPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能         ' A  @" ~' ^1 N2 {
GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
: n# ]; O2 ?( e) Q% |+ CGPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz         
" U' n( I  G3 z+ A; K* a) u+ P: R  JGPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB,&GPIO_InitStructure);
) _- `6 n  m) d6 |% x0 ]
//初始化代码分析:这几行代码主要是初始化结构体参数GPIO_InitStructure,该参数指定了GPIO的一些具体的配置,注意第二行代码,我们把PB3,PB4和PB5设置成复用功能。第三行我们采用推挽输出,可以得到更大的驱动能力。第四行我们把IO的最大翻转速度配置成100MHZ,因为考虑PB3作为SPI的时钟口,最高的频率可达42MHZ。其它关于GPIO库函数GPIO_Init(GPIOB, &GPIO_InitStructure)的详细解释,请见我的《STM32库函数说明及示例-GPIO篇》。
. c' w5 c+ d( f
l  GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1         GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为! U1 ]7 u  Y3 [& r+ m* _3 O1 W0 l
SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1
$ P, y: C+ a: Z/ h+ ~+ A
代码分析:以上三行实现管脚复用功能的具体配置,我们分别把PB3,PB4和PB5配置成SPI1接口的复用功能。关于该函数的详细说明,请见我的《STM32库函数说明及示例-GPIO篇》。
1 t& z; D0 ]$ ~9 |8 g6 L
l  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);        , q! g; _- B" |! Y% `) s" V
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);         对SPI1接口的时钟进行复位,以便进行SPI接口的初始化操作l  SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;           : w; ?0 f) F+ S- ?/ G7 {4 }
SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI        - ~' I" Y3 K: A& s
SPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          
9 ~( [2 z! s: g% ~SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          ; r  H' [+ `: m# ^- i9 A4 ~
SPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;            ! D/ `8 |/ v' h1 ^. s. P
SPI_InitStructure.SPI_NSS= SPI_NSS_Soft;                       
5 J* C5 b& g. Q1 ]' M1 z+ i7 mSPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256;                           
$ w/ J8 R: r! W9 J  MSPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;                  0 A: ~6 b, B) A
SPI_InitStructure.SPI_CRCPolynomial= 7;      //CRC值计算的多项式         
" c* b' p$ n2 r% ~" g+ aSPI_Init(SPI1,&SPI_InitStructure);   
   & u# h0 w( `: W6 \2 Z3 M; F& q
代码分析:这一段代码是SPI接口初始化的关键代码,在该代码中,对结构体变量SPI_InitStructure里面的成员变量进行了初始化。以下分别进行说明:3 U! N8 s7 q3 l" i' h7 k3 R7 w
Ø  第一行使用宏SPI_Direction_2Lines_FullDuplex将SPI接口配置成双线全双工模式,因为硬件接口使用了输入和输出两个IO,所以应该是双线模式,而且W25Q16的数据是在收发两个方向同时传输,所以是全双工模式。
) n0 u6 W4 p  x" p  P, B# W3 yØ  第二行设置STM32的SPI接口工作在主模式(SPI_Mode_Master)。因为读写数据的请求都是由STM32主动发出,所以是主设备。这里,大家要搞清楚主从设备的区别,通信的发起方就是主设备。  [$ V# Z0 t( }- r) V6 u
Ø  第三行设置数据长度为8位(SPI_DataSize_8b),因为W25Q16的最小传输数据单位(或者说每个地址对应的数据是1个字节)是1个字节。1个字节是8位,所以选择8位。* r% o/ ^) G4 C$ B3 N
Ø  第四行和第五行分别设置极性和相位为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时序图

  f7 m- T; ~! A1 v6 M! d
EFS4H%C)ZCAY~S5GEM)6B@V.png
0 l5 ]5 `& R3 ]! s+ W$ n! S8 ~图2

" g3 X$ s2 @- l0 H# W8 `6 BØ  第六行设置SPI_NSS信号,由软件产生。因为我们没有使用硬件NSS信号,所以选择由软件产生NSS信号。大家注意我们的CS管脚是连接到普通IO上面的,因此没有使用硬件NSS。4 B3 s+ k& V; Z  A9 l6 m! L
Ø  第七行配置SPI时钟的分频值为256(SPI_BaudRatePrescaler_256),因此实际的时钟值是APB2的时钟/256 Ø  第八行配置SPI的第一位是最高有效位(SPI_FirstBit_MSB)。这个也是和W25Q16相关的。请见下图红色箭头部分。7 s8 x4 p% p. C  d  K1 N- Y
%IH74PDFQ}Z7`EPZE0Q6XT5.png # H$ ]; M5 ~" R0 w. Z  k
      
% _3 Y! }3 h3 R0 JØ  第九行配置SPI的CRC校验多项式为7
6 o3 w7 b$ f- ~' |; XØ  第十行调用SPI_Init(SPI1, &SPI_InitStructure);对SPI1接口进行初始化。
7 b: X, j, K- ~4 z" }8 Ql  SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输  6 C1 d! N! j/ d
以上第一行使能SPI接口,第二行使用SPI接口写入一个0XFF,以启动数据的传输。相当于启动数据发送器。

6 j9 w: H7 T$ R8 d  Q+ f5 [8 ?
赞 收藏 评论0 发布时间:2022-2-10 21:34

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版