当前位置: 首页 > news >正文

STM32 HAL库驱动W25QXX Flash

STM32 HAL库驱动W25QXX Flash

1. 概述

W25QXX系列是一种SPI接口的Flash存储器,广泛应用于嵌入式系统中作为数据存储设备。本文档详细介绍了基于STM32 HAL库的W25QXX Flash驱动实现,包括硬件连接、驱动函数实现以及使用示例。

项目源码仓库:STM32_Sensor_Drives

2. 硬件连接

在这里插入图片描述

W25QXX Flash通过SPI接口与STM32连接,主要包括以下引脚:

  • SCK - 连接到STM32的SPI1_SCK (PA5)
  • MISO - 连接到STM32的SPI1_MISO (PA6)
  • MOSI - 连接到STM32的SPI1_MOSI (PA7)
  • CS - 连接到STM32的GPIO (PA4)

3. 驱动实现

3.1 SPI配置

首先,我们需要配置SPI接口以与W25QXX通信。在spi.c文件中,SPI1的初始化配置如下:

void MX_SPI1_Init(void)
{hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_MASTER;hspi1.Init.Direction = SPI_DIRECTION_2LINES;hspi1.Init.DataSize = SPI_DATASIZE_8BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}
}

这里配置SPI为主模式,8位数据宽度,高电平空闲,第二个边沿采样,软件控制NSS,波特率预分频为8,MSB优先传输。

3.2 GPIO配置

W25QXX的片选信号需要通过GPIO控制,在gpio.c中配置如下:

void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);/*Configure GPIO pin : PA4 */GPIO_InitStruct.Pin = GPIO_PIN_4;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

PA4配置为推挽输出模式,用于控制W25QXX的片选信号。初始状态设置为高电平(未选中)。

3.3 W25QXX命令定义

spi.h文件中,定义了W25QXX的各种命令和片选引脚:

#define ManufactDeviceID_CMD	0x90
#define READ_STATU_REGISTER_1   0x05
#define READ_STATU_REGISTER_2   0x35
#define READ_DATA_CMD	        0x03
#define WRITE_ENABLE_CMD	    0x06
#define WRITE_DISABLE_CMD	    0x04
#define SECTOR_ERASE_CMD	    0x20
#define CHIP_ERASE_CMD	        0xc7
#define PAGE_PROGRAM_CMD        0x02#define W25Q64_CHIP_SELECT_GPIO_Port GPIOA
#define W25Q64_CHIP_SELECT_Pin GPIO_PIN_4

这些命令用于实现读取ID、读写数据、擦除扇区等操作。

3.4 SPI基础通信函数

spi.c文件中,实现了三个基础的SPI通信函数:

/*** @brief    SPI发送指定长度的数据* @param    buf  —— 发送数据缓冲区首地址* @param    size —— 要发送数据的字节数* @retval   成功返回HAL_OK*/
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{return HAL_SPI_Transmit(&hspi1, send_buf, size, 100);
}/*** @brief   SPI接收指定长度的数据* @param   buf  —— 接收数据缓冲区首地址* @param   size —— 要接收数据的字节数* @retval  成功返回HAL_OK*/
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{return HAL_SPI_Receive(&hspi1, recv_buf, size, 100);
}/*** @brief   SPI在发送数据的同时接收指定长度的数据* @param   send_buf  —— 接收数据缓冲区首地址* @param   recv_buf  —— 接收数据缓冲区首地址* @param   size —— 要发送/接收数据的字节数* @retval  成功返回HAL_OK*/
static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)
{return HAL_SPI_TransmitReceive(&hspi1, send_buf, recv_buf, size, 100);
}

这三个函数分别用于发送数据、接收数据和同时发送接收数据,是W25QXX驱动的基础。

3.5 W25QXX驱动函数实现

3.5.1 读取Flash ID
/*** @brief   读取Flash内部的ID* @param   none* @retval  成功返回device_id*/
uint16_t W25QXX_ReadID(void)
{uint8_t recv_buf[2] = {0};    //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device IDuint16_t device_id = 0;uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00};   //待发送数据,命令+地址/* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);/* 发送并读取数据 */if (HAL_OK == SPI_Transmit(send_data, 4)) {if (HAL_OK == SPI_Receive(recv_buf, 2)) {device_id = (recv_buf[0] << 8) | recv_buf[1];}}/* 取消片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);return device_id;
}

该函数用于读取W25QXX的制造商ID和设备ID,通过发送0x90命令和三个字节的地址(全0),然后读取两个字节的数据。

3.5.2 读取状态寄存器
/*** @brief     读取W25QXX的状态寄存器,W25Q64一共有2个状态寄存器* @param     reg  —— 状态寄存器编号(1~2)* @retval    状态寄存器的值*/
static uint8_t W25QXX_ReadSR(uint8_t reg)
{uint8_t result = 0; uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};switch(reg){case 1:send_buf[0] = READ_STATU_REGISTER_1;case 2:send_buf[0] = READ_STATU_REGISTER_2;case 0:default:send_buf[0] = READ_STATU_REGISTER_1;}/* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);if (HAL_OK == SPI_Transmit(send_buf, 4)) {if (HAL_OK == SPI_Receive(&result, 1)) {HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);return result;}}/* 取消片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);return 0;
}

该函数用于读取W25QXX的状态寄存器,W25Q64有两个状态寄存器,通过参数reg选择要读取的寄存器。

3.5.3 等待Flash空闲
/*** @brief	阻塞等待Flash处于空闲状态* @param   none* @retval  none*/
static void W25QXX_Wait_Busy(void)
{while((W25QXX_ReadSR(1) & 0x01) == 0x01); // 等待BUSY位清空
}

该函数通过循环检查状态寄存器1的最低位(BUSY位),等待其变为0,表示Flash处于空闲状态。

3.5.4 读取数据
/*** @brief   读取SPI FLASH数据* @param   buffer      —— 数据存储区* @param   start_addr  —— 开始读取的地址(最大32bit)* @param   nbytes      —— 要读取的字节数(最大65535)* @retval  成功返回0,失败返回-1*/
int W25QXX_Read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
{uint8_t cmd = READ_DATA_CMD;start_addr = start_addr << 8;W25QXX_Wait_Busy();/* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);SPI_Transmit(&cmd, 1);if (HAL_OK == SPI_Transmit((uint8_t*)&start_addr, 3)) {if (HAL_OK == SPI_Receive(buffer, nbytes)) {HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);return 0;}}HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);return -1;
}

该函数用于从指定地址读取指定长度的数据,首先发送读取命令(0x03),然后发送3字节地址,最后读取数据。

3.5.5 写使能和写禁止
/*** @brief    W25QXX写使能,将S1寄存器的WEL置位* @param    none* @retval*/
void W25QXX_Write_Enable(void)
{uint8_t cmd= WRITE_ENABLE_CMD;HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);SPI_Transmit(&cmd, 1);HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);W25QXX_Wait_Busy();}/*** @brief    W25QXX写禁止,将WEL清零* @param    none* @retval    none*/
void W25QXX_Write_Disable(void)
{uint8_t cmd = WRITE_DISABLE_CMD;HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);SPI_Transmit(&cmd, 1);HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);W25QXX_Wait_Busy();
}

这两个函数分别用于使能和禁止写操作。在进行写入或擦除操作前,必须先调用写使能函数。

3.5.6 扇区擦除
/*** @brief    W25QXX擦除一个扇区* @param   sector_addr    —— 扇区地址 根据实际容量设置* @retval  none* @note    阻塞操作*/
void W25QXX_Erase_Sector(uint32_t sector_addr)
{uint8_t cmd = SECTOR_ERASE_CMD;sector_addr *= 4096;    //每个块有16个扇区,每个扇区的大小是4KB,需要换算为实际地址sector_addr <<= 8;W25QXX_Write_Enable();  //擦除操作即写入0xFF,需要开启写使能W25QXX_Wait_Busy();        //等待写使能完成/* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);SPI_Transmit(&cmd, 1);SPI_Transmit((uint8_t*)&sector_addr, 3);HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);W25QXX_Wait_Busy();       //等待扇区擦除完成
}

该函数用于擦除指定的扇区,每个扇区大小为4KB。擦除前需要先使能写操作,擦除后需要等待操作完成。

3.5.7 页编程(写入数据)
/*** @brief    页写入操作* @param    dat —— 要写入的数据缓冲区首地址* @param    WriteAddr —— 要写入的地址* @param   byte_to_write —— 要写入的字节数(0-256)* @retval    none*/
void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{uint8_t cmd = PAGE_PROGRAM_CMD;WriteAddr <<= 8;W25QXX_Write_Enable();/* 使能片选 */HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);SPI_Transmit(&cmd, 1);SPI_Transmit((uint8_t*)&WriteAddr, 3);SPI_Transmit(dat, nbytes);HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);W25QXX_Wait_Busy();
}

该函数用于向指定地址写入数据,写入前需要先使能写操作,写入后需要等待操作完成。W25QXX的页大小为256字节,一次写入不能超过一页。

4. 使用示例

main.c文件中,展示了如何使用W25QXX驱动进行读写操作:

