STM32H7外部flash(W25Q128)+SPI+DMA读写

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

复制代码
相关推荐
Industio_触觉智能1 小时前
开源鸿蒙OpenHarmony再进阶,OneConnect规范即将升级为国家级行业标准
嵌入式硬件·物联网·硬件架构·智能硬件·openharmony·开源鸿蒙
一个嵌入式学徒3 小时前
STM32F4 HAL库常用API手册(非CubeMX生成,需手动调用)
stm32·单片机·嵌入式硬件
m0_377108143 小时前
小智ai,esp32
单片机
是摆烂第一名呀3 小时前
RK3576+GMSL摄像头底层适配与调试
linux·arm开发·驱动开发·嵌入式硬件·数码相机
Mr-pn-junction13 小时前
clk_gate
单片机·嵌入式硬件·fpga开发
尼喃17 小时前
662K和65Z5三脚稳压芯片PW6566,附引脚定义与典型电路图
嵌入式硬件
李永奉20 小时前
杰理可视化SDK开发-【BUG】配置“TWS两边同时按消息使能”功能后,按键单击功能无效失灵
开发语言·单片机·嵌入式硬件·物联网·bug
执迷不悟82321 小时前
51单片机--中断/定时/PWM
单片机·嵌入式硬件·51单片机
A小调的码农1 天前
OPENOCD+MSYS+VSCODE:从“灯不亮“到“终于亮了“
ide·vscode·stm32·单片机·编辑器