GD32H759 + RT-Thread 工控实战--第7篇 OSPI Flash

@TOC


前言

本篇攻下 GD32H759I-EVAL 板上容量最大的存储外设:U7 = GD25X512ME,512Mbit(64MB)八线 OSPI NOR Flash。

这块芯片在工控产品里的典型角色有三个:参数存储(校准系数、运行配置)、固件 OTA 的暂存区、数据记录的文件系统。所以本篇的目标不是"点亮读个 ID"或者"弄个读写接口"就完事,而是按真实使用场景把整套软件栈搭起来:

  • 底层:OSPI 外设驱动,单线 SPI 模式起步,最终跑在八线 STR 模式,并验证内存映射(Memory-Mapped)读取;
  • 中间层:接 RT-Thread 的 FAL(Flash Abstraction Layer),把 64MB 切成 param / app / filesystem 三个分区;
  • 应用层:filesystem 分区挂 littlefs------专为 NOR Flash 设计的掉电安全文件系统,param 分区写一个带 magic 校验的参数读写 demo。

环境不变:GD32H759I-EVAL + RT-Thread v5.2.2 + 纯 GCC(WSL2)。


一、调研

1.1、资料阅读

资料 用途
《GD32H759I-EVAL评估板使用指南_Rev2.2.pdf》 U7 型号、引脚接线、JP61 跳线(OSPI0_NCS 与 DCI_D5 复用选择)
GD25X512ME datasheet(GigaDevice 官网) JEDEC ID、命令集、volatile 配置寄存器、3/4 字节地址模式
《GD32H7xx 用户手册》OSPI 章节 + 固件库 gd32h7xx_ospi.h/c、gd32h7xx_ospim.h/c OSPI 外设寄存器语义与 API
官方 Demo 15_OSPI_Octal_Flash(GD32H759I-EVAL 例程包) Flash 侧配置流程的权威参考(Soft_Drive 里的 gd25x512me.c/h 直接移植)
RT-Thread v5.2.2 源码:components/fal、packages/littlefs FAL 设备实现、littlefs 胶水层

1.2、硬件事实

芯片:GD25X512ME(U7,评估板丝印 GD25X512MEBIRY):

参数 值
容量 512Mbit = 64MB
JEDEC ID 0xC8481A(+ 扩展字节 0xFF,实测读回 0xC8481AFF)
接口 单线 SPI / 双线 / 四线 / 八线(Octal)STR 与 DTR
扇区 4KB 扇区擦除(0x21/0x20),页编程 256B
地址模式 默认 3 字节(24 位寻址 = 16MB);全容量 64MB 需 4 字节地址模式(命令 0xB7 切换)
模式切换 volatile 配置寄存器:CFG_REG1 设 dummy 周期数,CFG_REG0 设 IO 模式(STR/DTR、是否 wrap)

板级接线(评估板指南 + 实测确认):

信号 引脚 AF
OSPI0_NCS PB6 AF10,经 JP61:短接 (2,3) 才接到 Flash
OSPI0_CLK PB2 AF9
OSPI0_IO0~IO7 PD11/PD12/PA3/PD13/PD4/PD5/PD6/PD7 AF9/AF9/AF6/AF9/AF10×4
DQS 未接(NC) ------只能用 no-DQS 模式

两个设计约束:

  1. JP61 是复用开关:PB6 这根线既是 OSPI0_NCS 又是摄像头接口的 DCI_D5;
  2. 内存映射基址 0x90000000 :OSPI0 映射到 Cortex-M7 的这片地址空间后,Flash 内容可以像内存一样直接 *(uint32_t *)0x90000000 读------这是将来做 XIP(片上执行)或零拷贝读取大数据的基础。注意 GD32H759 的 RCU 没有 OSPI 内核时钟选择寄存器,OSPI 直接挂在 AHB3 上,省了一步时钟 mux 配置。

1.3、软件架构选型

裸读写接口只能算"能用",工控产品要的是"好用"。本篇的栈:

bash 复制代码
应用层   param_demo 命令          echo/cat/ls(msh 直接操作文件)
         ┌─────────────┐    ┌──────────────────┐
