【STM32】HAL库中的实现(九):SPI(串行外设接口)

SPI 接口通信原理

SPI(Serial Peripheral Interface)是全双工主从通信协议,特点是:

信号线 功能
SCK 串行时钟
MOSI 主设备输出,从设备输入
MISO 主设备输入,从设备输出
CS(NSS) 片选信号,低电平选中


(1)SPI每发送一个数据的同时会接收到一个字节的数据。

(2)SPI有4条线,MISO,MOSI,SCLK三条数据线,还有片选线CS,片选线对于SPI接口的从设备是低电平有效,主机输出一个低电平从机就被选中。这样就方便一个主机可以连接多个从设备,只需要使用不同的片选线。

SPI 四种模式

SPI的相位(CPHA)和极性(CPOL)都可以为0或1,对应的4种组合构成了SPI的4种模式:

Mode 0: CPOL=0, CPHA=0

Mode 1 :CPOL=0, CPHA=1

Mode 2 :CPOL=1, CPHA=0

Mode 3 :CPOL=1, CPHA=1

📘 这里我们将 W25Qxx 作为 SPI 从机 (你也可以用其他外设作为从机使用,例如 93C46 EEPROM存储器),STM32 为主机

W25Q64 是华邦公司推出的大容量SPI FLASH 产品,W25Q64 的容量为 64Mb,W25Q128的容量为128Mb。

W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V。

需要理解 SPI原理 可移步至:【STM32】SPI接口原理与配置(提供完整实例代码)。此处仅简化带过,不再过多详细说明SPI的工作原理。

这篇博客实现了 STM32 使用 HAL 库通过 SPI 接口驱动 W25Qxx Flash 存储器 的代码框架,并通过 CubeMX、原理图、调试串口等方式进行了全流程验证。我将从原理、配置、电路、代码结构、调试技巧 全方面为你系统说明HAL库中的SPI使用方法。


STM32 CubeMX配置SPI访问W25Qxx

一、硬件接线与原理图说明


如图所示:

CLK----SPI1_SCK(PA5)

SO-----SPI1_MISO(PA6)

SI------SPI1_MOSI(PA7)

CS----PB11 通过软件片选(PA4)

复制代码
STM32引脚					W25Qxx引脚				说明
PA5							CLK						SPI SCK
PA6							MISO					SPI MISO
PA7							MOSI					SPI MOSI
PA4							CS						软件控制 FLASH 片选
VCC							VCC						3.3V
GND							GND						地线
/WP							GND						关闭写保护
/HOLD						VCC						禁止暂停传输

注:外接 10kΩ 上拉电阻到 MOSI/MISO/CS(推荐)

SPI引脚设置模式:

⚙️ 二、CubeMX 配置说明

SPI1 配置

模式:全双工主机(Full-Duplex Master)

数据大小:8 bits

时钟极性 CPOL = 1

时钟相位 CPHA = 1(W25Qxx 数据手册建议)

NSS 信号:Software(软件管理片选)

为片选引脚配置GPIO:(SPI 连接到的资源:PA4作为片选引脚,一般来说我们常用片选引脚作为模拟片选管脚使用)。

GPIO 配置

PA4 → 设置为 GPIO Output Push Pull,用户标签:FLASH_CS

配置好后点击创建工程。

三、软件实现结构说明

我们在这里复习一下此前的标准库 SPI 配置五步法:

步骤 函数
1️⃣ 初始化 GPIO GPIO_Init()
2️⃣ 设置 SPI 参数 SPI_Init()
3️⃣ 使能 SPI SPI_Cmd()
4️⃣ 发送接收数据 SPI_I2S_SendData() / ReceiveData()
5️⃣ 检查状态 SPI_I2S_GetFlagStatus()

在这里,HAL库配置则简单得多(用 STM32CubeMX 生成 HAL 工程后):

c 复制代码
HAL_SPI_Transmit(&hspi1, data, len, timeout);
HAL_SPI_Receive(&hspi1, data, len, timeout);
HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, len, timeout);

当然,我们在这里是 STM32 CubeMX配置SPI访问W25Qxx

工程简单分为 3 层结构:

1️⃣ 应用层: main.c

c 复制代码
W25qxx_Init();
printf("Read Device ID: 0x%08X",W25qxx_ReadDeviceID());

