stm32中spi可以随便接吗_STM32的SPI模式读写FLASH芯片全面讲解

论坛 期权论坛 脚本     
已经匿名di用户   2022-5-29 19:22   1723   0

例程完整代码:

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线接收数据,并把他们使用指针的方式记录起来。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:81
帖子:4969
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP