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

【模块系列】STM32W25Q64

以W25Q64JV芯片为例。从芯片概述、引脚描述、时序介绍、存储框图、案例代码等几个方向进行介绍。部分图片以及内容摘抄至芯片手册

芯片概述

  W25Q64JV串行闪存,隶属于25Q系列,适用于代码映射至RAM、直接从双/四路SPI执行代码以及存储语音、文本与数据。工作电压范围在2.7~3.6V,待机状态下低至1µA电流。

  以W25Q64JV为例,其阵列被组织成32768个可编程页面,每个页面256字节。一直最多可编程256字节。页面可以以16、128、256个为一组或整个芯片进行擦除,也就是4KB、32KB、64KB、全部。

  支持标准串行外设接口SPI,双/四引脚SPI。此外,该设备支持JEDEC标准制造商和设备ID,以及64位唯一序列号和三个256字节的安全寄存器。

在这里插入图片描述

省略多路SPI下的引脚功能介绍,以标准SPI操作为主:

引脚名称I/O功能
/CSI片选输入
DOI/O数据输出
/WPI/O写保护输入
GND
DII/O数据输入
CLKI串行时钟输入
/HOLDI/O保持输入
VCC电源
  • /CS必须从高电平转换为低电平,才能接收新的指令
  • /HOLD可以理解成设备选中引脚,通常在多设备使用同一SPI总线时使用

时序介绍

以常用的获取芯片ID的时序流程为例

在这里插入图片描述

在这里插入图片描述

  • "9Fh"表示,十六进制的9F

通过手册中8.1.2 指令集表1(标准SPI指令)或者时序中的注释可以知道,在该指令下,接收到的三个字节,分别是制造商内容类型容量,像笔者使用的是W25Q64JV,执行指令后返回EF 40 17,华邦制造SPI类型64Mbit(8M),得到这样的信息就证明,时序是通的。

存储框图

在这里插入图片描述

256字节=1页=116扇区=1256块 256字节 = 1页 = \frac{1}{16}扇区 = \frac{1}{256}块 256字节=1=161扇区=2561

  这里在对芯片概述里的写入和擦除部分做描述,简单的来说,就是在写入时,最大不能超过1页也就是256字节,擦除时只能选择16页、128页、256页或者整个芯片都擦除的方式,对应的就是扇区(16页-4KB)、半块(128页-32KB)、(256页-64KB),等名词常提到的擦除,也表示不同颗粒度。

  擦除操作后的数据读取值为0xFF。擦除操作:是将目标区域的所有位强制置为1,即每个的字节变为0xFF。编程操作:将已擦除的1改写为0

代码案例

STM32F103C8T6+硬件SPI1。因为代码库是笔者从一个小项目剥离出来的,不相关的方向已经尽量给出替换成接口函数,如果还有什么问题可以提出来!

原理图以及配置
笔者使用W25Q64的硬件连接,以及STM32CubeMX上的配置
在这里插入图片描述

在这里插入图片描述

程序文件

ZQXY_W25Q64.h

