STM32F429入门(二十一):SPI协议及SPI读写FLASH 您所在的位置:网站首页 stm32f429和407有什么区别 STM32F429入门(二十一):SPI协议及SPI读写FLASH

STM32F429入门(二十一):SPI协议及SPI读写FLASH

2024-07-07 03:42| 来源: 网络整理| 查看: 265

IIC主要用于通讯速率一般的场合,而SPI一般用于较高速的场合。

一、SPI协议简介

SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设 备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间, 要求通讯速率较高的场合。

(一)物理层

 

SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为SS,它们的作用介绍如下:

SS:从设备选择信号线,常称为片选信号线,也称为NSS、CS。每个从设备都有独立的一条SS信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。IIC协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI协议中没有设备地址,它使用SS信号线来寻址,当主机选择从设备时,把该从设备的SS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以SS线置低电平为开始信号,以SS线被拉高作为结束信号。

SCK:时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样。(同步通讯)STM32 的 SPI 时钟频率最大为 fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。

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

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

(二)协议层

SPI协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。

(1)SPI基本通讯过程

通讯的起始和停止信号

SS信号线由高变低,是SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机检测到NSS线检测的起始信号后,就知道被选中了,开始准备与主机通讯。

当信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

数据有效性

SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步,MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。(图示为下降沿采集数据)

CPOL/CPHA及通讯模式

时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、NSS线为高电平时SCK的状态)。CPOL=0时,SCK在空闲状态时为低电平,CPOL=1时则相反。

时钟相位CPHA是指数据在采用的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。当CPHA=1时,数据线在SCK的“偶数边沿”采样。(无关上升沿下降沿),下图为奇数边沿采样。

 由CPOL及CPHA的不同状态,SPI分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。

 

二、STM32的SPI外设架构

 

STM32的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟 频率为fpclk/2 (STM32F429型号的芯片默认fpclk1为90MHz,fpclk2为45MHz), 完全支持SPI协议的4种模式,数据帧长度可设置为8位或16位,可设置数据 MSB先行(高位先行,从左往右)或LSB先行(低位先行,从右往左)。它还支持双线全双工(前面小节说明的都是这种模式)、 双线单向以及单线模式。

1-通讯引脚,2-时钟控制逻辑,3-数据控制逻辑,4-整体控制逻辑。

(1)通讯引脚

其中SPI1\SPI4\SPI5\SPI6是APB2上的设备,最高通讯速率达到45Mbit/s,SPI2\SPI3是APB1上的设备,最高通信速率为22.5Mbit/s。其它功能上没有差异。SPI2\SPI3引脚上上均有I2S,可用来设置音频,但是IIS与SPI不可以共用。

(2)时钟控制逻辑

SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是SCK引脚的输出时钟频率。

 其中fpclk频率是指SPI所在的APB总线频率,APB1为fpclk1,APB2为fpclk2。为了协调通讯速度比较慢的设备。

(3)数据控制逻辑

SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源于接收缓冲区及发送缓冲区。

通过写SPI的数据寄存器DR把数据填充到发送缓冲区中。

通过读数据寄存器DR,可以获取接收缓冲区的内容。

其中数据帧长度可以通过控制寄存器DR的DFF位配置成8位及16位模式:配置LSBFIRST位可以选择MSB先行还是LSB先行。

SPI 的 MOSI 及 MISO 都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及 MISO、MOSI 线。当向外发送数据的时候,数据移位寄存器以 “发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的 时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。

 

(4)整体控制逻辑

整体控制逻辑复制协调整个SPI外设。控制逻辑的工作模式根据我们配置的“控制寄 存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的 SPI 模式、波特率、LSB 先行、主从模式、单双向模式等等。我们可以通过工作状态寄存器读取SPI的工作状态,“状态寄存器(SR)”。控制逻辑还可以根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。在实际的应用中,我们一般不使用SPI外设的标准NSS信号线,而是更简单地使用普通GPIO,软件控制它地电平输出,从而产生通讯起始和停止信号。

(5)通讯过程

 TXE标志代表的是缓冲区是否为空,当TXE为0时,发送缓冲区为非空,若为1时,发送缓冲区为空。当其为空时,也就说明可以准备发送下一个数据。RXNE为接收缓冲区是否为空的标志,其中0代表接收缓冲区为空,1代表接收缓冲区非空。

控制NSS信号线,产生起始信号。