2️⃣ 驱动层: w25qxx.c / w25qxx.h

  • 驱动层内封装了 ReadID / WritePage / ReadBuffer / Erase / Init 等函数
  • 支持多种容量芯片识别(W25Q16~W25Q512)

3️⃣ 配置层: w25qxx_config.h

c 复制代码
#define _W25QXX_SPI        hspi1
#define _W25QXX_CS_GPIO    FLASH_CS_GPIO_Port
#define _W25QXX_CS_PIN     FLASH_CS_Pin

补充知识储备:可自行查询SPIFLASH资源使用:W25Q64数据手册.pdf

可详细了解W25Qxx的内部详情



完整代码

📄 main.c

c 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"

#include "w25qxx.h"
#include "w25qxx_config.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
	HAL_UARTEx_ReceiveToIdle_IT(  &huart1 , U1RxData, U1RxDataSize);
	
	W25qxx_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	
	printf("Read Device ID: 0x%08X",W25qxx_ReadDeviceID() );
	
  while (1)
  {

	
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
/*参考链接:https://www.cnblogs.com/kdsj/p/15371137.html */
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

📄 w25qxx.c

c 复制代码
#include "w25qxx.h"
#include "w25qxx_config.h"
 
#if (_W25QXX_DEBUG == 1)
#include <stdio.h>
#endif
 
#define W25QXX_CS_H 			HAL_GPIO_WritePin(_W25QXX_CS_GPIO, _W25QXX_CS_PIN, GPIO_PIN_SET);
#define W25QXX_CS_L 			HAL_GPIO_WritePin(_W25QXX_CS_GPIO, _W25QXX_CS_PIN, GPIO_PIN_RESET);
 
w25qxx_t	w25qxx;
 
#if (_W25QXX_USE_FREERTOS == 1)
#define	W25qxx_Delay(delay)		osDelay(delay)
#include "cmsis_os.h"
#else
#define	W25qxx_Delay(delay) for(uint8_t i=0;i<255;i++);;
#endif
 
//###################################################################################################################
uint8_t	W25qxx_Spi(uint8_t	Data)
{
	uint8_t	ret;
	HAL_SPI_TransmitReceive(&_W25QXX_SPI, &Data, &ret, 1, 100);
	return ret;	
}
//###################################################################################################################
uint32_t W25qxx_ReadID(void)
{
	uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
	
	W25QXX_CS_L;
	W25qxx_Spi(W25X_JedecDeviceID);
	Temp0 = W25qxx_Spi(W25QXX_DUMMY_BYTE);
	Temp1 = W25qxx_Spi(W25QXX_DUMMY_BYTE);
	Temp2 = W25qxx_Spi(W25QXX_DUMMY_BYTE);
	W25QXX_CS_H;
	Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
	
	return Temp;
}


uint32_t W25qxx_ReadDeviceID(void)
{
	uint16_t Temp = 0;
	
	W25QXX_CS_L;
	W25qxx_Spi(W25X_ManufactDeviceID);
	W25qxx_Spi(0x00);
	W25qxx_Spi(0x00);
	W25qxx_Spi(0x00);
	Temp |= W25qxx_Spi(W25QXX_DUMMY_BYTE) << 8;
	Temp |= W25qxx_Spi(W25QXX_DUMMY_BYTE);
//	Temp2 = W25qxx_Spi(W25QXX_DUMMY_BYTE);
	W25QXX_CS_H;
//	Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
	
	return Temp;
}
//###################################################################################################################
void W25qxx_ReadUniqID(void)
{
	W25QXX_CS_L;
	W25qxx_Spi(0x4B);
	for(uint8_t	i = 0; i < 4; i++)
	{
		W25qxx_Spi(W25QXX_DUMMY_BYTE);
	}
	for(uint8_t	i = 0; i < 8; i++)
	{
		w25qxx.UniqID[i] = W25qxx_Spi(W25QXX_DUMMY_BYTE);
	}
	W25QXX_CS_H;
}
//###################################################################################################################
void W25qxx_WriteEnable(void)
{
	W25QXX_CS_L;
	W25qxx_Spi(W25X_WriteEnable);
	W25QXX_CS_H;
	W25qxx_Delay(1);
}
//###################################################################################################################
void W25qxx_WriteDisable(void)
{
	W25QXX_CS_L;
	W25qxx_Spi(W25X_WriteDisable);
	W25QXX_CS_H;
	W25qxx_Delay(1);
}
//###################################################################################################################
uint8_t W25qxx_ReadStatusRegister(uint8_t SelectStatusRegister_1_2_3)
{
	uint8_t	status=0;
	W25QXX_CS_L;
	if(SelectStatusRegister_1_2_3 == 1)
	{
		W25qxx_Spi(W25X_ReadStatusReg1);
		status=W25qxx_Spi(W25QXX_DUMMY_BYTE);	
		w25qxx.StatusRegister1 = status;
	}
	else if(SelectStatusRegister_1_2_3 == 2)
	{
		W25qxx_Spi(0x35);
		status=W25qxx_Spi(W25QXX_DUMMY_BYTE);	
		w25qxx.StatusRegister2 = status;
	}
	else
	{
		W25qxx_Spi(0x15);
		status=W25qxx_Spi(W25QXX_DUMMY_BYTE);	
		w25qxx.StatusRegister3 = status;
	}	
	W25QXX_CS_H;
	
	return status;
}
//###################################################################################################################
void W25qxx_WriteStatusRegister(uint8_t	SelectStatusRegister_1_2_3, uint8_t Data)
{
	W25QXX_CS_L;
	if(SelectStatusRegister_1_2_3 == 1)
	{
		W25qxx_Spi(W25X_WriteStatusReg1);
		w25qxx.StatusRegister1 = Data;
	}
	else if(SelectStatusRegister_1_2_3 == 2)
	{
		W25qxx_Spi(0x31);
		w25qxx.StatusRegister2 = Data;
	}
	else
	{
		W25qxx_Spi(0x11);
		w25qxx.StatusRegister3 = Data;
	}
	W25qxx_Spi(Data);
	W25QXX_CS_H;
}
//###################################################################################################################
void W25qxx_WaitForWriteEnd(void)
{
	W25QXX_CS_L;
	W25qxx_Spi(W25X_ReadStatusReg1);
	do
	{
		w25qxx.StatusRegister1 = W25qxx_Spi(W25QXX_DUMMY_BYTE);
		W25qxx_Delay(1);
	}
	while ((w25qxx.StatusRegister1 & WIP_Flag) == 0x01);
	W25QXX_CS_H;
 
}
//###################################################################################################################
bool W25qxx_Init(void)
{
	w25qxx.Lock=1;	
//	while(HAL_GetTick()<100)
//		W25qxx_Delay(1);
	W25QXX_CS_H;
//  W25qxx_Delay(100);
	uint32_t	id;
#if (_W25QXX_DEBUG==1)
	printf("w25qxx Init Begin...\r\n");
#endif
	id = W25qxx_ReadID();
	
#if (_W25QXX_DEBUG==1)
	printf("w25qxx ID:0x%X\r\n",id);
#endif
	switch(id&0x0000FFFF)
	{
		case 0x401A:	// 	w25q512
			w25qxx.ID=W25Q512;
			w25qxx.BlockCount=1024;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q512\r\n");
#endif
		break;
		case 0x4019:	// 	w25q256
			w25qxx.ID=W25Q256;
			w25qxx.BlockCount=512;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q256\r\n");
#endif
		break;
		case 0x4018:	// 	w25q128
			w25qxx.ID=W25Q128;
			w25qxx.BlockCount=256;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q128\r\n");
#endif
		break;
		case 0x4017:	//	w25q64
			w25qxx.ID=W25Q64;
			w25qxx.BlockCount=128;
#if (_W25QXX_DEBUG == 1)
			printf("w25qxx Chip: w25q64\r\n");
#endif
		break;
		case 0x4016:	//	w25q32
			w25qxx.ID=W25Q32;
			w25qxx.BlockCount=64;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q32\r\n");
#endif
		break;
		case 0x4015:	//	w25q16
			w25qxx.ID=W25Q16;
			w25qxx.BlockCount=32;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q16\r\n");
#endif
		break;
		case 0x4014:	//	w25q80
			w25qxx.ID=W25Q80;
			w25qxx.BlockCount=16;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q80\r\n");
#endif
		break;
		case 0x4013:	//	w25q40
			w25qxx.ID=W25Q40;
			w25qxx.BlockCount=8;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q40\r\n");
#endif
		break;
		case 0x4012:	//	w25q20
			w25qxx.ID=W25Q20;
			w25qxx.BlockCount=4;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q20\r\n");
#endif
		break;
		case 0x4011:	//	w25q10
			w25qxx.ID=W25Q10;
			w25qxx.BlockCount=2;
#if (_W25QXX_DEBUG==1)
			printf("w25qxx Chip: w25q10\r\n");
#endif
		break;
		default:
#if (_W25QXX_DEBUG==1)
				printf("w25qxx Unknown ID\r\n");
#endif
			w25qxx.Lock=0;	
			return false;		
	}		
	w25qxx.PageSize = 256;
	w25qxx.SectorSize = 0x1000;
	w25qxx.SectorCount = w25qxx.BlockCount * 16;
	w25qxx.PageCount = (w25qxx.SectorCount * w25qxx.SectorSize) / w25qxx.PageSize;
	w25qxx.BlockSize = w25qxx.SectorSize*16;
	w25qxx.CapacityInKiloByte = (w25qxx.SectorCount * w25qxx.SectorSize) / 1024;
	W25qxx_ReadUniqID();
	W25qxx_ReadStatusRegister(1);
	W25qxx_ReadStatusRegister(2);
	W25qxx_ReadStatusRegister(3);
#if (_W25QXX_DEBUG == 1)
	printf("w25qxx Page Size: %d Bytes\r\n",w25qxx.PageSize);
	printf("w25qxx Page Count: %d\r\n",w25qxx.PageCount);
	printf("w25qxx Sector Size: %d Bytes\r\n",w25qxx.SectorSize);
	printf("w25qxx Sector Count: %d\r\n",w25qxx.SectorCount);
	printf("w25qxx Block Size: %d Bytes\r\n",w25qxx.BlockSize);
	printf("w25qxx Block Count: %d\r\n",w25qxx.BlockCount);
	printf("w25qxx Capacity: %d KiloBytes\r\n",w25qxx.CapacityInKiloByte);
	printf("w25qxx Init Done\r\n");
#endif
	w25qxx.Lock = 0;	
	return true;
}	
//###################################################################################################################
void W25qxx_EraseChip(void)
{
	while(w25qxx.Lock==1)
	{
		W25qxx_Delay(1);
	}
	w25qxx.Lock=1;	
#if (_W25QXX_DEBUG == 1)
	uint32_t	StartTime=HAL_GetTick();	
	printf("w25qxx EraseChip Begin...\r\n");
#endif
	W25qxx_WriteEnable();
	W25QXX_CS_L;
	W25qxx_Spi(W25X_ChipErase);
	W25QXX_CS_H;
	W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG == 1)
	printf("w25qxx EraseBlock done after %d ms!\r\n",HAL_GetTick()-StartTime);
#endif
	w25qxx.Lock = 0;	
}
//###################################################################################################################
void W25qxx_EraseSector(uint32_t SectorAddr)
{
	while(w25qxx.Lock == 1)
	{
		W25qxx_Delay(1);
	}
	w25qxx.Lock = 1;	
#if (_W25QXX_DEBUG == 1)
	uint32_t	StartTime=HAL_GetTick();	
	printf("w25qxx EraseSector %d Begin...\r\n",SectorAddr);
#endif
	W25qxx_WriteEnable();
	W25qxx_WaitForWriteEnd();
	W25QXX_CS_L;
	W25qxx_Spi(W25X_SectorErase);
	if(w25qxx.ID == W25Q256)
	{
		W25qxx_Spi((SectorAddr & 0xFF000000) >> 24);
	}
	W25qxx_Spi((SectorAddr & 0xFF0000) >> 16);
	W25qxx_Spi((SectorAddr & 0xFF00) >> 8);
	W25qxx_Spi(SectorAddr & 0xFF);
	W25QXX_CS_H;
	W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG==1)
	printf("w25qxx EraseSector done after %d ms\r\n",HAL_GetTick()-StartTime);
