找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1239

积分

0

好友

158

主题
发表于 8 小时前 | 查看: 3| 回复: 0

SFUD(Serial Flash Universal Driver)是一款开源的串行 SPI Flash 通用驱动库。它通过提供一套跨平台、统一且易用的 API,将开发者从适配不同厂商、不同型号 SPI Flash 芯片的繁琐工作中解放出来,极大地简化了底层存储驱动的开发。

应用场景

SFUD 的通用性使其能够广泛应用于多种嵌入式及物联网项目中。

嵌入式系统开发

  • IoT 智能网关:用于存储设备配置参数、采集的用户数据以及固件镜像,为远程 OTA(空中下载)升级提供可靠的存储基础。
  • 工业 PLC:存储控制程序、关键的工艺参数和运行日志,其高可靠性设计确保了工业场景下数据的安全与完整。
  • 智能手表:存储用户的健康监测数据、运动记录以及个人系统配置,在有限的资源下实现数据的有效管理。

固件更新系统

  • 远程 OTA 升级:通过网络接收新固件包,经安全校验后写入 Flash 的特定分区,并可支持断点续传功能。
  • 双分区固件管理:实现 A/B 分区切换机制,确保在主分区升级失败时,系统能自动回滚至备份分区,保障设备可用性。
  • 固件完整性校验:在写入前后,使用 MD5 或 SHA 等哈希算法验证固件包的完整性,防止因传输错误导致刷入损坏的固件。

数据存储应用

  • 配置参数管理:采用键值对或结构体等组织形式存储设备参数,并可通过版本号进行管理,便于后期维护与兼容。
  • 事件日志记录:实现循环日志缓冲区,当存储空间写满时自动覆盖最早的数据,确保最新的关键事件总是被保留。
  • 用户数据持久化:可靠地保存用户的个性化设置和配置,即使在设备断电重启后,这些数据也能保持不变。

快速上手

核心 API 概览

SFUD 提供了一套简洁的核心 API 用于 Flash 的基本操作。

API 函数 功能描述 参数说明 返回值
sfud_init() 初始化 SFUD 库,探测并初始化 SPI Flash 设备 sfud_err:操作结果,SFUD_SUCCESS 表示成功
sfud_get_device() 获取 Flash 设备对象 index:设备索引(从 0 开始) const sfud_flash*:Flash 设备指针,NULL 表示失败
sfud_erase() 擦除 Flash 指定区域(库自动选择最佳擦除命令) flash:Flash 设备指针<br>addr:起始地址(必须对齐到擦除块边界)<br>size:擦除大小(必须是擦除块大小的整数倍) sfud_err:操作结果
sfud_write() 向 Flash 写入数据(自动处理页边界和写前擦除检查) flash:Flash 设备指针<br>addr:起始地址<br>size:写入大小<br>data:要写入的数据缓冲区 sfud_err:操作结果
sfud_read() 从 Flash 读取数据 flash:Flash 设备指针<br>addr:起始地址<br>size:读取大小<br>data:用于存储读取数据的缓冲区 sfud_err:操作结果
sfud_chip_erase() 擦除整个 Flash 芯片(耗时较长) flash:Flash 设备指针 sfud_err:操作结果
sfud_is_flash_busy() 检查 Flash 是否处于忙状态(例如正在擦除或写入) flash:Flash 设备指针 booltrue 表示忙,false 表示空闲
sfud_get_error_code_string() 获取错误码对应的可读字符串描述 error_code:错误码 const char*:错误描述字符串

API 使用示例

以下代码片段展示了 SFUD 核心 API 的基本调用流程。

初始化和获取设备

// 初始化 SFUD 库
if (sfud_init() != SFUD_SUCCESS) {
    printf("SFUD init failed\r\n");
    return -1;
}

// 获取第一个 Flash 设备
const sfud_flash *flash = sfud_get_device(0);
if (flash == NULL) {
    printf("Get flash device failed\r\n");
    return -1;
}

擦除操作

// 擦除 4KB 扇区
sfud_err result = sfud_erase(flash, 0x00000000, 4096);
if (result != SFUD_SUCCESS) {
    printf("Erase failed: %s\r\n", sfud_get_error_code_string(result));
    return -1;
}

// 擦除整个芯片(耗时较长)
result = sfud_chip_erase(flash);
if (result != SFUD_SUCCESS) {
    printf("Chip erase failed: %s\r\n", sfud_get_error_code_string(result));
    return -1;
}

读写操作

