spi学习

更新时间:2023-03-17 03:09:02 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第15章

SPI接口

15.1 SPI协议简介

SPI 协议(Serial PeripheralInterface),即串行外围设备接口,是一种高速全双工的通信总线,它由摩托罗拉公司提出,当前最新的为V04.01—2004版。它被广泛地使用在ADC、LCD等设备与MCU间通信的场合。

15.1.1 SPI信号线

SPI包含4条总线,SPI总线包含4条总线,分别为S S、SCK、MOSI、MISO。它们

的作用介绍如下:

1)SS(SlaveSelect):片选信号线,当有多个SPI设备与MCU相连时,每个设备的这个片选信号线是与MCU单独的引脚相连的,而其他的SCK、MOSI、MISO线则为多个设备并联到相同的SPI总线上,见图15-1。当SS信号线为低电平时,片选有效,开始SPI通信。

图16-1SPI多设备通信

2)SCK(SerialClock):时钟信号线,由主通信设备产生,不同的设备支持的时钟 频率不一样,如STM32的SPI时钟频率最大为fPCLK/2。

3)MOSI (Master Output, Slave Input):主设备输出 /从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。

4)MISO(Master Input, Slave Output):主设备输入 /从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。

15.1.2 SPI模式

根据SPI时钟极性(CPOL)和时钟相位(CPHA)配置的不同,分为4种SPI模式。时钟极性是指SPI通信设备处于空闲状态时(也可以认为这是SPI通信开始时,即

SS为低电平时),SCK信号线的电平信号。CPOL二0时,SCK在空闲状态时为低

电平,CPOL二1时则相反。

时钟相位是指数据采样的时刻,当CPHA二0时,MOSI或MISO数据线上的信号将会在SCK时钟线的奇数边沿被采样。当CPHA二1时,数据线在SCK的偶数边沿采样。见图15-2

CPOL二0 或二1的情况 111

2016-08-09 08:05:32

-------------------------------------------- 在相位采样 注意

采样时刻 MISO或 MOSI的时序 片选信号线

图16-2CPHA=0时,SPI时序

我们来分析这个 CPHA二0的时序图。

首先,由主机把片选信号线S S 拉低,即为图中的S S(O)时序,意为主机输出,

SS(I)时序实际上也是S S 线信号,S S(I)时序表示从机接收到S S片选被拉低的信号。

在SS被拉低的时刻,SCK分为两种情况,若我们设置为CPOL二0,则SCK时序在这

个时刻为低电平,若设置为 CPOL二1,则 SCK 在这个时刻为高电平。

无论CPOL二0还是二1,因为我们配置的时钟相位CPHA二0,在采样时刻的时序中我们 可以看到,采样时刻都是在SCK的奇数边沿(注意奇数边沿有时为下降沿,有时为上升 沿)。

因此,MOSI和MISO数据线的有效信号在SCK的奇数边沿保持不变,这个信号将在SCK奇数边沿时被采集,在非采样时刻,MOSI和MISO的有效信号才发生切换。

对于CPHA二1的情况也很类似,但数据信号的采样时刻为偶数边沿,其时序图见图15-3。使用SPI协议通信时,主机和从机的时序要保持一致,即两者都选择相同的SPI模式。

图16-3CPHA=1时,SPI时序

15.2 STM32 的 SPI特性及架构

STM32的小容量产品有1个SPI接口,中容量的有2个,而大容量的则有3个。其特性如下:

? 单次传输可选择为8或16位。 ? 波特率预分频系数(最大为fPCLK/2)。 ? 时钟极性(CPOL)和相位(CPHA)可编程设置。

? 数据顺序的传输顺序可进行编程选择,MSB在前或LSB在前。 ? 可触发中断的专用发送和接收标志。

? 可以使用DMA进行数据传输操作。

15.2.1 STM32 的 SPI架构分析

图15-4所示为STM32的SPI架构图,可以看到MISO数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由我们的软件从接收缓冲区读出了。

当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到MOSI数据线。

SCK的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(BR)来控制它输出的波特率。

控制寄存器CR1掌管着主控制电路,STM32的SPI模块的协议设置(时钟极性、相位等)就是由它来制定的。而控制寄存器CR2则用于设置各种中断使能。

最后为NSS引脚,这个引脚扮演着SPI协议中的 S S片选信号线的角色,如果我们

把NSS引脚配置为硬件自动控制,SPI模块能够自动判别它能否成为SPI 的主机,或自动

进入SPI从机模式。但实际上我们用得更多的是由软件控制某些GPIO引脚单独作为SS

信号,这个GPIO引脚可以随便选择。

图16-4 STM32的SPI架构图