#endif
	w25qxx.Lock=0;
}
//###################################################################################################################
void W25qxx_EraseBlock(uint32_t BlockAddr)
{
	while(w25qxx.Lock==1)
	{
		W25qxx_Delay(1);
	}
	w25qxx.Lock=1;	
#if (_W25QXX_DEBUG==1)
	printf("w25qxx EraseBlock %d Begin...\r\n",BlockAddr);
	W25qxx_Delay(100);
	uint32_t StartTime=HAL_GetTick();	
#endif
	W25qxx_WriteEnable();
	W25qxx_WaitForWriteEnd();
	BlockAddr = BlockAddr * w25qxx.SectorSize * 16;
	W25QXX_CS_L;
	W25qxx_Spi(W25X_BlockErase);
	if(w25qxx.ID == W25Q256)
	{
		W25qxx_Spi((BlockAddr & 0xFF000000) >> 24);
	}
	W25qxx_Spi((BlockAddr & 0xFF0000) >> 16);
	W25qxx_Spi((BlockAddr & 0xFF00) >> 8);
	W25qxx_Spi(BlockAddr & 0xFF);
	W25QXX_CS_H;
	W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG==1)
	printf("w25qxx EraseBlock done after %d ms\r\n",HAL_GetTick()-StartTime);
	W25qxx_Delay(100);