// 写入数据
uint8_t write_buf[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                         0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
result = sfud_write(flash, 0x00000000, sizeof(write_buf), write_buf);
if (result != SFUD_SUCCESS) {
    printf("Write failed: %s\r\n", sfud_get_error_code_string(result));
    return -1;
}

// 读取数据
uint8_t read_buf[16];
result = sfud_read(flash, 0x00000000, sizeof(read_buf), read_buf);
if (result != SFUD_SUCCESS) {
    printf("Read failed: %s\r\n", sfud_get_error_code_string(result));
    return -1;
}

集成实现

集成步骤

将 SFUD 集成到你的项目中通常包含以下几个步骤。

1. 获取源码
从官方仓库克隆源代码:

# 从 GitHub 克隆源码
git clone https://github.com/armink/SFUD.git

2. 添加文件到项目

  • sfud.csfud.h 核心文件添加到项目的源代码目录。
  • sfud_flash_def.h(包含预定义的芯片支持列表)添加到项目中。
  • 根据你的目标平台(如 RT-Thread、Arduino、裸机等),从 port 目录下选择或参考对应的平台适配文件。

3. 实现硬件适配函数
SFUD 需要通过你实现的底层 spi_write_read 函数与硬件 SPI 交互。以下是一个基于 STM32 HAL 库的示例:

/**
 * SPI 接口读写函数
 * @param spi SPI 设备结构体
 * @param write_buf 写入缓冲区
 * @param write_size 写入大小
 * @param read_buf 读取缓冲区
 * @param read_size 读取大小
 * @return 操作结果
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf, size_t read_size)
{
    // 片选使能
    HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_RESET);

    // 写入数据
    if (write_size > 0) {
        HAL_SPI_Transmit(&hspi1, (uint8_t *)write_buf, write_size, 1000);
    }

    // 读取数据
    if (read_size > 0) {
        HAL_SPI_Receive(&hspi1, read_buf, read_size, 1000);
    }

    // 片选禁用
    HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_SET);

    return SFUD_SUCCESS;
}

此函数是驱动与硬件连接的桥梁,任何 SPI 通信问题都可能首先在这里出现,是 嵌入式系统开发 中硬件调试的关键环节之一。

4. 初始化和使用 SFUD
完成硬件适配后,便可在应用层调用 SFUD 进行完整的读写测试。

#include "sfud.h"

int main(void) {
    sfud_err result = SFUD_SUCCESS;
    const sfud_flash *flash = NULL; // 注意:标准API返回的是const指针

    // 1. 初始化SFUD库
    result = sfud_init();
    if (result != SFUD_SUCCESS) {
        printf("SFUD init failed: %d\r\n", result);
        return -1;
    }

    // 2. 获取Flash设备对象(索引号根据你在sfud_cfg.h中的配置)
    flash = sfud_get_device(0); // 获取设备表中第一个Flash设备
    if (flash == NULL) {
        printf("Get SFUD flash device failed!\r\n");
        return -1;
    }

    // 3. 打印Flash信息
    printf("Flash info:\r\n");
    printf("  Name: %s\r\n", flash->name);
    printf("  Size: %ld bytes\r\n", flash->chip.size);
    printf("  Sector size: %ld bytes\r\n", flash->chip.sector_size);
    printf("  Page size: %ld bytes\r\n", flash->chip.page_size);

    // 4. 擦除第一个扇区(必须确保地址0是扇区起始地址,且size是扇区大小的整数倍)
    result = sfud_erase(flash, 0, flash->chip.sector_size); // 使用芯片自身的扇区大小更安全
    if (result != SFUD_SUCCESS) {
        printf("Erase failed: %d\r\n", result);
        return -1;
    }
    printf("Erase sector success\r\n");

    // 5. 写入数据(确保目标区域已擦除)
    uint8_t write_data[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                              0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
    result = sfud_write(flash, 0, sizeof(write_data), write_data);
    if (result != SFUD_SUCCESS) {
        printf("Write failed: %d\r\n", result);
        return -1;
    }
    printf("Write data success\r\n");

    // 6. 读取数据并验证
    uint8_t read_data[16];
    result = sfud_read(flash, 0, sizeof(read_data), read_data);
    if (result != SFUD_SUCCESS) {
        printf("Read failed: %d\r\n", result);
        return -1;
    }

    bool data_match = true;
    for (int i = 0; i < sizeof(write_data); i++) {
        if (read_data[i] != write_data[i]) {
            data_match = false;
            break;
        }
    }

    if (data_match) {
        printf("Data verify success\r\n");
    } else {
        printf("Data verify failed\r\n");
        // 注意:写入后立即读取不一致,通常是因为写入前未正确擦除
        return -1;
    }

    printf("SFUD test completed successfully!\r\n");
    return 0;
}

常见问题处理

在集成和使用过程中,你可能会遇到以下典型问题。

问题 可能原因 解决方案
初始化失败 SPI 硬件连接错误、时钟配置不正确、片选信号异常。 检查硬件连线,使用逻辑分析仪抓取 SPI 波形,确认时序和 片选 信号符合芯片手册要求。
芯片识别失败 使用的 Flash 芯片型号不在 SFUD 默认支持列表中。 首先检查 sfud_flash_def.h 确认是否支持。若不支持,可在配置文件中通过 SFUD_FLASH_DEVICE_TABLE 宏手动添加芯片参数。
写入失败 目标存储区域在写入前未被擦除(Flash 特性要求写前必须擦)。 确保在执行 sfud_write() 前,已对目标地址范围调用 sfud_erase()。SFUD 的写函数内部有检查,但显式擦除是良好实践。
读取数据错误 地址计算超出芯片范围,或发生了位翻转(概率较低)。 核对读写地址是否在芯片容量内。对于关键数据,可考虑增加 ECC 校验或使用文件系统。
性能不佳 SPI 时钟频率设置过低。 在硬件和芯片允许的范围内,尽可能提高 SPI 主时钟频率,这能显著提升读写速度。

参考资料

[1] 嵌入式存储开发高效应用, 微信公众号:mp.weixin.qq.com/s/yLymgb9SOAO9mRMdyiJ7qQ

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:EDUSRC支付逻辑漏洞挖掘:思路复盘与实战案例解析
下一篇:FLUX.2 [klein] 9B模型评测:开源文生图的速度、质量与显存占用权衡
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-10 08:21 , Processed in 0.310359 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表