一、SPI简介 






二、SPI协议

主设备将待发送的 1 字节数据(如 0x55)加载到 "主发送移位寄存器",从设备将待返回的 1 字节数据(如 0xAA)加载到 "从发送移位寄存器"。
-
主设备将发送寄存器的最高位放到 MOSI 线上,从设备在该时钟沿采样 MOSI 的数据,存入自己的接收寄存器。
-
从设备将发送寄存器的最高位放到 MISO 线上,主设备在该时钟沿采样 MISO 的数据,存入自己的接收寄存器。
传输的时候高位优先,循环移位 8 次后,主机和从机间就完成了一个字节的收发




模式 0(CPOL=0,CPHA=0)------ 最常用
-
空闲状态:SCLK = 低电平
-
传输过程(以主设备发数据、从设备收数据为例):
-
主设备先把要发送的 1 位数据放到 MOSI 线(数据先稳定);
-
SCLK 从「低→高」(第一个跳变沿:上升沿)→ 从设备采样(读) MOSI 上的数据;
-
SCLK 从「高→低」(第二个跳变沿:下降沿)→ 主设备更新 MOSI 上的下一位数据;
-
-
核心口诀:低空闲,上升沿读,下降沿写
-
典型应用:SPI Flash(如 W25Q64)、OLED 屏幕、SD 卡、大部分传感器(新手优先记这个模式)。

模式 1(CPOL=0,CPHA=1)
-
空闲状态:SCLK = 低电平
-
传输过程:
-
SCLK 从「低→高」(第一个跳变沿:上升沿)→ 主设备更新 MOSI 数据;
-
SCLK 从「高→低」(第二个跳变沿:下降沿)→ 从设备采样(读)数据;
-
-
核心口诀:低空闲,上升沿写,下降沿读。

模式 2(CPOL=1,CPHA=0)
-
空闲状态:SCLK = 高电平
-
传输过程:
-
主设备先把数据放到 MOSI 线;
-
SCLK 从「高→低」(第一个跳变沿:下降沿)→ 从设备采样(读)数据;
-
SCLK 从「低→高」(第二个跳变沿:上升沿)→ 主设备更新数据;
-
-
核心口诀:高空闲,下降沿读,上升沿写。

模式 3(CPOL=1,CPHA=1)
-
空闲状态:SCLK = 高电平
-
传输过程:
-
SCLK 从「高→低」(第一个跳变沿:下降沿)→ 主设备更新 MOSI 数据;
-
SCLK 从「低→高」(第二个跳变沿:上升沿)→ 从设备采样(读)数据;
-
-
核心口诀:高空闲,下降沿写,上升沿读。






三、 SPI外设关键知识点












四、SPI-FLASH模块

