例程完整代码:
SPI协议简介
SPI协议,即串行外围设备接口,是一种告诉全双工的通信总线,它被广泛地使用在ADC,LCD等设备与MCU间通信的场合。
SPI信号线
SPI包含4条总线,分别为SS,SCK,MOSI,MISO.作用如下:
1) SS:片选信号线,当有多个SPI设备和MCU相连时,每个设备的这个片选信号线是与MCU单独的引脚相连的,而其他的SCK,MOSI,MISO线则为多个设备并联到相同的SPI总线上,当SS信号线为低电平时,片选有效,开始SPI通信.
2) SCK:时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样.
3) MOSI:主设备输出/从设备输入引脚,主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上的数据方向为从主机到从机.
4)MISO:主设备输入/从设备输出引脚,这条线上数据是从机到主机.
SPI模式
根据时钟极性(CPOL)和时钟相位(CPHA)配置的不同,分为4种SPI模式。
时钟极性是指SPI通信设备处于空闲状态时(也可以认为这是SPI通信开始时,即SS线为低电平),SCK信号线的电平信号。CPOL=0时,SCK在空闲状态时为低电平,CPOL=1时则相反。
时钟相位是指数据采样的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的奇数边沿被采样。当CPHA=1时,数据线在SCK的偶数边沿被采样。
下面以CPHA=0为例讲解SPI时序。
首先,由主机把片选信号NSS拉低,意为主机输出。
在NSS被拉低的时刻,SCK分为两种情况,若我们设置CPOL=0,则SCK时序在这时为低电平,若设置为CPOL=1,则SCK在这个时刻为高电平。
无论CPOL为0还是1,因为我们配置的时钟相位CPHA=0,在采样时刻的时序中我们可以看到,采样时刻都是在SCK的奇数边沿(注意奇数边沿有时为下降沿,有时为上升沿)。
因此,MOSI和MISO数据线的有效信号在SCK的奇数边沿保持不变,这个信号将会在SCK奇数边沿时被采集,在非采样时刻,MOSI和MISO的有效信号才发生切换。
对于CPHA=1的情况也类似,只是数据信号的采样时刻为偶数边沿。
注意:使用SPI协议通信时,主机和从机的时序要保持一致,即两者都选择相同的SPI模式。
STM32的SPI特性
STM32的小容量产品有一个SPI接口,中容量有两个,而大容量则有3个,其特征如下:
* 单次传输可选择为8位或16位。
* 时钟极性(CPOL)和相位(CPHA)可编程设置。
* 数据顺序的传输顺序可进行编程选择,MSB在前或LSB在前。
* 可出发中断的专用发送和接收标志。
* 可以使DMA进行数据传输操作。
STM32的SPI架构分析
上图所示为STM32的架构图,可以看到,MISO数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由软件从接收缓冲区读出了。
当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到MOSI数据线。
SCK的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(BR)来控制它输出的波特率。
控制寄存器CR1掌控着主控制电路,STM32的SPI模块的协议设置(时钟极性,相位等)就由它来制定。而控制寄存器CR2则用于设置各种中断使能。
最后为NSS引脚,这个引脚扮演着SPI协议中的SS片选信号线的角色,如果我们把NSS引脚配置为硬件自动控制,SPI模块能够自动判别它能否成为SPI的主机,或自动进入SPI从机模式。但实际上我们用的更多的是由软件控制某些GPIO引脚单独作为SS信号,这个GPIO引脚可以随便选择。
SPI接口读取FLASH实例分析
本文章以STM32通过SPI读写FLASH的例程来逐步讲解STM32的SPI配置及FLASH芯片的普遍驱动方式,尽量做到讲解精细易懂。
本实验使用STM32的SPI2,采用主模式,全双工通信,通过查询发送数据寄存器和接收数据寄存器状态确保通信正常。操作的FLASH芯片型号为W25Q16。
SPI2与芯片引脚连接为:PB12--CS,PB14--SO,PB13--CLK,PB15--SI.
本试验没有使用中断,采用轮询标志位的方式来确保SPI正常通信。
本例程完整代码地址:
https://pan.baidu.com/s/1_bQI2V0YToQe1GJlac0rFQpan.baidu.com
main文件:
配置好所需的库文件之后,我们就从main函数开始
int main(void)
{
USART1_Config();
SPI_FLASH_Init()
DeviceID=SPI_FLASH_ReadDeviceID();
Delay(200);
FlashID=SPI_FLASH_ReadID();
printf(“\r\n FlashID is 0x%X, Manufacturer Device ID is 0x%X \r\n”,FlashID,DeviceID);
if(FlashID==sFLASH_ID)
{
printf("\r\n检测到串行flash W25Q16\r\n");
SPI_FLASH_SectorErase(FLASH_SectorToErase);
SPI_FlASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress , BufferSize );
printf("\r\n写入的数据 :%s \r\n" , Tx_Buffer );
SPI_FLASH_BufferRead(Rx_Buffer,FLASH_ReadAddress, BufferSize );
printf("\r\n读出的数据:%s \r\n" ,Rx_Buffer );
TransferStatus1=Buffercmp(Tx_Buffer, Rx_Buffer , BufferSize);
if(PASSED == TransferStatus1 )
{
printf("\r\n 测试成功 \r\n");
}
else
{
printf("\r\n 测试失败 \r\n");
}
}
else
{
printf( "\r\n 获取不到W25X16 ID \r\n" );
}
SPI_Flash_PowerDown();
while(1);
}
本实验中,main函数调用的所有函数都是用户函数:
1)调用USART1_Config()初始化串口。
2)调用SPI_FLASH_Init()初始化SPI模块。
3)调用SPI_FLASH_ReadDeviceID()读取Flash器件生产厂商的ID信息。
4)调用SPI_FLASH_ReadID()读取器件的设备ID信息。
读取器件的ID信息可以让我们知道设备与主机是否能够正常工作,也便于我们区分不同的器件。
5)若读取得到的ID正确,则调用SPI_FLASH_SectorErase()把Flash的内容擦除,擦除后调用SPI_FLASH_BufferWrite()向FLASH写入数据,然后再调用SPI_FLASH_BufferRead()从刚刚写入的地址中读出数据。最后调用Buffercmp()函数对写入的数据与读取的数据进行比较,若写入的数据与读出的数据相同,则把标志变量TransferStatus1赋值为PASSED。
6)最后调用SPI_FLASH_PowerDown()函数关闭Flash设备的电源,因为数据写入到Flash后并不会因断电而丢失,我们使用它时才重新开启Flash电源。
接下来我们详细分析main函数中调用的以上用户函数是怎样编写的。
这里USART的配置不做说明,主要就是通过电脑串口打印实验信息,跟这篇文章所涉及的主要内容不搭边。
SPI初始化
SPI_FLASH_Init()函数初始化了SPI复用的GPIO引脚,启动了GPIO及SPI1外设的时钟,并初始化了SPI的模式。
void SPI_FLASH_Init()
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
//PB13 SPI2-SCK
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PB14 SPI2-MISO
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PB15 SPI2-MOSI
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//PB12 SPI2-CS
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
SPI_FLASH_CS_HIGH();
//SPI2配置
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_Init(SPI2,&SPI_InitStructure);
SPI_Cmd(SPI2,ENABLE);
}
SPI_FLASH_Init()分为两部分,一部分为GPIO的配置,一部分为SPI模式的配置。
这里GPIO的配置及为SPI2相应引脚根据数据手册配置成相应模式即可。
对STM32的SPI初始化配置,是根据将要与之通信的Flash设备的SPI特性来制定的,初始化时,有如下相关结构体成员:
1)SPI_Mode:STM32的SPI设备可以工作于主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave),这两个模式的最大区别为SPI的SCK信号线的时序,SCK的时序是由通信中的主机产生的。若被配置为从机模式,STM32的SPI模块将接收外来的SCK信号。
2)SPI_DataSize:这个成员可以选择SPI每次通信的数据大小为8位还是16位。从FLASH的数据手册我们可以查到,本FLASH通信的数据帧大小为8位,STM32的SPI模块设置要与之相同。
3)SPI_CPOL和SPI_CPHA:这两个成员配置SPI的时钟极性和时钟相位,这两个配置影响到SPI的通信模式,该设置要符合将要互相通信的设备的要求.CPOL分别可以取SPI_CPOL_High(SPI通信空闲时SCK为高电平)和SPI_CPOL_Low(SPI通信空闲时SCK为低电平)。CPHA则可以取SPI_CPHA_1Edge(在SCK的奇数边沿采集数据)和SPI_CPHA_2Edge(在SCK的偶数边沿采集数据)。
查阅FLASH的使用手册,可以了解到这个FLASH支持以SPI的模式0和模式3通信。即在SPI空闲时,SCK为低电平,奇数边沿采样(模式0);也可以在SPI空闲时,SCK为高电平,偶数边沿采样(模式3),即无论CPOL的状态是什么,Flash的数据采样时刻为SCK的上升沿。
我们在本实验中配置使用它的模式3,即把CPOL赋值为SPI_CPOL_High;CPHA赋值为SPI_CPHA_2Edge。
4)SPI_NSS:本成员配置NSS引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard)与软件模式(SPI_NSS_Soft),在硬件模式中的SPI片选信号由硬件自动产生,而软件模式则需要我们亲自把相应的GPIO端口拉高或拉低来产生非片选和片选信号。如果外界条件允许,硬件模式还会自动将STM32的SPI设置为主机。
5)SPI_BaudRatePrescaler:本成员设置波特率分频值,分频后的时钟即为SPI的SCK信号线的时钟频率。
6)SPI_FirstBit:所有串行的通信协议都会有MSB(高位数据在前)还是LSB先行(低位数据在前)的问题,而STM32的SPI模块可以通过这个结构体成员,对这个特性编程控制。据Flash的通信时序,我们向这个成员赋值为MSB先行(SPI_FirstBit_MSB).
7) SPI_CRCPolynomial:这是SPI的CRC校验中的多项式,若我们使用CRC校验时,就使用这个成员的参数(多项式)来计算CRC的值。由于本实验的Flash不支持CRC校验,所以我们向这个结构体成员赋值为7实际上是没有意义的。
控制FLASH的命令
实际上,编写设备的驱动都有一定的规律可循。
首先我们要确定设备使用的是什么通信协议。如EEPROM使用的是I2C协议。本章的Flash使用的是SPI,那么我们就根据它的通信协议,选择好STM32的硬件模块,并进行相应的I2C或SPI模块初始化。
因为不同的设备都会相应的有不同的指令,如EEPROM中会把第一个数据解释为存储矩阵的地址(实质就是指令)。而FLASH则定义了更多的指令,有写指令、读指令、读ID指令等。
对主机来说,这些指令只是它遵守最基本的通信协议发送出的数据。但设备把这些数据解释为不同的意义(指令编码),所以才成为指令。在我们配置好STM32的协议模块后,想要控制设备,就要遵守相应设备所定义的命令规则。
综上所述,驱动的编写原理:确定通信协议模块,通过协议收发命令、数据,进而驱动设备。
下图为Flash的各种命令和命令解释时序
指令表中的A0~A23指地址,M0~M7为器件的制造商ID,D0~D7为数据。
在命令列表中可以了解到读取设备ID的命令(Device ID)编码为ABh、dummy、dummy、dummy。表示此命令由这四个字节组成,其中dummy意为任意编码,表示这些编码可以发送任意数据。命令列表中带括号的字节数据表示由Flash返回给主机的响应,可以看到Release Power down or HPM/DeviceID命令的第5个字节为从机返回的响应。(ID1-ID0)即返回设备的ID号。
读Device指令时序的编程
下图为读Device指令的时序图
以看到主机首先通过MOSI线(即Flash的DI线)发送第一个字节为ABh编码,紧接着三个字节的dummy编码(即3个字节的伪数据),然后Flash就忽略DI线上的信号,通过MISO线(即Flash的DO线)把它的Flash设备ID发送给主机。
了解了Device ID命令及其时序,我们分析SPI_FLASH_ReadDeviceID()函数来说明驱动编写原理。
这个函数实现了读取Flash的ID 的功能
u32 SPI_FLASH_ReadDeviceID(void)
{
u32 Temp=0;
SPI_FLASH_CS_LOW();
//发送4字节指令
SPI_FLASH_SendByte(W25X_DeviceID);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_SendByte(Dummy_Byte);
//读取Flash返回的第5字节数据
Temp=SPI_FLASH_SendByte(Dummy_Byte);
SPI_FLASH_CS_HIGH();
return Temp;
}
这个函数的代码流程严格遵从DeviceID命令的时序:
1)SPI_FLASH_CS_LOW(),拉低CS线,片选FLASH,以使能FLASH设备。
2)利用SPI_FLASH_SendByte()向Flash发送第一个命令字节编码W25X_DeviceID,该宏展开后为0xAB.
3)根据指令表,发送完这个指令后,后面紧跟着三个字节的dummy byte,我们把Dummy_Byte宏定义为0xFF,实际上改成其它编码都可以,无影响。
4)完整的命令在前面已经发送完毕,根据时序,在第5个字节,Flash通过DO端口输出它的器件ID,我们调用函数SPI_FLASH_SendByte()接收返回的数据,并赋值给Temp变量。SPI_FLASH_ReadDeviceID()函数的返回值即为读取得到的器件ID。
5)拉高片选信号,结束通信。
这就完成了读FLASH的ID。在这个读FlashID 函数中多次调用了一个相对底层的用户函数,它实现了利用SPI发送和接收数据的功能:
u8 SPI_FLASH_SendByte(u8 byte)
{
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
SPI_I2S_SendData(SPI2,byte);
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
return SPI_I2S_ReceiveData(SPI2);
}
1)调用库函数SPI_I2S_GetFlagStatus()等待发送寄存器清空。
2)发送数据寄存器准备好后,调用库函数SPI_I2S_SendData()向从机发送数据。
3)调用库函数SPI_I2S_GetFlagStatus()等待接收数据寄存器非空。
4)接收寄存器非空时,调用SPI_I2S_ReceiveData()获取接收寄存器中的数据并作为函数的返回值,这个数据即由从机发回给主机的数据。
这是最底层的发送数据和接收数据的函数,利用库函数的标识检测确保通信正常。
读取厂商ID
对于其他函数,编写的方法是类似的,如读取厂商ID 的函数SPI_FLASH_ReadID()。
u32 SPI_FLASH_ReadID(void)
{
u32 Temp=0,Temp0=0,Temp1=0,Temp2=0;
SPI_CS_LOW();
SPI_FLASH_SendByte(W25X_JedecDeviceID);
Temp0=SPI_FLASH_SendByte(Dummy_Byte);
Temp1=SPI_FLASH_SendByte(Dummy_Byte);
Temp2=SPI_FLASH_SendByte(Dummy_Byte);
Temp=(Temp0<<16)|(Temp1<<8)|Temp2;
return Temp;
}
这里的W25X_JedcDeviceID的宏定义为0x9F.
这个函数根据命令流程,发送一个字节的命令代码(9F)后,从机就通过DO线返回厂商ID即0~16位的设备ID 。
FLASH芯片的读写以及擦除
1.扇区擦除
根据Flash的存储原理,在写入数据前要先对存储区域进行擦除。
所以执行SPI_FLASH_SectorErase()函数对要写入的扇区进行擦除,也称为预写。
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_SectorErase);
SPI_FLASH_SendByte((SectorAddr & 0xFF0000)>>16);
SPI_FLASH_SendByte((SectorAddr & 0xFF00)>>8);
SPI_FLASH_SendByte(SectorAddr & 0xFF);
SPI_FLASH_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
先忽略SPI_FLASH_WriteEnable()和SPI_FLASH_WaitForWriteEnd()函数。其余为纯粹的关于FLASH擦除操作,扇区擦除命令时序如下图:
其中第一个字节为扇区擦除命令编码(20h),紧跟其后的为要进行擦除的24位起始地址。
根据Flash的说明,它把整个存储矩阵分为块区和扇区,每块(Block)的大小为64KB,每个扇区(Sector)的大小为4KB,对存储矩阵进行擦除时,最小的单位为扇区。
2.写使能
根据Flash的读写要求,在进行写入、扇区擦除、块擦除、整片擦除及写状态寄存器前,都需要发送写使能命令。
在SPI_FLASH_SectroErase()函数中我们调用了SPI_FLASH_WriteEnable(),具体实现如下:
void SPI_FLASH_WriteEnable(void)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_WriteEnable);
SPI_FLASH_CS_HIGH();
}
本函数比较简单,就是根据写使能命令的时序,发送写使能命令write enable(06h)。
3.读Flash状态
在擦除函数SPI_FLASH_SectorErase()中,还调用了用户函数SPI_FLASH_WaitForWriteEnd()来确保在Flash不忙碌的时候,才发送命令与数据。
这个函数通过读取Flash的状态寄存器来获知它的工作状态。
void SPI_FLASH_WaitForWriteEnd(void)
{
u8 FLASH_Status=0;
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadStatusReg);
do
{
FLASH_Status=SPI_FLASH_SendByte(Dummy_Byte);
}
while((FLASH_Status & WIP_Flag) == SET );
SPI_FLASH_CS_HIGH();
}
本函数实质是不断检测Flash状态寄存器的Busy位,直到Flash的内部写时序完成,从而确保下一通信操作正常,这里WIP_Flag宏定义为0x01。
主机通过发送读状态寄存器命令Read Status Register(05h),返回它的8位状态寄存器值。
本函数检测的就是状态寄存器的Bit0位,即BUSY位。Flash在执行内部写时序的时候,除了读状态寄存器的命令,其他一切命令都会忽略,并且BUSY位保持为1,即我们需要等待BUSY位为0的时候,再向FLASH发送其他指令。
4.向FLASH写入数据
对Flash写入数据,最小单位是256字节,厂商把这个单位称为页。
写入时,一般也只有页写入的方式。
因而我们为了方便地把一个很长的数组写入Flash中,一般需要进行转换,把数组按页分好,再写入Flash中,如同I2C通信中对EEPROM的页写入一样,只是页的大小不同而已。
void SPI_FLASH_BufferWrite(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u8 NumOfPage=0,NumOfSingle=0,Addr=0,count=0,temp=0;
Addr=WriteAddr % SPI_FLASH_PageSize;
count=SPI_FLASH_PageSize-Addr;
NumOfPage=NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle=NumByteToWrite % SPI_FLASH_PageSize;
if (Addr == 0) /* WriteAddr is SPI_FLASH_PageSize aligned */
{
if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
else /* WriteAddr is not SPI_FLASH_PageSize aligned */
{
if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
{
if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
{
temp = NumOfSingle - count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
}
else
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if (NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
在SPI_FLASH_BufferWrite()中,对数组进行分页后,它调用了用户函数SPI_FLASH_PageWrite来对数据进行页写入。
void SPI_FLASH_PageWrite(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_CS_LOW();
//页编程指令
SPI_FLASH_SendByte(W25X_PageProgram);
//发送高8位地址
SPI_FLASH_SendByte((WriteAddr&0xFF0000)>>16);
//发送中8位地址
SPI_FLASH_SendByte((WriteAddr&0xFF00)>>8);
//发送低8位地址
SPI_FLASH_SendByte(WriteAddr&0xFF);
if(NumByteToWrite>SPI_FLASH_PerWritePageSize)
{
NumByteToWrite=SPI_FLASH_PerWritePageSize;
}
while(NumByteToWrite--)
{
SPI_FLASH_SendByte(*pBuffer);
pBuffer++;
}
SPI_FLASH_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
页写入时序如下图,发送完页写入指令PageProgram(02h)及地址后,可以连续写入最多256字节数据,在发送完数据之后,我们调用SPI_FLASH_WaitForWriteEnd()等待Flash内部写时序完成再退出函数。
5.从Flash读取数据
对于读取数据,发出一个命令后,可以无限制的把整个Flash的数据都读取完,若认为读取数据的数据量足够了,可以拉高片选信号以表示读取数据结束。
void SPI_FLASH_BufferRead(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadData);
SPI_FLASH_SendByte((ReadAddr&0xFF0000)>>16);
SPI_FLASH_SendByte((ReadAddr&0xFF00)>>8);
SPI_FLASH_SendByte(ReadAddr&0xFF);
while(NumByteToRead--)
{
*pBuffer=SPI_FLASH_SendByte(Dummy_Byte);
pBuffer++;
}
SPI_FLASH_CS_HIGH();
}
以上为读数据的时序图,SPI_FLASH_BufferRead()函数首先发送读数据指令ReadData(03h)。
紧接着发送24位读数据起始地址,STM32再通过DO线接收数据,并把他们使用指针的方式记录起来。