15.3 SPI 接口读取Flash实例分析

注意:ISO/MINI开发板上的SPIFLASH已经换成了W25Q64型号了,容量是

8MB,光盘里面配套的程序也换成了W25Q64的了。但是教程跟视频上是用W25X16来讲解,W25Q64跟W25Q16不一样的地方是容量和ID,其他都是一样的,都是同一个公司同系列的产品,并不影响大家的学习。

本章以SPI读写Flash的例程为大家讲解SPI的使用。

配套STM32开发板用的是ISO/MINI,它有3个SPI接口。本实验使用SPI1,各信号 线相应连接到Flash(型号:W25X16/W25Q16)的CS、CLK、DO和DIO线,实现SPI 通信,对Flash进行读写,其中W25X16和W25Q16在程序上不同的地方是FLASH的ID 不一样,其他程序都完全兼容。

本实验采用主模式,全双工通信。通过查询发送数据寄存器和接收数据寄存器状态确保通信正常。

1.

实验描述

读取Flash的ID信息,写入数据,并读取出来进行校验,通过串口打印写入与读取出来的数据,输出测试结果。

2.

硬件连接

野火MINISTM32开发板 PA4-SPI1-NSS:W25X16-CS PA5-SPI1-SCK:W25X16-CLK PA6-SPI1-MISO:W25X16-DO PA7-SPI1-MOSI:W25X16-DIO 野火ISOSTM32开发板 PA4-SPI1-NSS:W25X16-CS PA5-SPI1-SCK:W25X16-CLK PA6-SPI1-MISO:W25X16-DO PA7-SPI1-MOSI:W25X16-DIO

3.

用到的库文件

start stm32f10xhd.s corecm3.c systemstm32f10x.c stm32f10xgpio.c stm32f10xrcc.c stm32f10xspi.c stm32f10xusart.c STARTUP CMSIS FWlib

4.

用户编写的文件

main.c stm32f10xit.c bsP spiflash.c USER

bspusart1.c

图16-5 配套STM32开发板SPI-FLASH硬件原理图

15.3.2配置工程环境

本SPI-2M-FLASH实验中我们用到了GPIO、RCC、USART及SPI外设,所以我们先要把以下库文件添加到工程中:stm32f10xgpio.c、stm32f10xrcc.c、stm32f10xusart.c、stm32f10x spi.c。本实验中没有使用中断,采用轮询标志位的方式来确保 SPI正常通信。接下来添加旧工程中的外设用户文件 usart.c,以便调试和观察实验效果。新建 bsp spiflash.c 及 bsp spi fl ash.h 文件,并在 stm32f10x conf.h 中把使用到的 ST库的头文件注释去掉。见代码清单15-1。

代码清单15-1Flash例程的stm32f10x_conf.h文件配置

01 02 3 4 5 06 07 08 9 10 11 12

**

*************************************************************** * @fi le Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h * @author MCD Application Team * @version V3.5.0 * @date 08-April-2011

* @brief Library confi guration fi le.

************************************************************/ #include \ #include \ #include \ #include \

15.3.3 main文件

配置好所需的库文件之后,我们就从main函数开始,分析见代码清单15-2。

代码清单15-2Flash例程的main函数

01 /*

02 * 函数名:main 03 * 描述 :主函数 04 * 输入 :无 05 * 输出 :无 06 */

07 int main(void) 08 {

09 /* 配置串口1为:115200 8-N-1 */ 10 USARTx_Config(); 11 printf(\这是一个8M串行flash(W25Q64)实验\\r\\n\); 12 13 /* 8M串行flash W25Q64初始化*/ 14 SPI_FLASH_Init(); 15 16 /* Get SPI Flash Device ID */ 17 DeviceID = SPI_FLASH_ReadDeviceID(); 18 19 Delay( 200 ); 20 21 /* Get SPI Flash ID */ 22 FlashID = SPI_FLASH_ReadID(); 23 24 printf(\Manufacturer Device ID is 0x%X\\r\\n\, FlashID, DeviceID); 25 26 /* Check the SPI Flash ID */ 27 if (FlashID == sFLASH_ID) { /* #define sFLASH_ID 0xEF4017 */ 28 printf(\检测到华邦串行 flash W25Q64!\\r\\n\); 29 30 /* Erase SPI FLASH Sector to write on */ 31 SPI_FLASH_SectorErase(FLASH_SectorToErase); 32 33 /* 将发送缓冲区的数据写到flash中*/ 34 SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize); 35 printf(\写入的数据为:%s \\r\\t\, Tx_Buffer); 36 37 /* 将刚刚写入的数据读出来放到接收缓冲区中*/ 38 SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize); 39 printf(\读出的数据为:%s \\r\\n\, Rx_Buffer); 40 41 /* 检查写入的数据与读出的数据是否相等*/ 42 TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize); 43 44 if ( PASSED == TransferStatus1 ) { 45 printf(\串行flash(W25Q64)测试成功!\\n\\r\); 46 } else { 47 printf(\串行flash(W25Q64)测试失败!\\n\\r\); 48 } 49 }// if (FlashID == sFLASH_ID) 50 else { 51 printf(\获取不到 W25Q64ID!\\n\\r\); 52 } 53 54 SPI_Flash_PowerDown(); 55 while (1); 56 }