把要发送的数据写入到”数据寄存器DR“中,该数据会被存储到发送缓冲区。

通讯开始,SCK时钟开始运行。MOSI把发送缓冲区中的数据一位一位地传输出去;MISO则把数据一位一位地存储进接收缓冲区中;

当发送完一帧数据的时候,”状态寄存器SR“中的"TXE标志位"会被置1,表示传输完一帧,发送缓冲区已空;类似的,当接收完一帧数据的时候,”RXNE标志位“会被置1,表示传输完一帧,接收缓冲区非空;

等待到”TXE标志位“为1时,若还要继续发送数据,则再次往”数据寄存器DR“写入数据即可;等待到”RXENE标志位“为1时,通过读取”数据寄存器DR“可以获取接收缓冲区中的内容。

假如使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入同一个中断服务函数,到SPI中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用DMA方式来收发”数据寄存器DR“中的数据。

需要注意的是CR寄存器中的SSM位:

 当我们让这个寄存器置1时,我们可以通过软件来模拟SPI,这也是比较常用的方式。

三、SPI结构体

typedef struct { uint16_t SPI_Direction; /*设置 SPI 的单双向模式 */ uint16_t SPI_Mode; /*设置 SPI 的主/从机端模式 */ uint16_t SPI_DataSize; /*设置 SPI 的数据帧长度,可选 8/16 位 */ uint16_t SPI_CPOL; /*设置时钟极性 CPOL,可选高/低电平*/ uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */ uint16_t SPI_NSS; /*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/ uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子,fpclk/分频数=fSCK */ uint16_t SPI_FirstBit; /*设置 MSB/LSB 先行 */ uint16_t SPI_CRCPolynomial; /*设置 CRC 校验的表达式 */ } SPI_InitTypeDef;

SPI_Direction:有双线全双工、双线只接收、单线只接收、单线只发送模式。

SPI_Mode:主机模式、从机模式。这两个模式的最大区别是在于时钟信号线SCK信号线的时序,SCK的时序由通讯中的主机产生。若被设置为从机模式,则要接受外来的SCK信号。

SPI_DataSize:可以选择SPI通讯的数据帧大小为8位或者16位。

SPI_CPOL和SPI_CPHA:这两个成员配置SPI的时钟极性CPOL和时钟相位CPHA,这两个配置影响到SPI的通讯模式。时钟极性CPOL成员,可以设置为高电平或者为低电平。时钟相位CPHA成员,可以设置为在SCK奇数边沿采集数据或者是偶数边沿。

SPI_NSS:可以选择硬件模式或软件模式。在硬件模式中的SPI片选信号由SPI硬件自动产生,而软件模式则需要亲自把相应的GPIO端口拉高或者置低产生非片选和片选信号。

SPI_BaudRatePrescaler:参数可以设定为2、4、6、8、16、32、64、128、256分频。

SPI_FirstBit:MSB先行(高数据在前)还是LSB先行(低位数据在前)。

SPI_CRCPolynomial:适用于比较复杂的环境,这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数 (多项式),来计算 CRC 的值。

四、实践——SPI读写串行FLASH

 上面是我们即将改写的FLASH芯片。容量为16M。NCS引脚也为NSS引脚,DIO为MOSI引脚,DO为MISO引脚。WP为写保护引脚,低电平有效。HOLD为暂停通讯或结束通讯,用的很少,接为高电平。以下为引脚的连接图。

 在FLASH中,它一共有0-255即256个块(Block),每个块是64KB,16M=64*255/1024。每个块右分为0-15个扇区(Sector),每个扇区4KB。写入数据之前,必须要擦除数据,再重新写入数据,擦除的最小单位为扇区。设备ID为4018H,设备ID可以用来判断设备是否连接正常,以及设备是否配套正确。擦除整个芯片的命令为:C7h/60h。擦除扇区的命令为20h。此芯片为MSB先行。以上为该芯片手册中得出。

 

了解这款FLASH后,我们开始进行读写。

(1)定义引脚以及时钟