闪存 (Flash Memory) 全面解析
闪存是一种非易失性固态存储技术,断电后仍能保留数据,结合了 ROM 的非易失性与 RAM 的可读写性,是现代电子设备中最常用的存储介质之一。
一、基本概念与工作原理
- 核心定义
闪存 (Flash Memory) 是电可擦除可编程只读存储器 (EEPROM) 的变种 ,通过浮栅 MOSFET (Floating Gate MOSFET) 存储电荷来表示数据,无需机械部件,具有低功耗、高可靠性、抗震动等优点。
- 工作原理(浮栅技术)
| 操作 | 原理 | 实现方式 |
|---|---|---|
| 写入 (编程) | 向浮栅注入电子 | 热电子注入或 Fowler-Nordheim 隧穿效应,施加高电压使电子穿过氧化层进入浮栅 |
| 擦除 | 从浮栅移除电子 | 反向隧穿,施加反向高电压使电子离开浮栅 |
| 读取 | 检测浮栅电荷状态 | 测量 MOSFET 阈值电压变化,判断存储的是 0 还是 1 |
关键特性 :闪存必须先擦除后写入 ,且擦除操作以 ** 块 (block)为单位,写入以页 (page)** 为单位(NOR 闪存支持字节级写入)。
二、闪存的主要类型
- NOR Flash vs NAND Flash(两种基本架构)
| 特性 | NOR Flash | NAND Flash | 典型应用 |
|---|---|---|---|
| 读取速度 | 快 (随机读取优异) | 较慢 (顺序读取较好) | NOR: 程序存储 (如 STM32 固件);NAND: 数据存储 (如 SSD、U 盘) |
| 写入 / 擦除速度 | 慢 | 快 | - |
| 擦除单位 | 大 (64KB~128KB) | 小 (4KB~16KB) | - |
| 存储密度 | 低 | 高 (成本更低) | - |
| 接口 | 类 RAM 接口 (随机访问) | I/O 接口 (串行访问) | - |
| 可靠性 | 高 (位翻转率低) | 较低 (需 ECC 纠错) | - |
- 按存储单元密度分类(NAND Flash)
| 类型 | 每单元存储比特数 | 特点 | 应用场景 |
|---|---|---|---|
| SLC (单层单元) | 1bit | 最快、最耐用 (10 万~100 万次擦写)、成本最高 | 工业控制、企业级 SSD、缓存 |
| MLC (多层单元) | 2bit | 平衡性能与成本 (1 万~10 万次擦写) | 消费级 SSD、高端手机 |
| TLC (三层单元) | 3bit | 容量大、成本低 (1000~3000 次擦写) | 主流 SSD、移动设备、U 盘 |
| QLC (四层单元) | 4bit | 容量最大、成本最低 (500~1000 次擦写) | 大容量存储、数据中心冷存储 |
| PLC (五层单元) | 5bit | 实验阶段,极高密度 | 未来大容量存储 |
- 按结构分类:2D NAND vs 3D NAND
-
2D NAND:平面结构,通过缩小工艺提升密度,已接近物理极限 (10nm 以下)
-
3D NAND:垂直堆叠多层存储单元 (64 层~300 + 层),大幅提升密度,降低成本,是当前主流技术
- 代表技术:铠侠 BiCS、三星 V-NAND、长江存储 Xtacking
三、闪存的优势与局限性
- 优势
-
非易失性:断电数据不丢失,优于 DRAM
-
固态结构:无机械部件,抗震、低噪音、寿命长
-
低功耗:比 HDD 和 DRAM 功耗低,适合移动设备
-
快速访问:平均响应时间 (微秒级) 远快于 HDD (毫秒级)
-
体积小:适合轻薄设备设计
- 局限性
-
有限擦写寿命:每个存储块只能擦写有限次数 (1000~100 万次),需通过 ** 磨损均衡 (Wear Leveling)** 技术延长寿命
-
写入前擦除:不能直接覆盖数据,需先擦除整个块,影响写入性能
-
位翻转风险:长期使用或受干扰可能出现位错误,需 **ECC (错误校验与纠正)** 技术保障数据完整性
-
数据保持期:存储数据会随时间流失,需定期刷新 (通常 10 年以上保持期)
四、闪存的应用领域
-
消费电子:智能手机、平板电脑、数码相机、U 盘、SD 卡、MP3 播放器
-
计算机存储:SSD (固态硬盘)、混合硬盘 (SSHD)、内存扩展卡
-
嵌入式系统:STM32 等单片机固件存储、路由器 / 交换机配置存储、工业控制设备程序存储
-
企业级应用:数据中心存储阵列、云存储设备、高速缓存系统
-
汽车电子:车载娱乐系统、导航、自动驾驶数据存储、ECU 固件
-
物联网 (IoT):传感器数据存储、智能家居设备、可穿戴设备
五、闪存技术发展历程与未来趋势
- 发展里程碑
-
1984 年:东芝公司 Fujio Masuoka 发明闪存概念
-
1988 年:英特尔推出首款商用 NOR Flash 芯片
-
1989 年:东芝推出首款 NAND Flash 芯片
-
2009 年:三星推出首款 3D NAND 原型
-
2013 年:商用 3D NAND 开始量产
-
2025 年:3D NAND 层数突破 300 层,接口速度达 4.8Gb/s
-
未来发展方向
-
更高堆叠层数:3D NAND 向 500 + 层发展,提升存储密度
-
新材料应用:从氧化硅向二维材料 (如石墨烯)、相变材料发展,突破物理极限
-
存储类内存 (SCM):结合 DRAM 速度与闪存非易失性的新型存储技术
-
更高性能接口:PCIe 5.0/6.0、NVMe 2.0 等,提升数据传输速度
-
更低功耗设计:适应移动设备和物联网低功耗需求
-
增强安全性:硬件加密、安全擦除、防篡改技术提升数据安全性
六、嵌入式系统中的闪存使用(STM32 开发者视角)
对于 STM32 等单片机开发者,闪存主要用于:
-
程序存储:NOR Flash 存储固件代码,支持直接执行 (XIP, Execute In Place)
-
数据存储:片内 Flash 或外接 NAND Flash 存储配置参数、日志数据等
-
升级功能:通过 IAP (In Application Programming) 技术实现固件远程升级
开发要点:
-
了解 STM32 Flash 编程规则(如擦除扇区大小、编程对齐要求)
-
使用 HAL 库 Flash 操作函数(如 HAL_FLASH_Program、HAL_FLASHEx_Erase)
-
注意 Flash 擦写次数限制(通常 1 万~10 万次),避免频繁写入同一区域
-
重要数据建议使用双备份 和校验和机制保障完整性