这个例程的main函数很长,主要是因为后面有很多与实验验证有关的测试代码,实际上我们使用SPI读写Flash可以写得十分简洁。

沿着main函数的流程先走一遍,大家要清楚地认识到,本实验中main函数调用的所有函数都是用户函数。

1)调用USART1Config()初始化串口。 2)调用SPIFLASHInit()初始化SPI模块。

3)调用SPIFLASHReadDeviceID()读取Flash器件生产厂商的ID信息。 4)调用SPIFLASHReadID()读取Flash器件的设备ID信息。

读取器件的ID信息可以让我们知道设备与主机是否能够正常工作,也便于我们区分不同的器件。根据本实验使用的datasheet说明可以查询到器件的ID,见表15-1。我们可以在程序中进行验证。

表15-1Flash数据手册的设备ID说明

MANUFACTURERID (M7-M0) Winbond SerialFlash EFH (ID7-ID0) ABh,90h (ID15-ID0) 9Fh DeviceID Instruction W25X16 W25X32 W25X64 14h 15h 16h 3015h 3016h 3017h 5)若读取得的ID正确,则调用SPIFLASHSectorErase()把Flash的内容擦 除,擦除后调用SPIFLASHBufferWrite()向Flash写入数据,然后再调用 SPIFLASHBufferRead()从刚刚写入的地址中读出数据。最后调用Buffercmp()函数对写入 的数据与读取的数据进行比较,若写入的数据与读出的数据相同,则把标志变量 TransferStatus1赋值为PASSED(自定义的枚举变量)。

6)根据标志变量TransferStatus1判断Flash数据的“擦除—写入—读取”是否正常,分情况输出测试信息到终端。

7)若在读取Flash的ID信息时就出错,则直接向终端输出“检测不到Flash”的信 息。

8)最后调用SPIFlashPowerDown()函数关闭Flash设备的电源,因为数据写入到 Flash后并不会因断电而丢失,我们在使用它时才重新开启Flash的电源。接下来我们详细分析main函数中调用的以上用户函数是怎样编写的。

15.3.4 SPI初始化

SPIFLASHInit()函数初始化了SPI1复用到的GPIO引脚,启动了GPIO及SPI1外设 的时钟,并初始化了SPI的模式。实现见代码清单15-3。

代码清单15-3SPI_FLASH_Init()函数

