例程完整代码:

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

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

  1. STM32的SPI模式读写FLASH芯片全面讲解

    SPI协议简介 SPI协议,即串行外围设备接口,是一种告诉全双工的通信总线,它被广泛地使用在ADC,LCD等设备与MCU间通信的场合. SPI信号线 SPI包含4条总线,分别为SS,SCK,MOSI, ...

  2. stm32中spi可以随便接吗_stm32之SPI通信协议实例详解

    之前一直对SPI通信一知半解,所以想抽空把它搞得明白一些.考虑到之前是结合Flash芯片来学的,十分不直观,而且主要把时间和精力都花在Flash芯片的datasheet和驱动上了,SPI通信也没学好. ...

  3. fpga使用spi协议擦除读写flash芯片

    一.需求说明 fpga首先全擦除flash芯片,然后往flash芯片中写入一页数据,再接着读取这一页数据. 二.硬件介绍 1.flash资源介绍 M25P16芯片是flash芯片,容量是16Mbit. ...

  4. STM32学习之SPI协议(读写FLASH)

    关于STM32学习分享 第八章 SPI协议(读写FLASH) 文章目录 关于STM32学习分享 前言 二.代码 1.spi_flash.c 2.spi_flash.h 3.main.c 总结 前言 开 ...

  5. STM32自学笔记-4-SPI和Flash芯片

    I2C中曾经说过其实目前基本用Flash而不用E2PROM,E2PROM最大的特点是按字节操作,Flash则是按扇区操作.目前STM32单片机教程里基本上都是用W25Q128这款SPI Flash芯片 ...

  6. FPGA实现的SPI协议(二)----基于SPI接口的FLASH芯片M25P16的使用

    写在前面 SPI协议系列文章: FPGA实现的SPI协议(一)----SPI驱动 FPGA实现的SPI协议(二)----基于SPI接口的FLASH芯片M25P16的使用 在上篇文章,简要介绍了SPI协 ...

  7. stm32中常见的通信协议之SPI

    目录 1.SPI总线 2.SPI的寻址方式 3.SPI的工作原理总结 4.SPI的通讯过程 5.SPI的极性和相位 6.IIC和SPI的异同 7.stm32中SPI配置中常用的寄存器 8.stm32中 ...

  8. STM32笔记(十二)---SPI读写FLASH

    SPI读写FLASH 文章目录 SPI读写FLASH 一.SPI协议简介 1.1 SPI 物理层 1.2 协议层 1.2.1 SPI 基本通讯过程 1.2.2 通讯的起始和停止信号 1.2.3 数据有 ...

  9. STM32中的通信协议

    STM32中的通信协议 通讯协议是指在嵌入式开发中,不同的硬件系统或者操作系统之间进行数据交换的方式,是一种数据通讯的规约. 通讯协议有很多种,而我今天要说的是串口通讯协议,而且是基于STM32来说的 ...

最新文章

  1. centos 6.8 搭建svn服务器
  2. 新手探索NLP(十一)——知识图谱
  3. roc曲线怎么绘制_利用ROC曲线寻找最佳cutoff值(连续型变量组成的riskscore)
  4. linux 嗅探密码,Linux下嗅探又一实例截取
  5. JVM入门到放弃之基本概念
  6. docker的介绍和常用命令
  7. java应用程序怎样获取外接设备信号 通过usb
  8. Idle进程的切换过程
  9. 材料成型计算机仿真技术,材料成型计算机模拟分析(各种仿真软件介绍).ppt
  10. 计算机房管理制度通知,计算机房管理制度.doc
  11. Windows Server 2016关闭自动更新
  12. 微信小程序: wx:key详解
  13. 前端图片上传问题整理
  14. 论文笔记—RGB-D SLAM in Dynamic Environments Using Static Point Weighting
  15. 如何在今日头条做推广?今日头条推广怎么样?
  16. glReadPixels读取保存图片全黑
  17. matlab 点云根据法向量投影到六个平面
  18. The day that you see me old-当我日渐老去的时候
  19. 如何向开源社区贡献代码
  20. 西北大学844计算机考研真题,2018年西北大学信息科学与技术学院844软件工程学科专业基础综合之计算机操作系统考研基础五套测试题...

热门文章

  1. 试题 算法训练 逗志芃的危机 (Java实现 通俗易懂)
  2. 打包aab_[Android][AAB]使用Google Play的AAB打包,出现页面渲染失败
  3. 牛客网——求最小公倍数
  4. python做网络测试工具一
  5. 从志愿军“断刀”再论敏捷之道(上篇)
  6. xp如何添加桌面计算机回收站,xp系统桌面回收站不见了怎么办
  7. 科学计算机如何开机,计算器上关机和开机键分别是什么?
  8. 看雪2w3w安卓高级研修Frida原理学习
  9. 重庆邮电大学计算机2019湖北分数线,重庆邮电大学2019年各省各批次录取分数线...
  10. 搭建游戏联运系统需要什么条件?