#pragma once
// #include "ZQXY_Core.h"
#include <stdint.h>
#include <string.h>
#include "spi.h"/*** 寄存器 ***/
// 写操作控制命令
#define ZQXY_W25Q64_WRITE_ENABLE 0x06   // 写使能命令,允许写入和擦除操作
#define ZQXY_W25Q64_WRITE_DISABLE 0x04  // 写禁用命令,禁止写入和擦除操作// 状态寄存器读写命令
#define ZQXY_W25Q64_READ_STATUS_REGISTER_1 0x05  // 读状态寄存器1(包含忙标志、写使能状态等)
#define ZQXY_W25Q64_READ_STATUS_REGISTER_2 0x35  // 读状态寄存器2(包含四线模式等状态)
#define ZQXY_W25Q64_WRITE_STATUS_REGISTER 0x01   // 写状态寄存器命令// 编程(写入)命令
#define ZQXY_W25Q64_PAGE_PROGRAM 0x02       // 页编程命令(最多256字节)
#define ZQXY_W25Q64_QUAD_PAGE_PROGRAM 0x32  // 四线页编程命令(提高写入速度)// 擦除命令
#define ZQXY_W25Q64_BLOCK_ERASE_64KB 0xD8  // 64KB块擦除命令
#define ZQXY_W25Q64_BLOCK_ERASE_32KB 0x52  // 32KB块擦除命令
#define ZQXY_W25Q64_SECTOR_ERASE_4KB 0x20  // 4KB扇区擦除命令(最小擦除单元)
#define ZQXY_W25Q64_CHIP_ERASE 0xC7        // 整片擦除命令(擦除整个芯片)// 擦除控制命令
#define ZQXY_W25Q64_ERASE_SUSPEND 0x75  // 暂停擦除操作
#define ZQXY_W25Q64_ERASE_RESUME 0x7A   // 恢复擦除操作// 电源管理命令
#define ZQXY_W25Q64_POWER_DOWN 0xB9                        // 进入深度断电模式(低功耗)
#define ZQXY_W25Q64_HIGH_PERFORMANCE_MODE 0xA3             // 进入高性能模式
#define ZQXY_W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF        // 连续读模式复位命令
#define ZQXY_W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB  // 退出断电模式并读取设备ID// ID读取命令
#define ZQXY_W25Q64_MANUFACTURER_DEVICE_ID 0x90  // 读取制造商和设备ID
#define ZQXY_W25Q64_JEDEC_ID 0x9F                // 读取JEDEC ID(制造商+存储器类型+容量)// 读取数据命令
#define ZQXY_W25Q64_READ_DATA 0x03                // 普通读取命令(最高25MHz)
#define ZQXY_W25Q64_FAST_READ 0x0B                // 快速读取命令(需要虚拟字节)
#define ZQXY_W25Q64_FAST_READ_DUAL_OUTPUT 0x3B    // 双线输出快速读取
#define ZQXY_W25Q64_FAST_READ_DUAL_IO 0xBB        // 双线IO快速读取
#define ZQXY_W25Q64_FAST_READ_QUAD_OUTPUT 0x6B    // 四线输出快速读取
#define ZQXY_W25Q64_FAST_READ_QUAD_IO 0xEB        // 四线IO快速读取
#define ZQXY_W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3  // 八字节四线IO读取
#define ZQXY_W25Q64_DUMMY_BYTE 0xFF               // 虚拟字节(用于时序对齐)/*** 接口函数 ***/
void ZQXY_W25Q64_DelayMs(uint32_t ms);                       // 延时函数
void ZQXY_W25Q64_CS_Control(uint8_t state);                  // CS控制函数
void ZQXY_W25Q64_Transmit(uint8_t* tx_data, uint16_t size);  // SPI发送函数
void ZQXY_W25Q64_Receive(uint8_t* rx_data, uint16_t size);   // SPI接收函数/*** 基本函数 ***/
void ZQXY_W25Q64_Init(void);           // 初始化W25Q64
void ZQXY_W25Q64_ReadID(uint8_t* id);  // 读取W25Q64 ID
void ZQXY_W25Q64_WriteEnable(void);    // 写使能
void ZQXY_W25Q64_WaitBusy(void);       // 等待设备空闲void ZQXY_W25Q64_ChipErase(void);                // 整片擦除
void ZQXY_W25Q64_BlockErase(uint32_t address);   // 块擦除
void ZQXY_W25Q64_SectorErase(uint32_t address);  // 扇区擦除void ZQXY_W25Q64_PageProgram(uint32_t address, const uint8_t* data, uint16_t size);  // 页编程
void ZQXY_W25Q64_ReadData(uint32_t address, uint8_t* data, uint16_t size);           // 读取数据

ZQXY_W25Q64.c

该文键件中,记得替换自己的延时函数控制片选引脚函数初始化函数。以及选用的串口调试。如果使用的不是硬件SPI1,那么收发函数也要修改下。

