STM32--SPI通信

一、SPI简介

二、SPI协议

主设备将待发送的 1 字节数据(如 0x55)加载到 "主发送移位寄存器",从设备将待返回的 1 字节数据(如 0xAA)加载到 "从发送移位寄存器"。

  • 主设备将发送寄存器的最高位放到 MOSI 线上,从设备在该时钟沿采样 MOSI 的数据,存入自己的接收寄存器。

  • 从设备将发送寄存器的最高位放到 MISO 线上,主设备在该时钟沿采样 MISO 的数据,存入自己的接收寄存器。

传输的时候高位优先,循环移位 8 次后,主机和从机间就完成了一个字节的收发

模式 0(CPOL=0,CPHA=0)------ 最常用

  • 空闲状态:SCLK = 低电平

  • 传输过程(以主设备发数据、从设备收数据为例):

    1. 主设备先把要发送的 1 位数据放到 MOSI 线(数据先稳定);

    2. SCLK 从「低→高」(第一个跳变沿:上升沿)→ 从设备采样(读) MOSI 上的数据;

    3. SCLK 从「高→低」(第二个跳变沿:下降沿)→ 主设备更新 MOSI 上的下一位数据;

  • 核心口诀:低空闲,上升沿读,下降沿写

  • 典型应用:SPI Flash(如 W25Q64)、OLED 屏幕、SD 卡、大部分传感器(新手优先记这个模式)。

模式 1(CPOL=0,CPHA=1)

  • 空闲状态:SCLK = 低电平

  • 传输过程:

    1. SCLK 从「低→高」(第一个跳变沿:上升沿)→ 主设备更新 MOSI 数据;

    2. SCLK 从「高→低」(第二个跳变沿:下降沿)→ 从设备采样(读)数据;

  • 核心口诀:低空闲,上升沿写,下降沿读。

模式 2(CPOL=1,CPHA=0)

  • 空闲状态:SCLK = 高电平

  • 传输过程:

    1. 主设备先把数据放到 MOSI 线;

    2. SCLK 从「高→低」(第一个跳变沿:下降沿)→ 从设备采样(读)数据;

    3. SCLK 从「低→高」(第二个跳变沿:上升沿)→ 主设备更新数据;

  • 核心口诀:高空闲,下降沿读,上升沿写。

模式 3(CPOL=1,CPHA=1)

  • 空闲状态:SCLK = 高电平

  • 传输过程:

    1. SCLK 从「高→低」(第一个跳变沿:下降沿)→ 主设备更新 MOSI 数据;

    2. SCLK 从「低→高」(第二个跳变沿:上升沿)→ 从设备采样(读)数据;

  • 核心口诀:高空闲,下降沿写,上升沿读。

三、 SPI外设关键知识点

四、SPI-FLASH模块

闪存 (Flash Memory) 全面解析

闪存是一种非易失性固态存储技术,断电后仍能保留数据,结合了 ROM 的非易失性与 RAM 的可读写性,是现代电子设备中最常用的存储介质之一。


一、基本概念与工作原理

  1. 核心定义

闪存 (Flash Memory) 是电可擦除可编程只读存储器 (EEPROM) 的变种 ,通过浮栅 MOSFET (Floating Gate MOSFET) 存储电荷来表示数据,无需机械部件,具有低功耗、高可靠性、抗震动等优点。

  1. 工作原理(浮栅技术)
操作 原理 实现方式
写入 (编程) 向浮栅注入电子 热电子注入或 Fowler-Nordheim 隧穿效应,施加高电压使电子穿过氧化层进入浮栅
擦除 从浮栅移除电子 反向隧穿,施加反向高电压使电子离开浮栅
读取 检测浮栅电荷状态 测量 MOSFET 阈值电压变化,判断存储的是 0 还是 1

关键特性 :闪存必须先擦除后写入 ,且擦除操作以 ** 块 (block)为单位,写入以页 (page)** 为单位(NOR 闪存支持字节级写入)。


二、闪存的主要类型

  1. NOR Flash vs NAND Flash(两种基本架构)
特性 NOR Flash NAND Flash 典型应用
读取速度 快 (随机读取优异) 较慢 (顺序读取较好) NOR: 程序存储 (如 STM32 固件);NAND: 数据存储 (如 SSD、U 盘)
写入 / 擦除速度 -
擦除单位 大 (64KB~128KB) 小 (4KB~16KB) -
存储密度 高 (成本更低) -
接口 类 RAM 接口 (随机访问) I/O 接口 (串行访问) -
可靠性 高 (位翻转率低) 较低 (需 ECC 纠错) -
  1. 按存储单元密度分类(NAND Flash)
