梳理STM32F429之通信传输部分---NO.8 硬件SPI

Posted Sumjess

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了梳理STM32F429之通信传输部分---NO.8 硬件SPI相关的知识,希望对你有一定的参考价值。


目录

 

​一、STM32 的 SPI 特性及架构:​

​二、SPI 初始化结构体详解:​

​三、硬件SPI—读写串行 FLASH 实验​


一、STM32 的 SPI 特性及架构:

1、引脚简介:

(1)  

梳理STM32F429之通信传输部分---NO.8

 (Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、 CS,以下用 NSS 表示。

(2) SCK (Serial Clock): 时钟信号线,用于通讯数据同步。两个设备之间通讯时,通讯速率受限于低速设备。

(3) MOSI (Master Output, Slave Input): 主设备输出/从设备输入引脚。

(4) MISO(Master Input,, Slave Output): 主设备输入/从设备输出引脚。

2、STM32 的 SPI 通讯引脚:

梳理STM32F429之通信传输部分---NO.8

       其中 SPI1SPI4SPI5SPI6 APB2 上的设备,最高通信速率达 45Mbtis/s, SPI2SPI3 APB1 上的设备,最高通信速率为 22.5Mbits/s。

3、通讯过程:

梳理STM32F429之通信传输部分---NO.8

4、SPI 的四种模式:

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

梳理STM32F429之通信传输部分---NO.8

  • CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
  • CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
  • CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
  • CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿

5、 W25Q256内存划分情况:

W25Q256芯片:

每个芯片 0-511个块   --- 每个块64KB

每个块有 0-15个扇区 --- 每个扇区4KB

512块 X 64KB = 32768‬KB = 32M ( 32768‬KB / 1024 )

梳理STM32F429之通信传输部分---NO.8

二、SPI 初始化结构体详解:

       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_CPOLSPI_CPHA

梳理STM32F429之通信传输部分---NO.8


       时钟极性 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 的值。

三、硬件SPI—读写串行 FLASH 实验

闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。

       我们生活中常用的 U 盘、 SD卡、 SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是 FLASH 芯片只能一大片一大片地擦写,而 EEPROM 可以单个字节擦写。

主模式, 通过查询事件的方式来确保正常通讯。
1、硬件设计

梳理STM32F429之通信传输部分---NO.8

       本实验中的 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)编程要点

  1. 初始化通讯使用的目标引脚及端口时钟;
  2. 使能 SPI 外设的时钟;
  3. 配置 SPI 外设的模式、地址、速率等参数并使能 SPI 外设;
  4. 编写基本 SPI 按字节收发的函数;
  5. 编写对 FLASH 擦除及读写操作的的函数;
  6. 编写测试程序,对读写数据进行校验。

(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 的指令

梳理STM32F429之通信传输部分---NO.8

梳理STM32F429之通信传输部分---NO.8

       ⑥ 定义 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”。所以这里涉及到数据“擦除”的概念。

梳理STM32F429之通信传输部分---NO.8

       扇区擦除指令的第一个字节为指令编码,紧接着发送的 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 ) 以上是关于梳理STM32F429之通信传输部分---NO.8 硬件SPI的主要内容,如果未能解决你的问题,请参考以下文章

STM32F429 定时器触发 USART DMA 传输问题

STM32F429开发板用户手册第18章 STM32F429的GPIO应用之跑马灯

STM32F429开发板用户手册第19章 STM32F429的GPIO应用之按键FIFO

STM32F429第二十三篇之电容按键

STM32F429第二十七篇之DMA实验详解

STM32F429第二十七篇之DMA实验详解