#endif
	w25qxx.Lock=0;
}
//###################################################################################################################
uint32_t W25qxx_PageToSector(uint32_t PageAddress)
{
	return ((PageAddress * w25qxx.PageSize) / w25qxx.SectorSize);
}
//###################################################################################################################
uint32_t W25qxx_PageToBlock(uint32_t PageAddress)
{
	return ((PageAddress * w25qxx.PageSize) / w25qxx.BlockSize);
}
//###################################################################################################################
uint32_t W25qxx_SectorToBlock(uint32_t SectorAddress)
{
	return ((SectorAddress * w25qxx.SectorSize) / w25qxx.BlockSize);
}
//###################################################################################################################
uint32_t W25qxx_SectorToPage(uint32_t SectorAddress)
{
	return (SectorAddress * w25qxx.SectorSize) / w25qxx.PageSize;
}
//###################################################################################################################
uint32_t W25qxx_BlockToPage(uint32_t BlockAddress)
{
	return (BlockAddress * w25qxx.BlockSize) / w25qxx.PageSize;
}
//###################################################################################################################
void W25qxx_WriteByte(uint8_t pBuffer, uint32_t WriteAddr)
{
	while(w25qxx.Lock==1)
	{
		W25qxx_Delay(1);
	}
	w25qxx.Lock = 1;
#if (_W25QXX_DEBUG==1)
	uint32_t	StartTime=HAL_GetTick();
	printf("w25qxx WriteByte 0x%02X at address %d begin...",pBuffer,WriteAddr);
#endif
	W25qxx_WriteEnable();
	W25qxx_WaitForWriteEnd();
	W25QXX_CS_L;
	W25qxx_Spi(W25X_PageProgram);
	if(w25qxx.ID == W25Q256)
	{
		W25qxx_Spi((WriteAddr & 0xFF000000) >> 24);
	}
	W25qxx_Spi((WriteAddr & 0xFF0000) >> 16);
	W25qxx_Spi((WriteAddr & 0xFF00) >> 8);
	W25qxx_Spi(WriteAddr & 0xFF);
	W25qxx_Spi(pBuffer);
	W25QXX_CS_H;
	W25qxx_WaitForWriteEnd();
#if (_W25QXX_DEBUG==1)
	printf("w25qxx WriteByte done after %d ms\r\n",HAL_GetTick()-StartTime);
#endif
	w25qxx.Lock=0;
}
//###################################################################################################################
void W25qxx_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
	while(w25qxx.Lock==1)
	{
		W25qxx_Delay(1);
	}
	w25qxx.Lock = 1;
	W25qxx_WriteEnable();
	W25qxx_WaitForWriteEnd();
	W25QXX_CS_L;
	W25qxx_Spi(W25X_PageProgram);
	if(w25qxx.ID == W25Q256)
	{
		W25qxx_Spi((WriteAddr & 0xFF000000) >> 24);
	}
	W25qxx_Spi((WriteAddr & 0xFF0000) >> 16);
	W25qxx_Spi((WriteAddr & 0xFF00) >> 8);
	W25qxx_Spi(WriteAddr & 0xFF);
	if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
	{
		NumByteToWrite = SPI_FLASH_PerWritePageSize;
	}
	while (NumByteToWrite--)
	{
		W25qxx_Spi(*pBuffer);
		pBuffer++;
	}
	W25QXX_CS_H;
	W25qxx_WaitForWriteEnd();
	w25qxx.Lock=0;
}
//###################################################################################################################
void W25qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
	uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
 
	Addr = WriteAddr % SPI_FLASH_PageSize;
	count = SPI_FLASH_PageSize - Addr;
	NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
	NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
	if (Addr == 0) /* WriteAddr is SPI_FLASH_PageSize aligned  */
	{
		if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
		{
			W25qxx_WritePage(pBuffer, WriteAddr, NumByteToWrite);
		}
		else /* NumByteToWrite > SPI_FLASH_PageSize */
		{
			while (NumOfPage--)
			{
				W25qxx_WritePage(pBuffer, WriteAddr, SPI_FLASH_PageSize);
				WriteAddr +=  SPI_FLASH_PageSize;
				pBuffer += SPI_FLASH_PageSize;
			}
			W25qxx_WritePage(pBuffer, WriteAddr, NumOfSingle);
		}
	}
	else /* WriteAddr is not SPI_FLASH_PageSize aligned  */
	{
		if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
		{
			if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
			{
				temp = NumOfSingle - count;
				W25qxx_WritePage(pBuffer, WriteAddr, count);
				WriteAddr +=  count;
				pBuffer += count;
				W25qxx_WritePage(pBuffer, WriteAddr, temp);
			}
			else
			{
				W25qxx_WritePage(pBuffer, WriteAddr, NumByteToWrite);
			}
		}
		else /* NumByteToWrite > SPI_FLASH_PageSize */
		{
			NumByteToWrite -= count;
			NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
			NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			W25qxx_WritePage(pBuffer, WriteAddr, count);
			WriteAddr +=  count;
			pBuffer += count;
			while (NumOfPage--)
			{
				W25qxx_WritePage(pBuffer, WriteAddr, SPI_FLASH_PageSize);
				WriteAddr +=  SPI_FLASH_PageSize;
				pBuffer += SPI_FLASH_PageSize;
			}
			if (NumOfSingle != 0)
			{
				W25qxx_WritePage(pBuffer, WriteAddr, NumOfSingle);
			}
		}
	}
}
//###################################################################################################################
void W25qxx_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
	W25QXX_CS_L;
	W25qxx_Spi(W25X_ReadData);
	if(w25qxx.ID == W25Q256)
	{
		W25qxx_Spi((ReadAddr & 0xFF000000) >> 24);
	}
	W25qxx_Spi((ReadAddr & 0xFF0000) >> 16);
	W25qxx_Spi((ReadAddr& 0xFF00) >> 8);
	W25qxx_Spi(ReadAddr & 0xFF);
	while (NumByteToRead--)
	{
		*pBuffer = W25qxx_Spi(W25QXX_DUMMY_BYTE);
		pBuffer++;
	}
	W25QXX_CS_H;
}
//###################################################################################################################

