@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 模式 |
两个设计约束:
- JP61 是复用开关:PB6 这根线既是 OSPI0_NCS 又是摄像头接口的 DCI_D5;
- 内存映射基址
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)。