目录
一、STM32 的 SPI 特性及架构:
二、SPI 初始化结构体详解:
三、硬件SPI—读写串行 FLASH 实验
1、引脚简介:
(1) (Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、 CS,以下用 NSS 表示。
(2) SCK (Serial Clock): 时钟信号线,用于通讯数据同步。两个设备之间通讯时,通讯速率受限于低速设备。
(3) MOSI (Master Output, Slave Input): 主设备输出/从设备输入引脚。
(4) MISO(Master Input,, Slave Output): 主设备输入/从设备输出引脚。
2、STM32 的 SPI 通讯引脚:
其中 SPI1、 SPI4、 SPI5、 SPI6 是 APB2 上的设备,最高通信速率达 45Mbtis/s, SPI2、SPI3 是 APB1 上的设备,最高通信速率为 22.5Mbits/s。
3、通讯过程:
4、SPI 的四种模式:
由 CPOL 及 CPHA 的不同状态, SPI 分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。
5、 W25Q256内存划分情况:
W25Q256芯片:
每个芯片 0-511个块 --- 每个块64KB
每个块有 0-15个扇区 --- 每个扇区4KB
512块 X 64KB = 32768KB = 32M ( 32768KB / 1024 )
STM32标准库提供了SPI初始化结构体及初始化函数来配置SPI外设。初始化结构体及函数定义在库文件“stm32f4xx_spi.h”及“stm32f4xx_spi.c”中。
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;
(1) SPI_Direction
本成员设置 SPI 的通讯方向,可设置为双线全双工(SPI_Direction_2Lines_FullDuplex),双线只接收(SPI_Direction_2Lines_RxOnly),单线只接收(SPI_Direction_1Line_Rx)、单线只发送模式(SPI_Direction_1Line_Tx)。
(2) SPI_Mode
本成员设置 SPI 工作在主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。
(3) SPI_DataSize
本成员可以选择 SPI 通讯的数据帧大小是为 8 位(SPI_DataSize_8b)或16 位(SPI_DataSize_16b)。
(4) SPI_CPOL 和 SPI_CPHA
时钟极性 CPOL 成员,可设置为高电平(SPI_CPOL_High)或低电平(SPI_CPOL_Low )。
时钟相位 CPHA 则可以设置为 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 或SPI_CPHA_2Edge (在 SCK 的偶数边沿采集数据) 。
(5) SPI_NSS
本成员配置 NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_Hard )与软件模式(SPI_NSS_Soft ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
(6) SPI_BaudRatePrescaler
本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。
(7) SPI_FirstBit
所有串行的通讯协议都会有 MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。
(8) SPI_CRCPolynomial
这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式),来计算 CRC 的值。
FLSAH 存储器又称闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。
我们生活中常用的 U 盘、 SD卡、 SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是 FLASH 芯片只能一大片一大片地擦写,而 EEPROM 可以单个字节擦写。
一种使用 SPI 通讯的串行 FLASH 存储芯片的读写实验,实验中 STM32 的 SPI 外设采用主模式, 通过查询事件的方式来确保正常通讯。
1、硬件设计
本实验中的 FLASH 芯片(型号: W25Q256)是一种使用 SPI 通讯协议的 NOR FLASH存储器,它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SDI 引脚NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS 引脚是一个普通的 GPIO,不是 SPI 的专用NSS 引脚,所以程序中我们要使用软件控制的方式。
FLASH 芯片中还有 WP 和 HOLD 引脚。 WP 引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。 HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。
我们直接接电源,不使用通讯暂停功能。
2、软件设计
(1)编程要点
(2)代码分析
① SPI 硬件相关宏定义
//SPI 号及时钟初始化函数
#define FLASH_SPI SPI5
#define FLASH_SPI_CLK RCC_APB2Periph_SPI5
#define FLASH_SPI_CLK_INIT RCC_APB2PeriphClockCmd
//SCK 引脚
#define FLASH_SPI_SCK_PIN GPIO_Pin_7
#define FLASH_SPI_SCK_GPIO_PORT GPIOF
#define FLASH_SPI_SCK_GPIO_CLK RCC_AHB1Periph_GPIOF
#define FLASH_SPI_SCK_PINSOURCE GPIO_PinSource7
#define FLASH_SPI_SCK_AF GPIO_AF_SPI5
//MISO 引脚
#define FLASH_SPI_MISO_PIN GPIO_Pin_8
#define FLASH_SPI_MISO_GPIO_PORT GPIOF
#define FLASH_SPI_MISO_GPIO_CLK RCC_AHB1Periph_GPIOF
#define FLASH_SPI_MISO_PINSOURCE GPIO_PinSource8
#define FLASH_SPI_MISO_AF GPIO_AF_SPI5
//MOSI 引脚
#define FLASH_SPI_MOSI_PIN GPIO_Pin_9
#define FLASH_SPI_MOSI_GPIO_PORT GPIOF
#define FLASH_SPI_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOF
#define FLASH_SPI_MOSI_PINSOURCE GPIO_PinSource9
#define FLASH_SPI_MOSI_AF GPIO_AF_SPI5
//CS(NSS)引脚
#define FLASH_CS_PIN GPIO_Pin_6
#define FLASH_CS_GPIO_PORT GPIOF
#define FLASH_CS_GPIO_CLK RCC_AHB1Periph_GPIOF
//控制 CS(NSS)引脚输出低电平
#define SPI_FLASH_CS_LOW() {FLASH_CS_GPIO_PORT->BSRRH=FLASH_CS_PIN;}
//控制 CS(NSS)引脚输出高电平
#define SPI_FLASH_CS_HIGH() {FLASH_CS_GPIO_PORT->BSRRL=FLASH_CS_PIN;}
与 FLASH 通讯使用的 SPI 号 、引脚号、引脚源以及复用功能映射都以宏封装起来,并且定义了控制 CS(NSS)引脚输出电平的宏,以便配置产生起始和停止信号时使用。
② 初始化 SPI 的 GPIO
/**
* @brief SPI_FLASH 初始化
* @param 无
* @retval 无
*/
void SPI_FLASH_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能 FLASH_SPI 及 GPIO 时钟 */
/*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,
SPI_FLASH_SPI_MISO_GPIO 和 SPI_FLASH_SPI_SCK_GPIO 时钟使能 */
RCC_AHB1PeriphClockCmd (FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK|
FLASH_SPI_MOSI_GPIO_CLK|FLASH_CS_GPIO_CLK, ENABLE);
/*!< SPI_FLASH_SPI 时钟使能 */
FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE);
//设置引脚复用 GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT,FLASH_SPI_SCK_PINSOURCE,
FLASH_SPI_SCK_AF); GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT,FLASH_SPI_MISO_PINSOURCE,
FLASH_SPI_MISO_AF); GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT,FLASH_SPI_MOSI_PINSOURCE,
FLASH_SPI_MOSI_AF);
/*!< 配置 SPI_FLASH_SPI 引脚: SCK */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: MISO */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: MOSI */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: CS */
GPIO_InitStructure.GPIO_Pin = FLASH_CS_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStructure);
/* 停止信号 FLASH: CS 引脚高电平*/
SPI_FLASH_CS_HIGH();
}
向 GPIO 初始化结构体赋值,把 SCK/MOSI/MISO 引脚初始化成复用推挽模式。而CS(NSS)引脚由于使用软件控制,我们把它配置为普通的推挽输出模式。
③ 配置 SPI 的模式
/**
* @brief SPI_FLASH 初始化
* @param 无
* @retval 无
*/
void SPI_FLASH_Init(void)
{
SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure;
/* 使能 FLASH_SPI 及 GPIO 时钟 */
/*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,
SPI_FLASH_SPI_MISO_GPIO,SPI_FLASH_SPI_SCK_GPIO 时钟使能 */
RCC_AHB1PeriphClockCmd (FLASH_SPI_SCK_GPIO_CLK |FLASH_SPI_MISO_GPIO_CLK|FLASH_SPI_MOSI_GPIO_CLK|FLASH_CS_GPIO_CLK, ENABLE);
/*!< SPI_FLASH_SPI 时钟使能 */
//设置引脚复用
GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT,FLASH_SPI_SCK_PINSOURCE,FLASH_SPI_SCK_AF);
GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT,FLASH_SPI_MISO_PINSOURCE,FLASH_SPI_MISO_AF);
GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT,FLASH_SPI_MOSI_PINSOURCE,FLASH_SPI_MOSI_AF);
/*!< 配置 SPI_FLASH_SPI 引脚: SCK */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: MISO */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: MOSI */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT,
&GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: CS */
GPIO_InitStructure.GPIO_Pin = FLASH_CS_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStructure);
/* 停止信号 FLASH: CS 引脚高电平*/
SPI_FLASH_CS_HIGH();
/* FLASH_SPI 模式配置 */
// FLASH 芯片 支持 SPI 模式 0 及模式 3,据此设置 CPOL CPHA
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_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(FLASH_SPI, &SPI_InitStructure);
/* 使能 FLASH_SPI */
SPI_Cmd(FLASH_SPI, ENABLE);
/* 使 SPI_FLASH 进入 4 字节地址模式 */
SPI_FLASH_Mode_Init();
}
这段代码中,把 STM32 的 SPI 外设配置为主机端,双线全双工模式,数据帧长度为 8位,使用 SPI 模式 3(CPOL=1, CPHA=1), NSS 引脚由软件控制以及 MSB 先行模式。最后一个成员为 CRC 计算式,由于我们与 FLASH 芯片通讯不需要 CRC 校验,并没有使能 SPI的 CRC 功能,这时 CRC 计算式的成员值是无效的。最后配置 W25Q256 进入 4 字节地址模式,默认是 3 字节地址模式。
④ 使用 SPI 发送和接收一个字节的数据
/*
* @brief 使用 SPI 发送一个字节的数据
* @param byte:要发送的数据
* @retval 返回接收到的数据
*/
u8 SPI_FLASH_SendByte(u8 byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待发送缓冲区为空,TXE 事件 */
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET)
{
if ((SPITimeout--) == 0)
return SPI_TIMEOUT_UserCallback(0);
}
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
SPI_I2S_SendData(FLASH_SPI, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接收缓冲区非空,RXNE 事件 */
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET)
{
if ((SPITimeout--) == 0)
return SPI_TIMEOUT_UserCallback(1);
}
/* 读取数据寄存器,获取接收缓冲区数据 */
return SPI_I2S_ReceiveData(FLASH_SPI);
}
/**
* @brief 使用 SPI 读取一个字节的数据
* @param 无
* @retval 返回接收到的数据
*/
u8 SPI_FLASH_ReadByte(void)
{
return (SPI_FLASH_SendByte(Dummy_Byte));
}
⑤ 控制 FLASH 的指令
⑥ 定义 FLASH 指令编码表
FLASH 芯片的常用指令编码使用宏来封装起来,后面需要发送指令编码的时候我们直接使用这些宏即可。
/*命令定义-开头*******************************/
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_PageProgram 0x02
#define W25X_BlockErase 0xD8
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID 0x9F
#define W25X_Enter4ByteMode 0xB7
#define W25X_ReadStatusRegister3 0x15
#define WIP_Flag 0x01
#define Dummy_Byte 0xFF
⑦ 读取 FLASH 芯片 ID
/*
* @param 无
* @retval FLASH ID
*/
u32 SPI_FLASH_ReadID(void)
{
u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
/* 开始通讯:CS 低电平 */
SPI_FLASH_CS_LOW();
/* 发送 JEDEC 指令,读取 ID */
SPI_FLASH_SendByte(W25X_JedecDeviceID);
/* 读取一个字节数据 */
Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
/* 读取一个字节数据 */
Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
/* 读取一个字节数据 */
Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
/* 停止通讯:CS 高电平 */
SPI_FLASH_CS_HIGH();
/*把数据组合起来,作为函数的返回值*/
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
实现了“ JEDEC ID”指令的时序:发送一个字节的指令编码“ W25X_JedecDeviceID”,然后读取 3 个字节,获取 FLASH 芯片对该指令的响应,最后把读取到的这 3 个数据合并到一个变量 Temp 中,然后作为函数返回值,把该返回值与我们定义的宏“ sFLASH_ID”对比,即可知道 FLASH 芯片是否正常。
⑧ FLASH 写使能以及读取当前状态
在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“ Write Enable”命令即可写使能。
写使能命令
/**
* @brief 向 FLASH 发送 写使能 命令
* @param none
* @retval none
*/
void SPI_FLASH_WriteEnable(void)
{
/* 通讯开始:CS 低 */
SPI_FLASH_CS_LOW();
/* 发送写使能命令*/
SPI_FLASH_SendByte(W25X_WriteEnable);
/*通讯结束:CS 高 */
SPI_FLASH_CS_HIGH();
}
通过读状态寄存器等待 FLASH 芯片空闲
/*WIP(BUSY)标志:FLASH 内部正在写入*/
#define WIP_Flag 0x01
/**
* @brief 等待 WIP(BUSY)标志被置 0,即等待到 FLASH 内部数据写入完毕
* @param none
* @retval none
*/
void SPI_FLASH_WaitForWriteEnd(void)
{
u8 FLASH_Status = 0;
/* 选择 FLASH: CS 低 */
SPI_FLASH_CS_LOW();
/* 发送 读状态寄存器 命令 */
SPI_FLASH_SendByte(W25X_ReadStatusReg);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 若 FLASH 忙碌,则等待 */
do
{
/* 读取 FLASH 芯片的状态寄存器 */
FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
if ((SPITimeout--) == 0)
{
SPI_TIMEOUT_UserCallback(4);
return;
}
}
while ((FLASH_Status & WIP_Flag) == SET); /* 正在写入标志 */
/* 停止信号 FLASH: CS 高 */
SPI_FLASH_CS_HIGH();
}
发送读状态寄存器的指令编码“ W25X_ReadStatusReg”后,在 while 循环里持续获取寄存器的内容并检验它的“ WIP_Flag 标志” (即 BUSY 位),一直等待到该标志表示写入结束时才退出本函数,以便继续后面与 FLASH 芯片的数据通讯。
⑨ FLASH 扇区擦除
由于 FLASH 存储器的特性决定了它只能把原来为“ 1”的数据位改写成“0”,而原来为“ 0”的数据位不能直接改写为“ 1”。所以这里涉及到数据“擦除”的概念。
扇区擦除指令的第一个字节为指令编码,紧接着发送的 4 个字节用于表示要擦除的存储矩阵地址。要注意的是在扇区擦除指令前,还需要先发送“写使能”指令,发送扇区擦除指令后,通过读取寄存器状态等待扇区擦除操作完毕。
/*
* @brief 擦除 FLASH 扇区
* @param SectorAddr:要擦除的扇区地址
* @retval 无
*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
/* 发送 FLASH 写使能命令 */
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
/* 擦除扇区 */
/* 选择 FLASH: CS 低电平 */
SPI_FLASH_CS_LOW();
/* 发送扇区擦除指令*/
SPI_FLASH_SendByte(W25X_SectorErase);
/*发送擦除扇区地址的高 8 位*/
SPI_FLASH_SendByte((SectorAddr & 0xFF000000) >> 24);
/*发送擦除扇区地址的中前 8 位*/
SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
/* 发送擦除扇区地址的中后 8 位 */
SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
/* 发送擦除扇区地址的低 8 位 */
SPI_FLASH_SendByte(SectorAddr & 0xFF);
/* 停止信号 FLASH: CS 高电平 */
SPI_FLASH_CS_HIGH();
/* 等待擦除完毕*/
SPI_FLASH_WaitForWriteEnd();
}
⑩ FLASH 的页写入
FLASH 芯片也有页写入命令,使用页写入命令最多可以一次向 FLASH 传输 256 个字节的数据,我们把这个单位为页大小。
/**
* @brief 对 FLASH 按页写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度,必须小于等于 SPI_FLASH_PerWritePageSize
* @retval 无
*/
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 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 内部写入结束。
⑪ 不定量数据写入
/**
* @brief 对 FLASH 写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度
* @retval 无
*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/*mod 运算求余,若 writeAddr 是 SPI_FLASH_PageSize 整数倍,运算结果 Addr 值为0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
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);
}
}
}
}
页的大小以及实际数据写入的时候,使用的是针对 FLASH 芯片的页写入函数,且在实际调用这个“不定量数据写入”函数时,还要注意确保目标扇区处于擦除状态。
⑫ 从 FLASH 读取数据
发送了指令编码及要读的起始地址后, FLASH 芯片就会按地址递增的方式返回存储矩阵的内容,读取的数据量没有限制,只要没有停止通讯, FLASH 芯片就会一直返回数据。
/**
* @brief 读取 FLASH 数据
* @param pBuffer,存储读出数据的指针
* @param ReadAddr,读取地址
* @param NumByteToRead,读取数据长度
* @retval 无
*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 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();
}
(3)main 函数
#define RxBufferSize1 (countof(TxBuffer1) - 1)
#define countof(a) (sizeof(a) / sizeof(*(a)))
#define BufferSize (countof(Tx_Buffer)-1)
#define FLASH_WriteAddress 0x00000
#define FLASH_ReadAddress FLASH_WriteAddress
#define FLASH_SectorToErase FLASH_WriteAddress
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = " stm32 开发板\r\n";
uint8_t Rx_Buffer[BufferSize];
//读取的 ID 存储位置
IO uint32_t DeviceID = 0;
IO uint32_t FlashID = 0;
IO TestStatus TransferStatus1 = FAILED;
// 函数原型声明
void Delay( IO uint32_t nCount);
/*
* 函数名:main
* 描述 :主函数
* 输入 :无
* 输出 :无
*/
int main(void)
{
LED_GPIO_Config();
LED_BLUE;
/* 配置串口 1 为:115200 8-N-1 */
Debug_USART_Config();
printf("\r\n 这是一个 16M 串行 flash(W25Q128)实验 \r\n");
Delay( 200 );
/* 获取 SPI Flash ID */
FlashID = SPI_FLASH_ReadID();
/* 检验 SPI Flash ID */
if (FlashID == sFLASH_ID || FlashID2 == sFLASH_ID)
{
printf("\r\n 检测到 SPI FLASH W25Q256 !\r\n");
/* 擦除将要写入的 SPI FLASH 扇区,FLASH 写入前要先擦除 */
SPI_FLASH_SectorErase(FLASH_SectorToErase);
/* 将发送缓冲区的数据写到 flash 中 */
SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
printf("\r\n 写入的数据为:\r\n%s", Tx_Buffer);
/* 将刚刚写入的数据读出来放到接收缓冲区中 */
SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
printf("\r\n 读出的数据为:\r\n%s", Rx_Buffer);
/* 检查写入的数据与读出的数据是否相等 */
TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
if ( PASSED == TransferStatus1 )
{
LED_GREEN;
printf("\r\n16M 串行 flash(W25Q256)测试成功!\n\r");
}
else
{
LED_RED;
printf("\r\n16M 串行 flash(W25Q256)测试失败!\n\r");
}
}// if (FlashID == sFLASH_ID)
else
{
LED_RED;
printf("\r\n 获取不到 W25Q256 ID!\n\r");
}
SPI_Flash_PowerDown();
while (1);
}