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

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

[复制链接]
bb123 发布时间:2016-8-25 15:48

# {  R! @. L2 D3 ]( z% o

% G! \5 y" ~& W' r, ^( {+ y; j
STM32接口篇之SPI接口
                                                        ---SPI接口操作FLASH存储芯片W25Q16初始化代码详细分析$ |! D( h1 |$ f& s4 h. D% v
$ s3 z5 Z, p1 e- K) A
我的其它两篇帖子请参考:2 G9 J) A" p. {: m( Q3 I9 @
STM32库函数注释翻译,每个函数使用说明及使用示例% U; f: l  M; p" x1 ]: _- ]2 n) a
STM32库函数翻译,每个函数说明及示例-RCC(时钟)篇(上)) B: O$ X4 ?: a2 c7 y0 K
$ A" K7 ^- Q7 Z$ X8 n! o3 j
8 @% ~, s; U; i& b' j' p% i  Q
文档目的:
学习STM32F4的SPI接口和FLASH存储芯片W25Q16,通过对初始化代码分析,更好的了解STM32中SPI接口编程中的一些关键知识点。

, _! m. U- i- W
学习素材:
l  W25Q16数据手册
l  电路原理图
l  STM32F4应用手册
针对W25Q16的SPI接口初始化代码, Q' M# L: G+ i& h4 b4 S1 n8 Z

7 ?  ^% w& h/ A& t; @3 S/ i% r1 I' T; a6 _
1 硬件电路说明
(1)=关键原理图

+ D# b/ @# W! D; b$ i) D; r: O
FLASH芯片接口图

1 m! M$ l% f. ~
1.jpg
+ W, w& a9 U* N  U