01 void SPI_FLASH_Init(void) 02 { 3 SPI_InitTypeDef SPI_InitStructure; 4 GPIO_InitTypeDef GPIO_InitStructure; 05 06 7 /* Enable SPI1 and GPIO clocks */ 8 /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO, 9 SPI_FLASH_SPI_MISO_GPIO, SPI_FLASH_SPI_DETECT_GPIO 10 and SPI_FLASH_SPI_SCK_GPIO Periph clock enable */ 11 macSPI_GPIO_APBxClock_FUN ( macSPI_GPIO_CLK, ENABLE ); 12 13 /*!< SPI_FLASH_SPI Periph clock enable */ 14 macSPI_APBxClock_FUN ( macSPI_CLK, ENABLE );

15 16 /*!< Configure SPI_FLASH_SPI pins: SCK */ 17 GPIO_InitStructure.GPIO_Pin = macSPI_SCK_PIN; 18 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 19 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 20 GPIO_Init(macSPI_SCK_PORT, &GPIO_InitStructure); 21 22 /*!< Configure SPI_FLASH_SPI pins: MISO */ 23 GPIO_InitStructure.GPIO_Pin = macSPI_MISO_PIN; 24 GPIO_Init(macSPI_MISO_PORT, &GPIO_InitStructure); 25 26 /*!< Configure SPI_FLASH_SPI pins: MOSI */ 27 GPIO_InitStructure.GPIO_Pin = macSPI_MOSI_PIN; 28 GPIO_Init(macSPI_MOSI_PORT, &GPIO_InitStructure); 29 30 /*!< Configure SPI_FLASH_SPI_CS_PIN pin: SPI_FLASH Card CS pin */ 31 GPIO_InitStructure.GPIO_Pin = macSPI_CS_PIN; 32 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 33 GPIO_Init(macSPI_CS_PORT, &GPIO_InitStructure); 34 35 /* Deselect the FLASH: Chip Select high */ 36 macSPI_FLASH_CS_HIGH(); 37 38 /* SPI1 configuration */ 39 // W25X16: data input on the DIO pin is sampled on the rising edge of the CLK. 40 // Data on the DO and DIO pins are clocked out on the falling edge of CLK. 41 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; 42 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; 43 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; 44 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; 45 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 46 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; 47 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; 48 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; 49 SPI_InitStructure.SPI_CRCPolynomial = 7; 50 SPI_Init(macSPIx , &SPI_InitStructure); 51 52 /* Enable SPI1 */ 53 SPI_Cmd(macSPIx , ENABLE); 54 }

SPIFLASHInit()分为两部分。从开始到第37行为GPIO配置部分,之后为SPI模式 配置。

1.

GPIO端口初始化

我们根据《STM32数据手册》和《STM32参考手册》把PA5(SCK)、PA6 (MISO)、PA7(MOSI)配置成复用的推挽输出模式,而由于我们的NSS引脚使用的是软件模式,所以我们把PA4(NSS)配置成通用推挽输出。见表15-2和表15-3。

表15-2SPI1引脚定义

脚位 管脚 类 I/ 主功 可选的复用功能 BG A 1 4 4 BG A 1 0 0 WLLQ CF S P P 6 6 4 4 H7 E5 2 0 2 1 2 2 名称 型 O 能 LL电Q Q F F 平 P P 1 1 0 4 0 4 2 9 3 0 3 1 4 0 4 1 4 2 PA4 I/O PA4 默认复用功能 重映射功能 J3 G3 K3 L3 H3 PA5 I/O PA5 SPI1NSS/USART2C K DACOUT1/ADC12IN4 SPI1SCK DACOUT2/ADC12IN5 SPI1MISO /TIM8BKIN ADC12IN6/TIM3C H1 J3 G5 PA6 I/O PA6 TIM1BKIN M3 K3 G4 2 3 3 2 4 3 I/O PA7 PA7 SPI1MOSI /TIM8CH1N ADC12IN7/TIM3C H2 TIM1CH1N 表15-3SPI的GPIO引脚复用设置

SPI引脚 配置 主模式 从模式 全双工模式/主模式 全双工模式/从模式 简单的双向数据线/主模式 简单的双向数据线/从模式 全双工模式/主模式 全双工模式/从模式 简单的双向数据线/主模式 简单的双向数据线/从模式 GPIO配置 推挽复用输出 浮空输入 推挽复用输出 浮空输入或带上拉输入 SPIxMOSI 推挽复用输出 未用,可作为通用I/O 浮空输入或带上拉输入 推挽复用输出 SPIxMISO 未用,可作为通用I/O 推挽复用输出 浮空输入或带上拉输入 硬件主/从模式 或带下拉输入 SPIxNSS 推挽复用输出 硬件主模式/NSS输出使能 软件模式 未用,可作为通用I/O 在本函数中第36行的SPIFLASHCSHIGH()实际上是一个自定义的宏,如下: 01 #definemacSPI_FLASH_CS_LOW() macSPI_CS_PIN )

SPIxSCK GPIO_ResetBits(macSPI_CS_PORT,

实际上这个宏是用来把SPI的NSS(PA4)引脚拉高,从而禁止SPI的通信,在我们需要进行SPI通信时,我们会使用下面的宏把NSS(PA4)引脚置低:

01 #definemacSPI_FLASH_CS_HIGH() macSPI_CS_PIN )

GPIO_SetBits(macSPI_CS_PORT,

这样控制片选信号的方式,就是前面提到的软件控制方式,如果我们连接了多个SPI 设备,这个片选信号可以由其他GPIO引脚产生。

2.

SPI模式初始化

从代码的第41行至第53行,为SPI模式的初始化。对STM32的SPI初始化配置,是根据将要与之通信的Flash 设备的SPI特性来制定的。初始化时,有如下相关的结构体成员。

1)SPIMode:STM32的SPI设备可以工作于主机模式(SPIMode Master)或从机模式(SPIModeSlave),这两个模式的最大区别为SPI的SCK信号线的时序,SCK的时序是由通信中的主机产生的。若被配置为从机模式,STM32的SPI模块将接受外来的SCK信号。本实验中STM32作为SPI通信中的主机,我们向这个成员赋值为主机模式(SPI ModeMaster)。

2)SPIDataSize:这个成员可以选择SPI每次通信的数据大小(称为数据帧)为8位还是16位。从Flash的数据手册我们可以查到,本Flash通信的数据帧大小为8位,STM32的SPI模块设置要与之相同。

3)SPICPOL和SPICPHA:这两个成员即配置SPI的时钟极性(CPOL)和时钟相位(CPHA),这两个配置影响到SPI的通信模式,该设置要符合将要互相通信的设备的要求。CPOL分别可以取SPICPOLHigh(SPI通信空闲时SCK为高电平)和SPI CPOL Low( SPI 通信空闲时 SCK 为低电平)。 CPHA 则可以取 SPI CPHA1Edge