📄 w25qxx.h

c 复制代码
#ifndef __W25QXX_H__
#define __W25QXX_H__
 
#include <stdbool.h>
#include "spi.h"
 
#define SPI_FLASH_PageSize                  	256
#define SPI_FLASH_PerWritePageSize          	256
 
#define W25X_WriteEnable		              	0x06 
#define W25X_WriteDisable		              	0x04 
#define W25X_ReadStatusReg1		              	0x05 
#define W25X_WriteStatusReg1	              	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 WIP_Flag                              	0x01  /* Write In Progress (WIP) flag */
	
#define W25QXX_DUMMY_BYTE					  	0xFF
 
#pragma pack(1)
 
typedef enum
{
	W25Q10=1,
	W25Q20,
	W25Q40,
	W25Q80,
	W25Q16,
	W25Q32,
	W25Q64,
	W25Q128,
	W25Q256,
	W25Q512,
	
}W25QXX_ID_t;
 
typedef struct
{
	W25QXX_ID_t	ID;
	uint8_t		UniqID[8];
	uint16_t	PageSize;
	uint32_t	PageCount;
	uint32_t	SectorSize;
	uint32_t	SectorCount;
	uint32_t	BlockSize;
	uint32_t	BlockCount;
	uint32_t	CapacityInKiloByte;
	uint8_t		StatusRegister1;
	uint8_t		StatusRegister2;
	uint8_t		StatusRegister3;	
	uint8_t		Lock;
	
}w25qxx_t;
 