#define FLASH_SPI SPI5 #define FLASH_SPI_CLK RCC_APB2Periph_SPI5 #define RCC_APB_CLOCK_FUN RCC_APB2PeriphClockCmd #define FLASH_SPI_CS_GPIO_PORT GPIOF #define FLASH_SPI_CS_GPIO_CLK RCC_AHB1Periph_GPIOF #define FLASH_SPI_CS_PIN GPIO_Pin_6 #define FLASH_SPI_SCK_GPIO_PORT GPIOF #define FLASH_SPI_SCK_GPIO_CLK RCC_AHB1Periph_GPIOF #define FLASH_SPI_SCK_PIN GPIO_Pin_7 #define FLASH_SPI_SCK_AF GPIO_AF_SPI5 #define FLASH_SPI_SCK_SOURCE GPIO_PinSource7 #define FLASH_SPI_MISO_GPIO_PORT GPIOF #define FLASH_SPI_MISO_GPIO_CLK RCC_AHB1Periph_GPIOF #define FLASH_SPI_MISO_PIN GPIO_Pin_8 #define FLASH_SPI_MISO_AF GPIO_AF_SPI5 #define FLASH_SPI_MISO_SOURCE GPIO_PinSource8 #define FLASH_SPI_MOSI_GPIO_PORT GPIOF #define FLASH_SPI_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOF #define FLASH_SPI_MOSI_PIN GPIO_Pin_9 #define FLASH_SPI_MOSI_AF GPIO_AF_SPI5 #define FLASH_SPI_MOSI_SOURCE GPIO_PinSource9

(2)引脚初始化(复用GPIO)

#define CS_HIGH_DISABLE() GPIO_SetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN) #define CS_LOW_ENABLE() GPIO_ResetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN) void FLASH_SPI_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; //1.初始化GPIO RCC_AHB1PeriphClockCmd(FLASH_SPI_CS_GPIO_CLK|FLASH_SPI_SCK_GPIO_CLK|FLASH_SPI_MISO_ GPIO_CLK|FLASH_SPI_MOSI_GPIO_CLK,ENABLE); /* 连接 引脚源*/ GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT,FLASH_SPI_SCK_SOURCE,FLASH_SPI_SCK_AF); /* 连接 */ GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT,FLASH_SPI_MISO_SOURCE,FLASH_SPI_MISO_AF); GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT,FLASH_SPI_MOSI_SOURCE,FLASH_SPI_MOSI_AF); /* 使能 SPI 时钟 */ RCC_APB_CLOCK_FUN(FLASH_SPI_CLK, ENABLE); /* GPIO初始化 */ GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //复用引脚配置为输出模式也可以进行输入 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; /* 配置SCK引脚为复用功能 */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN ; GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure); /* 配置MISO引脚为复用功能 */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN; GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure); /* 配置MOSI引脚为复用功能 */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN; GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure); /*CS引脚 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出,本身硬件就有一个上拉 /* 配置SCK引脚为复用功能 */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN ; GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure); //2.配置SPI工作模式 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //最快的分频 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge ; //偶数边沿 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High ; //空闲时SCK时钟高电平 SPI_InitStructure.SPI_CRCPolynomial = 0 ; //不需要使用CRC校验 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据帧 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双向 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//软件配置 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机 SPI_Init(FLASH_SPI,&SPI_InitStructure); SPI_Cmd(FLASH_SPI,ENABLE); CS_HIGH_DISABLE(); }

为了看初始化是否成功,我们可以获取设备ID来检验。获取ID的命令为:9Fh。

 