(在SCK的奇数边沿采集数据)和SPICPHA2Edge(在SCK的偶数边沿采集数据)。 查阅本Flash 的使用手册,见图15-6。可以了解到这个Flash支持以SPI的模式0和模式3通信。即在SPI空闲时,SCK 为低电平,奇数边沿采样(模式0);也可以在SPI空闲时,SCK为高电平,偶数边沿采样(模式3)。即无论CPOL的状态是什么,Flash的数据采样时刻为SCK的上升沿。我们在本实验配置使用它的模式3,即把CPOL赋值为 SPI CPOL High ;CPHA 赋值为 SPI CPHA2Edge。

图16-6 Flash时序说明图

4)SPINSS:本成员配置NSS引脚的使用模式,可以选择为硬件模式

(SPI NSS Hard )与软件模式(SPI NSS Soft),在硬件模式中的 SPI片选信号由硬件自动产生,而软件模式则需要我们亲自把相应的GPIO端口拉高或置低产生非片选和片选信号。如果外界条件允许,硬件模式还会自动将STM32的SPI设置为主机。本实验使用软件模式,向这个成员赋值为 SPI NSS Soft。

5)SPI BaudRatePrescaler :本成员设置波特率分频值,分频后的时钟即为 SPI 的SCK信号线的时钟频率。这个成员参数可设置为 f PCLK 的2、4、6、8、16、32、64、128、256分频。本实验向这个成员赋值为SPIBaudRatePrescaler4,即fPCLK 的4分频。

6)SPI FirstBit :所有串行的通信协议都会有 MSB 先行(高位数据在前)还是LSB先行(低位数据在前)的问题,而STM32的SPI 模块可以通过这个结构体成员,对这个特性编程控制。据Flash 的通信时序,我们向这个成员赋值为MSB 先行 (SPI FirstBitMSB)。

7)SPICRCPolynomial:这是SPI的CRC校验中的多项式,若我们使用CRC校验时,就使用这个成员的参数(多项式)来计算CRC的值。由于本实验的Flash不支持CRC校验,所以我们向这个结构体成员赋值为7实际上是没有意义的。

配置完这些结构体成员后,我们要调用SPIInit()函数把这些参数写入寄存器中,实现 SPI的初始化,然后调用SPICmd()来使能SPI1外设。

15.3.5 控制Flash的命令

实际上,编写设备的驱动都有一定的规律可循。首先我们要确定设备使用的是什么通信协议。如第14章的EEPROM使用的是I2C,

本章的Flash使用的是SPI。那么我们就先根据它的通信协议,选择好STM32的硬件模块,并进行相应的I2C或SPI模块初始化。

因为不同的设备都会相应的有不同的指令,如EEPROM中会把第一个数据解释为存储矩阵的地址(实质就是指令)。而Flash则定义了更多的指令,有写指令、读指令、读ID指令等。

对主机来说,这些指令只是它遵守最基本的通信协议发送出的数据。但设备把这些数据解释成不同的意义(指令编码),所以才成为指令。在我们配置好STM32的协议模块后,想要控制设备,就要遵守相应设备所定义的命令规则。

所以写驱动并不难:就是确定通信协议模块,通过协议收发命令、数据,进而驱动设备。我们把这个称为驱动编写原理。这也是设备厂商的设计准则。

我们先来查看Flash的各种指令和命令解释时序,见表15-4。

表15-4Flash命令

