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

STM32接口篇之SPI接口和W25Q16初始化详解  

[复制链接]
bb123 发布时间:2016-8-25 15:48
7 i2 Q- n" f9 y; I2 s

& G+ R3 g" K5 w
STM32接口篇之SPI接口
                                                        ---SPI接口操作FLASH存储芯片W25Q16初始化代码详细分析8 K, m+ e$ U) F# D/ S

2 W- }3 k8 @) s% I, V我的其它两篇帖子请参考:  @8 u( ?9 Y& l! X
STM32库函数注释翻译,每个函数使用说明及使用示例7 l8 {; R4 R8 j  w6 t$ i; D
STM32库函数翻译,每个函数说明及示例-RCC(时钟)篇(上)# {! v7 K/ U- C8 t% p% @% c5 p5 g
$ H8 x  u% i; T

7 E" [0 Q2 |$ G文档目的:
学习STM32F4的SPI接口和FLASH存储芯片W25Q16,通过对初始化代码分析,更好的了解STM32中SPI接口编程中的一些关键知识点。
  s$ f$ H- h: y. X1 P
学习素材:
l  W25Q16数据手册
l  电路原理图
l  STM32F4应用手册
针对W25Q16的SPI接口初始化代码
5 h; c+ j3 A5 q* i. V
' v* m: }' G. I7 ]4 }( I# n- y! p3 j/ a- a" m$ y
1 硬件电路说明
(1)=关键原理图

% s5 r* B" t6 _
FLASH芯片接口图

1 A; L+ R' J* Q
1.jpg
3 w1 \$ p5 _0 A. T! j
" E! p- F2 C: e
GPIO接口图
2.jpg
5 K1 Z4 E2 E$ o2 J! I4 a. `" O8 C
3.jpg
+ |9 A% a- e9 {# H! C+ M8 h! t' q1 q1 f
(2)   原理图说明
首先我们看到上面第一个图,第一个图展示了W25Q16的原理图信息,我们配合W25Q16的数据手册(第六页 见下图),我们分析具体IO的连接和功能,分析结果见下表
4.jpg
5.jpg 1 b3 m' }/ h' k& g9 H- M

6 D" C1 @& y4 J6 u; @( o, t9 n
  
管脚号
  
名称
连接的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的复用方法,在上表中已经给出。
8 X& R1 F4 _) R
(3)   编程需求
通过上表的分析,我们可以得出如下编程需求:
l  PB3(CLK),PB4(MOSI),PB5(MISO)初始化为复用功能(可以使用复用SPI接口或者SPI3接口,因为SPI1和SPI3控制器的相关引脚都在这三个IO上,见GPIO接口图)
l  PB0初始化为普通IO,该IO的功能是在操作SPI接口的时候,拉低该IO,操作结束以后,拉高该IO。
l  初始化配置SPI控制器,该控制器的初始化要配合W25Q16芯片手册进行。
: C% X8 e7 M: g, {

% x" z- L( |, p% p. j
2 初始化程序分析(1)   代码展示" n. @: w% ~( J6 ^" t; k3 k0 D
void SPI1_Init(void), b. X0 Y4 ?1 d+ g) C; i. t- s
{                 & L* A6 t' K: g/ ~& h/ {' ^9 u
GPIO_InitTypeDef  GPIO_InitStructure;         % n* _0 S- z5 J0 c) S1 V/ j
SPI_InitTypeDef  SPI_InitStructure;         
2 h6 @! w5 I/ N9 N: v- @/ }2 |RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟        
/ O6 r4 a1 O" Y  ]+ O0 ~; } RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟         , [9 A3 ?3 d$ r" t
//GPIOFB3,4,5初始化设置         
7 q5 R$ D+ _$ G5 Y+ \# MGPIO_InitStructure.GPIO_Pin= GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;        + t+ Y2 F' [- o! B( i
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能        ) u2 Z- _  ?- N3 ?% a2 i$ I
GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
# c% x. r) K/ S4 ~+ eGPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz        5 R' f- R9 [" F5 C! e
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉         
' }9 R) L; n9 |, N- K) i: HGPIO_Init(GPIOB,&GPIO_InitStructure);//初始化        
2 z: W* @# d5 e: f GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3复用为 SPI1         / y" g' P% y) I. c: g
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为 SPI1        
3 t3 L' W! k. t& Q2 D3 T$ \1 J) j GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1              
1 D% V& O+ s; E% b0 O0 | RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1        " K4 }! S' M2 N/ e" K$ a5 s
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1         
6 s/ C- S7 `; D/ ]SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;           
% Q: V+ M  T% y7 n2 {, GSPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI         
' V! z% c0 D1 p) K5 k) SSPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          ) b9 i' B& B- {9 I# z0 b) q
SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          ' ], C( V; O/ T2 |6 x8 d. O
SPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;   //串行同步时钟的第二个跳变沿(上升或下降)数据被采样         
: r0 v  D$ L2 {+ @# D1 m' }6 lSPI_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值计算的多项式         
* U5 l' f' Z9 PSPI_Init(SPI1,&SPI_InitStructure);        
! @" g& i' O( w- xSPI_Cmd(SPI1, ENABLE); //使能SPI外设         
* T* K6 R7 G6 @/ T$ bSPI1_ReadWriteByte(0xff);//启动传输             ( H+ h* h2 A# y5 d7 h- X# M4 ?: K. O
}     ! W; j, R- e& L
* c1 `# J* L4 ^7 s# g0 K- {+ Y! P
void W25QXX_Init(void){     " D2 _5 l8 P* T9 B2 n3 W, I8 Y
GPIO_InitTypeDef  GPIO_InitStructure;   
  o  J+ L. Z9 ~! M1 bRCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟
) Q. \8 w; t% q4 J//GPIOB0    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;//PB0   
$ X. K/ r2 m6 p5 {2 p( u" u! S: \GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//输出   
% n5 n4 J6 a  E' m! N% E9 PGPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽输出   
1 c3 ?( x: p# [; _8 g" [GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz    " P3 O, N7 u" D6 \  X
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉   2 z3 G, Z% g, H: P
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化         9 v" Q: z& ]( e
W25QXX_CS=1;                           //SPI FLASH不选中        ; h7 x  ]8 P, M# x5 t8 P! B
SPI1_Init();                                         //初始化SPI         
: m; Q( I: C$ K' KSPI1_SetSpeed(SPI_BaudRatePrescaler_2);             //设置为42M时钟,高速模式         + \6 ~: v% T) _: d; y2 x$ V2 D
W25QXX_TYPE=W25QXX_ReadID();        //读取FLASH ID.
* O2 d+ t, b3 z" X" S0 m* X$ _}  9 [1 T* E, {* g! g
(2)   代码分析1.  初始化代码总体分析由展示的代码,我们可以看出,对W25Q16的初始化主要使用了两个函数,主要调用函数void W25QXX_Init(void)对W25Q16进行初始化,而该函数又调用了SPI1_Init()函数对SPI1接口进行初始化。代码主要流程图如下: 6.jpg
4 [9 `0 r4 y7 d: s
4 C; o% L8 i( x通过以上流程图,我们可以看出对W25Q16的初始化,主要是初始化SPI接口。下面对代码具体说明。
1 x1 f  q$ y( A3 W$ w1 k2.  代码详细分析(我们主要分析SPI1接口的初始化函数)我们先分析函数voidSPI1_Init(void),对该函数里面的没一行代码进行详细说明l  GPIO_InitTypeDef GPIO_InitStructure;
8 l+ w( z" D+ p3 |+ oSPI_InitTypeDef  SPI_InitStructure;/ g: C5 |' b% ^! e! ~
代码分析:首先定义了两个结构体变量,这两个结构体的类型定义在GPIO和SPI的库函数中,主要作为GPIO和SPI初始化调用的参数,我们一会详细分析。4 `  C: X5 j& \$ H9 J  |
l  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟3 t0 H. q1 M! D0 ^3 K
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟4 B5 {0 _8 i  I9 c
代码分析:STM32有着强大且细分的时钟系统,我们可以单独的对每一个控制器的时钟进行使能,这样可以更好的降低功耗。因此,这两句分别使能GPIOB和SPI1的时钟。关于时钟库函数的说明,请看我的《STM32库函数说明及示例-RCC篇》
2 r+ d* N; j( x: v/ k' m9 U. q2 al  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
$ k* L- X4 P: U' e) D& v+ aGPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能         
% ?" A8 p( ~. p: VGPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
/ O7 Q! \0 J3 V$ W" I$ p) HGPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz         
$ P  K8 H9 w  C2 B! ]1 dGPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB,&GPIO_InitStructure);

5 Z! a- I' y4 ]/ L; k/ k//初始化代码分析:这几行代码主要是初始化结构体参数GPIO_InitStructure,该参数指定了GPIO的一些具体的配置,注意第二行代码,我们把PB3,PB4和PB5设置成复用功能。第三行我们采用推挽输出,可以得到更大的驱动能力。第四行我们把IO的最大翻转速度配置成100MHZ,因为考虑PB3作为SPI的时钟口,最高的频率可达42MHZ。其它关于GPIO库函数GPIO_Init(GPIOB, &GPIO_InitStructure)的详细解释,请见我的《STM32库函数说明及示例-GPIO篇》。

8 l% M' s- S( [; |l  GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1         GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为. A" R5 Z$ }% ?
SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1

7 _3 J. v" U5 ~  I代码分析:以上三行实现管脚复用功能的具体配置,我们分别把PB3,PB4和PB5配置成SPI1接口的复用功能。关于该函数的详细说明,请见我的《STM32库函数说明及示例-GPIO篇》。
8 ?$ r- p* J8 C: f6 ?3 O
l  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);        
( C0 N: V4 ^8 I' Q% ~, w3 g RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);         对SPI1接口的时钟进行复位,以便进行SPI接口的初始化操作l  SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;           
2 A& D! w3 J' s& k/ k# g( f7 oSPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI        ' |8 n8 n4 ]8 p
SPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          # D9 G6 i  {6 w* b1 B8 L
SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          6 P: c* [& s* A$ b% h1 A6 {9 {6 ]/ v* j
SPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;            & v; M  y% n$ u, `
SPI_InitStructure.SPI_NSS= SPI_NSS_Soft;                       
8 H. S" y' Z) A; X1 S SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256;                           ) J" S! ]$ X4 G2 D7 g
SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;                  
/ ^$ c9 W! ^! o( kSPI_InitStructure.SPI_CRCPolynomial= 7;      //CRC值计算的多项式         
# ^6 |, W0 [' ?8 O8 J  CSPI_Init(SPI1,&SPI_InitStructure);   
   
) Q% j" G4 M: w. T' V4 E. T代码分析:这一段代码是SPI接口初始化的关键代码,在该代码中,对结构体变量SPI_InitStructure里面的成员变量进行了初始化。以下分别进行说明:; L6 N! s6 r. z+ ?6 A
Ø  第一行使用宏SPI_Direction_2Lines_FullDuplex将SPI接口配置成双线全双工模式,因为硬件接口使用了输入和输出两个IO,所以应该是双线模式,而且W25Q16的数据是在收发两个方向同时传输,所以是全双工模式。
, E0 U  j0 Q- i0 Y2 m8 [Ø  第二行设置STM32的SPI接口工作在主模式(SPI_Mode_Master)。因为读写数据的请求都是由STM32主动发出,所以是主设备。这里,大家要搞清楚主从设备的区别,通信的发起方就是主设备。+ q' ?5 C6 ?% v
Ø  第三行设置数据长度为8位(SPI_DataSize_8b),因为W25Q16的最小传输数据单位(或者说每个地址对应的数据是1个字节)是1个字节。1个字节是8位,所以选择8位。& V8 N. [- L5 U$ n  h% t* a5 W( b
Ø  第四行和第五行分别设置极性和相位为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也是可以的。
8.jpg
图1:SPI MODE3时序图
) k! o# A; \8 f8 G1 b" q* Y
7.jpg . ^; t" a5 p9 g+ X5 z
图2

% b; a' Y) M6 U1 ^1 D$ b8 vØ  第六行设置SPI_NSS信号,由软件产生。因为我们没有使用硬件NSS信号,所以选择由软件产生NSS信号。大家注意我们的CS管脚是连接到普通IO上面的,因此没有使用硬件NSS。
: @! ~+ [) [2 n9 k+ X& eØ  第七行配置SPI时钟的分频值为256(SPI_BaudRatePrescaler_256),因此实际的时钟值是APB2的时钟/256 Ø  第八行配置SPI的第一位是最高有效位(SPI_FirstBit_MSB)。这个也是和W25Q16相关的。请见下图红色箭头部分。8 q  ?) ^! w2 G
9.jpg
; `, ^7 y. k8 Z4 Y" s5 r, e4 g      5 l' M  D6 A, o$ H5 ~' q
Ø  第九行配置SPI的CRC校验多项式为71 o4 E: T9 l5 U9 s  L$ b
Ø  第十行调用SPI_Init(SPI1, &SPI_InitStructure);对SPI1接口进行初始化。
1 a% S7 q' k$ V. ~' S( bl  SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输  
4 M7 }8 N- K6 j. |. L: Z, E以上第一行使能SPI接口,第二行使用SPI接口写入一个0XFF,以启动数据的传输。相当于启动数据发送器。

4 c5 o7 c2 m) ]: @- `% |
& G, l; s( `4 `3 b: v0 t6 c' P2 h7 l* H8 B; W. R$ ?$ U6 _, f

基于STM32的SPI接口操作W25Q16初始化代码详细分析.pdf

下载

760.51 KB, 下载次数: 997

STM32-SPI-W25Q16

评分

参与人数 1 ST金币 +10 收起 理由
zero99 + 10

查看全部评分

赞 1 收藏 7 评论25 发布时间:2016-8-25 15:48

举报

25个回答
samyang13 回答时间:2019-3-4 20:19:29
我有个疑问哈,以上芯片资料上的截图中,写操作在时钟上升沿,读操作在时钟下降沿。楼主的配置中SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          
. k+ v6 i+ t! KSPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;   " Z5 L$ P6 ^- t3 w8 i+ x* u" Y
这个是上升沿有效,那么怎么同时保证读和写两个操作都可行的呢?
bb123 回答时间:2016-8-25 15:52:13
本帖最后由 bb123 于 2016-8-25 15:53 编辑 : |; d" T8 o3 @8 c  {9 _+ D
! R" @2 E4 v# Q1 h& o: d
发新贴了 SPI接口和W25Q16 请大家一起学习分享 有什么问题请及时提出
+ p) X. o8 o; u* u我的其它两篇帖子请参考5 A$ I! v7 [% ~7 [; E
STM32库函数注释翻译,每个函数使用说明及使用示例/ Z' W! k- e0 `1 @, D
https://www.stmcu.org.cn/module/forum/thread-607584-1-1.html
' ]4 Z  T( R' r& g- f% W5 ?STM32库函数翻译,每个函数说明及示例-RCC(时钟)篇(上)& ~! j  ^- I* s3 P: v/ Q
https://www.stmcu.org.cn/module/forum/thread-607608-1-2.html) J' B9 {, ]; d2 i" [4 L
& p( ?7 C: m, N/ w; ?
youyouxiaojin86 回答时间:2019-9-4 13:21:05
以前用STM25P32没出现过问题,现在改用W25Q32,居然出问题了,偶尔初上电时读数据错误。再等闲学习下。
beebird 回答时间:2016-8-26 23:03:58
好文章,正准备学STM32的SPI的使用
disheng4688 回答时间:2016-8-27 08:58:56
谢谢分享。
kunchen 回答时间:2017-2-6 09:25:16
谢谢分享
yimao111 回答时间:2017-5-16 10:55:21
楼主,你的2.5 引脚的MOSI和MISO 弄反了吧
yimao111 回答时间:2017-5-16 10:56:26
楼主,你的2.5 引脚的MOSI和MISO 弄反了吧
lanmanck 回答时间:2017-5-22 11:39:18
yimao111 发表于 2017-5-16 10:55
8 b4 G5 Y! ^5 o9 E0 |楼主,你的2.5 引脚的MOSI和MISO 弄反了吧
9 T" T  g- v( \2 A* f, ~7 N
他用io模拟,你管得着!
langtaosha 回答时间:2017-6-10 10:45:30
多谢楼主,辛苦了
艾汇思 回答时间:2017-6-14 10:22:59
楼主,您把整个源代码发出来给我们学习好不好?谢谢了!
samhong 回答时间:2017-7-28 20:41:03
楼主,艾汇思朋友,晚上好!建议个人努力把代码努出来,发给楼主点评,相互交流,相互进步!!我们一起来...
361741352 回答时间:2017-8-31 10:57:19
楼主为什么我用模式0,就不能读啦呢。
lelefj 回答时间:2018-3-25 20:07:31
只能回一个下一个了、
王希瑞 回答时间:2018-4-10 15:58:34
yimao111 发表于 2017-5-16 10:55/ R+ @+ Y# L! O& b
楼主,你的2.5 引脚的MOSI和MISO 弄反了吧
7 ?" f! I7 }7 _& {. I0 x( P
没有反,是正确的
, m( k8 T& R( H9 {$ H2 W& t$ i
星光月 回答时间:2018-4-11 11:34:30
谢谢分享,资料很有用
6 t% L* Z% F8 n( W7 |1 K
12下一页

所属标签

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