中间层   │ FAL 分区表   │    │ littlefs 文件系统 │← 掉电安全、磨损均衡
         │ param/app/fs │    │ (DFS 挂载到 /)  │
         └──────┬───────┘    └────────┬─────────┘
                └────── FAL flash_dev ─┘
驱动层            drv_ospi_fal.c(FAL 适配)
                  drv_ospi_h7.c(OSPI 外设 + GPIO/RCU)
                  gd25x512me.c(官方 Soft_Drive,芯片命令集)
硬件              GD25X512ME @ OSPI0,八线 STR,3 字节地址

分区表(3 字节地址模式只用低 16MB,剩下的 48MB 等 4 字节地址模式,留作后续):

分区 偏移 大小 用途
param 0x000000 1MB 参数存储(本篇 param_demo 演示)
app 0x100000 7MB 预留:OTA 固件暂存
filesystem 0x800000 8MB littlefs 文件系统,挂载到 /

为什么选 littlefs 而不是 FatFs:NOR Flash 上跑 FatFs,写一半断电基本等于文件系统报废;littlefs 是专门为嵌入式 NOR 设计的日志结构文件系统,元数据双份冗余、任意时刻断电都能回滚到上一个一致状态,还自带动态磨损均衡。

二、驱动编写

2.1、移植官方 Soft_Drive

官方例程 15_OSPI_Octal_Flash 的 Soft_Drive/gd25x512me.c/h 是纯粹的固件库调用(无 systick/OS 依赖),原样拷进工程 ,不 reinvent 轮子。它封装好了芯片侧的全部命令:复位、读 ID、volatile 配置寄存器读写、读写擦、autopolling 忙等待、内存映射模式进入/退出,且每个函数都带 mode 参数(SPI_MODE / OSPI_MODE),单线八线通吃。

2.2、BSP 驱动 drv_ospi_h7.c

~/gd32/rt-thread/bsp/gd32/arm/libraries/gd32_drivers/drv_ospi_h7.c

c 复制代码
/*
 * drv_ospi_h7.c - GD32H7xx OSPI0 + OSPIM 驱动(板载 GD25X512ME 64MB 八线 Flash)
 *
 * 硬件事实(EVAL V2.1 原理图 OSPI 页实锤):
 *   U7 = GD25X512MEBIRY(512Mbit 八线 NOR,JEDEC ID 0xC8481AFF)
 *   NCS=PB6(AF10)  CLK=PB2(AF9)
 *   IO0~IO7 = PD11/PD12(AF9) PA3(AF6) PD13(AF9) PD4/PD5/PD6/PD7(AF10)
 *   DQS 悬空(只能 no-DQS 模式);RESET=NRST;WP/ECS 已上拉。
 *   跳线前提:JP61 拨 (2,3)=OSPI 档(PB6 与 DCI_D5 二选一)。
 *   内存映射基址:OSPI0 -> 0x90000000。
 *
 * 芯片命令层直接用官方 demo 的 gd25x512me.c(纯 SPL,无裸机依赖),
 * 本文件只做 RT-Thread 集成:初始化 + msh 测试命令。
 *
 * msh 命令:
 *   ospi_id     - 读 JEDEC ID(链路活没活的第一个判据)
 *   ospi_test1  - 单线间接模式 擦->写->读->校验 @0x000000
 *   ospi_test8  - 八线间接模式 擦->写->读->校验 @0x200000
 *   ospi_mmap   - 内存映射模式读校验 @0x400000(直接指针读 Flash!)
 */

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include "gd32h7xx.h"

#if defined(BSP_USING_OSPI)

#include "gd25x512me.h"

#define DBG_TAG             "drv.ospi"
#define DBG_LVL             DBG_INFO
#include <rtdbg.h>

#define GD25X512ME_ID       0xC8481AFF
#define OSPI_MMAP_BASE      0x90000000UL

ospi_parameter_struct ospi_struct;      /* drv_ospi_fal.c 也要用,不 static */