( U, R0 o  @0 g7 ~6 U* ]
GPIO接口图
2.jpg
% e2 p& m. w9 E& I, O- N) E* I
3.jpg 0 h% K' J1 x6 I
(2)   原理图说明
首先我们看到上面第一个图,第一个图展示了W25Q16的原理图信息,我们配合W25Q16的数据手册(第六页 见下图),我们分析具体IO的连接和功能,分析结果见下表
4.jpg
5.jpg
" T0 v- t5 @4 C  A2 q+ r1 X& U- U0 ^
/ ~* X( w* [) s! E8 Q9 {
  
管脚号
  
名称
连接的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的复用方法,在上表中已经给出。
" |( b" w( s4 O# P
(3)   编程需求
通过上表的分析,我们可以得出如下编程需求:
l  PB3(CLK),PB4(MOSI),PB5(MISO)初始化为复用功能(可以使用复用SPI接口或者SPI3接口,因为SPI1和SPI3控制器的相关引脚都在这三个IO上,见GPIO接口图)
l  PB0初始化为普通IO,该IO的功能是在操作SPI接口的时候,拉低该IO,操作结束以后,拉高该IO。
l  初始化配置SPI控制器,该控制器的初始化要配合W25Q16芯片手册进行。
) U5 k' f$ V) g, M
( v! b+ \1 m8 k2 b1 H# i
2 初始化程序分析(1)   代码展示8 p% x8 b9 u* M) b- [4 O' g( \
void SPI1_Init(void)! W5 `6 t# P1 {- `0 p3 l
{                 & o3 D7 a0 l+ ]9 w) y0 C4 j1 o. W
GPIO_InitTypeDef  GPIO_InitStructure;         
7 \' a  o$ C4 D0 O( ?SPI_InitTypeDef  SPI_InitStructure;         / I, T1 f% f4 y: M
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟        
9 t3 _! Q; E6 G RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟         
4 u4 G: l: g) N# a8 `+ F//GPIOFB3,4,5初始化设置         
0 F5 g* ~4 l; o8 |; P4 T! [GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;        & q1 V( _" ^5 ^3 @+ U4 I2 r7 P
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能        
( w; Y. k- W! ~2 F  X% y; W6 d" I" G GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
# Y/ M9 T6 _: \0 S) v/ RGPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz        9 n+ o. g; y$ B0 [
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉         
" F6 U( _/ n- eGPIO_Init(GPIOB,&GPIO_InitStructure);//初始化        
5 t: ?: \# _. F: u! O" } GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3复用为 SPI1         " q7 b7 R' b  `
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为 SPI1        
$ c, q" g) V; r6 Z2 X GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1              
. P1 t* l- G" ^5 Y RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1        
+ k- q1 [9 J6 K6 V; u( Z7 w1 m RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1         ) U, G% M0 n+ h
SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;           - \* w; v' y0 Q7 w( |
SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI         , ]$ j* k' I& r$ ]; ?
SPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          
+ z2 L$ ~) F4 xSPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          
/ G0 o, `/ f  TSPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;   //串行同步时钟的第二个跳变沿(上升或下降)数据被采样         * I7 L! a0 m0 O# B7 C  h( T
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值计算的多项式         
5 [9 C8 G7 n* `! f: C, I1 zSPI_Init(SPI1,&SPI_InitStructure);        : ]# _: ?/ \9 |8 ]8 C
SPI_Cmd(SPI1, ENABLE); //使能SPI外设         - U  l4 K  {* m% s; q8 c6 T7 _' A
SPI1_ReadWriteByte(0xff);//启动传输             # u# a# C' K7 `' I1 h. m
}     
% ^6 |; e. u/ K* {" L
/ j6 `; y3 {0 B. x+ T# evoid W25QXX_Init(void){     ! z2 l* K4 V# F7 R2 Y# W
GPIO_InitTypeDef  GPIO_InitStructure;   
7 E& L$ {( G. k. p) H0 Q% qRCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟+ V9 P# P  @, I4 e! \& _# D! u( h
//GPIOB0    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;//PB0    , m! t* ?4 n3 Z  p$ V8 q2 V
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//输出    1 J* w  _- n7 n' V
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽输出   
! B9 H6 J" E7 |: d+ S* o. BGPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz   
9 P1 J" }* a8 s/ H& g/ v9 U9 wGPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉   $ d) a5 Z/ R  c& X; k* k1 m2 {
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化         
% t. u1 a) P- b! z. bW25QXX_CS=1;                           //SPI FLASH不选中          Q, R. @" Q' d8 Z
SPI1_Init();                                         //初始化SPI         $ A$ V9 s2 g7 u0 ]. R  q9 ^
SPI1_SetSpeed(SPI_BaudRatePrescaler_2);             //设置为42M时钟,高速模式         
  `! w. V: d3 @% |: L& n W25QXX_TYPE=W25QXX_ReadID();        //读取FLASH ID.. h9 q8 \2 J# q
}  - ?3 o+ u# L# M0 o& V  X3 W% ]4 p
(2)   代码分析1.  初始化代码总体分析由展示的代码,我们可以看出,对W25Q16的初始化主要使用了两个函数,主要调用函数void W25QXX_Init(void)对W25Q16进行初始化,而该函数又调用了SPI1_Init()函数对SPI1接口进行初始化。代码主要流程图如下: 6.jpg
4 F/ Q- W, N- b& X9 q# b
5 \4 Z1 x, Y5 L8 O' B( `/ @8 d通过以上流程图,我们可以看出对W25Q16的初始化,主要是初始化SPI接口。下面对代码具体说明。 / J  C' S2 Z% [2 e" l) w+ A+ y
2.  代码详细分析(我们主要分析SPI1接口的初始化函数)我们先分析函数voidSPI1_Init(void),对该函数里面的没一行代码进行详细说明l  GPIO_InitTypeDef GPIO_InitStructure;
& V6 g/ R# P5 o( i3 O7 zSPI_InitTypeDef  SPI_InitStructure;& ~6 v( f& u% W+ c8 i% v$ Y4 W! ~
代码分析:首先定义了两个结构体变量,这两个结构体的类型定义在GPIO和SPI的库函数中,主要作为GPIO和SPI初始化调用的参数,我们一会详细分析。
% i* v  R! ?! Vl  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟- J9 ]- M# @3 I3 z3 \
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟+ l0 e" R5 b+ k8 {& i5 k  Z0 e1 g; R) h
代码分析:STM32有着强大且细分的时钟系统,我们可以单独的对每一个控制器的时钟进行使能,这样可以更好的降低功耗。因此,这两句分别使能GPIOB和SPI1的时钟。关于时钟库函数的说明,请看我的《STM32库函数说明及示例-RCC篇》
, N$ ?. f" L) i0 ^6 M( S3 g9 Wl  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;( h, B2 A7 S: q, B9 S* R) G
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能         
' X  D) v! L0 i  ]3 VGPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         ! i6 }8 \# Y  _1 \
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz         ( I- d: E, ~: L3 y
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB,&GPIO_InitStructure);
0 ]! Z) @1 `# `
//初始化代码分析:这几行代码主要是初始化结构体参数GPIO_InitStructure,该参数指定了GPIO的一些具体的配置,注意第二行代码,我们把PB3,PB4和PB5设置成复用功能。第三行我们采用推挽输出,可以得到更大的驱动能力。第四行我们把IO的最大翻转速度配置成100MHZ,因为考虑PB3作为SPI的时钟口,最高的频率可达42MHZ。其它关于GPIO库函数GPIO_Init(GPIOB, &GPIO_InitStructure)的详细解释,请见我的《STM32库函数说明及示例-GPIO篇》。
# _; ]' b# M4 B
l  GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1         GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为9 a: F# l- e2 d, t* i8 k
SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1
% l" [' ^$ Q( U6 z+ i# u
代码分析:以上三行实现管脚复用功能的具体配置,我们分别把PB3,PB4和PB5配置成SPI1接口的复用功能。关于该函数的详细说明,请见我的《STM32库函数说明及示例-GPIO篇》。