#include "ZQXY_W25Q64.h"static const char* TAG = "[ZQXY_W25Q64]";// 如果没有串口打印功能,可以取消下面这行注释来禁用日志输出
// #define ZQXY_LOGI(tag, format, ...)/*** 接口函数 ***/
/*** @brief 延时函数* @param ms 延时毫秒数*/
void ZQXY_W25Q64_DelayMs(uint32_t ms) { ZQXY_DelayMs(ms); }/*** @brief 控制片选引脚* @param state 1表示片选高电平,0表示片选低电平*/
void ZQXY_W25Q64_CS_Control(uint8_t state)
{if (state == 1) {HAL_GPIO_WritePin(W25Q64_CS_GPIO_Port, W25Q64_CS_Pin, GPIO_PIN_SET);} else {HAL_GPIO_WritePin(W25Q64_CS_GPIO_Port, W25Q64_CS_Pin, GPIO_PIN_RESET);}
}/*** @brief SPI发送数据* @param tx_data 要发送的数据缓冲区* @param size 发送数据的大小*/
void ZQXY_W25Q64_Transmit(uint8_t* tx_data, uint16_t size)
{HAL_SPI_Transmit(&hspi1, tx_data, size, HAL_MAX_DELAY);
}/*** @brief SPI接收数据* @param rx_data 接收数据的缓冲区* @param size 接收数据的大小*/
void ZQXY_W25Q64_Receive(uint8_t* rx_data, uint16_t size)
{HAL_SPI_Receive(&hspi1, rx_data, size, HAL_MAX_DELAY);
}/*** 基本函数 ***/
/*** @brief 初始化W25Q64*/
void ZQXY_W25Q64_Init(void)
{// 初始化SPI等相关配置ZQXY_W25Q64_DelayMs(10);HAL_GPIO_WritePin(W25Q64_WP_GPIO_Port, W25Q64_WP_Pin, GPIO_PIN_SET);ZQXY_LOGI(TAG, "W25Q64 Initialized");
}/*** @brief 读取W25Q64 ID* @param re_id 存储读取ID的缓冲区* @note 读取90h返回厂商+ID(2位字节),读取ABh返回ID(1位字节),读取9Fh返回厂商+内存类型+ID(3位字节)* @note 厂商:EFh 华邦。ID:14h 16M、15h 32M、16h 64M、17 128M。内存类型:30h 旧版、40h SPI、60h QPI* @note 若是没有片选则读不出任何数据,必须先拉低片选引脚* */
void ZQXY_W25Q64_ReadID(uint8_t* id)
{uint8_t cmd;uint8_t rx_data[3];memset(rx_data, 0, sizeof(rx_data));// 读取JEDEC IDcmd = ZQXY_W25Q64_JEDEC_ID;ZQXY_W25Q64_CS_Control(0);ZQXY_W25Q64_Transmit(&cmd, 1);ZQXY_W25Q64_Receive(rx_data, 3);ZQXY_W25Q64_CS_Control(1);ZQXY_LOGI(TAG, "W25Q64 JEDEC ID: %02X %02X %02X", rx_data[0], rx_data[1], rx_data[2]);// 如果提供了ID缓冲区,则将读取的ID存储到该缓冲区if (id != NULL) {id[0] = rx_data[0];id[1] = rx_data[1];id[2] = rx_data[2];}
}/*** @brief 写使能*/
void ZQXY_W25Q64_WriteEnable(void)
{uint8_t cmd = ZQXY_W25Q64_WRITE_ENABLE;// 发送命令ZQXY_W25Q64_CS_Control(0);  ZQXY_W25Q64_Transmit(&cmd, 1);  ZQXY_W25Q64_CS_Control(1);
}/*** @brief 等待设备空闲*/
void ZQXY_W25Q64_WaitBusy(void)
{uint8_t cmd = ZQXY_W25Q64_READ_STATUS_REGISTER_1;uint8_t status = 0;// 轮询状态寄存器,直到忙标志位清零do{ZQXY_W25Q64_CS_Control(0);ZQXY_W25Q64_Transmit(&cmd, 1);ZQXY_W25Q64_Receive(&status, 1);ZQXY_W25Q64_CS_Control(1);ZQXY_W25Q64_DelayMs(1);} while (status & 0x01);  
}/*** @brief 块擦除* @param address 要擦除的地址(64KB对齐)*/
void ZQXY_W25Q64_BlockErase(uint32_t address)
{// 检查地址是否对齐if (address % 65536 != 0) {ZQXY_LOGI(TAG, "Block erase address must be 64KB aligned");return;}// 写使能ZQXY_W25Q64_WriteEnable();// 构建块擦除命令uint8_t cmd[4];cmd[0] = ZQXY_W25Q64_BLOCK_ERASE_64KB;  cmd[1] = (address >> 16) & 0xFF;        cmd[2] = (address >> 8) & 0xFF;         cmd[3] = address & 0xFF;                // 发送擦除命令ZQXY_W25Q64_CS_Control(0);    ZQXY_W25Q64_Transmit(cmd, 4); ZQXY_W25Q64_CS_Control(1);    // 等待擦除完成ZQXY_W25Q64_WaitBusy();  
}/*** @brief 扇区擦除* @param address 要擦除的地址(4KB对齐)*/
void ZQXY_W25Q64_SectorErase(uint32_t address) 
{// 检查地址是否对齐if (address % 4096 != 0) {ZQXY_LOGI(TAG, "Sector erase address must be 4KB aligned");return;}// 写使能ZQXY_W25Q64_WriteEnable();// 构建块擦除命令uint8_t cmd[4];cmd[0] = ZQXY_W25Q64_SECTOR_ERASE_4KB;cmd[1] = (address >> 16) & 0xFF;cmd[2] = (address >> 8) & 0xFF;cmd[3] = address & 0xFF;// 发送扇区擦除命令ZQXY_W25Q64_CS_Control(0);ZQXY_W25Q64_Transmit(cmd, 4);ZQXY_W25Q64_CS_Control(1);// 等待擦除完成ZQXY_W25Q64_WaitBusy();  
}/*** @brief 整片擦除*/
void ZQXY_W25Q64_ChipErase(void) 
{// 写使能ZQXY_W25Q64_WriteEnable();// 构建整片擦除命令uint8_t cmd = ZQXY_W25Q64_CHIP_ERASE;// 发送整片擦除命令ZQXY_W25Q64_CS_Control(0);ZQXY_W25Q64_Transmit(&cmd, 1);ZQXY_W25Q64_CS_Control(1);// 等待擦除完成ZQXY_W25Q64_WaitBusy();
}/*** @brief 页编程* @param address 编程起始地址* @param data 要写入的数据* @param size 写入数据的大小(最大256字节)*/
void ZQXY_W25Q64_PageProgram(uint32_t address, const uint8_t* data, uint16_t size)
{// 检查参数if (size > 256) {ZQXY_LOGI(TAG, "Page program size exceeds 256 bytes");return;}else if (data == NULL || size == 0) {ZQXY_LOGI(TAG, "Invalid page program parameters");return;}// 写使能ZQXY_W25Q64_WriteEnable();  // 构建页编程命令uint8_t tx_cmd[4];tx_cmd[0] = ZQXY_W25Q64_PAGE_PROGRAM;tx_cmd[1] = (address >> 16) & 0xFF;tx_cmd[2] = (address >> 8) & 0xFF; tx_cmd[3] = address & 0xFF;        // 发送页编程命令和数据ZQXY_W25Q64_CS_Control(0);ZQXY_W25Q64_Transmit(tx_cmd, 4); ZQXY_W25Q64_Transmit((uint8_t*)data, size);ZQXY_W25Q64_CS_Control(1);// 等待设备空闲ZQXY_W25Q64_WaitBusy();  ZQXY_LOGI(TAG, "Page program completed at address: 0x%06X", address);
}/*** @brief 读取数据* @param address 读取起始地址* @param data 存储读取数据的缓冲区* @param size 读取数据的大小*/
void ZQXY_W25Q64_ReadData(uint32_t address, uint8_t* data, uint16_t size)
{// 检查参数if (size == 0 || data == NULL){ZQXY_LOGI(TAG, "Invalid read parameters");return;}// 等待设备空闲ZQXY_W25Q64_WaitBusy();  // 构建读取命令uint8_t rx_cmd[4];rx_cmd[0] = ZQXY_W25Q64_READ_DATA;  rx_cmd[1] = (address >> 16) & 0xFF; rx_cmd[2] = (address >> 8) & 0xFF;  rx_cmd[3] = address & 0xFF;         // 发送读取命令和接收数据ZQXY_W25Q64_CS_Control(0);ZQXY_W25Q64_Transmit(rx_cmd, 4);ZQXY_W25Q64_Receive(data, size);ZQXY_W25Q64_CS_Control(1);ZQXY_LOGI(TAG, "Read %d bytes from address 0x%06X", size, address);
}