uint32_t Read_Device_ID(void) { uint8_t temp[3]; //拉低片选 CS_LOW_ENABLE(); Read_Write_Byte(JEDEC_ID); temp[0] = Read_Write_Byte(DUMMY); //发送任意字节,产生时序,下面同理 temp[1] = Read_Write_Byte(DUMMY); temp[2] = Read_Write_Byte(DUMMY); //拉高片选 CS_HIGH_DISABLE(); //将数据进行组合 return temp[0]>16)&0xFF); Read_Write_Byte((addr>>8)&0xFF); Read_Write_Byte(addr&0xFF); //拉高片选 CS_HIGH_DISABLE(); //等待内部时序(等待擦除完成) } //写使能函数 void Write_Enable(void) { //拉低片选 CS_LOW_ENABLE(); Read_Write_Byte(WRITE_ENABLE); //拉高片选 CS_HIGH_DISABLE(); }

在读取的过程中,我们需要得知状态,看它是否空闲后再写入数据、擦除数据、读取数据,这个函数需要在拉低片选前使用:

void Wait_for_Ready(void) { uint8_t reg_status=0x01; while(reg_status &0x01) { //拉低片选 CS_LOW_ENABLE(); //读状态寄存器 Read_Write_Byte(READ_STATUS); reg_status = Read_Write_Byte(DUMMY); //拉高片选 CS_HIGH_DISABLE(); } }

读取数据的函数如下(整块数据而非单个):

void Read_buffer(uint8_t* pdata,uint32_t addr,uint32_t numByteTorRead) { Wait_for_Ready(); //拉低片选 CS_LOW_ENABLE(); Read_Write_Byte(READ_DATA); Read_Write_Byte((addr>>16)&0xFF); Read_Write_Byte((addr>>8)&0xFF); Read_Write_Byte(addr&0xFF); while(numByteTorRead--) { *pdata = Read_Write_Byte(DUMMY); pdata++; } //拉高片选 CS_HIGH_DISABLE(); }

完成了读数据,接下来是写入数据,最多写入256个数据:

void Write_buffer(uint8_t* pdata,uint32_t addr,uint32_t numByteTorWrite) { Write_Enable(); Wait_for_Ready(); //拉低片选 CS_LOW_ENABLE(); Read_Write_Byte(PAGE_PROGRAM); Read_Write_Byte((addr>>16)&0xFF); Read_Write_Byte((addr>>8)&0xFF); Read_Write_Byte(addr&0xFF); while(numByteTorWrite--) { Read_Write_Byte(*pdata); pdata++; } //拉高片选 CS_HIGH_DISABLE(); }

主函数:

uint8_t readBuff[4096] = {0x0}; uint8_t writeBuff[256] = {0x0}; int main(void) { uint32_t device_id = 0; uint32_t i=0; /*初始化USART 配置模式为 115200 8-N-1,中断接收*/ Debug_USART_Config(); FLASH_SPI_Config(); /* 发送一个字符串 */ Usart_SendString( DEBUG_USART,"这是一个FLASH实验\n"); printf("这是一个FLASH实验\n"); device_id = Read_Device_ID(); printf("device_id =0x%x",device_id); erace_setor(0x00);//FLASH先擦除后写入 //读出擦除后的数据 Read_buffer(readBuff,0x00,4096); printf("\r\n*************读出擦除后的数据**********\r\n"); for(i=0;i> 16); /* 发送擦除扇区地址的中后8位 */ SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8); /* 发送擦除扇区地址的低8位 */ SPI_FLASH_SendByte(SectorAddr & 0xFF); /* 停止信号 FLASH: CS 高电平 */ SPI_FLASH_CS_HIGH(); /* 等待擦除完毕*/ SPI_FLASH_WaitForWriteEnd(); }

(4)FLASH的页写入

FLASH的页写入命令最多一次可以传输256个字节数据,这个单位也是页大小。FLASH页写入的时序如图:

 

从时序图可知,第 1 个字节为“页写入指令”编码,24 字节为要写入的“地址 A”, 接着的是要写入的内容,最多个可以发送 256 字节数据,这些数据将会从“地址 A”开始, 按顺序写入到 FLASH 的存储矩阵。若发送的数据超出 256 个,则会覆盖前面发送的数据。

与擦除指令不一样,页写入指令的地址并不要求按 256 字节对齐,只要确认目标存储 单元是擦除状态即可(即被擦除后没有被写入过)。所以,若对“地址 x”执行页写入指令后, 发送了 200 个字节数据后终止通讯,下一次再执行页写入指令,从“地址(x+200)”开始写 入 200 个字节也是没有问题的(小于 256 均可)。

/* * @brief 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区 * @param pBuffer,要写入数据的指针 * @param WriteAddr,写入地址 * @param NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize * @retval 无 */ void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u32 NumByteToWrite) { /* 发送FLASH写使能命令 */ SPI_FLASH_WriteEnable(); /* 选择FLASH: CS低电平 */ SPI_FLASH_CS_LOW(); /* 写页写指令*/ SPI_FLASH_SendByte(W25X_PageProgram); /*发送写地址的高8位*/ SPI_FLASH_SendByte((WriteAddr & 0xFF000000) >> 24); /*发送写地址的中前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; FLASH_ERROR("SPI_FLASH_PageWrite too large!"); } /* 写入数据*/ while (NumByteToWrite--) { /* 发送当前要写入的字节数据 */ SPI_FLASH_SendByte(*pBuffer); /* 指向下一字节数据 */ pBuffer++; } /* 停止信号 FLASH: CS 高电平 */ SPI_FLASH_CS_HIGH(); /* 等待写入完毕*/ SPI_FLASH_WaitForWriteEnd(); }

先发送“写使能”命令,接着才开始页写入时序,然后发送指令 编码、地址,再把要写入的数据一个接一个地发送出去,发送完后结束通讯,检查 FLASH 状态寄存器,等待 FLASH 内部写入结束。

当我们有不定量数据写入时,大于256时,可以用下面的函数:

/** * @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区 * @param pBuffer,要写入数据的指针 * @param WriteAddr,写入地址 * @param NumByteToWrite,写入数据长度 * @retval 无 */ void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u32 NumByteToWrite) { u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0; /*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/ Addr = WriteAddr % SPI_FLASH_PageSize; /*差count个数据值,刚好可以对齐到页地址*/ count = SPI_FLASH_PageSize - Addr; /*计算出要写多少整数页*/ NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; /*mod运算求余,计算出剩余不满一页的字节数*/ NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; /* Addr=0,则WriteAddr 刚好按页对齐 aligned */ if (Addr == 0) { /* NumByteToWrite < SPI_FLASH_PageSize */ if (NumOfPage == 0) { 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); } } /* 若地址与 SPI_FLASH_PageSize 不对齐 */ else { /* NumByteToWrite < SPI_FLASH_PageSize */ if (NumOfPage == 0) { /*当前页剩余的count个位置比NumOfSingle小,写不完*/ if (NumOfSingle > count) { temp = NumOfSingle - count; /*先写满当前页*/ SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); WriteAddr += count; pBuffer += count; /*再写剩余的数据*/ SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp); } else /*当前页剩余的count个位置能写完NumOfSingle个数据*/ { SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); } } else /* NumByteToWrite > SPI_FLASH_PageSize */ { /*地址不对齐多出的count分开处理,不加入这个运算*/ 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); } } } }