INSTRUCTIONNAME WriteEnable WriteDisable BYTE1 CODE 06h 04h BYTE2 BYTE3 BYTEBYT4 E5 BYTE6 N- BYTES ReadStatusRegister WriteStatusRegister ReadData FastRead 05h 01h 03h 0Bh (S7-S0)(1) S7-S0 A23-A16 A23-A16 A15-A8 A15-A8 A7-A0 (D7- D0) A7-A0 dummy (Nextbyte) (D7-D0) (2) continuous (Next Byte) continuous (onebyteper 4clocks,continuous ) Up to256bytes FastReadDualOutput A23-A16 A23-A16 A23-A16 A23-A16 A15-A8 A15-A8 A15-A8 A15-A8 A7-A0 dummy 3Bh PageProgram BlockErase (64KB) SectorErase (4KB) ChipErase Power-down ReleasePower-down / DeviceID Manufacturer/ DeviceID(3) 02h D8h 20h C7h B9h ABh 90h A7-A0 (D7- D0) A7-A0 A7-A0 I/O二 (D6,D4,D2, D0)O二 (D7,D5,D3, D1) (Nextbyte) (ID7- ID0)(4) (M7- M0) (ID7-ID0) dummy dummy (M7-M0) Manufacturer dummy dummy dummy 00h JEDECID 9Fh 指令表中的(ID15- (ID7- ID8) ID0) MemorCapaciyType ty A0~A23指地址,M0~M7为器件的制造商ID(MANUFACTURERID),D0~D7为数据。

在命令列表中可以了解到读取设备ID的命令(DeviceID)编码为ABh、dummy、 dummy、dummy。表示此命令由这四个字节组成,其中dummy意为任意编码,表示这些 编码可以发送任意数据。命令列表中带括号的字节数据表示由Flash返回给主机的响应, 可以看到ReleasePower-down/DeviceID命令的第5个字节为从机返回的响应,(ID7~ ID0)即返回设备的ID号。

使用DeviceID命令时的时序图见图15-7,可以看到主机首先通过MOSI线(即Flash的DIO线)发送第一个字节为ABh编码,紧接着三个字节的dummy编码,然后Flash就忽略DIO线上的信号,通过MISO线(即Flash的DO 线)把它的Flash设备ID发送给主机。

图16-7 Flash读ID命令解释时序

了解了DeviceID命令及其时序,我们分析SPIFLASHReadDeviceID()函数来说明驱 动编写原理。这个函数实现了读取Flash的ID的功能,见代码清单15-4。

代码清单15-4SPI_FLASH_ReadDeviceID()函数

01 u32 SPI_FLASH_ReadDeviceID(void) 02 { 03 u32 Temp = 0; 04 5 /* Select the FLASH: Chip Select low */ 6 macSPI_FLASH_CS_LOW(); 07 8 /* Send \ 9 SPI_FLASH_SendByte(W25X_DeviceID); 10 SPI_FLASH_SendByte(Dummy_Byte); 11 SPI_FLASH_SendByte(Dummy_Byte); 12 SPI_FLASH_SendByte(Dummy_Byte); 13 14 /* Read a byte from the FLASH */ 15 Temp = SPI_FLASH_SendByte(Dummy_Byte); 16 17 /* Deselect the FLASH: Chip Select high */ 18 macSPI_FLASH_CS_HIGH(); 19 20 return Temp; 21 }

这个函数的代码流程严格遵从DeviceID命令的时序:

1)第6行,调用宏SPIFLASHCSLOW(),这是一个自定义的宏,使Flash的片选 有效,以使能Flash设备。

2)第9行,利用用户函数SPIFLASHSendByte()来向Flash发送第一个命令字节编 码“W25XDeviceID”(自定义的宏:0xAB)。

3)根据指令表,发送完这个指令后,后面紧跟着三个字节的“dummybyte”,我们把Dummy Byte宏定义为“0xff”,实际上改成其他宏编码也无影响。

4)第15行,完整的命令在前面已经发送完毕,根据时序,在第5个字节Flash通过 DO端口输出它的器件ID,我们调用函数SPIFLASHSendByte()接收返回的数据,并赋 值给Temp变量。SPIFLASHReadDeviceID()函数的返回值即为读取得的器件ID。

5)把片选信号拉高,结束通信。

这就完成了读Flash的ID。在这个读FlashID 函数中多次调用了一个相对底层的用户函数,它实现了利用SPI发送和接收数据的功能,见代码清单15-5。

代码清单15-5SPI_FLASH_SendByte()函数

01 u8 SPI_FLASH_SendByte(u8 byte) 02 { 3 /* Loop while DR register in not emplty */ 4 while (SPI_I2S_GetFlagStatus(macSPIx , SPI_I2S_FLAG_TXE) == RESET); 05 6 /* Send byte through the SPI1 peripheral */ 7 SPI_I2S_SendData(macSPIx , byte); 08 9 /* Wait to receive a byte */ 10 while (SPI_I2S_GetFlagStatus(macSPIx , SPI_I2S_FLAG_RXNE) == RESET); 11 12 /* Return the byte read from the SPI bus */ 13 return SPI_I2S_ReceiveData(macSPIx ); 14 }

1)调用库函数SPII2SGetFlagStatus()等待发送数据寄存器清空。

2)发送数据寄存器准备好后,调用库函数SPII2SSendData()向从机发送数据。 3)调用库函数SPII2SGetFlagStatus()等待接收数据寄存器非空。