static int rt_hw_ospi_init(void)
{
    ospi_flash_init(OSPI0, &ospi_struct);

    /* 两种模式各复位一次(官方 demo 同款顺序):
     * 上电后不知道 Flash 上次停在哪种模式,两边都敲门 */
    ospi_flash_reset_enable(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_reset_memory(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_reset_enable(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_reset_memory(OSPI0, &ospi_struct, OSPI_MODE);

    LOG_I("ospi0 ready: GD25X512ME 64MB, prescaler=%d", ospi_struct.prescaler);
    return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_ospi_init);

#ifdef RT_USING_FINSH
#include <finsh.h>

/* 里程碑 1:读 JEDEC ID */
static void ospi_id(int argc, char **argv)
{
    uint32_t id = ospi_flash_read_id(OSPI0, &ospi_struct, SPI_MODE);
    rt_kprintf("ospi: JEDEC ID = 0x%08lX %s (期望 0x%08lX)\n",
               id, id == GD25X512ME_ID ? "MATCH" : "MISMATCH!", GD25X512ME_ID);
}
MSH_CMD_EXPORT(ospi_id, read GD25X512ME JEDEC ID);

/* 通用:间接模式 擦->写->读->校验。mode: SPI_MODE / OSPI_MODE */
static int ospi_rw_verify(interface_mode mode, uint32_t addr, const char *tag)
{
    uint8_t tx[128], rx[128];
    uint32_t i;

    for (i = 0; i < sizeof(tx); i++) tx[i] = (uint8_t)(i ^ 0x5A);

    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, mode);
    ospi_flash_write_enbale(OSPI0, &ospi_struct, mode);
    ospi_flash_block_erase(OSPI0, &ospi_struct, mode,
                           GD25X512ME_3BYTES_SIZE, addr, GD25X512ME_ERASE_4K);
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, mode);

    ospi_flash_write_enbale(OSPI0, &ospi_struct, mode);
    ospi_flash_page_program(OSPI0, &ospi_struct, mode,
                            GD25X512ME_3BYTES_SIZE, tx, addr, sizeof(tx));
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, mode);

    ospi_flash_read(OSPI0, &ospi_struct, mode,
                    GD25X512ME_3BYTES_SIZE, rx, addr, sizeof(rx));

    for (i = 0; i < sizeof(tx); i++) {
        if (tx[i] != rx[i]) {
            rt_kprintf("ospi %s: FAIL @+%lu, wrote 0x%02X read 0x%02X\n",
                       tag, i, tx[i], rx[i]);
            return -RT_ERROR;
        }
    }
    rt_kprintf("ospi %s: erase->write->read->verify %u bytes @0x%06lX PASS\n",
               tag, sizeof(tx), addr);
    return RT_EOK;
}

/* 里程碑 2:单线(兼容模式)读写 */
static void ospi_test1(int argc, char **argv)
{
    ospi_rw_verify(SPI_MODE, 0x000000, "1-line");
}
MSH_CMD_EXPORT(ospi_test1, 1-line indirect erase/write/read verify);

