|
7 i2 Q- n" f9 y; I2 s STM32接口篇之SPI接口 ---SPI接口操作FLASH存储芯片W25Q16初始化代码详细分析8 K, m+ e$ U) F# D/ S我的其它两篇帖子请参考: @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 文档目的: 学习STM32F4的SPI接口和FLASH存储芯片W25Q16,通过对初始化代码分析,更好的了解STM32中SPI接口编程中的一些关键知识点。 s$ f$ H- h: y. X1 P 学习素材: l W25Q16数据手册 l 电路原理图 l STM32F4应用手册 针对W25Q16的SPI接口初始化代码( I# n- y! p3 j/ a- a" m$ y 1 硬件电路说明 (1)=关键原理图 FLASH芯片接口图
GPIO接口图
(2) 原理图说明 首先我们看到上面第一个图,第一个图展示了W25Q16的原理图信息,我们配合W25Q16的数据手册(第六页 见下图),我们分析具体IO的连接和功能,分析结果见下表
注意:配合(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, {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; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟 , [9 A3 ?3 d$ r" t //GPIOFB3,4,5初始化设置 GPIO_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;//推挽输出 GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz 5 R' f- R9 [" F5 C! e GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化 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 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1 RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1 " K4 }! S' M2 N/ e" K$ a5 s RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1 SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode= SPI_Mode_Master; //设置SPI工作模式:设置为主SPI SPI_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; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样 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值计算的多项式 SPI_Init(SPI1,&SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); //使能SPI外设 SPI1_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; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟 //GPIOB0 GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;//PB0 GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//输出 GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽输出 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 SPI1_SetSpeed(SPI_BaudRatePrescaler_2); //设置为42M时钟,高速模式 + \6 ~: v% T) _: d; y2 x$ V2 D W25QXX_TYPE=W25QXX_ReadID(); //读取FLASH ID. } 9 [1 T* E, {* g! g (2) 代码分析1. 初始化代码总体分析由展示的代码,我们可以看出,对W25Q16的初始化主要使用了两个函数,主要调用函数void W25QXX_Init(void)对W25Q16进行初始化,而该函数又调用了SPI1_Init()函数对SPI1接口进行初始化。代码主要流程图如下:
通过以上流程图,我们可以看出对W25Q16的初始化,主要是初始化SPI接口。下面对代码具体说明。 2. 代码详细分析(我们主要分析SPI1接口的初始化函数)我们先分析函数voidSPI1_Init(void),对该函数里面的没一行代码进行详细说明l GPIO_InitTypeDef GPIO_InitStructure; SPI_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篇》 l GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能 GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出 GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化代码分析:这几行代码主要是初始化结构体参数GPIO_InitStructure,该参数指定了GPIO的一些具体的配置,注意第二行代码,我们把PB3,PB4和PB5设置成复用功能。第三行我们采用推挽输出,可以得到更大的驱动能力。第四行我们把IO的最大翻转速度配置成100MHZ,因为考虑PB3作为SPI的时钟口,最高的频率可达42MHZ。其它关于GPIO库函数GPIO_Init(GPIOB, &GPIO_InitStructure)的详细解释,请见我的《STM32库函数说明及示例-GPIO篇》。 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 代码分析:以上三行实现管脚复用功能的具体配置,我们分别把PB3,PB4和PB5配置成SPI1接口的复用功能。关于该函数的详细说明,请见我的《STM32库函数说明及示例-GPIO篇》。 8 ?$ r- p* J8 C: f6 ?3 O l RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE); RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE); 对SPI1接口的时钟进行复位,以便进行SPI接口的初始化操作l SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; SPI_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; SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256; ) J" S! ]$ X4 G2 D7 g SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial= 7; //CRC值计算的多项式 SPI_Init(SPI1,&SPI_InitStructure); 代码分析:这一段代码是SPI接口初始化的关键代码,在该代码中,对结构体变量SPI_InitStructure里面的成员变量进行了初始化。以下分别进行说明:; L6 N! s6 r. z+ ?6 A Ø 第一行使用宏SPI_Direction_2Lines_FullDuplex将SPI接口配置成双线全双工模式,因为硬件接口使用了输入和输出两个IO,所以应该是双线模式,而且W25Q16的数据是在收发两个方向同时传输,所以是全双工模式。 Ø 第二行设置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也是可以的。
图1:SPI MODE3时序图 ) k! o# A; \8 f8 G1 b" q* Y
图2 Ø 第六行设置SPI_NSS信号,由软件产生。因为我们没有使用硬件NSS信号,所以选择由软件产生NSS信号。大家注意我们的CS管脚是连接到普通IO上面的,因此没有使用硬件NSS。 Ø 第七行配置SPI时钟的分频值为256(SPI_BaudRatePrescaler_256),因此实际的时钟值是APB2的时钟/256 Ø 第八行配置SPI的第一位是最高有效位(SPI_FirstBit_MSB)。这个也是和W25Q16相关的。请见下图红色箭头部分。8 q ?) ^! w2 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接口进行初始化。 l SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输 以上第一行使能SPI接口,第二行使用SPI接口写入一个0XFF,以启动数据的传输。相当于启动数据发送器。 2 h7 l* H8 B; W. R$ ?$ U6 _, f |
åºäºSTM32çSPIæ¥å£æä½W25Q16åå§å代ç 详ç»åæ.pdf
下载760.51 KB, 下载次数: 997
STM32-SPI-W25Q16
微信公众号
手机版
SPI_InitStructure.SPI_CPHA= SPI_CPHA_2Edge; " Z5 L$ P6 ^- t3 w8 i+ x* u" Y
这个是上升沿有效,那么怎么同时保证读和写两个操作都可行的呢?
! R" @2 E4 v# Q1 h& o: d
发新贴了 SPI接口和W25Q16 请大家一起学习分享 有什么问题请及时提出
我的其它两篇帖子请参考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
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; ?
他用io模拟,你管得着!
没有反,是正确的