【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 工程后):
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
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
#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
/* 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
#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");
#endifid = W25qxx_ReadID();#if (_W25QXX_DEBUG==1)printf("w25qxx ID:0x%X\r\n",id);
#endifswitch(id&0x0000FFFF){case 0x401A: // w25q512w25qxx.ID=W25Q512;w25qxx.BlockCount=1024;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q512\r\n");
#endifbreak;case 0x4019: // w25q256w25qxx.ID=W25Q256;w25qxx.BlockCount=512;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q256\r\n");
#endifbreak;case 0x4018: // w25q128w25qxx.ID=W25Q128;w25qxx.BlockCount=256;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q128\r\n");
#endifbreak;case 0x4017: // w25q64w25qxx.ID=W25Q64;w25qxx.BlockCount=128;
#if (_W25QXX_DEBUG == 1)printf("w25qxx Chip: w25q64\r\n");
#endifbreak;case 0x4016: // w25q32w25qxx.ID=W25Q32;w25qxx.BlockCount=64;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q32\r\n");
#endifbreak;case 0x4015: // w25q16w25qxx.ID=W25Q16;w25qxx.BlockCount=32;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q16\r\n");
#endifbreak;case 0x4014: // w25q80w25qxx.ID=W25Q80;w25qxx.BlockCount=16;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q80\r\n");
#endifbreak;case 0x4013: // w25q40w25qxx.ID=W25Q40;w25qxx.BlockCount=8;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q40\r\n");
#endifbreak;case 0x4012: // w25q20w25qxx.ID=W25Q20;w25qxx.BlockCount=4;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q20\r\n");
#endifbreak;case 0x4011: // w25q10w25qxx.ID=W25Q10;w25qxx.BlockCount=2;
#if (_W25QXX_DEBUG==1)printf("w25qxx Chip: w25q10\r\n");
#endifbreak;default:
#if (_W25QXX_DEBUG==1)printf("w25qxx Unknown ID\r\n");
#endifw25qxx.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");
#endifw25qxx.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");
#endifW25qxx_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);
#endifw25qxx.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);
#endifW25qxx_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);
#endifw25qxx.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();
#endifW25qxx_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);
#endifw25qxx.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);
#endifW25qxx_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);
#endifw25qxx.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
#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
#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 封装让原来标准库中复杂的流程变得更加简洁、高效。
以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!