类型 每单元存储比特数 特点 应用场景
SLC (单层单元) 1bit 最快、最耐用 (10 万~100 万次擦写)、成本最高 工业控制、企业级 SSD、缓存
MLC (多层单元) 2bit 平衡性能与成本 (1 万~10 万次擦写) 消费级 SSD、高端手机
TLC (三层单元) 3bit 容量大、成本低 (1000~3000 次擦写) 主流 SSD、移动设备、U 盘
QLC (四层单元) 4bit 容量最大、成本最低 (500~1000 次擦写) 大容量存储、数据中心冷存储
PLC (五层单元) 5bit 实验阶段,极高密度 未来大容量存储
  1. 按结构分类:2D NAND vs 3D NAND
  • 2D NAND:平面结构,通过缩小工艺提升密度,已接近物理极限 (10nm 以下)

  • 3D NAND:垂直堆叠多层存储单元 (64 层~300 + 层),大幅提升密度,降低成本,是当前主流技术

    • 代表技术:铠侠 BiCS、三星 V-NAND、长江存储 Xtacking

三、闪存的优势与局限性

  1. 优势
  • 非易失性:断电数据不丢失,优于 DRAM

  • 固态结构:无机械部件,抗震、低噪音、寿命长

  • 低功耗:比 HDD 和 DRAM 功耗低,适合移动设备

  • 快速访问:平均响应时间 (微秒级) 远快于 HDD (毫秒级)

  • 体积小:适合轻薄设备设计

  1. 局限性
  • 有限擦写寿命:每个存储块只能擦写有限次数 (1000~100 万次),需通过 ** 磨损均衡 (Wear Leveling)** 技术延长寿命

  • 写入前擦除:不能直接覆盖数据,需先擦除整个块,影响写入性能

  • 位翻转风险:长期使用或受干扰可能出现位错误,需 **ECC (错误校验与纠正)** 技术保障数据完整性

  • 数据保持期:存储数据会随时间流失,需定期刷新 (通常 10 年以上保持期)


四、闪存的应用领域

  1. 消费电子:智能手机、平板电脑、数码相机、U 盘、SD 卡、MP3 播放器

  2. 计算机存储:SSD (固态硬盘)、混合硬盘 (SSHD)、内存扩展卡

  3. 嵌入式系统:STM32 等单片机固件存储、路由器 / 交换机配置存储、工业控制设备程序存储

  4. 企业级应用:数据中心存储阵列、云存储设备、高速缓存系统

  5. 汽车电子:车载娱乐系统、导航、自动驾驶数据存储、ECU 固件

  6. 物联网 (IoT):传感器数据存储、智能家居设备、可穿戴设备


五、闪存技术发展历程与未来趋势

  1. 发展里程碑
  • 1984 年:东芝公司 Fujio Masuoka 发明闪存概念

  • 1988 年:英特尔推出首款商用 NOR Flash 芯片

  • 1989 年:东芝推出首款 NAND Flash 芯片

  • 2009 年:三星推出首款 3D NAND 原型

  • 2013 年:商用 3D NAND 开始量产

  • 2025 年:3D NAND 层数突破 300 层,接口速度达 4.8Gb/s

  1. 未来发展方向

  2. 更高堆叠层数:3D NAND 向 500 + 层发展,提升存储密度

  3. 新材料应用:从氧化硅向二维材料 (如石墨烯)、相变材料发展,突破物理极限

  4. 存储类内存 (SCM):结合 DRAM 速度与闪存非易失性的新型存储技术

  5. 更高性能接口:PCIe 5.0/6.0、NVMe 2.0 等,提升数据传输速度

  6. 更低功耗设计:适应移动设备和物联网低功耗需求

  7. 增强安全性:硬件加密、安全擦除、防篡改技术提升数据安全性


六、嵌入式系统中的闪存使用(STM32 开发者视角)

对于 STM32 等单片机开发者,闪存主要用于:

  1. 程序存储:NOR Flash 存储固件代码,支持直接执行 (XIP, Execute In Place)

  2. 数据存储:片内 Flash 或外接 NAND Flash 存储配置参数、日志数据等

  3. 升级功能:通过 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。