4)接收寄存器非空时,调用SPII2SReceiveData()获取接收寄存器中的数据并作为 函数的返回值,这个数据即由从机发送给主机的数据。

这是最底层的发送数据和接收数据的函数,利用了库函数的标志检测确保通信正常。

15.3.6 读取厂商ID

对于其他函数,编写的方法是类似的,如读取厂商ID的函数SPIFLASHReadID()见 代码清单15-6。

代码清单15-6SPI_FLASH_ReadID()函数

01 u32 SPI_FLASH_ReadID(void) 02 { 03 u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0; 04 5 /* Select the FLASH: Chip Select low */ 6 macSPI_FLASH_CS_LOW(); 07 8 /* Send \ 9 SPI_FLASH_SendByte(W25X_JedecDeviceID); 10 11 /* Read a byte from the FLASH */ 12 Temp0 = SPI_FLASH_SendByte(Dummy_Byte); 13 14 /* Read a byte from the FLASH */ 15 Temp1 = SPI_FLASH_SendByte(Dummy_Byte); 16 17 /* Read a byte from the FLASH */ 18 Temp2 = SPI_FLASH_SendByte(Dummy_Byte); 19 20 /* Deselect the FLASH: Chip Select high */ 21 macSPI_FLASH_CS_HIGH(); 22 23 Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2; 24 25 return Temp;

26 }

这个函数根据命令流程,发送一个字节的命令编码“JEDEC ID(9Fh)”后,从机就通过DO线返回厂商ID及0~16位的设备ID。图15-8为读厂商ID的时序图。

1.

图16-8读厂商ID 时序图

15.3.7 擦除Flash内容

扇区擦除

根据Flash的存储原理,在写入数据前要先对存储区域进行擦除,所以执行 SPIFLASHSectorErase()函数对要写入的扇区进行擦除,也称为预写。见代码清单 15-7。

代码清单15-7SPI_FLASH_SectorErase()函数

01 void SPI_FLASH_SectorErase(u32 SectorAddr) 02 { 3 /* Send write enable instruction */ 4 SPI_FLASH_WriteEnable(); 5 SPI_FLASH_WaitForWriteEnd(); 6 /* Sector Erase */ 7 /* Select the FLASH: Chip Select low */ 8 macSPI_FLASH_CS_LOW(); 9 /* Send Sector Erase instruction */ 10 SPI_FLASH_SendByte(W25X_SectorErase); 11 /* Send SectorAddr high nibble address byte */ 12 SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16); 13 /* Send SectorAddr medium nibble address byte */

14 15 16 17 18 19 20 21 }

SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8); /* Send SectorAddr low nibble address byte */ SPI_FLASH_SendByte(SectorAddr & 0xFF);

/* Deselect the FLASH: Chip Select high */ macSPI_FLASH_CS_HIGH();

/* Wait the end of Flash writing */ SPI_FLASH_WaitForWriteEnd();

先忽略其中的SPIFLASHWriteEnable()和SPIFLASHWaitForWriteEnd()函数,从 第8行至第18行是纯粹的关于Flash擦除操作,扇区擦除命令时序如图15-9所示。

图16-9扇区擦除时序

其中第一个字节为扇区擦除命令编码(20h),紧跟其后的为要进行擦除的24位起始地址。根据Flash的说明,它把整个存储矩阵分为块区和扇区,每块(Block)的大小为64KB,每个扇区(Sector)的大小为4KB,对存储矩阵进行擦除时,最小的单位为扇区。由于篇幅问题,就不显示它的地址分布图了。

2.

写使能

根据Flash的读写要求,在进行页写入、扇区擦除、块擦除、整片擦除及写状态寄存 器前,都需要先发送写使能命令。在SPIFLASHSectorErase()函数的第4行就调用了用 户函数SPIFLASHWriteEnable(),它的具体实现见代码清单15-8。

代码清单15-8SPI_FLASH_WriteEnable()函数

01 void SPI_FLASH_WriteEnable(void) 02 { 3 /* Select the FLASH: Chip Select low */ 4 macSPI_FLASH_CS_LOW(); 05 6 /* Send \ 7 SPI_FLASH_SendByte(W25X_WriteEnable); 08 9 /* Deselect the FLASH: Chip Select high */ 10 macSPI_FLASH_CS_HIGH(); 11 }

本函数十分简单,就是根据写使能命令的时序,发送写使能命令writeenable (06h)。见图15-10。

图16-10写使能命令时序

3.

读Flash状态