main.c(仅展示相关代码)

int mian()
{static const char* TAG = "[ZQXY_W25Q64]";// 初始化W25Q64ZQXY_W25Q64_Init();  // 读取W25Q64 IDZQXY_W25Q64_ReadID();       // 擦除整片 + 读取3字节ZQXY_W25Q64_ChipErase();    ZQXY_W25Q64_ReadData(0x000000, RxDataBuffer, 3);ZQXY_LOGI(TAG, "ReadData: %d %d %d", RxDataBuffer[0], RxDataBuffer[1], RxDataBuffer[2]);ZQXY_DelayMs(3000);// 写入3字节数据 + 读取3字节RxDataBuffer[0] = 0x01;RxDataBuffer[1] = 0x02;RxDataBuffer[2] = 0x03;ZQXY_W25Q64_PageProgram(0x000000, RxDataBuffer, 3);memset(RxDataBuffer, 0, sizeof(RxDataBuffer));ZQXY_W25Q64_ReadData(0x000000, RxDataBuffer, 3);ZQXY_LOGI(TAG, "ReadData: %d %d %d", RxDataBuffer[0], RxDataBuffer[1], RxDataBuffer[2]);ZQXY_DelayMs(3000);
}

调试信息
上述代码在串口中打印出的信息

在这里插入图片描述