三、快速排查步骤

  1. 硬件排查:用万用表测量 Flash 的 VCC/GND 电压,检查 SPI 引脚连接是否正确,重点确认 MISO 引脚。

  2. 软件配置检查:确认 SPI 模式为 3、分频为 512(低速)、CS 引脚操作正确。

  3. 时序验证:添加总线预热函数,确保指令发送前后的延时满足要求。

  4. 流程检查:确保先初始化 SPI,再读取 ID,且初始化仅执行一次,执行后清除错误标志

经过层层验证,由于主包使用的是诺存25Q64的flash芯片所以需要总线预热

(3)、总线预热

总线预热本质是嵌入式开发中针对串行总线(如 SPI、I2C) 低速外设(如 NorFlash、EEPROM)时序补偿与稳定性优化手段,并非所有场景都需要,但在以下典型场景中是必要的:


一、硬件特性限制导致的首次通信不稳定

  1. STM32F1 等无硬件 FIFO 的单片机
  • 这类单片机的 SPI 外设没有硬件 FIFO 缓冲,首次通信时时钟信号容易出现抖动,导致数据采样错误。

  • 总线预热通过空字节收发可以稳定 SPI 时钟逻辑,让外设与主机的时钟同步进入稳定状态。

  • ✅ 你的场景(STM32F103RCT6)正是典型案例,F1 的 SPI 无 FIFO,首次通信抖动是核心痛点。

  1. NorFlash、EEPROM 等串行存储芯片
  • 这类芯片通电后需要3-5ms 的内部初始化时间(如诺存 25Q64),在此期间接收指令会无响应。

  • 总线预热通过CS 引脚翻转 + 空字节收发,可以触发芯片完成内部初始化,感知总线存在,避免首次指令发送失败。


二、时序敏感的指令或通信场景

  1. 高时序要求的指令(如 0x9FH JEDEC ID 指令)
  • 0x9FH 指令对 SPI 时钟稳定性的要求远高于 0x90H 指令,首次通信时的时钟抖动会直接导致采样错误(返回 0x7FFFFF)。

  • 总线预热通过延长延时 + 空字节收发,可以提升时钟稳定性,满足高时序指令的要求。

  1. 低温 / 高干扰环境
  • 在低温或电磁干扰较强的环境下,硬件的时序裕度会大幅降低,首次通信容易出现误码。

  • 总线预热可以主动让总线进入稳定状态,减少环境干扰对通信的影响,提升鲁棒性。


三、调试中发现的典型问题场景

如果你的工程出现以下现象,说明需要总线预热:

  1. 首次通信失败,第二次成功:比如首次读取 0x9FH ID 返回 0xFFFFFF,但第二次读取成功,这是典型的 "总线未稳定" 问题。

  2. 上电后首次操作无响应:比如上电后首次发送擦除指令,Flash 无反馈,但后续操作正常。

  3. 多设备共享总线时通信异常:总线上挂多个设备时,切换设备后的首次通信容易因电平残留导致错误,预热可以清除残留。


四、不需要总线预热的场景

  • 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);
}
相关推荐
帅次5 小时前
系统分析师-信息物理系统分析与设计
stm32·单片机·嵌入式硬件·mcu·物联网·iot·rtdbs
澜莲Alice5 小时前
STM32 MPLAB X IDE 软件安装-玩转单片机-英文版沉浸式安装
stm32·单片机·嵌入式硬件
良许Linux5 小时前
IIC总线的硬件部分的两个关键点:开漏输出+上拉电阻
单片机·嵌入式硬件
✎ ﹏梦醒͜ღ҉繁华落℘6 小时前
单片机基础知识 -- ADC分辨率
单片机·嵌入式硬件
Q_21932764556 小时前
车灯控制与报警系统设计
人工智能·嵌入式硬件·无人机
雾削木6 小时前
树莓派部署 HomeAssistant 教程
stm32·单片机·嵌入式硬件
Q_21932764557 小时前
基于单片机的破壁机自动控制系统设计
单片机·嵌入式硬件
我是一棵无人问荆的小草7 小时前
stm32f103芯片多个IO配置成外部中断
stm32·单片机·嵌入式硬件
wjykp7 小时前
ESP32xxx烧录
stm32·单片机·嵌入式硬件
早起huo杯黑咖啡8 小时前
【NOR Flash】关于芯片的高耐久性分区的编程/擦除周期和最小保留时间的数据
单片机·嵌入式硬件