(5)从FLASH读取数据

相对于写入,FLASH 芯片的数据读取要简单得多,使用读取指令“Read Data”即可。

发送了指令编码及要读的起始地址后,FLASH 芯片就会按地址递增的方式返回存储矩 阵的内容,读取的数据量没有限制,只要没有停止通讯,FLASH 芯片就会一直返回数据。

/* * @brief 读取FLASH数据 * @param pBuffer,存储读出数据的指针 * @param ReadAddr,读取地址 * @param NumByteToRead,读取数据长度 * @retval 无 */ void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u32 NumByteToRead) { /* 选择FLASH: CS低电平 */ SPI_FLASH_CS_LOW(); /* 发送 读 指令 */ SPI_FLASH_SendByte(W25X_ReadData); /* 发送 读 地址高8位 */ SPI_FLASH_SendByte((ReadAddr & 0xFF000000) >> 24); /* 发送 读 地址中前8位 */ SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16); /* 发送 读 地址中后8位 */ SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8); /* 发送 读 地址低8位 */ SPI_FLASH_SendByte(ReadAddr & 0xFF); /* 读取数据 */ while (NumByteToRead--) { /* 读取一个字节*/ *pBuffer = SPI_FLASH_SendByte(Dummy_Byte); /* 指向下一个字节缓冲区 */ pBuffer++; } /* 停止信号 FLASH: CS 高电平 */ SPI_FLASH_CS_HIGH(); }

 六、FLASH存储小数和整数

需要注意的是,存储各种数据类型的时候,我们需要将不同的数据类型分在不同的扇区,不可以混着,主要原因是下面这个动态存储,因为不同的字节数存储的方式不一样,比如说,存储整数,我们将整数通过十六进制传入,占两个字节,当我们需要读取时,四个字节四个字节的读,即合为一个整数,若这个时候我们用浮点数的方式来运算,它就为八个字节八个字节的读,会出错。所以,当我们存储不管是浮点数还是整数,存储方式都一样,可是你想读出来的时候,你就应该区分他们之间的区别,不可以混为一谈,怎么读数据,还是取决于上位机的处理。

/*写入小数数据到第一页*/ SPI_FLASH_BufferWrite((void*)double_buffer, SPI_FLASH_PageSize*1, sizeof(double_buffer)); /*写入整数数据到第二页*/ SPI_FLASH_BufferWrite((void*)int_bufffer, SPI_FLASH_PageSize*2, sizeof(int_bufffer));

 SPI协议初步就学习到这啦,国庆也就结束了,好像任务量也没有完成很多,接下来也要忙比赛啦,希望自己再接再厉。

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有