int main(void)
{/* 省略初始化代码 *//* Infinite loop *//* USER CODE BEGIN WHILE */printf("System will start while\n");printf("read data before write\r\n");W25QXX_Read(read_buf, 0, 10);snprintf(str, 15, "read data: %x", read_buf[2]);printf("%s\r\n", str);/* 擦除该扇区 */printf("erase sector 0 \r\n");W25QXX_Erase_Sector(0);/* 再次读数据 */printf("read erase data\r\n");W25QXX_Read(read_buf, 0, 10);memset(str, 0, sizeof(str)); // 清空读缓冲区snprintf(str, 15, "read data: %x", read_buf[2]);printf("%s\r\n", str);/* 写数据 */printf("write data \r\n");for (i = 0; i < 10; i++){write_buf[i] = i;}W25QXX_Page_Program(write_buf, 0, 10); // 写数据/* 再次读数据 */printf("read write data \r\n");W25QXX_Read(read_buf, 0, 10);memset(str, 0, sizeof(str)); // 清空读缓冲区snprintf(str, 15, "read data: %x", read_buf[2]);printf("%s\r\n", str);while (1){HAL_Delay(200);}
}

这个示例展示了完整的W25QXX操作流程:

  1. 首先读取Flash中的数据
  2. 擦除扇区0
  3. 再次读取数据,验证擦除效果
  4. 写入新数据
  5. 再次读取数据,验证写入效果

5. 串口打印功能

为了方便调试,在usart.c中实现了printf函数的重定向:

int fputc(int ch, FILE *f)
{if (f == stdout)  // 仅处理标准输出{HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 100);  // 阻塞发送if (ch == '\n')  // 发送\n时自动补充\rHAL_UART_Transmit(&huart2, (uint8_t *)"\r", 1, 100);}return ch;
}

通过重定向fputc函数,使得printf函数的输出通过UART2发送,便于观察程序运行状态。

6. 总结

本文档详细介绍了基于STM32 HAL库的W25QXX Flash驱动实现,包括:

  1. SPI和GPIO的配置
  2. W25QXX的命令定义
  3. 基础SPI通信函数
  4. W25QXX驱动函数实现(读ID、读写数据、擦除扇区等)
  5. 使用示例
  6. 串口打印功能

通过这些代码,可以方便地在STM32项目中使用W25QXX Flash进行数据存储和读取。

7. 注意事项

  1. W25QXX的写入操作需要先擦除扇区,因为Flash只能将1变为0,不能将0变为1
  2. 擦除和写入操作前必须先使能写操作
  3. 写入数据不能跨页,一页为256字节
  4. 擦除和写入操作需要等待完成,否则可能导致数据错误
  5. 频繁擦写同一区域会导致Flash寿命减少,建议实现磨损均衡算法

8. 参考资料

  • W25Q64数据手册
  • STM32 HAL库文档
  • STM32 SPI通信指南
http://www.dtcms.com/a/325835.html

相关文章:

  • es基本概念-自学笔记
  • 嵌入式硬件中MOS管图形详解
  • Unity笔记(五)知识补充——场景切换、退出游戏、鼠标隐藏锁定、随机数、委托
  • Mini-Omni: Language Models Can Hear, Talk While Thinking in Streaming
  • 数据库的基本操作(约束与DQL查询)
  • 分治-归并-912.排序数组-力扣(LeetCode)
  • 京东科技集团寻求稳定币链上活动规划师
  • 150V降压芯片DCDC150V100V80V降压12V5V1.5A车载仪表恒压驱动H6203L惠洋科技
  • shape转换ersi json 修改增加多部件要素处理和空洞处理
  • 安卓\android程序开发之基于 Android 的校园报修系统的设计与实现
  • Android.mk教程
  • RFID系统:物联网时代的数字化管理中枢
  • 算法训练营day45 动态规划⑫ 115.不同的子序列、583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇
  • Java -- 集合 --Collection接口和常用的方法
  • (3万字详解)Linux系统学习:深入了解Linux系统开发工具
  • leetcode 15 三数之和
  • 【《数字货币量化交易:Linux下策略回测平台的搭建》】
  • 2025-2026 专升本论文写作【八项规范】
  • [202404-B]画矩形
  • 微信小程序常用 API
  • Arcpy-重采样记录
  • B站直播, 拼接4个窗口,能否实现
  • 从源码看 Coze:Agent 的三大支柱是如何构建的?
  • 【优化】图片批量合并为word
  • 嵌入式学习day24
  • MySQL的索引(索引的数据结构-B+树索引):
  • P2865 [USACO06NOV] Roadblocks G
  • 音视频学习(五十三):音频重采样
  • 数据备份与进程管理
  • AI大模型:(二)5.1 文生视频(Text-to-Video)模型发展史