http://www.lryc.cn/news/625574.html

相关文章:

  • TDengine IDMP 运维指南(4. 使用 Docker 部署)
  • 第六天~提取Arxml中CAN物理通道信息CANChannel--Physical Channel
  • 5. Dataloader 自定义数据集制作
  • C语言基础:(十八)C语言内存函数
  • java17学习笔记-Deprecate the Applet API for Removal
  • 算法——质数筛法
  • yolov5s.onnx转rk模型以及相关使用详细教程
  • 假设检验的原理
  • python的社区互助养老系统
  • word如何转换为pdf
  • MFC中使用EXCEL的方法之一
  • ios使用saveVideoToPhotosAlbum 保存视频失败提示 invalid video
  • 基于单片机的智能声控窗帘
  • 437. 路径总和 III
  • Qt 插件开发全解析:从接口定义,插件封装,插件调用到插件间的通信
  • SWMM排水管网水力、水质建模及在海绵与水环境中的应用
  • 第5章 高级状态管理
  • 结合BI多维度异常分析(日期-> 商家/渠道->日期(商家/渠道))
  • 深入理解 CAS:无锁编程的核心基石
  • nginx安装配置教程
  • 理解JavaScript中的函数赋值和调用
  • Gemini CLI 详细操作手册
  • 传统概率信息检索模型:理论基础、演进与局限
  • JETSON ORIN NANO进阶教程(六、安装使用Jetson-container)
  • elementplus组件文本框设置前缀
  • 网络基础——网络传输基本流程
  • 【服务器】Apache Superset功能、部署与体验
  • C++高频知识点(二十四)
  • 【基础-判断】所有使用@Component修饰的自定义组件都支持onPageShow,onBackPress和onPageHide生命周期函数
  • 一个基于前端技术的小狗寿命阶段计算网站,帮助用户了解狗狗在不同年龄阶段的特点和需求。