五、SPI-FLASH实战


1、实战问题:
(1)、设备id出错
void norflash_init(void)
{
// 定义变量存储读取到的设备ID
uint16_t id = 0;
// 初始化SPI总线(配置SPI时钟、模式、引脚等)
spi_init();
// 读取NorFlash的设备ID
id = norflash_read_id();
if(id == 0x0000)
{
// ID匹配,初始化成功,打印提示信息
printf("初始化成功\r\n");
}
else
{
// ID不匹配,初始化失败,打印提示信息
printf("初始化失败\r\n");
printf("读取到的ID: 0x%04X\r\n", id); // 打印实际读取到的ID
}
}

(2)、原因分析
一、核心现象解读
读取到的 ID 始终为 0xFFFF,说明 SPI 通信完全失败,没有从 NorFlash 获取到任何有效数据,本质是主机与 Flash 之间的通信链路出现了根本性问题。
二、分模块原因分析(按排查优先级)
🔍 1. 硬件问题(最常见,优先排查)
-
引脚连接错误 / 虚焊 SPI 的 SCLK(时钟)、MISO(主机输入,核心!)、MOSI(主机输出)、CS(片选)引脚接错或虚焊,尤其是MISO 引脚,如果连接异常,主机无法接收 Flash 返回的 ID 数据,直接返回全 FF。
-
Flash 供电异常NorFlash 的 VCC/GND 未正确连接,或电源电压不足(需 3.3V),导致芯片无法上电初始化,无任何指令响应。
-
CS 引脚硬件电平异常CS 引脚被外部电路强制拉高 / 拉低,导致软件无法通过 CS 引脚选中 Flash,通信自然失败。
-
芯片损坏Flash 芯片因静电、过压等原因损坏,或进入写保护 / 锁死状态,无法响应指令。
💻 2. 软件配置错误
-
SPI 模式错误 诺存 25Q64 等 NorFlash 要求 SPI 模式为模式 3(CPOL=HIGH、CPHA=2EDGE),若配置为模式 0/1/2,时钟采样边沿与 Flash 不匹配,会导致数据采样错误。
-
**SPI 分频过高(时钟太快)**STM32F103 的 SPI 若使用高频分频(如 1 分频 = 72MHz),会超过 Flash 的最大时钟上限(一般为 80MHz,但低速更稳定),导致时序不匹配,数据传输错误。
-
CS 引脚操作错误 通信时 CS 引脚未正确拉低,或拉低后没有等待
tCS时序(一般需 5-100us)就发送指令,Flash 未准备好接收,无响应。 -
全局变量未正确初始化
g_spi_handler未正确定义 / 声明,导致 SPI 外设未完成初始化,通信时无时钟 / 数据输出。
⏱️ 3. 时序问题(针对 STM32F1 等无 FIFO 单片机)
-
缺少总线预热STM32F1 的 SPI 无硬件 FIFO,首次通信时钟易抖动,没有总线预热(空字节收发 + CS 翻转)会导致首次读取失败,返回全 FF。
-
指令时序不满足要求发送指令后没有等待 Flash 准备输出数据(如 0x90 指令地址位后需 20us 延时),或读取 ID 时字节间无延时,导致采样错误。
📋 4. 初始化流程错误
-
SPI 初始化顺序错误先执行读取 ID 操作,再初始化 SPI 外设,导致通信时 SPI 还未进入就绪状态。
-
错误状态未清除主循环中反复调用初始化,未清除 SPI 的溢出(OVR)、模式错误(MODF)等标志,错误状态累积导致通信失败。
-
指令码错误发送的指令码与注释不符(如要发 0x90 却发成 0x9F),Flash 不识别指令,返回全 FF。
三、快速排查步骤
-
硬件排查:用万用表测量 Flash 的 VCC/GND 电压,检查 SPI 引脚连接是否正确,重点确认 MISO 引脚。
-
软件配置检查:确认 SPI 模式为 3、分频为 512(低速)、CS 引脚操作正确。
-
时序验证:添加总线预热函数,确保指令发送前后的延时满足要求。
-
流程检查:确保先初始化 SPI,再读取 ID,且初始化仅执行一次,执行后清除错误标志
经过层层验证,由于主包使用的是诺存25Q64的flash芯片所以需要总线预热
(3)、总线预热
总线预热本质是嵌入式开发中针对串行总线(如 SPI、I2C)和 低速外设(如 NorFlash、EEPROM)的时序补偿与稳定性优化手段,并非所有场景都需要,但在以下典型场景中是必要的:
一、硬件特性限制导致的首次通信不稳定
- STM32F1 等无硬件 FIFO 的单片机
-
这类单片机的 SPI 外设没有硬件 FIFO 缓冲,首次通信时时钟信号容易出现抖动,导致数据采样错误。
-
总线预热通过空字节收发可以稳定 SPI 时钟逻辑,让外设与主机的时钟同步进入稳定状态。
-
✅ 你的场景(STM32F103RCT6)正是典型案例,F1 的 SPI 无 FIFO,首次通信抖动是核心痛点。
- NorFlash、EEPROM 等串行存储芯片
-
这类芯片通电后需要3-5ms 的内部初始化时间(如诺存 25Q64),在此期间接收指令会无响应。
-
总线预热通过CS 引脚翻转 + 空字节收发,可以触发芯片完成内部初始化,感知总线存在,避免首次指令发送失败。
二、时序敏感的指令或通信场景
- 高时序要求的指令(如 0x9FH JEDEC ID 指令)
-
0x9FH 指令对 SPI 时钟稳定性的要求远高于 0x90H 指令,首次通信时的时钟抖动会直接导致采样错误(返回 0x7FFFFF)。
-
总线预热通过延长延时 + 空字节收发,可以提升时钟稳定性,满足高时序指令的要求。
- 低温 / 高干扰环境
-
在低温或电磁干扰较强的环境下,硬件的时序裕度会大幅降低,首次通信容易出现误码。
-
总线预热可以主动让总线进入稳定状态,减少环境干扰对通信的影响,提升鲁棒性。
三、调试中发现的典型问题场景
如果你的工程出现以下现象,说明需要总线预热:
-
首次通信失败,第二次成功:比如首次读取 0x9FH ID 返回 0xFFFFFF,但第二次读取成功,这是典型的 "总线未稳定" 问题。
-
上电后首次操作无响应:比如上电后首次发送擦除指令,Flash 无反馈,但后续操作正常。
-
多设备共享总线时通信异常:总线上挂多个设备时,切换设备后的首次通信容易因电平残留导致错误,预热可以清除残留。
四、不需要总线预热的场景
-
STM32F4/F7 等带硬件 FIFO 的单片机:硬件 FIFO 可以缓冲数据,减少时钟抖动对首次通信的影响。
-
高速 SPI 通信(如 40MHz 以上):高速总线的时钟稳定性通常由硬件保证,无需额外预热。
-
单次通信且对稳定性要求低:比如偶尔读取一次状态寄存器,即使首次失败也不影响整体功能。
总结
总线预热不是 "万能药",而是针对特定硬件和时序问题的精准优化。在你的场景(STM32F103 + 诺存 25Q64+0x9FH 指令)中,它是解决首次通信不稳定的核心手段;而在其他硬件或场景下,可能不需要额外预热。
修改后

