1.基础配置
(1)关于基础配置

这里的Clock Polarity(CPOL)和Clock Phase(CPHA) 可以是这里的High和2 Edge
也可以是Low和1 Edge 。也就是模式0和模式3的区别
这里可以看一下官方的手册

(2)点击DMA配置,配置TX、RX

(3)DMA配置完这三个中断理论上是自动勾选上的

(4)关于IO口的配置
如果时钟较快的需要将io口速度设置为high或者very high!
别忘了还有一个CS引脚需要单独的自己去配置为gpio-output

2.代码部分
(1)spi_falsh.c
/**
* @file w25q128.c
* @brief 对于w25q128的读取写入操作
*
*/
/* Includes ------------------------------------------------------------------*/
#include "SPI_FLASH.h"
#include <string.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define WIP_Flag 0x01 /* Write In Progress (WIP) flag */
#define Dummy_Byte 0xFF
enum {
TRANSFER_WAIT,
TRANSFER_COMPLETE,
TRANSFER_ERROR
};
/* Private declaration--------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint32_t W25Q_ID;
uint8_t txBuff[W25Q_PAGE_SIZE];
uint8_t rxBuff[W25Q_PAGE_SIZE];
/* DMA全双工模式下的dummy缓冲区(32字节对齐以满足D-Cache要求) */
ALIGN_32BYTES(static uint8_t s_dummy_rx[W25Q_PAGE_SIZE]);
ALIGN_32BYTES(static uint8_t s_dummy_tx[W25Q_PAGE_SIZE]);
/* External variables --------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
static HAL_StatusTypeDef SPI_FLASH_sendCmd(uint8_t cmd);
__IO uint32_t wTransferState = TRANSFER_WAIT; //状态
/**
* @brief TxRx Transfer completed callback.
* @param hspi: SPI handle
* @note This example shows a simple way to report end of DMA TxRx transfer, and
* you can add your own implementation.
* @retval None
*/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
wTransferState = TRANSFER_COMPLETE;
}
/**
* @brief SPI error callbacks.
* @param hspi: SPI handle
* @note This example shows a simple way to report transfer error, and you can
* add your own implementation.
* @retval None
*/
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
wTransferState = TRANSFER_ERROR;
}
/**
* @brief 拉低片选,使能flash
*/
void SPI_FLASH_CS_LOW(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);
}
/**
* @brief 拉高片选,flash进入待机
*/
void SPI_FLASH_CS_HIGH(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
}
static HAL_StatusTypeDef SPI_FLASH_sendCmd(uint8_t cmd)
{
HAL_StatusTypeDef status = HAL_SPI_Transmit(&hspi2, &cmd, 1, 100);
return status;
}
/**
* @brief 执行一次flash的收发
* @param txdata 发送的字节
* @retval uint8_t 返回值是接收到的值
*/
uint8_t SPI_FLASH_ReadWrite(uint8_t txdata)
{
uint8_t rxdata;
HAL_SPI_TransmitReceive(&hspi2, &txdata, &rxdata, 1, 1000);
return rxdata;
}
/**
* @brief 读芯片的整体ID
* @note 读取制造商 ID(Manufacturer ID)与设备 ID(Device ID)组合(16-bit)
* @retval uint16_t 读到的ID
*/
uint16_t SPI_FLASH_ReadManufactDeviceID(void)
{
uint16_t deviceid;
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_READ_ManufactDeviceID); /* 发送读 ID 命令 */
SPI_FLASH_ReadWrite(0); /* 写入一个字节 */
SPI_FLASH_ReadWrite(0);
SPI_FLASH_ReadWrite(0);
deviceid = SPI_FLASH_ReadWrite(0xFF) << 8; /* 读取高8位字节 */
deviceid |= SPI_FLASH_ReadWrite(0xFF); /* 读取低8位字节 */
SPI_FLASH_CS_HIGH();
return deviceid;
}
/**
* @brief 读芯片的设备ID
* @note 读取设备 ID(Device ID)
* @retval uint16_t 读到的ID
*/
uint16_t SPI_FLASH_ReadDeviceID(void)
{
uint32_t Temp = 0;
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_READ_DeviceID);
SPI_FLASH_ReadWrite(0);
SPI_FLASH_ReadWrite(0);
SPI_FLASH_ReadWrite(0);
Temp = SPI_FLASH_ReadWrite(0);
SPI_FLASH_CS_HIGH();
return Temp;
}
/**
* @brief 读取完整 JEDEC ID
* @note 3 字节:Manufacturer + Memory Type + Capacity
* @retval uint32_t 读到的ID
*/
uint32_t SPI_FLASH_ReadJedecDeviceID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_READ_JEDEC_ID);
Temp0 = SPI_FLASH_ReadWrite(0);
Temp1 = SPI_FLASH_ReadWrite(0);
Temp2 = SPI_FLASH_ReadWrite(0);
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
SPI_FLASH_CS_HIGH();
return Temp;
}
/**
* @brief 设置 Flash 写使能锁存器
* @note 所有写/擦除操作前必须调用,否则 Flash 拒绝执行
*/
void SPI_FLASH_WriteEnable(void)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_WRITE_ENABLE);/* 发送写使能命令*/
SPI_FLASH_CS_HIGH();
}
/**
* @brief 进入低功耗模式
* @note 适用场景:电池供电设备需延长续航时使用。
*/
void SPI_Flash_PowerDown(void)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_POWER_DOWN); /* 发送掉电命令 */
SPI_FLASH_CS_HIGH();
}
/**
* @brief 唤醒 Flash
* @note 适用场景:电池供电设备需延长续航时使用。
*/
void SPI_Flash_WAKEUP(void)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_RELEASE_PD);/* 发送上电命令 */
SPI_FLASH_CS_HIGH();
}
/**
* @brief 擦除指定扇区(4KB),地址需为扇区对齐
* @param SectorAddr 扇区地址
*/
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
/* 发送FLASH写使能命令 */
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
SPI_FLASH_CS_LOW();
/*发送扇区擦除指令*/
SPI_FLASH_ReadWrite(W25Q_CMD_SECTOR_ERASE);
/*发送擦除扇区地址的高位*/
SPI_FLASH_ReadWrite((SectorAddr & 0xFF0000) >> 16);
/* 发送擦除扇区地址的中位 */
SPI_FLASH_ReadWrite((SectorAddr & 0xFF00) >> 8);
/* 发送擦除扇区地址的低位 */
SPI_FLASH_ReadWrite(SectorAddr & 0xFF);
SPI_FLASH_CS_HIGH();
/* 等待擦除完毕*/
SPI_FLASH_WaitForWriteEnd();
}
/**
* @brief 擦除指定扇区(64KB),地址需为扇区对齐
* @param BlockAddr 扇区地址
*/
void SPI_FLASH_BlockErase(uint32_t BlockAddr)
{
/* 发送FLASH写使能命令 */
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
SPI_FLASH_CS_LOW();
/*发送扇区擦除指令*/
SPI_FLASH_ReadWrite(W25Q_CMD_BLOCK_ERASE);
/*发送擦除扇区地址的高位*/
SPI_FLASH_ReadWrite((BlockAddr & 0xFF0000) >> 16);
/* 发送擦除扇区地址的中位 */
SPI_FLASH_ReadWrite((BlockAddr & 0xFF00) >> 8);
/* 发送擦除扇区地址的低位 */
SPI_FLASH_ReadWrite(BlockAddr & 0xFF);
SPI_FLASH_CS_HIGH();
/* 等待擦除完毕*/
SPI_FLASH_WaitForWriteEnd();
}
/**
* @brief 整片擦除
* @note 需要等待至多30秒
*/
void SPI_FLASH_CHIP_ERASE(void)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_CHIP_ERASE);
SPI_FLASH_CS_HIGH();
/* 整片擦除要 5~20 秒,不能用通用 WaitForWriteEnd */
uint8_t FLASH_Status = 0;
uint32_t start_tick = HAL_GetTick();
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_READ_STATUS1);
do {
FLASH_Status = SPI_FLASH_ReadWrite(Dummy_Byte);
if ((HAL_GetTick() - start_tick) > 10000) break; /* 10 秒 */
} while ((FLASH_Status & WIP_Flag) == SET);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待写入完毕
*/
void SPI_FLASH_WaitForWriteEnd(void)
{
uint8_t FLASH_Status = 0;
uint32_t start_tick = HAL_GetTick();
SPI_FLASH_CS_LOW();
SPI_FLASH_ReadWrite(W25Q_CMD_READ_STATUS1);
do {
FLASH_Status = SPI_FLASH_ReadWrite(Dummy_Byte);
/* 1 秒超时 */
if ((HAL_GetTick() - start_tick) > 1000) {
break; /*不 return,保证下面 CS_HIGH 执行 */
}
} while ((FLASH_Status & WIP_Flag) == SET);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 按页写入
* @param pBuffer 要写的数据
* @param WriteAddr 写地址(输入的地址需自行对其)
* @param NumByteToWrite 写的字节数
*/
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t cmd[4];
uint8_t dummy_rx[4];
if(NumByteToWrite > W25Q_PAGE_SIZE) /*进行限位,只能是单页写入*/
{
NumByteToWrite = W25Q_PAGE_SIZE;
}
/* 发送FLASH写使能命令 */
SPI_FLASH_WriteEnable();
/* 打包写页指令+地址 */
cmd[0] = W25Q_CMD_PAGE_PROGRAM;
cmd[1] = ((WriteAddr & 0xFF0000) >> 16);
cmd[2] = ((WriteAddr & 0xFF00) >> 8);
cmd[3] = (WriteAddr & 0xFF);
/* 选择FLASH: CS低电平 */
SPI_FLASH_CS_LOW();
/* 发送命令+地址(阻塞方式) */
HAL_SPI_TransmitReceive(&hspi2, cmd, dummy_rx, 4, 1000);
/* 清理D-Cache,确保DMA能读到pBuffer最新数据 */
SCB_CleanDCache_by_Addr((uint32_t *)pBuffer, NumByteToWrite);
/* 复位状态并启动全双工DMA(TX发数据,RX收dummy,修复单TX DMA导致OVR的问题) */
wTransferState = TRANSFER_WAIT;
if (HAL_SPI_TransmitReceive_DMA(&hspi2, pBuffer, s_dummy_rx, NumByteToWrite) != HAL_OK)
{
SPI_FLASH_CS_HIGH();
return;
}
/* 等待DMA传输完成(中断回调设置wTransferState) */
while (wTransferState == TRANSFER_WAIT)
{
}
if (wTransferState == TRANSFER_ERROR)
{
SPI_FLASH_CS_HIGH();
return;
}
/* 停止信号 FLASH: CS 高电平 */
SPI_FLASH_CS_HIGH();
/* 等待Flash内部写入完毕*/
SPI_FLASH_WaitForWriteEnd();
}
/**
* @brief 连续写函数(已有自动分割页写入功能)
* @param pBuffer 要写的数据(字节为单位)
* @param WriteAddr 写地址
* @param NumByteToWrite 写的字节数
* @note 写地址不用可以对齐整页,内部自动分割并写入
*/
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % W25Q_PAGE_SIZE;
count = W25Q_PAGE_SIZE - Addr;
NumOfPage = NumByteToWrite / W25Q_PAGE_SIZE;
NumOfSingle = NumByteToWrite % W25Q_PAGE_SIZE;
/* 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, W25Q_PAGE_SIZE);
WriteAddr += W25Q_PAGE_SIZE;
pBuffer += W25Q_PAGE_SIZE;
}
/*若有多余的不满一页的数据,把它写完*/
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 / W25Q_PAGE_SIZE;
NumOfSingle = NumByteToWrite % W25Q_PAGE_SIZE;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*把整数页都写了*/
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, W25Q_PAGE_SIZE);
WriteAddr += W25Q_PAGE_SIZE;
pBuffer += W25Q_PAGE_SIZE;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
/**
* @brief 连续读函数
* @param pBuffer 存储数据的地址
* @param WriteAddr 开始读地址
* @param NumByteToWrite 读字节数
*/
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
uint8_t cmd[4];
uint8_t dummy_rx[4];
/* 打包命令+地址 */
cmd[0] = W25Q_CMD_READ_DATA;
cmd[1] = (ReadAddr >> 16) & 0xFF;
cmd[2] = (ReadAddr >> 8) & 0xFF;
cmd[3] = ReadAddr & 0xFF;
SPI_FLASH_CS_LOW();
/* 命令+地址 一次发送(dummy_rx 接收无用回传数据) */
HAL_SPI_TransmitReceive(&hspi2, cmd, dummy_rx, 4, 1000);
/* 失效D-Cache,确保DMA写入的pBuffer对CPU可见 */
SCB_InvalidateDCache_by_Addr((uint32_t *)pBuffer, NumByteToRead);
/* 复位状态并启动全双工DMA(TX发dummy,RX收Flash数据) */
wTransferState = TRANSFER_WAIT;
if (HAL_SPI_TransmitReceive_DMA(&hspi2, s_dummy_tx, pBuffer, NumByteToRead) != HAL_OK)
{
SPI_FLASH_CS_HIGH();
return;
}
/* 等待DMA传输完成(中断回调设置wTransferState) */
while (wTransferState == TRANSFER_WAIT)
{
}
/* 失效D-Cache,确保CPU读到DMA写入的最新数据 */
SCB_InvalidateDCache_by_Addr((uint32_t *)pBuffer, NumByteToRead);
SPI_FLASH_CS_HIGH();
}
(2)spi_flash.h
/**
****************************************************************************
*/
#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H
#include "stm32h7xx_hal.h"
#include "main.h"
#include "spi.h"
#include "stdio.h"
/**************命令定义*************/
#define W25Q_CMD_WRITE_ENABLE 0x06
#define W25Q_CMD_WRITE_DISABLE 0x04
#define W25Q_CMD_READ_STATUS1 0x05
#define W25Q_CMD_READ_DATA 0x03
#define W25Q_CMD_PAGE_PROGRAM 0x02
#define W25Q_CMD_SECTOR_ERASE 0x20 // 4KB
#define W25Q_CMD_BLOCK_ERASE 0xD8 // 64KB
#define W25Q_CMD_CHIP_ERASE 0xC7
#define W25Q_CMD_READ_DeviceID 0xAB /*读设备ID*/
#define W25Q_CMD_READ_ManufactDeviceID 0x90 /*读制造商+设备ID*/
#define W25Q_CMD_READ_JEDEC_ID 0x9F
#define W25Q_CMD_POWER_DOWN 0xB9
#define W25Q_CMD_RELEASE_PD 0xAB
/**************命令定义-结尾*************/
#define W25Q_PAGE_SIZE 256
#define W25Q_SECTOR_SIZE 4096
#define W25Q_BLOCK_SIZE (64*1024)
#define W25Q_TOTAL_SIZE (16*1024*1024) // 16MB
// FLASH初始化
HAL_StatusTypeDef SPI_FLASH_Init(void);
// 读写数据
uint8_t SPI_FLASH_ReadWrite(uint8_t txdata);
// 读取ID
uint16_t SPI_FLASH_ReadManufactDeviceID(void);
uint16_t SPI_FLASH_ReadDeviceID(void);
uint32_t SPI_FLASH_ReadJedecDeviceID(void);
// 写使能
void SPI_FLASH_WriteEnable(void);
// 进入掉电模式
void SPI_Flash_PowerDown(void);
// 唤醒
void SPI_Flash_WAKEUP(void);
// 等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕
void SPI_FLASH_WaitForWriteEnd(void);
// 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
// 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
// 读取FLASH数据
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
void SPI_FLASH_BlockErase(uint32_t BlockAddr);
// 擦除FLASH扇区
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
// 擦除FLASH扇区,整片擦除
void SPI_FLASH_CHIP_ERASE(void);
// 片选拉低
void SPI_FLASH_CS_LOW(void);
// 片选拉高
void SPI_FLASH_CS_HIGH(void);
#endif
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
// 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
// 读取FLASH数据
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
void SPI_FLASH_BlockErase(uint32_t BlockAddr);
// 擦除FLASH扇区
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
// 擦除FLASH扇区,整片擦除
void SPI_FLASH_CHIP_ERASE(void);
// 片选拉低
void SPI_FLASH_CS_LOW(void);
// 片选拉高
void SPI_FLASH_CS_HIGH(void);
#endif