#pragma pack()
 
extern w25qxx_t	w25qxx;
 
/************************************************用户API*******************************************/
bool		W25qxx_Init(void);
 
uint32_t W25qxx_ReadDeviceID(void);
void		W25qxx_EraseChip(void);
void 		W25qxx_EraseSector(uint32_t SectorAddr);
void 		W25qxx_EraseBlock(uint32_t BlockAddr);
 
uint32_t	W25qxx_PageToSector(uint32_t PageAddress);
uint32_t	W25qxx_PageToBlock(uint32_t PageAddress);
uint32_t	W25qxx_SectorToBlock(uint32_t SectorAddress);
uint32_t	W25qxx_SectorToPage(uint32_t SectorAddress);
uint32_t	W25qxx_BlockToPage(uint32_t BlockAddress);
 
void 		W25qxx_WriteByte(uint8_t pBuffer, uint32_t WriteAddr);
void 		W25qxx_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void 		W25qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
 
void 		W25qxx_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);

#endif

📄 w25qxx_config.h

c 复制代码
#ifndef __W25QXX_CONFIG_H__
#define __W25QXX_CONFIG_H__
 
#include "main.h"
#include "usart.h"
#include "stdio.h"
 
#define _W25QXX_SPI                   hspi1
#define _W25QXX_CS_GPIO               FLASH_CS_GPIO_Port
#define _W25QXX_CS_PIN                FLASH_CS_Pin
#define _W25QXX_USE_FREERTOS          0
#define _W25QXX_DEBUG                 1
 