在擦除函数SPIFLASHSectorErase()中,还调用用户函数SPIFLASHWaitFor WriteEnd()来确保在Flash不忙碌的时候,才发送命令或数据。这个函数通过读取Flash的 状态寄存器来获知它的工作状态,实现见代码清单15-9。

代码清单15-9SPI_FLASH_WaitForWriteEnd()函数

01 void SPI_FLASH_WaitForWriteEnd(void) 02 { 03 u8 FLASH_Status = 0; 04 5 /* Select the FLASH: Chip Select low */ 6 macSPI_FLASH_CS_LOW(); 07 8 /* Send \ 9 SPI_FLASH_SendByte(W25X_ReadStatusReg); 10 11 /* Loop as long as the memory is busy with a write cycle */ 12 do { 13 /* Send a dummy byte to generate the clock needed by the FLASH 14 and put the value of the status register in FLASH_Status variable */ 15 FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte); 16 } while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */ 17 18 /* Deselect the FLASH: Chip Select high */ 19 macSPI_FLASH_CS_HIGH(); 20 }

本函数实质是不断循环地检测Flash状态寄存器的Busy位,直到Flash的内部写时序完成,从而确保下一通信操作正常。主机通过发送读状态寄存器命令ReadStatusRegister (05h),返回它的8位状态寄存器的值,这个命令的时序及状态寄存器位的说明见图 15-11和图15-12。

图16-11读状态寄存器时序

图16-12 状态寄存器位说明

本函数检测的就是状态寄存器的Bit0,即BUSY位。Flash在执行内部写时序的时候,除了读状态寄存器命令,其他一切命令都会忽略,并且BUSY位保持为1,即我们需要等待BUSY位为0的时候,再向Flash发送其他命令。

15.3.8 向Flash写入数据

对Flash写入数据,最小单位是256字节,厂商把这个单位称为页。写入时,一般也只有页写入的方式。因而我们为了方便地把一个很长的数组写入Flash中,一般需要进行转换,把数组按页分好,再写入Flash中,如同I2C例子中对EEPROM的页写入一样,只是页的大小不同而已,这个函数见代码清单15-10。

代码清单15-10SPI_FLASH_BufferWrite()函数

01 void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) 02 { 03 u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0; 04 5 Addr = WriteAddr % SPI_FLASH_PageSize; 6 count = SPI_FLASH_PageSize - Addr; 7 NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; 8 NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; 09 10 if (Addr == 0) { /* WriteAddr is SPI_FLASH_PageSize aligned */ 11 if (NumOfPage == 0) { /* NumByteToWrite < SPI_FLASH_PageSize */ 12 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); 13 } else { /* NumByteToWrite > SPI_FLASH_PageSize */ 14 while (NumOfPage--) {

15 SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize); 16 WriteAddr += SPI_FLASH_PageSize; 17 pBuffer += SPI_FLASH_PageSize; 18 } 19 20 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); 21 } 22 } else { /* WriteAddr is not SPI_FLASH_PageSize aligned */ 23 if (NumOfPage == 0) { /* NumByteToWrite < SPI_FLASH_PageSize */ 24 if (NumOfSingle > count) { /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */ 25 temp = NumOfSingle - count; 26 27 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); 28 WriteAddr += count; 29 pBuffer += count; 30 31 SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp); 32 } else { 33 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); 34 } 35 } else { /* NumByteToWrite > SPI_FLASH_PageSize */ 36 NumByteToWrite -= count; 37 NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; 38 NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; 39 40 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); 41 WriteAddr += count; 42 pBuffer += count; 43 44 while (NumOfPage--) { 45 SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize); 46 WriteAddr += SPI_FLASH_PageSize; 47 pBuffer += SPI_FLASH_PageSize; 48 } 49 50 if (NumOfSingle != 0) { 51 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); 52 } 53 } 54 } 55 }

在SPIFLASHBufferWrite()中,对数组进行分页后,它调用了用户函数 SPIFLASHPageWrite()来对数据进行按页写入,见代码清单15-11。

代码清单15-11SPI_FLASH_PageWrite()函数

01 void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) 02 { 3 /* Enable the write access to the FLASH */ 4 SPI_FLASH_WriteEnable(); 05 6 /* Select the FLASH: Chip Select low */ 7 macSPI_FLASH_CS_LOW(); 8 /* Send \ 9 SPI_FLASH_SendByte(W25X_PageProgram); 10 /* Send WriteAddr high nibble address byte to write to */ 11 SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16); 12 /* Send WriteAddr medium nibble address byte to write to */ 13 SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8); 14 /* Send WriteAddr low nibble address byte to write to */ 15 SPI_FLASH_SendByte(WriteAddr & 0xFF); 16

本文来源:https://www.bwwdw.com/article/71yt.html

Top