2、最终结果

完成代码
#include "./spi_flash.h"
#include "./spi.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "stm32f1xx_hal.h"
// ************************ 核心:通用总线预热函数(同时支持90H/9FH指令) ************************
/**
* @brief SPI总线+NorFlash通用预热(解决F103首次通信时序抖动,兼顾90H/9FH指令要求)
* @note 适配STM32F103RCT6,仅保留F1支持的SPI操作,时序优化满足两种指令的需求
* @retval 无
*/
static void norflash_bus_preheat(void)
{
// 1. 清除F103支持的SPI错误标志(初始化后残留,避免影响两种指令首次收发)
if(__HAL_SPI_GET_FLAG(&g_spi_handler, SPI_FLAG_OVR)) // 溢出错误(F1支持)
__HAL_SPI_CLEAR_OVRFLAG(&g_spi_handler);
if(__HAL_SPI_GET_FLAG(&g_spi_handler, SPI_FLAG_MODF)) // 模式错误(F1支持)
__HAL_SPI_CLEAR_MODFFLAG(&g_spi_handler);
// 2. CS引脚翻转预热(触发NorFlash完成内部通电初始化,兼顾两种指令时序要求)
SPI_CS(1); // 默认拉高(空闲状态)
delay_ms(3); // 等待Flash电源稳定(诺存25Q64需3ms左右,两种指令均适用)
SPI_CS(0); // 拉低选中Flash
delay_us(100); // 微秒级延时,匹配Flash tCS时序,避免指令发送无响应
SPI_CS(1); // 拉高释放Flash
delay_ms(2); // 稳定总线状态,清除残留电平干扰
// 3. 空字节收发预热(稳定F103 SPI时钟逻辑,解决两种指令首次采样错误)
SPI_CS(0);
delay_us(50); // 选中后延时,满足90H/9FH指令前置时序要求
for(uint8_t i=0; i<4; i++) // 发送4个空字节0xFF,预热SPI收发链路
{
spi_read_write_byte(0xFF); // 无实际数据,仅稳定时钟和数据总线
delay_us(10); // 每字节后短延时,避免F103 SPI时钟溢出(兼顾两种指令稳定性)
}
SPI_CS(1);
delay_ms(1); // 释放后延时,让Flash退出选中状态,准备接收指令
}
// ************************ 功能1:90H指令读取16位设备ID ************************
/**
* @brief 用90H指令读取NorFlash 16位设备ID(制造商ID+设备ID)
* @note 适配STM32F103RCT6,诺存25Q64标准值为0x5216
* @retval 16位ID(高8位:制造商ID,低8位:设备ID)
*/
uint16_t norflash_read_id_90(void)
{
uint16_t id_data = 0xFFFF; // 初始化为全FF,便于判断通信失败
uint8_t manu_id = 0xFF; // 制造商ID(0x52)
uint8_t dev_id = 0xFF; // 设备ID(0x16)
// 1. 选中NorFlash,开始90H指令通信
SPI_CS(0);
delay_us(50); // 精准微秒延时,匹配90H指令tCS时序要求
// 2. SPI就绪时执行操作,避免无效收发
if(HAL_SPI_GetState(&g_spi_handler) == HAL_SPI_STATE_READY)
{
// 发送90H指令+3个0x00地址位(90H指令要求)
spi_read_write_byte(0x90);
delay_us(10);
spi_read_write_byte(0x00);
delay_us(10);
spi_read_write_byte(0x00);
delay_us(10);
spi_read_write_byte(0x00);
delay_us(20); // 地址位后延时,等待Flash准备输出ID
// 读取制造商ID和设备ID
manu_id = spi_read_write_byte(0xFF);
delay_us(5);
dev_id = spi_read_write_byte(0xFF);
}
// 3. 拼接16位ID
id_data = ((uint16_t)manu_id << 8) | dev_id;
// 4. 释放NorFlash,结束通信
SPI_CS(1);
delay_us(50); // 匹配Flash tCH时序,避免总线残留数据
// 5. 清除本次通信溢出错误,避免累积影响
if(__HAL_SPI_GET_FLAG(&g_spi_handler, SPI_FLAG_OVR))
__HAL_SPI_CLEAR_OVRFLAG(&g_spi_handler);
return id_data;
}
// ************************ 功能2:9FH指令读取3字节JEDEC ID ************************
/**
* @brief 用9FH指令读取NorFlash 3字节JEDEC ID(制造商ID+类型ID+容量ID)
* @note 适配STM32F103RCT6,诺存25Q64标准值为0x522217
* @retval 32位整数(低24位有效,高8位补0)
*/
uint32_t norflash_read_jedec_id_9f(void)
{
uint32_t jedec_id = 0xFFFFFF; // 初始化为全FF,便于判断通信失败
uint8_t id1 = 0xFF; // 制造商ID(0x52)
uint8_t id2 = 0xFF; // 类型ID(0x22)
uint8_t id3 = 0xFF; // 容量ID(0x17)
// 1. 选中NorFlash,开始9FH指令通信
SPI_CS(0);
delay_us(50); // 精准微秒延时,匹配9FH指令tCS时序要求
// 2. SPI就绪时执行操作,避免无效收发
if(HAL_SPI_GetState(&g_spi_handler) == HAL_SPI_STATE_READY)
{
// 发送9FH指令(JEDEC ID核心指令)
spi_read_write_byte(0x9F);
delay_us(20); // 9FH指令时序要求更高,延长延时等待Flash准备输出
// 依次读取3字节ID数据
id1 = spi_read_write_byte(0xFF);
delay_us(5);
id2 = spi_read_write_byte(0xFF);
delay_us(5);
id3 = spi_read_write_byte(0xFF);
}
// 3. 拼接3字节JEDEC ID(低24位有效)
jedec_id = ((uint32_t)id1 << 16) | ((uint32_t)id2 << 8) | id3;
// 4. 释放NorFlash,结束通信
SPI_CS(1);
delay_us(50); // 匹配Flash tCH时序,避免总线残留数据
// 5. 清除本次通信溢出错误,避免累积影响
if(__HAL_SPI_GET_FLAG(&g_spi_handler, SPI_FLAG_OVR))
__HAL_SPI_CLEAR_OVRFLAG(&g_spi_handler);
return jedec_id;
}
// ************************ 最终版:初始化函数(通用预热+双指令读取+双校验) ************************
/**
* @brief NorFlash初始化(整合90H/9FH指令,双ID校验,提升稳定性和容错性)
* @note 基于STM32F103RCT6,先通用预热,再依次读取两种ID,输出清晰调试信息
* @retval 无
*/
void norflash_init(void)
{
uint16_t norflash_id_90 = 0xFFFF; // 90H指令读取结果
uint32_t norflash_id_9f = 0xFFFFFF; // 9FH指令读取结果
// 步骤1:初始化SPI总线(模式3+512分频,在spi.c中实现)
spi_init();
// 步骤2:通用总线预热(关键!解决两种指令首次通信不稳定问题)
norflash_bus_preheat();
// 步骤3:依次读取90H(16位ID)和9FH(3字节JEDEC ID),中间加短延时避免总线冲突
norflash_id_90 = norflash_read_id_90();
delay_ms(1); // 短延时,稳定总线状态
norflash_id_9f = norflash_read_jedec_id_9f();
// 步骤4:打印双指令读取结果,方便调试
printf("=== NorFlash 双指令读取结果 ===\r\n");
printf("90H指令读取16位ID: 0x%04X\r\n", norflash_id_90);
printf("9FH指令读取3字节JEDEC ID: 0x%06X\r\n", norflash_id_9f);
// 步骤5:双ID精准校验(适配诺存25Q64,容错性拉满)
// 只要其中一个ID匹配标准值,就判定为标准模式
if(norflash_id_90 == 0x5216 || norflash_id_9f == 0x522217)
{
printf("NorFlash初始化成功(标准模式)\r\n\r\n");
}
else if(norflash_id_90 != 0xFFFF || norflash_id_9f != 0xFFFFFF)
{
printf("NorFlash初始化成功(兼容模式)\r\n\r\n");
}
else
{
printf("NorFlash初始化失败(SPI通信或硬件异常)\r\n\r\n");
}
}
/**
* @brief NorFlash写使能(擦除/写入操作前必须调用)
* @note 发送0x06指令,置位WEL位(写使能锁存位)
* @retval 无
*/
void norflash_write_enable(void)
{
SPI_CS(0);
delay_us(50); // 补充延时,满足tCS时序要求
spi_read_write_byte(0x06);
SPI_CS(1);
delay_us(50); // 补充延时,满足tCH时序要求
}
/**
* @brief 读取NorFlash状态寄存器1(SR1)
* @note 发送0x05指令,读取Flash当前状态(BUSY位、WEL位等)
* @retval 状态寄存器1的值
*/
uint8_t norflash_read_sr1(void) // 修正函数名:srl -> sr1
{
uint8_t byte = 0;
SPI_CS(0);
delay_us(50); // 补充延时,满足tCS时序要求
spi_read_write_byte(0x05);
byte = spi_read_write_byte(0xFF);
SPI_CS(1);
return byte;
}
/**
* @brief 等待NorFlash退出忙碌状态
* @note 循环检测SR1的BUSY位(0x01),直到BUSY位清零
* @retval 无
*/
void norflash_wait_busy(void)
{
while((norflash_read_sr1() & 0x01) == 0x01); // 统一小写0x,保持规范
}
/**
* @brief 擦除NorFlash指定扇区(4KB)
* @note 发送0x20指令,扇区擦除耗时较长,需等待忙碌状态结束
* @param saddr: 扇区起始地址(必须对齐4KB边界)
* @retval 无
*/
void norflash_erase_sector(uint32_t saddr)
{
norflash_write_enable(); // 擦除前先写使能
SPI_CS(0);
delay_us(50); // 补充延时,满足tCS时序要求
spi_read_write_byte(0x20);
spi_read_write_byte((uint8_t)(saddr >> 16));
spi_read_write_byte((uint8_t)(saddr >> 8));
spi_read_write_byte((uint8_t)(saddr));
SPI_CS(1);
norflash_wait_busy(); // 等待擦除完成
}
/**
* @brief 向NorFlash指定地址写入一页数据(最大256字节)
* @note 发送0x02指令,页编程最大256字节,需对齐页地址,写入前需擦除
* @param addr: 写入起始地址
* @param pbuf: 待写入数据缓冲区指针
* @param datalen: 待写入数据长度(最大256)
* @retval 无
*/
void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen) // 补充datalen形参
{
norflash_write_enable(); // 写入前先写使能
SPI_CS(0);
delay_us(50); // 补充延时,满足tCS时序要求
spi_read_write_byte(0x02);
spi_read_write_byte((uint8_t)(addr >> 16));
spi_read_write_byte((uint8_t)(addr >> 8));
spi_read_write_byte((uint8_t)(addr));
for(uint16_t i = 0; i < datalen; i++) // 此时datalen已定义,无编译错误
{
spi_read_write_byte(pbuf[i]);
}
SPI_CS(1);
norflash_wait_busy(); // 等待写入完成
}
/**
* @brief 从NorFlash指定地址读取数据
* @note 发送0x03指令,读取操作不影响Flash忙碌状态,无需等待
* @param pbuf: 存储读取数据的缓冲区指针
* @param addr: 读取起始地址
* @param datalen: 待读取数据长度
* @retval 无
*/
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
SPI_CS(0);
delay_us(50); // 补充延时,满足tCS时序要求
spi_read_write_byte(0x03);
spi_read_write_byte((uint8_t)(addr >> 16));
spi_read_write_byte((uint8_t)(addr >> 8));
spi_read_write_byte((uint8_t)(addr));
for(uint16_t i = 0; i < datalen; i++)
{
pbuf[i] = spi_read_write_byte(0xFF);
}
SPI_CS(1);
}