6 r- p4 D, o- `6 O) cl  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);        
% C) r6 A. k- Q; ~ RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);         对SPI1接口的时钟进行复位,以便进行SPI接口的初始化操作l  SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;           : y% i" L+ M+ _. R4 k
SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI        ( q+ n0 H7 {4 `  L) Z5 Z
SPI_InitStructure.SPI_DataSize= SPI_DataSize_8b;                          
5 J! U& W. A3 X/ o7 y4 ?SPI_InitStructure.SPI_CPOL= SPI_CPOL_High;                          
! S  V- V3 p5 F0 A# oSPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;            
% T" M  a+ H. Z& F- q4 pSPI_InitStructure.SPI_NSS= SPI_NSS_Soft;                       1 p- d* I9 Z, E3 y. w" Z! r% i( a
SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256;                           8 }, o  q" u1 M1 X! ]+ R4 A
SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;                  
) `- J9 i( ^- e8 J, O: nSPI_InitStructure.SPI_CRCPolynomial= 7;      //CRC值计算的多项式         
2 g, i! ^" I1 O5 A2 w% O" ZSPI_Init(SPI1,&SPI_InitStructure);   
   
6 @$ k; e& A0 H) B* K) e+ Q代码分析:这一段代码是SPI接口初始化的关键代码,在该代码中,对结构体变量SPI_InitStructure里面的成员变量进行了初始化。以下分别进行说明:
5 g7 M. z- m: KØ  第一行使用宏SPI_Direction_2Lines_FullDuplex将SPI接口配置成双线全双工模式,因为硬件接口使用了输入和输出两个IO,所以应该是双线模式,而且W25Q16的数据是在收发两个方向同时传输,所以是全双工模式。; L4 F3 c, O5 R% m
Ø  第二行设置STM32的SPI接口工作在主模式(SPI_Mode_Master)。因为读写数据的请求都是由STM32主动发出,所以是主设备。这里,大家要搞清楚主从设备的区别,通信的发起方就是主设备。
) c" @9 n4 }& e/ aØ  第三行设置数据长度为8位(SPI_DataSize_8b),因为W25Q16的最小传输数据单位(或者说每个地址对应的数据是1个字节)是1个字节。1个字节是8位,所以选择8位。3 _% Q% w# O- G9 [( O  K6 u
Ø  第四行和第五行分别设置极性和相位为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时序图

3 l- H' S$ i+ B( _
7.jpg
2 r5 l( N. h) s' p8 V图2
3 M4 V5 {0 V8 a$ L2 O+ a
Ø  第六行设置SPI_NSS信号,由软件产生。因为我们没有使用硬件NSS信号,所以选择由软件产生NSS信号。大家注意我们的CS管脚是连接到普通IO上面的,因此没有使用硬件NSS。
' E( ^1 p/ B  iØ  第七行配置SPI时钟的分频值为256(SPI_BaudRatePrescaler_256),因此实际的时钟值是APB2的时钟/256 Ø  第八行配置SPI的第一位是最高有效位(SPI_FirstBit_MSB)。这个也是和W25Q16相关的。请见下图红色箭头部分。7 h- @7 Y% A0 p9 w0 ^: O
9.jpg
- F, }6 T" d" |; V) B# b3 x8 G      9 Q6 p# K1 K* [3 [& g0 ~
Ø  第九行配置SPI的CRC校验多项式为7
4 b: g8 G8 V0 CØ  第十行调用SPI_Init(SPI1, &SPI_InitStructure);对SPI1接口进行初始化。
* z! _, I6 T; |8 n+ tl  SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输  
* O$ A$ o# `$ F9 _5 f以上第一行使能SPI接口,第二行使用SPI接口写入一个0XFF,以启动数据的传输。相当于启动数据发送器。