#endif

文件结构:

导入文件后记得要为代码添加路径依赖:


烧录验证

烧录程序,开发板上电后首先读取FLASH芯片的ID,并通过串口显示给用户,然后输出操作提示,按下KEY0按键会擦除块0内容,擦除后按下KEY1按键读取内容会发现全是FF,然后按下KEY2按键将数据写入,此时再按下KEY1按键读取内容会发现和我们写入的内容一致,如下图所示为整个过程串口详细输出信息。

硬件SPI驱动w25qxx:

说明 SPI 初始化成功,识别芯片正确。

一些常见的问题排查
复制代码
问题						说明
读 ID 为 0xFFFFFF		片选脚未拉低或 SPI 配置错误
写入失败					没有先执行 W25qxx_WriteEnable()
读写数据出错				SPI 模式(CPOL/CPHA)不一致
烧录后串口无输出			波特率错误或未定义重定向 printf

SPI还可以实现很多功能,例如:✅ 写入一段数据并读取验证:W25qxx_WriteBuffer + W25qxx_ReadBuffer;✅ 擦除某个扇区:W25qxx_EraseSector(uint32_t sector);✅ 文件系统支持:对接 FATFS 做 SPI Flash 虚拟 U盘;✅ 多 SPI Flash:多片选,使用 GPIO 动态控制 CS 引脚;✅ 用 DMA 提高效率:配置 SPI DMA 模式,加快大容量数据传输。

这里的 STM32 HAL库 SPI 驱动 W25Qxx Flash 是嵌入式存储应用的经典实现,适合日志存储、配置保存、音视频缓存等场景,CubeMX 配合 HAL 封装让原来标准库中复杂的流程变得更加简洁、高效。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!

相关推荐
TDengine (老段)44 分钟前
TDengine IDMP 应用场景:微电网监控
大数据·数据库·物联网·ai·时序数据库·tdengine·涛思数据
8K超高清44 分钟前
广播级讯道摄像机CCU后挂上的PGM、ENG、PROD音频旋钮是做什么用的?
大数据·人工智能·科技·数码相机·音视频·智能硬件
AAA修煤气灶刘哥1 小时前
物联网-智能设备数据交互核心技术解析
物联网·华为
Light601 小时前
通用物联网接口调用完整解决方案(第二版)
物联网
wind_one11 小时前
项目:电动车报警器
stm32·单片机·嵌入式硬件
网易独家音乐人Mike Zhou1 小时前
【Python】圆柱体内部3D点云仿真及ply文件生成,圆形3D点云检测及拟合算法
stm32·单片机·mcu·物联网·算法·点云·iot
qq_411262422 小时前
大小端对SPI和QSPI显示的影响
单片机·嵌入式硬件
白书宇3 小时前
12.从零开始写LINUX内核--控制台初始化
linux·c语言·驱动开发·嵌入式硬件·microsoft·iot
✎ ﹏梦醒͜ღ҉繁华落℘4 小时前
单片机学习---字节对齐
单片机·嵌入式硬件·学习
CC呢4 小时前
基于单片机坐姿视力保护台灯
单片机·嵌入式硬件·坐姿视力保护灯