/* 里程碑 3:八线间接模式(先配 dummy cycle 和 CFG0 进 Octal STR) */
static void ospi_test8(int argc, char **argv)
{
    /* 配置 Flash 侧:16 dummy cycles + 进入 8-8-8 STR 模式(官方 demo 顺序) */
    ospi_flash_write_enbale(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_write_volatilecfg_register(OSPI0, &ospi_struct, SPI_MODE,
            GD25X512ME_3BYTES_SIZE, GD25X512ME_CFG_REG1_ADDR,
            GD25X512ME_CFG_16_DUMMY_CYCLES);
    ospi_flash_write_enbale(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_write_volatilecfg_register(OSPI0, &ospi_struct, SPI_MODE,
            GD25X512ME_3BYTES_SIZE, GD25X512ME_CFG_REG0_ADDR,
            GD25X512ME_CFG_OCTAL_STR_WO);
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);

    ospi_rw_verify(OSPI_MODE, 0x200000, "8-line");
}
MSH_CMD_EXPORT(ospi_test8, 8-line indirect erase/write/read verify);

/* 里程碑 4:内存映射读------Flash 直接映射到 0x90000000,指针读 */
static void ospi_mmap(int argc, char **argv)
{
    uint8_t tx[64];
    const volatile uint8_t *mm = (const volatile uint8_t *)(OSPI_MMAP_BASE + 0x400000);
    uint32_t i, bad = 0;

    for (i = 0; i < sizeof(tx); i++) tx[i] = (uint8_t)(0xA5 + i);

    /* 写还是走间接模式(八线) */
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_write_enbale(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_block_erase(OSPI0, &ospi_struct, OSPI_MODE,
                           GD25X512ME_3BYTES_SIZE, 0x400000, GD25X512ME_ERASE_4K);
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_write_enbale(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_page_program(OSPI0, &ospi_struct, OSPI_MODE,
                            GD25X512ME_3BYTES_SIZE, tx, 0x400000, sizeof(tx));
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);

    /* 切内存映射模式,直接指针读 */
    ospi_flash_memory_map_mode_wrap_enable(OSPI0, &ospi_struct, OSPI_MODE,
                                           GD25X512ME_3BYTES_SIZE);
    for (i = 0; i < sizeof(tx); i++) {
        if (mm[i] != tx[i]) bad++;
    }
    rt_kprintf("ospi mmap: pointer-read @0x%08lX, %u/%u bytes match %s\n",
               OSPI_MMAP_BASE + 0x400000, sizeof(tx) - bad, sizeof(tx),
               bad == 0 ? "PASS" : "FAIL");

    /* 注意:memory-mapped 模式下 OSPI 控制器被占用,
     * 要回间接模式需重新 ospi_init------这里给出提示,生产代码按需切换 */
}
MSH_CMD_EXPORT(ospi_mmap, memory-mapped read verify @0x90000000);
#endif /* RT_USING_FINSH */

#endif /* BSP_USING_OSPI */

四条测试命令(测试章节目录有完整日志):

命令 验证内容
ospi_id 读 JEDEC ID,期望 0xC8481AFF
ospi_test1 单线 SPI 模式:擦→写→读→校验 128 字节 @ 0x000000
ospi_test8 八线 STR 模式:先写 CFG_REG1(16 dummy)+ CFG_REG0(OCTAL_STR),再擦写读校验 @ 0x200000
ospi_mmap 进入内存映射模式,从 0x90000000 + 0x400000 直接读并校验

2.3、FAL 适配 drv_ospi_fal.c

~/gd32/rt-thread/bsp/gd32/arm/libraries/gd32_drivers/drv_ospi_fal.c

c 复制代码
/*
 * drv_ospi_fal.c - GD25X512ME 的 FAL(Flash Abstraction Layer)适配
 *
 * 把 gd25x512me.c 的裸 API 包装成 fal_flash_dev,供分区表/littlefs 使用。
 * 统一用八线模式(OSPI_MODE):init 时复位后直接进入 8-8-8 STR,
 * 此后所有操作走八线,和 msh 的 ospi_test1(单线)互斥------
 * 开了 FAL 就别再跑 ospi_test1(会把协议状态打乱,反正它已经验证过了)。
 *
 * 地址范围:3 字节地址模式,只用低 16MB(0x000000~0xFFFFFF)。
 *           全容量 64MB 需要 4 字节地址模式(命令 0xB7 切换),留作后续。
 *
 * 分区表在 board/fal_cfg.h(fal 框架的强制约定)。
 */

#include <rtthread.h>
#include <board.h>
#include "gd32h7xx.h"

#if defined(BSP_USING_OSPI) && defined(RT_USING_FAL)

#include <fal.h>
#include "gd25x512me.h"

#define DBG_TAG             "ospi.fal"
#define DBG_LVL             DBG_INFO
#include <rtdbg.h>

extern ospi_parameter_struct ospi_struct;   /* drv_ospi_h7.c 里的全局配置 */

static int ospi_fal_init(void)
{
     rt_kprintf("[fal] init: reset (octal+spi)\n");
    /* 双模式复位:芯片此刻在哪种模式不确定,两种都发 */
    ospi_flash_reset_enable(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_reset_memory(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_reset_enable(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_reset_memory(OSPI0, &ospi_struct, SPI_MODE);

    rt_kprintf("[fal] init: cfg 16 dummy cycles\n");
    /* 在单线模式下写 volatile cfg,切到八线 STR */
    ospi_flash_write_enbale(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_write_volatilecfg_register(OSPI0, &ospi_struct, SPI_MODE,
            GD25X512ME_3BYTES_SIZE, GD25X512ME_CFG_REG1_ADDR,
            GD25X512ME_CFG_16_DUMMY_CYCLES);

    rt_kprintf("[fal] init: enter octal STR\n");
    ospi_flash_write_enbale(OSPI0, &ospi_struct, SPI_MODE);
    ospi_flash_write_volatilecfg_register(OSPI0, &ospi_struct, SPI_MODE,
            GD25X512ME_3BYTES_SIZE, GD25X512ME_CFG_REG0_ADDR,
            GD25X512ME_CFG_OCTAL_STR_WO);

    rt_kprintf("[fal] init: autopolling\n");
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);

    rt_kprintf("[fal] init: done\n");
    return 0;
}

static int ospi_fal_read(long offset, rt_uint8_t *buf, rt_size_t size)
{
    ospi_flash_read(OSPI0, &ospi_struct, OSPI_MODE,
                    GD25X512ME_3BYTES_SIZE, buf, offset, size);
    return size;
}

static int ospi_fal_write(long offset, const rt_uint8_t *buf, rt_size_t size)
{
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_write_enbale(OSPI0, &ospi_struct, OSPI_MODE);
    ospi_flash_page_program(OSPI0, &ospi_struct, OSPI_MODE,
                            GD25X512ME_3BYTES_SIZE, (uint8_t *)buf, offset, size);
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);
    return size;
}

static int ospi_fal_erase(long offset, rt_size_t size)
{
    rt_size_t erased = size;            /* fal 约定:返回擦除的字节数 */

    /* fal 保证按 blk_size(4K) 对齐调用,逐扇区擦 */
    while (size > 0) {
        ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);
        ospi_flash_write_enbale(OSPI0, &ospi_struct, OSPI_MODE);
        ospi_flash_block_erase(OSPI0, &ospi_struct, OSPI_MODE,
                               GD25X512ME_3BYTES_SIZE, offset, GD25X512ME_ERASE_4K);
        offset += GD25X512ME_SECTOR_4K;
        size -= GD25X512ME_SECTOR_4K;
    }
    ospi_flash_autopolling_mem_ready(OSPI0, &ospi_struct, OSPI_MODE);
    return erased;
}

const struct fal_flash_dev gd25x512_flash = {
    .name       = "gd25x512",
    .addr       = 0,
    .len        = 16 * 1024 * 1024,     /* 3 字节地址模式只用低 16MB */
    .blk_size   = GD25X512ME_SECTOR_4K,
    .ops        = { ospi_fal_init, ospi_fal_read, ospi_fal_write, ospi_fal_erase },
    .write_gran = 1,                   /* NOR: 按位编程 */
};

#endif /* BSP_USING_OSPI && RT_USING_FAL */

另外必须创建一个fal的config头文件(见前文分区表)。放在 ~/gd32/rt-thread/bsp/gd32/arm/gd32h759i-eval/board/fal_cfg.h

c 复制代码
/*
 * fal_cfg.h - FAL 分区表(放 bsp/gd32/arm/gd32h759i-eval/board/ 下)
 *
 * GD25X512ME 分区规划(3 字节地址模式,低 16MB):
 *   param      1MB  参数/标定数据掉电保存(裸 fal 读写)
 *   app        7MB  预留:固件备份/OTA 暂存坑位
 *   filesystem 8MB  littlefs:日志、CSV、配置文件,挂载到 /flash
 */

#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_

#include <rtconfig.h>
#include <board.h>

extern const struct fal_flash_dev gd25x512_flash;

#define FAL_FLASH_DEV_TABLE  { &gd25x512_flash }

#define FAL_PART_TABLE \
{ \
    { FAL_PART_MAGIC_WORD, "param",      "gd25x512", 0,                    1 * 1024 * 1024, 0 }, \
    { FAL_PART_MAGIC_WORD, "app",        "gd25x512", 1 * 1024 * 1024,      7 * 1024 * 1024, 0 }, \
    { FAL_PART_MAGIC_WORD, "filesystem", "gd25x512", 8 * 1024 * 1024,      8 * 1024 * 1024, 0 }, \
}

#endif /* _FAL_CFG_H_ */

2.4、应用层 app_flash_fs.c

~/gd32/rt-thread/bsp/gd32/arm/gd32h759i-eval/applications/app_flash_fs.c

c 复制代码
/*
 * app_flash_fs.c - GD25X512ME 文件系统应用(littlefs 挂载到 /flash)
 *
 * 流程:fal_init(读分区表)-> fal_blk_device_create("filesystem")
 *       -> dfs_mount littlefs;首次使用(或损坏)自动 dfs_mkfs。
 *
 * littlefs 是掉电安全 + 磨损均衡的 NOR 专用文件系统------比 FatFS 适合
 * 裸 Flash(FatFS 会把同一扇区磨死)。
 *
 * msh 验证:
 *   mount 成功后 echo hello >/flash/test.txt; cat /flash/test.txt
 */

#include <rtthread.h>
#include <board.h>

#if defined(BSP_USING_OSPI) && defined(RT_USING_FAL) && defined(RT_USING_DFS)

#include <fal.h>
#include <string.h>
#include <dfs_fs.h>
#include <dfs_file.h>

#define DBG_TAG             "app.flashfs"
#define DBG_LVL             DBG_INFO
#include <rtdbg.h>

#define FS_PART_NAME    "filesystem"
#define FS_MOUNT_PATH   "/flash"

int flash_fs_init(void)
{
    rt_kprintf("[flash_fs] step1: fal_init\n");
    fal_init();

    rt_kprintf("[flash_fs] step2: blk_device_create\n");
    struct rt_device *dev = fal_mtd_nor_device_create("filesystem");
    if (!dev) { rt_kprintf("[flash_fs] blk create FAIL\n"); return -1; }

   rt_kprintf("[flash_fs] step3: dfs_mount at /\n");
    if (dfs_mount("filesystem", "/", "lfs", 0, 0) == 0) {
        rt_kprintf("[flash_fs] mounted at /\n");
        return 0;
    }

    rt_kprintf("[flash_fs] step4: mkfs (8MB erase, may take 2 min)\n");
    if (dfs_mkfs("lfs", "filesystem") != 0) {
        rt_kprintf("[flash_fs] mkfs FAIL\n");
        return -1;
    }
    rt_kprintf("[flash_fs] step5: remount\n");
    int ret = dfs_mount("filesystem", "/", "lfs", 0, 0);
    rt_kprintf("[flash_fs] remount ret=%d\n", ret);
    return ret;
}
INIT_APP_EXPORT(flash_fs_init);

/* 参数区裸读写演示:fal_partition_* 直接操作 "param" 分区,
 * 工控场景的标定参数掉电保存就这么用 */
#ifdef RT_USING_FINSH
#include <finsh.h>

static void param_demo(int argc, char **argv)
{
    const struct fal_partition *part = fal_partition_find("param");
    rt_uint32_t magic = 0x50415241;     /* 'PARA' */
    rt_uint32_t val = 0;
    rt_uint8_t buf[4];

    if (part == RT_NULL) {
        rt_kprintf("param partition not found\n");
        return;
    }

    if (argc > 1) {
        /* 写:param_demo 1234 */
        val = (rt_uint32_t)atoi(argv[1]);
        fal_partition_erase(part, 0, 4);
        memcpy(buf, &val, 4);
        fal_partition_write(part, 0, buf, 4);
        fal_partition_write(part, 4, (const rt_uint8_t *)&magic, 4);
        rt_kprintf("param: wrote %lu (+magic)\n", val);
    } else {
        /* 读:param_demo */
        fal_partition_read(part, 0, buf, 4);
        memcpy(&val, buf, 4);
        fal_partition_read(part, 4, buf, 4);
        memcpy(&magic, buf, 4);
        rt_kprintf("param: value=%lu magic=0x%08lX %s\n",
                   val, magic, magic == 0x50415241 ? "(valid)" : "(invalid!)");
    }
}
MSH_CMD_EXPORT(param_demo, param_demo [value] - param partition rw demo);
#endif

#endif /* BSP_USING_OSPI && RT_USING_FAL && RT_USING_DFS */

外加一个 param 分区的演示命令 param_demo [value]:无参读取参数区(带 magic 0x50415241 校验,首次自动写默认值),带参写入并立即回读验证------这就是"特殊存储"场景的读写接口模板,工控里的校准系数、运行配置照这个套路存即可。

2.5、集成进BSP

  • 将一些默认不编译的固件源码文件集成进编译脚本 此处要做一个重要说明 :在前几篇文章中我似乎忘记做这部分说明。只要是用了官方固件库中的源码,就要自己在脚本中添加对应的源码,默认是没有集成相关源码文件的。现在我贴出我处理的部分。如果发现某个链接错误可以针对检查这部分: ~/gd32/rt-thread/bsp/gd32/arm/libraries/gd32-arm-series-latest/GD32H7xx/SConscript
plain 复制代码
src = Split('''
GD32H7xx_standard_peripheral/Source/gd32h7xx_gpio.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_rcu.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_exti.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_misc.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_syscfg.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_pmu.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_dma.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_dci.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_ospi.c
GD32H7xx_standard_peripheral/Source/gd32h7xx_ospim.c
''')
  • kconfig中加入如下片段(放在BSP_USING_SDRAM段的后面): ~/gd32/rt-thread/bsp/gd32/arm/gd32h759i-eval/board/Kconfig
plain 复制代码
config BSP_USING_OSPI
        bool "Enable OSPI0 (onboard GD25X512ME 64MB octal flash)"
        default n
        help
            Onboard octal NOR flash U7 = GD25X512MEBIRY (512Mbit/64MB,
            JEDEC ID 0xC8481AFF), via OSPI0 + OSPIM port 0.
            Pins: NCS=PB6(AF10), CLK=PB2(AF9),
            IO0~IO7 = PD11/PD12(AF9), PA3(AF6), PD13(AF9),
            PD4/PD5/PD6/PD7(AF10). DQS not connected (no-DQS mode only).
            Jumper prerequisite: JP61 to (2,3) = OSPI side.
            Conflicts: PB6 with I2C0_SCL / DCI_D5 (both unused).
            Memory-mapped base: 0x90000000.
            Chip command layer: gd25x512me.c (from the official demo).
            Shell cmds: ospi_id / ospi_test1 / ospi_test8 / ospi_mmap.
  • sconscript添加编译集成 ~/gd32/rt-thread/bsp/gd32/arm/libraries/gd32_drivers/SConscript
py 复制代码
if GetDepend(['BSP_USING_OSPI']):
    src += ['drv_ospi_h7.c', 'gd25x512me.c']

if GetDepend(['BSP_USING_OSPI']) and GetDepend(['RT_USING_FAL']):
    src += ['drv_ospi_fal.c']

这里要说明的是,applications目录下的文件不需要特别编写集成,因为该目录下的script文件用通配的方式自动集成该目录下的所有.c文件。

  • menuconfig 此处要用到在线资源包(littlefs),因此要选择的选项如下:
plain 复制代码
(Top) → RT-Thread online packages → system packages
[*] Littlefs: A high-integrity embedded file system
plain 复制代码
(Top) → RT-Thread Components
[*] FAL: flash abstraction layer
plain 复制代码
(Top) → Hardware Drivers Config → On-chip Peripheral Drivers
[*] Enable RTC calendar (LXTAL, IRC32K fallback)
[*] Enable OSPI0 (onboard GD25X512ME 64MB octal flash)

说明:之所以需要RTC,因为文件系统需要。

三、测试

3.1、基本测试

注意JP61插2,3

shell 复制代码
msh />ospi_id
ospi: JEDEC ID = 0xC8481AFF OK

msh />ospi_test1
ospi_test1: SPI_MODE erase/write/read @0x000000 ... 128 bytes verify OK

msh />ospi_test8
ospi_test8: octal STR erase/write/read @0x200000 ... 128 bytes verify OK

msh />ospi_mmap
ospi_mmap: read @0x90400000 via memory-map ... verify OK

单线、八线、内存映射三条通路全部打通。到这里"驱动能用了"。

3.2、文件系统挂载

观察开机日志:

shell 复制代码
[flash_fs] step1: fal_init
[fal] init: reset (octal+spi)
[fal] init: cfg 16 dummy cycles
[fal] init: enter octal STR
[fal] init: autopolling
[fal] init: done
[I/FAL] ==================== FAL partition table ====================
[I/FAL] | name       | flash_dev |   offset   |    length  |
[I/FAL] -------------------------------------------------------------
[I/FAL] | param      | gd25x512  | 0x00000000 | 0x00100000 |
[I/FAL] | app        | gd25x512  | 0x00100000 | 0x00700000 |
[I/FAL] | filesystem | gd25x512  | 0x00800000 | 0x00800000 |
[I/FAL] =============================================================
[I/FAL] RT-Thread Flash Abstraction Layer initialize success.
[I/FAL] The FAL MTD NOR device (filesystem) created successfully
[flash_fs] step3: dfs_mount at /
packages/littlefs-latest/lfs.c:1383:error: Corrupted dir pair at {0x0, 0x1}
[flash_fs] step4: mkfs          ← 首次:空白 Flash,自动格式化
[flash_fs] step5: remount
[flash_fs] remount ret=0        ← 挂载成功

文件读写验证(注意 msh 不支持 > 重定向,echo 字符串 文件名 空格分隔):

shell 复制代码
msh />echo hello test.txt
msh />cat test.txt
hello
msh />ls
Directory /:
test.txt                    5

断电重启,再开机:

shell 复制代码
[flash_fs] step3: dfs_mount at /
[flash_fs] mounted at /         

msh />cat test.txt
hello                            ← 断电保持,数据还在

param 分区 demo:

shell 复制代码
msh />param_demo                 ← 首次:无 magic,写默认值
param: no valid data, write defaults (value=0)
msh />param_demo 1234
param: write value=1234 OK, readback=1234
(断电重启)
msh />param_demo
param: value=1234                ← 参数持久化成功

本系列环境:GD32H759I-EVAL + RT-Thread v5.2.2 + 纯 GCC(WSL2)。文中所有驱动源码与 Kconfig 片段均已整理,需要的读者可发邮件索取,也可以等本系列写后我公布的github仓库(我的邮箱:360168657@qq.com)。

相关推荐
元岳数字人小元2 小时前
数字人源码系统:模块化开发赋能智能场景灵活迭代升级
运维·人工智能·开源·人机交互·交互
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(82):ReasoningBank——从成功与失败经验中沉淀可复用的推理记忆
论文阅读·人工智能·学习·开源·github
似夜晓星辰2 小时前
用 Laya 决策模型(JEV 的开源实现)做一个智能家居决策 Demo
开源·智能家居
SL_staff3 小时前
合规性折旧:当知识资产因主权缺位在审计中‘功能性清零’
java·设计模式·开源
分布式存储与RustFS3 小时前
DPU卸载纠删码与加密:解决AI对象存储的CPU瓶颈
云原生·开源·对象存储·分布式存储·s3·rustfs·性能基准
凉、介3 小时前
深入理解 ARM SCP Firmware 的 Event 与 Notification 机制
学习·scp·嵌入式·arm
wflynn4 小时前
GitHub 日榜趋势速报 | 2026-09-27
开源·github
码途漫谈4 小时前
AI开始用网页做视频,HyperFrames让修改有了明确落点
开源·aigc
Runwise创新社区5 小时前
开源大模型从本地跑通到生产上线:以千问为例的五级部署阶梯与六项选型检查
人工智能·系统架构·开源