& v4 I4 x% N8 c3 A1 o
  t- O+ R  A9 P% o9 P1 j( T, O' T" X1 V: R0 t

基于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;                          8 o, t: Q- @: {6 q8 O& l% I" z: D" X
SPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge;   
* @; F) O2 f6 d' T0 m这个是上升沿有效,那么怎么同时保证读和写两个操作都可行的呢?
bb123 回答时间:2016-8-25 15:52:13
本帖最后由 bb123 于 2016-8-25 15:53 编辑
- `( d1 ~! B. y- D; }6 k; I4 C6 O4 {7 B  g9 f! ]9 z+ F% ^
发新贴了 SPI接口和W25Q16 请大家一起学习分享 有什么问题请及时提出, }, M/ J2 |& v$ T- L* D1 K  x+ x
我的其它两篇帖子请参考0 a3 U* Z! D6 H/ P( F
STM32库函数注释翻译,每个函数使用说明及使用示例, a7 T$ m$ ^+ Z* f8 G$ I
https://www.stmcu.org.cn/module/forum/thread-607584-1-1.html
" e$ T- ?/ ~+ W% j! ]$ ^8 MSTM32库函数翻译,每个函数说明及示例-RCC(时钟)篇(上)
7 p& r. p  @) o# M& \https://www.stmcu.org.cn/module/forum/thread-607608-1-2.html0 y2 v6 o$ t* _4 {* g' y
2 `0 O0 v# H9 h
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% w" H, F# y# @/ N: e* ]: ~
楼主,你的2.5 引脚的MOSI和MISO 弄反了吧

0 x' e- g' N! ^% p他用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:552 u( P5 e$ S+ x- T) g$ |
楼主,你的2.5 引脚的MOSI和MISO 弄反了吧

! f" {& m6 F8 R' C没有反,是正确的
) F$ v* M  ^- ~
星光月 回答时间:2018-4-11 11:34:30
谢谢分享,资料很有用
# h# Y8 K/ L" _" w& R  q1 O
12下一页

所属标签

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