匠芯创 D211 SerDes Kit — Bring-Up 指南(AI开发笔记)持续更新
SerDes Kit — Bring-Up 指南
D211BCX (d21x, QFN88, SIP DDR3 128MB) + W25N01GVZEIG SPI NAND | Bootloader (baremetal) + App (RT-Thread)
注:D211BCX 和 D211DCX 同为 DDR3,DDR 参数和 pinmux 模板兼容。以下日志均在 D211BCX 板实测通过。
修订记录
| 版本 | 日期 | 作者 | 变更 |
|---|---|---|---|
| v0.1 | 2026-07-06 | zhangpc | 初始 bring-up:板卡配置、bootloader、app、defconfigs、OneStep 脚本,Windows 编译验证通过 |
| v0.2 | 2026-07-07 | zhangpc | 启动链全链路文档化;image_cfg.json 七段解析;固件烧录(三种升级模式+两阶段流程+AICUPG_SUPPORT 陷阱+实测数据);D211BCX 串口输出完整记录;Shell 提示符确认与命令速查;工具链 V3.2.0;Git pull 工作流规则;AI 上下文补全 |
| v0.3 | 2026-07-08 | zhangpc | ddr_init.json 七段分层解析(DDR 参数+Mode Register+时序参数+升级检测+JTAG);与源码一致性校验修正(Ctrl+C 阻塞等待非 2 秒超时、DRAM 基址表述修正);SPI NAND 基准性能实测 |
| v0.4 | 2026-07-09 | zhangpc | SD 卡双卡 bring-up(8GB+16GB,4-bit 50MHz,首次 init 重试策略,速度协商两阶段 400KHz→50MHz) |
| v0.5 | 2026-07-14 | zhangpc | VS Code 终端 PowerShell 与 win_cmd.bat 不兼容问题:根因分析、两版 PowerShell 实测验证(5.1 + 7)、两种修复方式 |
| v0.6 | 2026-07-16 | zhangpc | SD 卡升级关闭 + MMC 驱动保留(AICUPG_SDCARD_ENABLE=n + AIC_BOOTLOADER_MMC_SUPPORT=y),Kconfig 依赖链分析 + BD_SDFAT32 边界;SD 卡 50MHz 崩溃修复:RT_MMCSD_STACK_SIZE 默认 1024 导致 mmcsd_detect 线程栈溢出(aic_sdmc_request 栈上分配 IDMA 描述符超限),改为 8192 |
| v0.7 | 2026-08-03 | zhangpc | AI 编译环境注入:win_cmd.bat 等效 PATH(MSYS cp、Python 3.8、OneStep),解决非 win_cmd.bat 终端下 SCons POST_ACTION cp *.pbp glob 展开失败;生成 serdes-build skill(.agents/skills/serdes-build/SKILL.md),覆盖编译/清理/重建完整生命周期,通过前置条件校验和语法验证 |
| v0.8 | 2026-08-05 | zhangpc | 烧录 CLI 工具修复:upgcmd/scons --aicupg 缺 MinGW 运行时 libgcc_s_dw2-1.dll(0xC0000135)定位并修复(从 AiBurn 复制到 tools/scripts/,本仓库已预置);记录修复后两阶段完整烧录日志(§10.7);方式一 upgcmd image 与方式二 scons --aicupg 端到端验证均通过(自动复位启动 + 串口验证) |
0. 概述
本文档记录了 SerDes Kit 项目在 D211BCX 芯片上的完整 bring-up 过程,涵盖:
- 板级层:
target/d21x/serdes-nand/— 时钟、引脚复用、DDR、分区布局 - Bootloader:
application/baremetal/serdes-boot/— 裸机 SPL,加载 OS - 应用:
application/rt-thread/serdes-app/— RT-Thread 应用,打印启动成功信息 - 构建系统:OneStep 工作流、defconfig 管理、SDK 修改
1. 环境搭建
工作区路径不能含空格:使用
SerDes_Kit而非SerDes Kit,否则 SCons 会截断路径。
不要用 SDK 根目录的
env.bat:它默认 Python 2.7,会导致PRJ_TOOLCHAIN_VER为 None 而无法编译。
VS Code:win_cmd.bat 是 cmd 批处理文件,不能直接用 PowerShell 运行。VS Code 自带的 .vscode/settings.json 中配置了 PowerShell profile 加载 win_cmd.bat,但这是官方模板的遗留问题——所有 PowerShell 版本均拒绝执行 .bat 文件(见下方详细分析)。
打开集成终端后,点击终端窗口右上角的 ▾ 下拉菜单,选择 Command Prompt。
持久化设置:按
Ctrl + ,打开设置 → 搜索terminal default profile windows→ 在 User 标签页的下拉菜单中选择 Command Prompt。设置后每次打开终端自动使用 cmd,无需每次手动切换。
根因分析:为什么 PowerShell 不能用?
SDK 官方 .vscode/settings.json 中给 PowerShell profile 配置了 win_cmd.bat 作为参数:
"PowerShell": {
"source": "PowerShell",
"args": ["${workspaceFolder}/win_cmd.bat"],
},
VS Code 将其转换为:
pwsh.exe -File C:\...\luban-lite\win_cmd.bat
PowerShell 的 -File 参数只接受 .ps1 脚本,传入 .bat 批处理文件即报错退出。而 win_cmd.bat 内部使用 set PATH=... 设置环境变量、doskey 注册命令别名,这些均是 cmd.exe 专属语法,PowerShell 下完全不兼容。
相比之下,Command Prompt 配置使用了正确的
/K开关:"args": ["/K ${workspaceFolder}/win_cmd.bat"]——执行完批处理后保持窗口不关闭。这是唯一正确的用法。
实测验证
PowerShell 7 (pwsh):
> pwsh.exe -NoLogo -NoProfile -File win_cmd.bat
Processing -File '...win_cmd.bat' failed because the file does not
have a '.ps1' extension. Specify a valid PowerShell script file
name, and then try again.
> echo %errorlevel%
64
Windows PowerShell 5.1 (powershell):
> powershell.exe -NoLogo -NoProfile -File win_cmd.bat
-File 参数失败,因为该文件没有 '.ps1' 扩展名。
请指定一个有效的 Windows PowerShell 脚本文件名,
然后重试。
> echo %errorlevel%
-196608
| PowerShell 版本 | 可执行文件 | 行为 | Exit Code |
|---|---|---|---|
| PowerShell 7 | pwsh.exe,来自 Microsoft Store | 明确拒绝,报 .ps1 扩展名错误 | 64 |
| Windows PowerShell 5.1 | powershell.exe,系统内置 | 同上 | -196608 |
结论:无论哪个 PowerShell 版本,-File 参数均无法执行 .bat 文件。换 PS5.1 不能解决问题,必须按上述方式切换到 Command Prompt 使用。
手动:双击项目根目录下的 win_cmd.bat(或 win_env.bat 使用 ConEmu)。
环境包含:
| 变量 | 值 |
|---|---|
| MSYS 工具 | tools\env\tools\bin(ls、cp、mkdir、rm 等) |
| Python 3.8 | tools\env\tools\Python38 |
| Python 2.7 | tools\env\tools\Python27\Scripts(旧脚本用) |
| ENV_ROOT | tools\env |
| RTT_ROOT | kernel\rt-thread |
OneStep 命令(官方推荐工作流)
| 命令 | 功能 |
|---|---|
list | 列出所有应用 defconfig(bootloader 自动隐藏,由系统自动匹配) |
lunch | 通过编号或完整名称加载 defconfig(如 lunch 23 或 lunch xxx_defconfig) |
m / mb | 编译 bootloader + app(一键命令) |
mu / ms | 仅编译 bootloader |
ma | 仅编译 app |
c | 清理 bootloader 和 app |
mc | 清理后重新编译 |
me / km | app 的 menuconfig |
bm | bootloader 的 menuconfig |
i | scons --info(当前项目信息) |
工具链选择
SDK 自带两套 RISC-V 工具链:
| 工具链 | Kconfig 标识 | 路径 |
|---|---|---|
| V2.6.1(默认) | PRJ_RV_TOOLCHAIN_V2_6_1 | toolchain/bin/ |
| V3.2.0 | PRJ_RV_TOOLCHAIN_V3_2_0 | toolchain/bin/ |
本项目选择 V3.2.0,在 app 和 bootloader 两个 defconfig 中均显式指定:
CONFIG_PRJ_RV_TOOLCHAIN_V3_2_0=y
重要:SDK 所有 demo 板不写工具链,靠 Kconfig 默认 V2.6.1。如果只在其中一个 defconfig 写 V3.2.0,
lunch切换工程时.config被覆盖,工具链会在 V2.6.1 ↔ V3.2.0 之间反复跳变。两个 defconfig 必须一致。
验证当前工具链:scons --info 输出中的 PRJ_TOOLCHAIN_VER 字段。
2. 快速编译
REM 1. 打开终端(VS Code 自动注入环境)
REM 2. 选择应用 defconfig
list
REM 例如 list 显示:"23. d21x_serdes-nand_rt-thread_serdes-app"
lunch 23
REM 或:lunch d21x_serdes-nand_rt-thread_serdes-app_defconfig
REM 3. 一键编译
m
m 命令处理完整流程:应用 bootloader defconfig → 编译 bootloader → 应用 app defconfig → 编译 app → 生成最终镜像。
输出文件
| 项目 | 镜像 |
|---|---|
| Bootloader | output/d21x_serdes-nand_baremetal_serdes-boot/images/bootloader.aic |
| RT-Thread App | output/d21x_serdes-nand_rt-thread_serdes-app/images/d21x_serdes_nand_page_2k_block_128k_v1.0.0.img |
清理
c REM 清理两个工程(通过 scons)
mc REM 清理后重新编译
完全重置(注意:需重新 lunch)
rmdir /s /q output & rmdir /s /q .log & del *.sconsign.dblite rtconfig.h cconfig.h partition_table.h d21x_bootloader_gcc.ld .config .config.old .defconfig .Kconfig.prj 2>nul & del packages\third-party\mbedtls\ports\src\tls_certificate.c 2>nul & del target\d21x\serdes-nand\pack\.image_cfg.json.tmp 2>nul & del target\d21x\serdes-nand\pack\bootloader.bin 2>nul & del target\d21x\demo128-nand\pack\bootloader.bin 2>nul & del target\d21x\demo128-nand\pack\.image_cfg.json.tmp 2>nul
删除所有构建产物、生成的头文件、日志和 Kconfig 状态。执行后必须重新 lunch + m。
3. 工程布局
target/d21x/serdes-nand/ ← 板卡硬件配置(你的硬件)
├── board.c ← 板级初始化:RT-Thread + Baremetal 双模式、memheap
├── pinmux.c ← 引脚复用:UART0、QSPI0、条件外设
├── sys_clk.c ← 时钟树:CPU 600M / AXI 240M / AHB 240M / APB 100M
├── SConscript ← 构建:Glob(*.c)
├── include/
│ └── board.h ← 板级头文件
└── pack/
├── image_cfg.json ← 128MB SPI NAND 分区布局与镜像组装规则
├── ddr_init.json ← DDR3 128MB @ 600MHz 初始化参数
├── env.txt ← U-Boot 环境变量
└── ota-subimgs.cfg ← OTA 子镜像清单
application/baremetal/serdes-boot/ ← Bootloader(裸机,无 OS)
├── main.c ← UART0 控制台、"SerDes Kit Boot" banner、nand_boot
├── Kconfig ← SPI NAND 启动默认配置
├── SConscript ← 构建入口
├── cmd/ ← Bootloader shell 命令(nand_boot、mem、run 等)
├── lib/ ← aicupg、fitimage、公共库
├── include/ ← Bootloader 头文件
└── ldscript/ ← 各芯片链接脚本(d21x_bootloader_gcc.ld.S)
application/rt-thread/serdes-app/ ← RT-Thread 应用
├── main.c ← 打印 "SerDes Kit App Boot Success!"
├── Kconfig ← App Kconfig(空,同 helloworld)
└── SConscript ← 构建入口
target/configs/
├── d21x_serdes-nand_baremetal_serdes-boot_defconfig ← Bootloader defconfig
└── d21x_serdes-nand_rt-thread_serdes-app_defconfig ← RT-Thread app defconfig
`02_BRING_UP.md` ← 本文档
4. 工作原理
4.1 启动链概览
D211BCX 上电后经历五个阶段,从芯片内置 ROM 代码到最终用户应用:
┌──────┐ ┌──────┐ ┌──────┐ ┌───────────────┐ ┌─────────────┐
│ ROM │───→│ PBP │───→│ SPL │───→│ Bootloader │───→│ RT-Thread │
│ │ │ │ │ │ │ (serdes-boot) │ │ (serdes-app)│
└──────┘ └──────┘ └──────┘ └───────────────┘ └─────────────┘
片上ROM NAND NAND NAND NAND
(不可改) (spl分区) (spl分区) (spl分区) (os分区)
4.2 各阶段详解
4.2.1 ROM(Read-Only Memory)
- 位置:芯片内部掩膜 ROM,出厂烧录,不可修改
- 职责:最小化初始化(时钟源选择、关闭看门狗),然后根据 boot 引脚状态决定从哪种介质启动
- 对于 NAND 启动:ROM 从 SPI NAND 的固定位置读取 PBP 到内部 SRAM,校验后跳转执行
4.2.2 PBP(Pre-Boot Program,预启动程序)
- 位置:存储在 NAND 的
spl分区开头,由 ROM 加载到 SRAM 运行 - 二进制文件:
d21x.pbp(芯片平台通用,位于bsp/artinchip/sys/d21x/) - 配置文件:
ddr_init.json(板卡专属,定义 DDR 参数、升级引脚、调试 UART) - 职责:
- 初始化 DDR 控制器(根据
ddr_init.json中的时序参数和频率) - 配置升级模式检测引脚(PA.0 拉低 = 进入 USB 刷机模式)
- 配置 JTAG 调试引脚(PA.8–PA.11)
- 从 NAND 中加载 SPL 到 DRAM,校验后跳转执行
- 初始化 DDR 控制器(根据
- 运行位置:SRAM(此时 DDR 尚未初始化完成),初始化 DDR 后切换到 DRAM
4.2.3 SPL(Secondary Program Loader,二级程序加载器)
- 位置:存储在 NAND 的
spl分区,紧接 PBP 之后 - 运行的是:
d21x.bin的早期初始化阶段(即 Bootloader 本体的一部分) - 二进制封装:
bootloader.aic中的loader.aic部分(由image_cfg.json定义组装规则) - 职责:
- 初始化更多硬件(PLL、时钟树、更多外设)
- 从 NAND 中读取完整的 bootloader 到 DRAM
- 校验 bootloader 完整性(CRC / 签名)
- 跳转到 bootloader 入口地址(
CONFIG_AIC_BOOTLOADER_TEXT_BASE,即0x47B80100)
- 和 PBP 的关系:PBP 是最小化的硬件初始化(仅 DDR),SPL 在 DDR 可用的前提下做更多初始化。两者共同构成
bootloader.aic镜像
4.2.4 Bootloader(serdes-boot,裸机引导程序)
- 位置:存储在 NAND 的
spl分区,由 SPL 加载到 DRAM 运行 - Kernel:
baremetal(无 RTOS,直接操作硬件寄存器) - 源码:
application/baremetal/serdes-boot/main.c - defconfig:
d21x_serdes-nand_baremetal_serdes-boot_defconfig - 职责:
- 初始化 UART0 控制台(115200 8N1)
- 显示 banner:
"SerDes Kit Boot [Built on ...]" - 进入交互式 shell(
aic />提示符),可用命令包括:nand_boot— 从 NAND 加载 OS 镜像到 DRAM 并跳转spinand— SPI NAND 读写/擦除mtd— MTD 分区操作part— 分区表查询
- 收到 4 个非 Ctrl+C 字符后自动执行
nand_boot加载 OS(console_get_ctrlc()循环 4 次检测,非固定 2 秒超时)
- 运行位置:Bootloader 加载基址 + 0x100(
0x47B80100,由CONFIG_AIC_BOOTLOADER_TEXT_BASE定义)。0x100 为 AIC 镜像头大小
4.2.5 RT-Thread App(serdes-app,应用程序)
- 位置:存储在 NAND 的
os分区,由 bootloader 的nand_boot命令加载 - Kernel:
rt-thread(RTOS 内核) - 镜像文件:
d21x_os.itb(FIT Image Tree Blob,包含内核 + device tree + 可选 ramdisk) - 源码:
application/rt-thread/serdes-app/main.c - defconfig:
d21x_serdes-nand_rt-thread_serdes-app_defconfig - 职责:
- RT-Thread 内核初始化(调度器、内存管理、设备框架)
- 调用
main()打印"SerDes Kit App [Built on ...]" - 进入 FinSH shell(
msh />提示符),可交互执行命令
- 运行位置:DRAM(精确地址由 RT-Thread 链接脚本决定)
4.3 镜像生成
关于 SPL:SPL 和 Bootloader 是同一个二进制文件
d21x.bin,不是两个独立文件。SPL(Secondary Program Loader)只是 bootloader 的早期初始化阶段,负责时钟、DDR 等基础初始化,完成后继续执行到完整的 bootloader(UART 控制台、shell 命令)。关于
.aic:这是 ArtInChip Image Container 格式——给原始二进制包裹一个固定结构的头部,包含加载地址、入口点、DDR 参数等元数据。它不是源码,不参与编译,纯粹是芯片 ROM/PBP 识别和加载用的容器格式。
4.3.1 各阶段运行文件对照
| 阶段 | 运行文件 | 来源 | 生成方式 | 配置文件 |
|---|---|---|---|---|
| PBP | d21x.pbp | SDK 预编译,bsp/artinchip/sys/d21x/ | 无需编译,mk_image.py 直接复制 | ddr_init.json |
| SPL + Bootloader | bootloader.aic | 编译 serdes-boot 源码 | scons 编译 → objcopy → AIC 包装 → 与 PBP 拼接,共 6 步 | bootloader defconfig、image_cfg.json |
| App | d21x_os.itb | 编译 serdes-app 源码 | scons 编译 → elf_parse.py 生成 .its → mk_image.py 打包 | app defconfig、image_cfg.json |
4.3.2 Bootloader 镜像生成流水线(d21x.bin 到 bootloader.aic 的完整过程)
serdes-boot 源码 中间产物 最终镜像
════════════════════════════════════════════════════════════════════════════
main.c + cmd/* + lib/*
│ riscv64-unknown-elf-gcc 编译 + 链接
↓
d21x.elf ← ELF 格式,含符号表、调试信息,可 GDB 调试
│ riscv64-unknown-elf-objcopy -O binary
↓
d21x.bin ← 去头后的纯二进制,可直接加载到 DRAM 运行
│ aic_build.py 自动 cp
↓
bootloader.bin ← d21x.bin 的副本,放到 pack/ 目录供打包使用
│ aicimage 工具 (mk_image.py 调用)
↓
loader.aic ← bootloader.bin + ddr_init.bin,包裹 AIC 容器头
│ (容器头记录: 加载地址 0x47B80000, 入口点 0x47B80100)
│
│ 同时: d21x.pbp + ddr_init.bin ──aicimage──→ pbp_ext.aic
│ (PBP 预编译 + DDR 参数,包裹 AIC 容器头)
│
└───────┬─────── concatenate (文件拼接) ─────────┐
↓ ↓
pbp_ext.aic loader.aic
└──────────────────┬──────────────────┘
↓
bootloader.aic ← 最终 boot 镜像
│
↓ 写入 NAND spl 分区
逐步说明:
| 步骤 | 输入 | 输出 | 工具 | 做了什么 | 性质 |
|---|---|---|---|---|---|
| 1. 编译 | serdes-boot/*.c | d21x.elf | riscv64-unknown-elf-gcc | C 源码编译 + 链接,按链接脚本(d21x_bootloader_gcc.ld)指定内存布局。编译哪些 .c 文件由 Kconfig/defconfig 决定 | 🔧 板卡配置决定 |
| 2. DDR 参数转换 | ddr_init.json | ddr_init.bin | mk_private_resource.py | JSON 配置转为二进制格式,供 PBP 和 loader 使用。DDR 类型(DDR3)、频率(600M)、容量(128M) 从 ddr_init.json 读取 | 🔧 板卡配置决定 |
| 3. 去头 | d21x.elf | d21x.bin | objcopy -O binary | 剥离 ELF 头、节区表、符号表,保留纯机器码。所有板都一样 | ✅ 所有板卡相同 |
| 4. 拷贝 | d21x.bin | bootloader.bin | aic_build.py 自动脚本 | 复制到 pack/ 目录,供 mk_image.py 读取。aic_build.py 通过命名匹配决定路径(我们的 SDK 补丁就在这里) | 🔧 板卡配置决定 |
| 5. AIC 包装 | bootloader.bin + ddr_init.bin | loader.aic | mk_image.py → aicimage | 包裹 AIC 头(加载地址/入口点从 Kconfig 取值),嵌入 DDR 参数(用于热复位场景) | 🔧 板卡配置决定 |
| 5b. AIC 包装 | d21x.pbp + ddr_init.bin | pbp_ext.aic | mk_image.py → aicimage | PBP 预编译二进制 + DDR 参数(ddr_init.json 转换)。内嵌的 ddr_init.bin 是板卡专属 | 🔧 板卡配置决定 |
| 6. 拼接 | pbp_ext.aic + loader.aic | bootloader.aic | concatenate | 按顺序拼接两个 AIC 文件为单一镜像。拼接顺序对所有板都一样 | ✅ 所有板卡相同 |
| App 编译 | serdes-app/*.c | d21x_os.itb | scons → elf_parse.py → mk_image.py | 编译 + 生成 .its + 打包为 FIT Image。内核/外设配置全部由 serdes-app 的 defconfig 决定 | 🔧 板卡配置决定 |
最终 .img | 各分区镜像 | *.img | mk_image.py | 分区布局、镜像组件列表由 image_cfg.json 决定(product: "serdes_nand"),文件名包含 NAND 几何参数 | 🔧 板卡配置决定 |
4.3.3 涉及文件一览
| 文件 | 类型 | 说明 |
|---|---|---|
d21x.pbp | SDK 预编译 | Pre-Boot Program,芯片平台通用,初始化 DDR |
d21x.elf | 编译产物 | ELF 格式,含符号表,可 GDB 调试 |
d21x.bin | 编译产物 | 去头纯二进制,即 SPL 和 Bootloader 的本体 |
bootloader.bin | 中间文件 | d21x.bin 的副本,供打包使用 |
ddr_init.bin | 中间文件 | ddr_init.json 转二进制 DDR 参数 |
pbp_ext.aic | 中间镜像 | PBP + DDR 参数,AIC 容器格式 |
loader.aic | 中间镜像 | bootloader.bin + DDR 参数,AIC 容器格式 |
bootloader.aic | 最终 boot 镜像 | pbp_ext.aic + loader.aic 拼接,写入 spl 分区 |
d21x_os.itb | 最终 OS 镜像 | FIT Image Tree Blob,RT-Thread 内核 + device tree,写入 os 分区 |
*.img | 烧录文件 | 完整 NAND flash 分区镜像,含 spl+env+os+rodata+data 所有分区 |
4.4 App ↔ Bootloader 匹配
构建系统通过命名约定自动发现 bootloader,无需显式配置:
App defconfig: d21x_serdes-nand_rt-thread_serdes-app_defconfig
└── chip ──┘└── board ──┘└ kernel ┘└── app ──┘
Bootloader: d21x_serdes-nand_baremetal_serdes-boot_defconfig
└── 相同前缀 ──┘└─ 按命名匹配 ──┘
只要 chip + board 匹配,系统就能找到对应的 bootloader。具体机制:
aic_build.py扫描target/configs/下所有{chip}_{board}_baremetal_*_defconfig- 排除
serdes-boot(我们在第 5.1 节打的补丁)和bootloader - 第一个匹配的 baremetal defconfig 自动作为 bootloader 编译
- OneStep 脚本(
make_boot_and_app.bat等)优先使用serdes-boot,否则回退bootloader
因此 lunch 只需加载 app defconfig,bootloader 自动匹配。m 命令会先编译 bootloader 再编译 app。
4.5 镜像打包
image_cfg.json 是镜像组装的总控文件,mk_image.py 读取它来执行分区计算→预处理→组件映射→最终打包的全流程。文件由三个顶层段组成:spi-nand、image、pre-process。
4.5.1 spi-nand — 存储设备与分区定义
定义 NAND flash 的物理参数和分区布局,每个分区有明确的用途。
| 分区 | 大小 | 用途 |
|---|---|---|
spl | 1 MB | 存放 Bootloader 镜像(bootloader.aic),芯片上电后 ROM/PBP/SPL 从这里加载 |
env | 256 KB | U-Boot 环境变量 A 区,存储启动参数(AB 系统选择、bootlimit 等) |
env_r | 256 KB | U-Boot 环境变量 B 区(冗余备份),A 区损坏时自动切换 |
os | 4 MB | RT-Thread OS 镜像 A 区(d21x_os.itb),正常启动时加载 |
os_r | 4 MB | OS 镜像 B 区(冗余备份),升级失败时自动回退到此处 |
rodata | 12 MB | 只读数据 A 区(字体、图片等静态资源),挂载为 FATFS |
rodata_r | 12 MB | 只读数据 B 区(冗余备份) |
data | 40 MB | 用户数据 A 区,通过 NFTL 管理(坏块替换+磨损均衡),挂载为 FATFS |
data_r | 40 MB | 用户数据 B 区(冗余备份) |
refresh | 1 MB | NAND 刷新元数据区,记录各块刷新状态,防止长期不刷新导致读干扰 |
分区总计约 114.5MB,加上坏块预留和 spare 区,总容量 128MB 合理。
JSON 中的对应配置:
"spi-nand": { // 设备的 key 必须与 image.info.media.type 一致
"size": "128m", // NAND 总容量
"partitions": {
"spl": { "size": "1m" }, // Bootloader 存储区
"env": { "size": "256k" }, // U-Boot 环境变量 A
"env_r": { "size": "256k" }, // U-Boot 环境变量 B(冗余)
"os": { "size": "4m" }, // RT-Thread OS 镜像 A
"os_r": { "size": "4m" }, // OS 镜像 B(冗余)
"rodata": { "size": "12m" }, // 只读数据 A
"rodata_r": { "size": "12m" }, // 只读数据 B
"data": { // 用户数据 A
"size": "40m",
"nftl": { "data": { "size": "-" }} // NFTL 管理卷,"-"=占满剩余空间
},
"data_r": { ... }, // 用户数据 B
"refresh": { "size": "1m" } // NAND 刷新元数据区
}
}
关键概念:
- AB 双分区:
os/os_r、rodata/rodata_r、data/data_r成对出现,支持安全升级(升级失败自动回退到备份分区) - NFTL(NAND Flash Translation Layer):
data分区使用 NFTL 而非直接 MTD 访问,由 NFTL 驱动管理坏块和磨损均衡 size: "-":表示使用分区的全部剩余空间
4.5.2 image.info — 镜像元信息
定义镜像的标识信息和存储介质参数。
"info": {
"platform": "d21x", // 芯片平台 → 写入镜像头、参与文件命名
"product": "serdes_nand", // 产品名 → 写入镜像头、参与文件命名
"version": "1.0.0", // 版本号 → 写入镜像头、参与文件命名
"media": { // 存储介质参数 → 用于计算分区地址和文件命名
"type": "spi-nand", // 介质类型,必须与顶层 key 一致
"device_id": 0, // 设备编号(多 NAND 时区分)
"array_organization": [
{ "page": "2k", "block": "128k", "oob": "64" } // W25N01GVZEIG 参数
]
}
}
platform、product、version 三个字段直接决定最终 .img 文件名,media.array_organization 提供 NAND 几何参数用于分区地址对齐和命名前缀。
4.5.3 image.updater — USB 升级镜像
USB 刷机时 PC 工具按顺序下载到芯片 RAM 并立即执行的文件列表,不写入 flash。
"updater": {
"ddr": { // 第一阶段:初始化 DDR
"file": "usbupg-ddr-init.aic", // PBP + ddr_init.bin
"attr": ["required", "run"], // required=必须存在 run=下载后立即执行
"ram": "0x00103000" // 加载到 SRAM 地址
},
"spl": { // 第二阶段:运行 Bootloader
"file": "bootloader.aic", // PBP + DDR + Bootloader 完整镜像
"attr": ["required", "run"],
"ram": "0x41000000" // 加载到 DRAM 地址(基址+16MB 偏移)
}
}
USB 升级流程:PC 发送 ddr 镜像 → 芯片在 SRAM 运行 PBP 初始化 DDR → PC 发送 spl 镜像 → 芯片在 DRAM 运行 Bootloader → Bootloader 接管 USB 通信完成固件烧写。
4.5.4 image.target — 烧录目标分区映射
定义哪些文件写入 NAND 的哪些分区。每个条目指定三个关键属性:file(源文件)、part(目标分区名)、attr(写入策略)。
"target": {
"spl": { "file": "bootloader.aic", "attr": ["mtd", "required"], "part": ["spl"] },
"env": { "file": "env.bin", "attr": ["mtd", "optional"], "part": ["env"] },
"env_r": { "file": "env.bin", "attr": ["mtd", "optional"], "part": ["env_r"] },
"os": { "file": "d21x_os.itb", "attr": ["mtd", "required"], "part": ["os"] },
"rodata": { "file": "rodata.fatfs", "attr": ["mtd", "optional"], "part": ["rodata"] },
"data": { "file": "data.fatfs", "attr": ["block","optional"],"part": ["data"] }
}
attr 字段含义:
| 值 | 含义 |
|---|---|
mtd | 直接写入 flash 原始分区(绕过文件系统,用于 spl/os/env/rodata) |
block | 通过 NFTL 块设备写入(支持坏块管理和磨损均衡,用于 data 分区) |
required | 此组件必须存在,否则打包失败 |
optional | 此组件可选,文件不存在时跳过不报错 |
写入规则:
spl和os标记为required——缺一不可,否则系统无法启动env、env_r写入同一个env.bin文件——env_r 是其冗余副本。env.bin内容来自pre-process.uboot_env步骤rodata(只读数据)和data(用户数据)是optional——当前最小配置下.fatfs文件尚未构建(无文件系统镜像),打包时自动跳过data使用block属性(NFTL),其他分区使用mtd(直接写)——因为 data 分区需要坏块管理和磨损均衡
AB 冗余分区的自动处理:分区列表定义了 os_r、rodata_r、data_r 三个冗余分区,但它们没有对应的 target 条目——mk_image.py 会根据 AB 系统环境变量自动将同一个文件写入 A 和 B 两套分区。同时还有 refresh 分区(1MB),存储 NAND 刷新元数据,防止同一位置长期不刷新导致读干扰。
最小配置下的实际写入:当前 defconfig 未启用文件系统镜像和 rodata/data 构建,因此打包时实际写入的分区为:
| 分区 | 文件 | 状态 |
|---|---|---|
spl | bootloader.aic | required — 必须存在 |
env / env_r | env.bin | optional — 存在,写入 |
os | d21x_os.itb | required — 必须存在 |
rodata / rodata_r | — | optional — 跳过 |
data / data_r | — | optional — 跳过 |
refresh | — | 系统预留 |
4.5.5 pre-process — 镜像预处理步骤
在写入分区之前,需要先把原始文件加工成目标镜像组件。此段定义了四个预处理步骤。
① aicimage — AIC 容器封装
将原始二进制包裹上 AIC 容器头(含加载地址、入口点、资源列表),生成 .aic 文件供芯片 ROM/PBP 识别。
"aicimage": {
"usbupg-ddr-init.aic": { // USB 升级用的 DDR 初始化镜像
"head_ver": "0x00010001", // AIC 头版本号
"resource": {
"private": "ddr_init.bin", // 私有数据:DDR 参数
"pbp": "d21x.pbp" // PBP 预编译二进制
}
},
"pbp_ext.aic": { // PBP + DDR 参数(用于拼接)
"head_ver": "0x00010001",
"resource": {
"pbp": "d21x.pbp",
"private": "ddr_init.bin"
},
"with_ext": "true" // 标记此容器用于拼接(非独立执行)
},
"loader.aic": { // Bootloader 代码容器
"head_ver": "0x00010001",
"loader": { // 可执行代码段
"file": "bootloader.bin", // 要包装的原始二进制
"load address": "CONFIG_AIC_BOOTLOADER_LOAD_BASE", // DRAM 加载地址
"entry point": "CONFIG_AIC_BOOTLOADER_TEXT_BASE" // 入口地址
},
"resource": {
"private": "ddr_init.bin" // 内嵌 DDR 参数(热复位时使用)
}
}
}
三个 .aic 文件的区别:
| 文件 | 用途 | 是否包含可执行代码 |
|---|---|---|
usbupg-ddr-init.aic | USB 升级:仅初始化 DDR | ❌ 仅 PBP + DDR 参数 |
pbp_ext.aic | 拼接组件:PBP + DDR | ❌ 仅 PBP + DDR 参数 |
loader.aic | 拼接组件:Bootloader 本体 | ✅ bootloader.bin |
load address 和 entry point 的值 CONFIG_AIC_BOOTLOADER_* 是 Kconfig 宏,编译 bootloader 时由 calc_linked_addr 展开为实际地址(0x47B80000 / 0x47B80100)。
② concatenate — 文件拼接
"concatenate": {
"bootloader.aic": ["pbp_ext.aic", "loader.aic"]
}
按数组顺序将多个文件首尾拼接为一个。bootloader.aic = pbp_ext.aic 的全部内容 + loader.aic 的全部内容。
芯片启动时 ROM 先读取 pbp_ext 部分执行 PBP 初始化 DDR,然后自动继续读取 loader 部分执行 Bootloader。
③ uboot_env — 环境变量生成
"uboot_env": {
"env.bin": {
"file": "env.txt", // 输入:文本格式的环境变量
"size": "4096", // 输出:4KB 固定大小
"redundant": "enable" // 生成 CRC32 校验的冗余副本
}
}
将 env.txt 中的键值对(如 bootlimit=5、osAB_next=A)转换为 4KB 二进制 blob,含 CRC32 校验头。redundant: enable 意味着文件内包含两份副本,一份损坏时自动使用另一份。
④ itb — FIT 镜像打包
"itb": {
"d21x_os.itb": {
"its": "d21x_os.its" // 输入:Image Tree Source 描述文件
}
}
elf_parse.py 先从 d21x.elf 中提取内核加载地址、入口点和设备树信息,生成 .its 描述文件。然后 mk_image.py 使用 mkimage 工具(U-Boot 的 FIT 工具)将内核二进制和设备树打包为一个 .itb(Image Tree Blob)文件,供 Bootloader 的 nand_boot 命令解析和加载。
4.5.6 组装流程总览
image_cfg.json 读取后
┌──────────────────────────────────────────────┐
│ │
spi-nand 段 ──→ 解析分区表 ──→ 计算每个分区起始地址和大小 │
│ │
pre-process 段 ──→ 执行预处理: │
① aicimage: 原始二进制 ──包装 AIC 头──→ .aic 容器文件 │
② concatenate: 多个 .aic ──按顺序拼接──→ 单一 .aic │
③ uboot_env: env.txt ──转换──→ env.bin (4KB + CRC) │
④ itb: .elf ──提取信息──→ .its ──打包──→ .itb │
│ │
image.info 段 ──→ 生成镜像头元信息(platform/product/version) │
│ │
image.target 段 ──→ 组件 → 分区映射,按 attr 规则写入各分区: │
required: bootloader.aic → spl, d21x_os.itb → os │
optional: env.bin → env/env_r, rodata.fatfs → rodata, ... │
│ │
↓ │
最终 {platform}_{product}_v{version}.img │
(镜像头 + 分区表 + spl 数据 + env 数据 + os 数据 + ...) │
│ │
└──────────────────────────────────────────────┘
4.5.7 烧录镜像命名规则
最终文件由 image_cfg.json 中的字段自动命名:
{platform}_{product}_v{version}.img
↓ ↓ ↓
"d21x" "serdes_nand" "1.0.0"
基础名: d21x_serdes_nand_v1.0.0.img
对于 NAND flash,构建系统自动附加几何参数前缀:
page_{pagesize}k_block_{blocksize}k_{基础名}
↓ ↓
2k (page) 128k (block)
实际生成: d21x_serdes_nand_page_2k_block_128k_v1.0.0.img
| 字段 | 来源 | 当前值 |
|---|---|---|
platform | info.platform | d21x |
product | info.product | serdes_nand |
version | info.version | 1.0.0 |
page | media.array_organization[0].page | 2k |
block | media.array_organization[0].block | 128k |
4.6 为什么 list 只显示 App
SDK 故意将 bootloader defconfig 从 list 中隐藏。用户只需选择 app;bootloader 通过命名约定自动匹配。我们的 aic_build.py 补丁扩展了此过滤器,同样隐藏 serdes-boot。
4.7 Kconfig 配置规范
显式 # CONFIG_xxx is not set vs 隐式默认值
defconfig 中显式写 # CONFIG_XXX is not set 与直接删除该行有本质区别:
| 写法 | 行为 |
|---|---|
# CONFIG_XXX is not set | 强制为 n,即使 Kconfig 中 default y |
| 直接删除该行 | 取 Kconfig 的 default 值(可能是 y 也可能是 n) |
示例:AIC_BOOTLOADER_CMD_MEM 在 Kconfig 中 default y。demo 板显式写了 # is not set 来关闭。如果不写这行,它就会默认开启。
Kconfig 覆盖优先级
Kconfig depends on > defconfig 显式值 > Kconfig default
(硬约束) (你的意图) (补缺)
defconfig.py 的处理流程:
- defconfig 复制为
.config defconfig.py检查 Kconfig 依赖:depends on不满足的强制改n- defconfig 未写的配置取 Kconfig
default值 select链传导:你开了 A,A 的select B自动把 B 设为y- defconfig 中配置的分组顺序遵循 SDK 惯例:身份 → 外设 → 内存 → 存储 → 升级 → 命令 → USB → MPP → ENV → NAND 管理
Bootloader 升级功能配置
| 功能 | 硬件要求 | 本板状态 |
|---|---|---|
| USB 刷机(从机) | USB device | [ON] AICUPG_USB_ENABLE(default y) |
| SD 卡升级 | SD 卡槽 | [OFF] # CONFIG_AICUPG_SDCARD_ENABLE is not set(见下方说明) |
| U 盘升级 | USB Host | [OFF] # CONFIG_AICUPG_UDISK_ENABLE is not set |
CONFIG_AIC_USING_USB1 是备用的 USB 物理控制器 ID(用于 U 盘升级走 USB1 口),不是 USB 从机刷机。真正的 USB 刷机走 AICUPG_USB_ENABLE(使用 USB0,default y)。
SD 卡升级关闭原因:
AICUPG_SDCARD_ENABLE是AIC_SDMC_DRV(MMC 驱动)的唯一 Kconfig select 入口。关了升级会连带砍掉整个 MMC 驱动链,导致part命令无法检测 SD 卡。- 解决:关闭升级但手动开启 MMC 驱动:
# CONFIG_AICUPG_SDCARD_ENABLE is not set+CONFIG_AIC_BOOTLOADER_MMC_SUPPORT=y。同时关闭AICUPG_MMC_ARTINCHIP(写 MMC 设备,升级专用)。
| 配置项 | 值 | 说明 |
|---|---|---|
AICUPG_SDCARD_ENABLE | n | 关闭 SD 卡升级自动检测/挂载 |
AICUPG_MMC_ARTINCHIP | n | 关闭写 MMC 设备 |
AIC_BOOTLOADER_MMC_SUPPORT | y | 保留 MMC 驱动,part 命令可用 |
边界情况:如果 SPI NAND 固件损坏导致 BROM fallback 返回
BD_SDFAT32,bootloader 仍会尝试执行aicupg fat mmc,但会因AICUPG_SDCARD_ENABLE=n而打印sdcard upgrade disabled.并停在命令行(cont_boot=0硬编码,不会启动 app)。只要 SPI NAND 有有效固件就不会触发。
mmcsd 线程栈溢出崩溃(RT_MMCSD_STACK_SIZE=1024 → 8192)
现象:插入 SD 卡后,App 自动检测到卡(SD card capacity 15558144 KB),50MHz 速度切换完成后立即 CPU Exception: NO.1(mcause=1),mepc 低 32 位恰好等于 sp(返回地址被栈数据覆盖)。
根因:RT-Thread mmcsd 框架的 mmcsd_detect 线程栈仅 1024 字节(kernel/rt-thread/components/drivers/sdio/mmcsd_core.c:26),而 drv_sdmc.c 的 aic_sdmc_request() 在栈上通过 ALLOC_CACHE_ALIGN_BUFFER 分配 IDMA 描述符数组。当 data->blks 较多时(如读分区表),叠加 bounce_buffer、编译器栈帧后轻松突破 1024 字节,溢出覆盖返回地址。
修复:scons --menuconfig:
RT-Thread Kernel → Device Drivers → Using SD/MMC device drivers
→ The stack size for mmcsd thread
或直接修改 defconfig:CONFIG_RT_MMCSD_STACK_SIZE=1024 → 8192(与其他 D21x 官方 defconfig 一致)。
验证:修复后日志正常:
[I/SDIO] SD card capacity 15558144 KB.
[I/SDMC] SDMC1, sclk 50000 KHz, clk expt 50000 KHz ...
[I/sdio_mgr] SD Card initialization complete. ← 不再崩溃
5. SDK 修改(一次性)
SDK 有三处修改以支持我们的自定义 bootloader 名称(serdes-boot):
5.1 tools/scripts/aic_build.py 第 124 行 — 在 list 中隐藏 serdes-boot
# zhangpc 20260706: Also filter serdes-boot so only app defconfigs are listed
if noboot and f.endswith('_defconfig') and 'bootloader' not in f and 'serdes-boot' not in f:
5.2 tools/scripts/aic_build.py 第 1589 行 — 打包时复制二进制文件
# zhangpc 20260706: Also match "serdes-boot" to support custom
# SerDes Kit bootloader app image packaging
if prj_kernel == 'baremetal' and ("bootloader" == prj_app or "serdes-boot" == prj_app):
5.3 OneStep 批处理脚本(6 个文件)
tools/scripts/onestep/clean_boot_and_app.bat、make_boot_and_app.bat、make_boot.bat、make_app.bat、app_menuconfig.bat、boot_menuconfig.bat
全部打了相同的补丁 — 当 serdes-boot defconfig 存在时优先使用,否则回退到 bootloader:
rem zhangpc 20260706: Prefer serdes-boot over bootloader for SerDes Kit board
set boot_defconfig=!chip!_!board!_baremetal_bootloader_defconfig
if exist %SDK_PRJ_TOP_DIR%\target\configs\!chip!_!board!_baremetal_serdes-boot_defconfig (
set boot_defconfig=!chip!_!board!_baremetal_serdes-boot_defconfig
)
以及对应的 baremetal_serdes-boot 判断,用于"当前已是 bootloader"的保护逻辑。
6. 板卡配置详解
6.1 时钟树(sys_clk.c)
| 时钟 | 频率 | 来源 |
|---|---|---|
| CPU | 600 MHz | PLL_INT0 |
| AXI0 | 240 MHz | — |
| AHB0 | 240 MHz | — |
| APB0 | 100 MHz | — |
| PLL_INT0 | 600 MHz | — |
| PLL_INT1 | 1200 MHz | — |
| PLL_FRA1 | 491.52 MHz | (音频) |
| PLL_FRA2 | 840 MHz | (显示) |
PLL_FRA0(DDR2/DDR3)被有意跳过 — DDR 由 PBP 在 bootloader 运行前初始化。
6.2 引脚复用(pinmux.c)
D21X 全系列芯片共享同一套 pinmux 寄存器映射。所有 D21X 板卡的 pinmux.c 使用相同的引脚功能码,差异仅在于各板启用的外设不同。demo128-nand 的 pinmux 是最完整版本(227 个条目),其他板是其子集。
| 功能 | 引脚 | 条件编译开关 |
|---|---|---|
| UART0 TX/RX | PA.0 / PA.1 | 始终启用 |
| QSPI0(SPI NAND) | PB.0–PB.5 | AIC_USING_QSPI0 |
| SDMC0(SD 卡) | PB.0–PB.5(与 QSPI0 共享) | AIC_USING_SDMC0 |
所有其他外设(UART1-4、I2C、CAN、显示、音频等)均在 pinmux.c 中存在,但由 #ifdef 保护 — 仅在 defconfig 中启用时才编译。
6.3 DDR3(ddr_init.json)
target/d21x/serdes-nand/pack/ddr_init.json,共 148 行。PBP 启动时读取此文件完成 DDR 控制器的初始化。文件由三大段组成:dram(DDR 参数)、system(系统配置)。
6.3.1 dram — DDR 控制器参数
分为 ddr2 和 ddr3 两个子段,PBP 根据芯片型号自动选择。D211BCX 使用 ddr3 段。
"dram": {
"ddr2": { ... }, // DDR2 参数,D211BB 芯片使用,当前保留兼容
"ddr3": { // ← D211BCX/DCX 使用此段
"type": "0x00000003", // 0x03 = DDR3
"memsize": "0x8000000", // 128MB (0x08000000)
"freq": "600000000", // 600MHz,D211BCX 最高 672MHz,兼容 SDK 所有板
"zq": "0x80005d5d", // ZQ 校准值,决定 IO 驱动强度
"odt": "0x00000000", // ODT (On-Die Termination) 配置
"para1": "0x000030DA", // DRAM 参数寄存器 1(时序、位宽等)
"para2": "0x02000000", // DRAM 参数寄存器 2
}
}
| 字段 | 值 | 说明 |
|---|---|---|
type | 0x03 | DDR 类型:0x02=DDR2, 0x03=DDR3 |
memsize | 0x8000000 | 128MB,PBP 写 DRAM 控制器地址范围 |
freq | 600000000 | 600MHz(芯片标称 672MHz,降频兼容) |
zq | 0x80005d5d | ZQ 阻抗校准,影响 DDR IO 引脚驱动能力 |
odt | 0x00 | ODT 终结电阻,SIP 封装通常关 |
para1 | 0x000030DA | 数据位宽、列地址宽度、BANK 数等 |
para2 | 0x02000000 | AXI 接口配置 |
6.3.2 mr0–mr6 — Mode Register 配置
DDR3 芯片有 7 个模式寄存器,PBP 通过 MRS 命令写入。
| 寄存器 | 值 | 含义 |
|---|---|---|
mr0 | 0x1C70 | CAS Latency=12, Burst Length=8, DLL Reset |
mr1 | 0x0040 | DLL Enable, ODT Rtt_Nom=disabled |
mr2 | 0x0018 | CAS Write Latency=8, Auto Self-Refresh=normal |
mr3 | 0x0000 | MPR (Multi-Purpose Register) 关闭 |
mr4 | 0x0000 | 未使用 |
mr5 | 0x0400 | CA Parity 关闭,ODT 输入缓冲使能 |
mr6 | 0x0848 | VrefDQ 训练范围 = 60.0% |
6.3.3 tpr0–tpr18 — 时序参数寄存器
19 个 Timing Parameter Register,控制 DDR3 PHY 的时序窗口。每个 tpr 对应 DDR 控制器的一个硬件寄存器。
| 关键 tpr | 值 | 控制内容 |
|---|---|---|
tpr0 | 0x0048A192 | tRAS/tRC/tRCD 等基础时序 |
tpr1 | 0x01B1A94B | tRRD/tFAW/tWTR 等 Bank 间时序 |
tpr2 | 0x00061043 | tXP/tCKE/tXSDLL 等 CKE 时序 |
tpr3 | 0x78787896 | DQS 门控训练参数 |
tpr13 | 0x0001FC01 | PHY 训练控制 |
tpr 值由芯片厂商标定,不建议手动修改——除非更换了不同型号的 DDR3 颗粒。本文件来自 demo128-nand,在 D211BCX 和 D211DCX 上多轮启动验证稳定。
6.3.4 system.upgmode — USB 升级模式检测
PBP 在初始化 DDR 后,根据此段检测是否进入 BROM USB 升级模式。
"upgmode": {
"upgmode_pin_cfg_reg": "0x18700080", // PA.0 的 PinMux 寄存器地址
"upgmode_pin_cfg_val": "0x10321", // PA.0 配置为 GPIO 输入
"upgmode_pin_input_reg": "0x18700000", // GPIO 输入数据寄存器
"upgmode_pin_input_msk": "0x1", // Bit[0] 掩码 = 只检测 PA.0
"upgmode_pin_input_val": "0x0", // 期望值 = 0(低电平 = 进入升级模式)
"upgmode_pin_pullup_dly": "500" // 上拉稳定延时 500μs
}
工作流程:PBP 配置 PA.0 为 GPIO 输入 → 延时 500μs 等待上拉稳定 → 读取 0x18700000 的 Bit[0] → 若为 0 则进入 BROM USB 升级,若为 1 则正常启动。
实际串口日志中,PA.0 拉低上电时 PBP 打印
USB,拉高时打印SPINAND,对应此段的检测结果。
6.3.5 system.uart — PBP 调试串口
PBP 阶段的日志通过此段配置的 UART 输出。
"uart": {
"main": {
"uart_id": "0", // UART0
"uart_tx_pin_cfg_reg": "0x18700080", // PA.0 的 PinMux 地址
"uart_tx_pin_cfg_val": "0x325", // 配置为 UART0 TX (Func 5)
"uart_rx_pin_cfg_reg": "0x18700084", // PA.1 的 PinMux 地址
"uart_rx_pin_cfg_val": "0x325" // 配置为 UART0 RX (Func 5)
}
}
文件中注释了 UART1/3/4/5 的替代引脚配置,换板卡时可直接取消注释切换调试串口,无需修改代码。
6.3.6 system.jtag — JTAG 调试引脚
"jtag": {
"jtag_only": "0", // 0=正常启动, 1=PBP 初始化后暂停等 JTAG
"main": {
"jtag_do_pin_cfg_reg": "0x187000A0", // PA.8 → JTAG DO
"jtag_di_pin_cfg_reg": "0x187000A4", // PA.9 → JTAG DI
"jtag_ms_pin_cfg_reg": "0x187000A8", // PA.10 → JTAG MS
"jtag_ck_pin_cfg_reg": "0x187000AC" // PA.11 → JTAG CK
}
}
jtag_only=1时芯片在 PBP 完 DDR 初始化后暂停,等待调试器连接。正常使用保持0。
DDR2 与 DDR3 双配置的兼容性
文件中同时包含 ddr2(504MHz, 64MB)和 ddr3(600MHz, 128MB)两套参数。PBP 根据芯片型号自动选择:
- D211BB → DDR2 段
- D211BC/BCX/DCX → DDR3 段
本配置均从 demo128-nand 复制,在 D211DCX 和 D211BCX 双板验证通过。
6.4 NAND 分区(image_cfg.json)
W25N01GVZEIG:128 MB,页大小 2K,块大小 128K,OOB 64 字节。
| 分区 | 大小 | 内容 |
|---|---|---|
| spl | 1 MB | bootloader.aic |
| env | 256 KB | env.bin |
| env_r | 256 KB | env.bin(冗余) |
| os | 4 MB | d21x_os.itb |
| os_r | 4 MB | d21x_os.itb(冗余) |
| rodata | 12 MB | rodata.fatfs |
| rodata_r | 12 MB | rodata.fatfs(冗余) |
| data | 40 MB | data.fatfs(NFTL) |
| data_r | 40 MB | data.fatfs(NFTL,冗余) |
| refresh | 1 MB | (保留) |
6.5 SPI NAND 性能
QSPI0 接口 100MHz,4 线模式(CLK + CS + D0-D3)。spinand_benchmark 0 实测:
| 操作 | 速度 | 说明 |
|---|---|---|
| 块擦除(128KB) | 237,036 KB/s | 540μs/块 |
| 页写入(2KB) | 3,745 KB/s | 534μs/页 |
| 页读取(2KB) | 7,193 KB/s | 278μs/页 |
结果解读:
- 擦除 237 MB/s:128KB 块擦除仅 540μs,远超 QSPI 带宽——因为擦除只在 NAND 内部完成,不经过 SPI 总线传输,实际命令只有几个字节
- 写入 3.7 MB/s:最慢的操作,瓶颈是 NAND 物理编程时间(每页需向浮栅注入电荷),QSPI 空闲等待
- 读取 7.2 MB/s:与启动日志
spl read: 7180 KB/s完全吻合,瓶颈是 NAND 页读出延迟(约 25μs tR 时间),远未达到 QSPI 400 Mbps 总线上限
三组速度反映的是 NAND 芯片物理特性,不是 QSPI 总线性能。100MHz QSPI 对这个 NAND 带宽充足,无需优化。
spinand_benchmark 0会擦除全片 NAND,测试前需确保已备份或在 bootloader shell 下执行后重新烧录。
测试步骤:
# 1. 上电后按 Ctrl+C 停在 bootloader shell
aic@tinySPL # spinand_benchmark 0
# 2. 等待测试完成(约 2-3 分钟),输出三行关键结果:
# block erase speed: 237036 KB/s
# page write speed: 3745 KB/s
# page read speed: 7193 KB/s
# 3. 重新烧录固件
# aicupg usb 0 → 用 AiBurn 或 aicupg 命令重新写入镜像
spinand_benchmark不带参数只显示帮助,spinand_bbt_benchmark是坏块表性能测试(会标记测试坏块),不要混淆。
6.6 CMA 堆(board.c)
RT-Thread 应用使用 32 MB CMA(CONFIG_AIC_DRAM_CMA_SIZE=0x2000000)作为 memheap 分配器。Bootloader 使用 aic_tlsf_heap。两种模式通过同一个 board.c 中的 #if defined(KERNEL_RTTHREAD) / #elif defined(KERNEL_BAREMETAL) 守卫支持。
6.7 SD 卡测试
Bootloader shell 中通过 part init 1 初始化 SDMC1 控制器,part list 查看分区信息。
测试环境
| 参数 | 值 |
|---|---|
| 控制器 | SDMC1 |
| 总线宽度 | 4-bit |
| 初始时钟 | 400 KHz(识别阶段) |
| 数据传输时钟 | 50 MHz(初始化成功后切换) |
| 分频器变化 | div 2-62 → div 1-0 |
日志中可见两阶段:
clk expt 400 KHz(低速协商)→clk expt 50000 KHz(高速传输)。App 启动时aic_sdmc_clk_init()已预设 50 MHz,part init阶段只做从低速到高速的切换。
8GB 卡(7456 MB)
aic@tinySPL # part init 1
SDMC1 BW 4, sclk 50000 KHz, clk expt 400 KHz(act 403 KHz), div 2-62
[E]mmc_send_if_cond()817 snd cmd failed. ← 首次电压协商失败,正常
[E]mmc_identification()1165 Card did not respond to voltage select!
sdmc 1 init failed. ← 需重试
aic@tinySPL # part init 1 ← 重新初始化
SDMC1 BW 4, sclk 50000 KHz, clk expt 50000 KHz(act 50000 KHz), div 1-0
mmc controller id 1
Capacity 7456 MB ← 识别成功
aic@tinySPL # part list
Start: 0x00100000; Size: 0x1d1e00000; ← 无分区表,裸盘
首次
part init 1失败是正常现象:SD 卡上电后需要电压协商时间,第一次可能超时。必须重试一次即可正常识别。part list读取 MBR 偶尔也报Can't read MBR header,重复执行即可恢复。
16GB 卡(15193 MB)
aic@tinySPL # part init 1
SDMC1 BW 4, sclk 50000 KHz, clk expt 50000 KHz(act 50000 KHz), div 1-0
mmc controller id 1
Capacity 15193 MB ← 一次成功
aic@tinySPL # part list
Start: 0x00400000; Size: 0x3b5580000; ← 无分区表,裸盘
结论
- SDMC1 工作正常,支持 4-bit 50MHz
- 首次
part init可能失败,必须重试一次 - 当前驱动不稳定的读取偶现 MBR 错误,但不影响功能
- 两张卡均为裸 FAT32 格式(无 GPT/MBR 分区表),适合直接
mount
7. 硬件参考
| 参数 | 值 |
|---|---|
| 芯片 | D211BCX(d21x,QFN88,SIP DDR3 128MB) |
| DDR | SIP DDR3 128 MB @ 600 MHz |
| NAND | W25N01GVZEIG(128 MB,页 2K,块 128K,OOB 64) |
| NAND ID | efaa2100(启动时 SPI NAND 驱动自动识别) |
| CPU 时钟 | 600 MHz |
| 控制台 | UART0,115200 baud,8N1 |
| USB | Device 模式(ID 0),控制器为 USB0;无 Host |
| SD 卡 | SDMC1 接口 |
8. 故障排除
构建错误
| 症状 | 原因 | 修复方法 |
|---|---|---|
SyntaxError 路径被截断 | 工作区路径含空格 | 使用 SerDes_Kit 而非 SerDes Kit |
Error in calling command:ls | MSYS 工具不在 PATH 中 | 使用 win_cmd.bat(见第 1 节) |
undefined reference to mtd_probe | 缺少 MTD 裸机驱动 | 在 bootloader defconfig 中添加 CONFIG_AIC_MTD_BARE_TEST=y |
region DRAM_CMA overflowed | CMA 大小未设置 | 在 app defconfig 中添加 CONFIG_AIC_DRAM_CMA_SIZE=0x2000000 |
undefined reference to aic_memheap_malloc | board.c 缺少 memheap 代码 | 确保 board.c 在 #ifdef RT_USING_MEMHEAP 下有 memheap 函数 |
File bootloader.bin is not exist | App 名称不是 “bootloader” | 见第 5.2 节 SDK 修改 |
| 二进制文件过大 | MPP 不必要的启用了 | 在 defconfig 中添加 # CONFIG_LPKG_MPP is not set |
ValueError: unsupported pickle protocol: 4 | .sconsign.dblite 被 Python 3.8 写入后由 Python 2.7 读取 | del /s /q *.sconsign.dblite 然后重新编译 |
UnicodeDecodeError: 'gbk' codec... | defconfig 注释含中文标点或 em dash(—) | 仅使用 ASCII 字符撰写 defconfig 注释 |
AiBurn 烧录 29% 卡住,日志 Dev lost | Bootloader 未编译 USB 升级模块(AICUPG_SUPPORT 默认 n) | bootloader defconfig 添加 CONFIG_AICUPG_SUPPORT=y,见第 10.4 节 |
无害警告
| 警告 | 说明 |
|---|---|
scons: warning: No version of Visual Studio compiler found | SCons 默认检测 MSVC。本项目使用 riscv64-unknown-elf-gcc,不使用 Visual Studio。 |
cc1.exe: warning: is shorter than expected | RISC-V 工具链的内部 GCC 警告。SDK 原始 demo 构建中同样存在。 |
[E]spinand_register_report_bitflip_cb error | 无害。Bootloader 和 App 各自初始化 SPI NAND 时重复注册 refresh 回调,第二次报"已存在"。 |
[E/USB] ep2 ... outepcfg = 0x80088200 | 无害。USB 端点硬件寄存器配置值打印,[E/] 前缀仅表示调试日志,非故障。 |
关于
[E]/[E/XXX]日志前缀:SDK 中许多模块使用[E]标签打印调试信息而非错误,常见的"无害 [E]"包括上述 SPI NAND refresh 重复注册、USB 端点寄存器值打印。判断标准:如果后面跟着正常的功能日志(如Found good block、Burn online successfully!),则前面出现的[E]可忽略。
9. 预期启动输出
烧录后,串口控制台(UART0,115200 8N1)完整输出如下(D211BCX 实测)。
9.1 PBP 阶段
Pre-Boot Program ... (25-08-20 14:55 307a198)
SPINAND
DDR3 128MB
Going to init DDR3. freq: 600MHz
Open Spread Spectrum
DDR3 initialized
43170 58477 154843
PBP done
三组数字是 DDR3 校准结果。D211BCX 实测值稳定在 43170/58477/154843 附近。
9.2 Bootloader 阶段
SerDes Kit Boot [Built on Jul 7 2026 17:33:52]
Reset flag: 0x100
Reset action: Warm-Reset, reason: External-PIN-Reset
qspi0 freq (input): 100000000Hz
qspi0 freq ( bus ): 100000000Hz
[E]spinand_register_report_bitflip_cb()1223 flash report_bitflip_cb exet
[E]spinand_probe()438 spinand_register_report_bitflip_cb error
Selecting default config 'Luban-lite firmware'
spl read: 296008 byte, 40255 us -> 7180 KB/s
CRC32 verify OK.
No config partition
243731 : Run APP
| 输出 | 说明 |
|---|---|
qspi0 freq 100MHz | QSPI0 总线频率,加载速度 ~7 MB/s |
[E]spinand_register... | 无害。Bootloader 和 App 各自初始化 SPI NAND 时重复注册 refresh 回调,第二次报已存在,不影响功能 |
CRC32 verify OK | OS 镜像完整性校验通过 |
No config partition | 未使用 AB 系统分区,使用默认启动配置 |
9.3 RT-Thread App 阶段
_ _ ___ ___ _ _
/ \ _ __| |_|_ _|_ __ / __| |__ (_)_ __
/ _ \ | '__| __|| || '_ \| | | '_ \| | '_ \
/ ___ \| | | |_ | || | | | |__| | | | | |_) |
/_/ \_\_| \__|___|_| |_|\___|_| |_|_| .__/
|_|
Welcome to ArtInChip Luban-Lite 1.3.1 [D21x Inside]
Image version: 1.0.0
Built on Jul 7 2026 17:34:46
[ 0.294] [I]aic_sdmc_clk_init()579 SDMC1 sclk: 50000 KHz, parent clk 600000 KHz
[ 0.301] I/SDMC: SDMC1, sclk 50000 KHz, clk expt 400 KHz(act 403 KHz), div 2-62
[ 0.310] [I]aic_sdmc_probe()832 SDMC1 driver loaded
qspi0 freq (input): 100000000Hz
qspi0 freq ( bus ): 100000000Hz
[ 0.320] [I]spinand_info_read()507 find raw ID efaa2100
[ 0.325] [I]spinand_flash_init()557 Enabled BUF, HWECC. Unprotected.
[ 0.335] I/WDT: ArtInChip WDT loaded
Reset flag: 0x100
Reset action: Warm-Reset, reason: External-PIN-Reset
Startup time: 0.346 sec
SerDes Kit App [Built on Jul 7 2026 17:34:32]
aic /> [ 0.363] I/SDMC: SDMC1, sclk 50000 KHz, clk expt 400 KHz(act 403 KHz), div 2-62
aic />
| 输出 | 说明 |
|---|---|
SDMC1 driver loaded | SD 卡驱动加载成功 |
find raw ID efaa2100 | 识别到 Winbond W25N01GV SPI NAND(ef=Winbond,aa21=W25N01GV) |
WDT loaded | 看门狗已启用 |
Startup time: 0.346 sec | 系统启动耗时 0.346 秒 |
aic /> | Finsh/MSH 交互终端就绪 |
9.4 交互验证
进入 aic /> 后可以执行:
| 命令 | 功能 |
|---|---|
help | 列出所有可用命令 |
ps | 查看线程状态 |
free | 查看内存使用 |
list_device | 列出所有注册的设备 |
reboot | 软复位芯片(复位后 PA.0 接地可进烧录模式) |
进入烧录模式:在 bootloader shell 中按 Ctrl+C 中断自动启动,然后执行
aicupg usb 0即可进入 USB 升级模式(无需 PA.0 接地)。
9.5 两种 Shell 提示符
启动过程中会遇到两种不同的命令行提示符,务必区分:
| 提示符 | 位于 | 进入方式 | 可用功能 |
|---|---|---|---|
aic@tinySPL # | Bootloader Shell | 上电后 Ctrl+C 中断自动启动(阻塞等字符,非固定延时) | 加载 OS、NAND 读写、分区操作、USB 烧录 |
aic /> | RT-Thread Finsh Shell | App 启动完成后自动进入 | 线程管理、内存查看、设备列表、RT-Thread 命令 |
提示符格式来自
baremetal/serdes-boot中的 Tiny Shell 实现。aic为板级前缀,tinySPL为 Shell 名称。D211BCX 和 D211DCX 均实测一致。
Bootloader Shell(aic@tinySPL #)可用命令
| 命令 | 功能 |
|---|---|
help | 列出所有 bootloader 命令 |
nand_boot | 从 NAND 加载 OS 镜像并跳转执行 |
spinand | SPI NAND 读写/擦除/信息查看 |
mtd | MTD 分区读写操作 |
part | 分区表查询 |
aicupg usb 0 | 进入 USB 升级模式(免 PA.0 接地) |
run | 执行启动脚本(等同于自动启动流程) |
RT-Thread Shell(aic />)可用命令
| 命令 | 功能 |
|---|---|
help | 列出所有 RT-Thread 命令 |
ps | 查看所有线程状态 |
free | 查看内存使用 |
list_device | 列出所有注册的设备 |
reboot | 软复位芯片 |
aicupg | 进入 USB 升级模式(自动复位后进入) |
version | 查看 RT-Thread 版本 |
10. 固件烧录
ArtInChip 平台通过 USB Device 接口实现裸机烧录,无需额外烧录器。官方文档(基于 Luban Linux SDK)描述了两阶段升级流程,Luban-Lite 实现方式相同但代码路径不同——本次所有实测均基于 Luban-Lite。
10.1 烧录工具
| 工具 | 方式 | 说明 |
|---|---|---|
| AiBurn(推荐) | GUI,安装即用 | 官方图形工具,自带 USB 驱动,本次 bring-up 首选 |
scons --aicupg | CLI | 调用 tools/scripts/upgcmd.exe(兼容性问题已定位并修复:缺 MinGW 运行时 libgcc_s_dw2-1.dll,从 AiBurn 复制到 tools/scripts/ 后实测可用,2026-08-05) |
10.2 进入 USB 升级模式的三种方式
官方文档指出,进入 BROM 升级模式可通过"检测特定按键"(PA.0 拉低)或"启动失败"自动触发。实际测试发现还有两种软件方式。
| # | 方式 | 操作 | 提示符 | 需要硬件操作 | 适用场景 |
|---|---|---|---|---|---|
| 1 | 硬件触发 | PA.0 接地后上电 | 无 | 是 | 空片首次烧录、bootloader 损坏 |
| 2 | Bootloader 命令 | aicupg usb 0 | aic@tinySPL # | 否 | 正常启动时 Ctrl+C 中断进入(阻塞等待,非固定延时) |
| 3 | App 命令 | aicupg | aic /> | 否 | 系统已运行,最快方式 |
方式一:硬件触发
上电时 PBP 检测 PA.0 电平:拉低 → 进入 BROM USB 模式,PBP 串口打印 USB(而非 SPINAND);悬空/拉高 → 正常从 NAND 启动。
方式二:Bootloader Shell
Ctrl+C 停在 aic@tinySPL #,执行 aicupg usb 0。Bootloader 通过 CherryUSB Gadget 注册 usb_dnl_aicupg 设备,PC 端 AiBurn 自动识别。无需芯片复位。
方式三:RT-Thread App Shell
在 aic /> 直接输入 aicupg(不带参数):
aic /> aicupg
[I]rt_hw_cpu_reset()93 Restarting system ...
Enter upgrade mode by software
芯片软件复位 → PBP 检测到升级标志 → 打印 USB → 进入 BROM USB 模式。这是最快的方式,系统已运行时无需碰硬件。
10.3 烧录流程(两阶段)
官方文档描述完整升级分为两个阶段,Luban-Lite 中的对应实现如下:
阶段一:BROM USB 下载(AiBurn 进度 0% ~ 29%)
BROM 内置的 USB Device 驱动工作,仅支持下载到 RAM 并执行,不支持写入存储介质。PC 工具按 image_cfg.json 定义的 updater 顺序发送两个组件:
[PC] ── usbupg-ddr-init.aic (PBP + DDR参数, ~30KB) ──→ [SRAM 0x00103000]
→ PBP 执行,初始化 DDR 控制器
[PC] ── bootloader.aic (PBP_ext + loader, 300~400KB) ──→ [DRAM 0x41000000]
→ Bootloader 在 DRAM 中执行
PC 与 chip 之间的通信通过标准命令协议完成(GET_HWINFO → SET_FWC_META → SEND_FWC_DATA → …)。传输完成后 BROM 断开 USB,控制权交给 bootloader。
AiBurn 日志中 no CSW 和 no resp 在此阶段正常——因为 BROM 主动断开 USB,PC 侧收不到后续握手包。
阶段二:Bootloader 烧写(29% ~ 100%)
Bootloader 运行后必须重新初始化 USB Device 并以 AICUPG 升级模式枚举。在 Luban-Lite 中,USB 设备通过 CherryUSB 框架实现,升级逻辑位于 application/baremetal/serdes-boot/lib/aicupg/。
成功后 PC 工具识别重连,进入烧写流程:
4. Bootloader 初始化 CherryUSB Device → USB 重连(~1.5s)
5. PC 发送 image.info(芯片型号、存储类型、分区表)
6. PC 发送 SET_FWC_META(各组件元信息)
7. Bootloader 全芯片擦除(128MB NAND,块粒度 256KB)
8. PC 发送各分区数据 → Bootloader 写入对应 NAND 分区
9. 烧录完成 → Bootloader 自动复位
NAND 写入通过 mtd_write() 逐块完成,日志中 Found good block N 表示坏块扫描通过。
29% 卡住判定:
| 日志 | 含义 |
|---|---|
no resp / no CSW | 正常,BROM 阶段收尾 |
Wait to reconnect dev | 等待 bootloader 回连 |
Dev lost!(10s 超时) | 失败:bootloader 未回连 |
Dev reconnected(~1.5s) | 成功:进入阶段二 |
10.4 实测数据
以下数据来自 D211BCX 板实测。
| demo128-nand | serdes-boot(修复前) | serdes-boot(修复后) | |
|---|---|---|---|
| SPL 大小 | 391,440 B | 168,720 B | 316,176 B |
| Bootloader 是否含 USB 升级 | ✅ | ❌ | ✅ |
| USB 重连 | ✅ ~1.5s | ❌ 10s 超时 | ✅ ~1.5s |
| 烧写分区 | 6 个 | — | 4 个 |
| 镜像总大小 | 12.5 MB | 677 KB | 972 KB |
| 烧录结果 | ✅ 成功 | ❌ 29% 卡住 | ✅ 成功 |
方式二实测日志(aicupg usb 0 从 bootloader shell 触发)
aic@tinySPL # aicupg usb 0
Init UPGMODE:
Full disk upgrade
[I/usbd_core] Open ep:0x81 type:2 mps:512 ← IN 端点
[I/usbd_core] Open ep:0x02 type:2 mps:512 ← OUT 端点
UPGMODE: Full disk upgrade
Firmware Component:
name: image.updater.ddr ← BROM 阶段组件(RAM 执行)
attr: required;run
Media: RAM(0)
Firmware Component:
name: image.updater.spl ← Bootloader 组件(RAM 执行)
...
Firmware Component:
name: image.info ← 镜像元信息
...
Firmware Component:
name: image.target.spl ← 写入 spl 分区
partition: spl
attr: mtd;required
Media: SPI_NAND(2)
Found good block 0 ← NAND 坏块扫描
Found good block 1
...
Firmware Component:
name: image.target.os ← 写入 os 分区
...
Going to reboot ... ← 烧录完成
注意:AiBurn 在烧录失败后会自动尝试同目录下的其他
.img文件。本次测试中先失败于我们的镜像(29%),AiBurn 自动尝试了demo128-nand镜像并擦除了整片 NAND。后续烧录前建议清理输出目录。
10.5 Kconfig 陷阱:AICUPG_SUPPORT 默认值
不同 bootloader 的 Kconfig 默认值不同:
| Kconfig 文件 | AICUPG_SUPPORT 默认值 |
|---|---|
application/baremetal/bootloader/Kconfig(demo) | y |
application/baremetal/serdes-boot/Kconfig(我们) | n |
我们在 Kconfig 中改成了 default n 以最小化配置,但这导致 USB 升级模块完全缺失——bootloader 能正常启动(UART 有输出)但永远不会回连 USB。
修复:bootloader defconfig 中加入 CONFIG_AICUPG_SUPPORT=y(AICUPG_USB_ENABLE 在其下默认 y,自动启用)。
教训:"最小化"只应关闭显示/音频等可选外设,升级功能是 bootloader 的核心职责,必须保持开启。
10.6 烧录故障速查
| 症状 | 原因 | 修复 |
|---|---|---|
upgcmd list 无输出 | 缺 MinGW 运行时 libgcc_s_dw2-1.dll(0xC0000135,2026-08-05 实测定位);或板子未进 BROM 模式 | 从 C:\ArtInChip\AiBurn\libgcc_s_dw2-1.dll 复制到 tools/scripts/(本仓库已预置);PA.0 上电前接地 |
AiBurn 29% 卡住,Dev lost | Bootloader 未编译 USB 升级模块 | CONFIG_AICUPG_SUPPORT=y |
no CSW / no resp 日志 | 正常现象 | 忽略,关注后面是否有 reconnected |
scons --aicupg 找到镜像但无动作 | 缺 libgcc_s_dw2-1.dll(upgcmd 启动失败静默退出;os.system 不检查返回值,见 aic_build.py L591);已修复后正常 | DLL 复制到 tools/scripts/ 后实测烧录成功(2026-08-05) |
| AiBurn 自动烧了其他镜像 | 同目录多个 .img,AiBurn 自动重试 | 清理输出目录或手动选择正确镜像 |
10.7 修复后完整烧录日志(2026-08-05 实测)
DLL 修复(§10.6)后,
upgcmd image与scons --aicupg两种方式均实测烧录成功,日志如下。
PC 端烧录工具输出(upgcmd,两阶段):
The Image file: .../output/d21x_serdes-nand_rt-thread_serdes-app/images/d21x_serdes_nand_page_2k_block_128k_v1.0.0.img, size 937472
Upgrade fwc: image.updater.ddr, size 30480 ...
Send the rest 30480
Upgrade fwc: image.updater.spl, size 260624 ...
Send the rest 260624
[ERROR ]: aicupg_trans_recv_pkt()208: CSW size 0, Pipe error(-9) ← 阶段切换,正常
[INFO ]: __do_fwc_upgrade()294: Switching to new stage, please ignore the error message.
Bus:Port 255: connected ← bootloader 重连
Upgrade fwc: image.info, size 2048 ...
Upgrade fwc: image.target.spl, size 260624 ...
Upgrade fwc: image.target.env, size 4096 ...
Upgrade fwc: image.target.env_r, size 4096 ...
Upgrade fwc: image.target.os, size 368640 ...
Burn .../d21x_serdes_nand_page_2k_block_128k_v1.0.0.img successfully!
Used time: 3.3 sec, Speed: 0.27 MB/s
设备端串口关键日志(PBP→bootloader→app 全链路):
Pre-Boot Program ... (25-08-20 14:55 307a198)
USB ← 进入 USB 升级模式
...
SerDes Kit Boot [Built on Aug 5 2026 14:49:09]
Init UPGMODE: Full disk upgrade
Found good block 0~3 ← NAND 坏块扫描
End of upgrading. ← 烧录完成
Going to reboot ... ← 自动复位(无需手动复位)
SPINAND ← 正常从 NAND 启动
SerDes Kit App [Built on Aug 5 2026 14:49:48]
PWM: 4-pos chase starting...
aic /> ← 启动成功
Startup time: 0.774 sec
判定:[ERROR] CSW size 0 / Pipe error(-9) 为阶段切换与设备重启时的正常现象(协议层已处理,见 §10.6 上方判定表);烧录完成后 bootloader 自动复位(Going to reboot),shcmd reset 为冗余操作(此时 USB 通道已关闭,执行会报 Pipe error(-9) EXIT=127,忽略即可)。
11. 附录:关键经验与规范
11.1 源码版权头格式
所有新建或修改的源文件使用以下版权格式:
C 源文件:
/*
* Copyright (c) 2026, Zhang Pengcheng <iamzhangpengcheng@qq.com>
*
* SPDX-License-Identifier: Apache-2.0
*
* Authors: zhangpc
*/
defconfig / Kconfig 文件:
# Copyright (c) 2026, Zhang Pengcheng <iamzhangpengcheng@qq.com>
# SPDX-License-Identifier: Apache-2.0
# Authors: zhangpc
规则:
- 不添加
Based on或Original声明 - 注释与 SDK demo 保持相同详细程度,不过度注释
- 注释使用英文
11.2 构建产物完整清理
scons -c(或 c 命令)仅清除当前工程的 .o 和输出镜像。以下清单需手动删除(完整命令见第 2 节「完全重置」):
| 产物 | 位置 |
|---|---|
output/ | 所有编译输出 |
.log/ | OneStep 构建日志 |
*.sconsign.dblite | SCons 增量编译缓存 |
rtconfig.h, cconfig.h, partition_table.h, d21x_bootloader_gcc.ld | 编译生成的头文件和链接脚本 |
.config, .config.old, .defconfig, .Kconfig.prj | Kconfig 配置状态 |
packages/third-party/mbedtls/ports/src/tls_certificate.c | mbedTLS 编译生成 |
target/d21x/serdes-nand/pack/.image_cfg.json.tmp | bootloader 编译生成(Kconfig 宏展开) |
target/d21x/serdes-nand/pack/bootloader.bin | bootloader 编译生成 |
11.3 AI Agent 上下文
以下内容供 AI 辅助编程工具(Qoder、Claude、Cursor 等)快速恢复项目上下文。
# SerDes Kit — AI Agent Context
## Project Identity
- Chip: D211BCX (d21x, QFN88, SIP DDR3 128MB @ 600MHz)
- NAND: W25N01GVZEIG (128MB, page 2K, block 128K, OOB 64), ID efaa2100
- Console: UART0, 115200 8N1
- USB: Device mode (USB0), no Host
- SD card: SDMC1
## Build
- Launch: double-click win_cmd.bat (NOT env.bat)
- VS Code: open integrated terminal (auto-injects via .vscode/settings.json)
- Quick: lunch <num> then m (builds bootloader + app)
- Clean all: rmdir /s /q output & del *.sconsign.dblite ... (see sec.2)
- Toolchain: V3.2.0 (explicit in both defconfigs; demo uses default V2.6.1)
### AI Build Environment (for non-win_cmd.bat shells)
When the AI agent builds from a bash/zsh shell (NOT win_cmd.bat), the SCons POST_ACTION `cp *.pbp` fails because the system `cp` cannot expand the glob. The fix is to inject the same PATH that `win_cmd.bat` sets:
```bash
export SDK_PRJ_TOP_DIR="$PWD"
export ENV_ROOT="$PWD/tools/env"
export PKGS_ROOT="$ENV_ROOT/packages"
export RTT_ROOT="$PWD/kernel/rt-thread"
export PYTHONIOENCODING=UTF-8
export PATH="$PWD/tools/env/tools/bin:$PWD/tools/env/tools/Python38:$PWD/tools/scripts/onestep:$PATH"
Then compile the full project (equivalent to m):
# Bootloader
scons --apply-def=d21x_serdes-nand_baremetal_serdes-boot_defconfig && scons -j8
# App
scons --apply-def=d21x_serdes-nand_rt-thread_serdes-app_defconfig && scons -j8
Key insight: tools/env/tools/bin/cp (MSYS cp) handles Windows glob patterns; bash’s /usr/bin/cp does not. The MSYS cp must appear first in PATH.
Defconfigs
- Bootloader: target/configs/d21x_serdes-nand_baremetal_serdes-boot_defconfig
- App: target/configs/d21x_serdes-nand_rt-thread_serdes-app_defconfig
Key Configs
- AICUPG_SUPPORT=y MUST be set in bootloader (our Kconfig defaults to n)
- MTD_BARE_TEST=y required for baremetal NAND boot
- DRAM_CMA_SIZE=0x2000000 required for RT-Thread app
- Use # CONFIG_xxx is not set for explicit disable (not deletion)
- Defconfig comments MUST be ASCII-only (GBK encoding)
- Display (DE/LVDS/RGB) explicitly disabled: no display hardware
- USB1 explicitly disabled: no physical host port
Flashing
- AiBurn (GUI) recommended over CLI (upgcmd.exe has issues)
- PA.0 low at power-on = BROM USB mode (hardware entry)
- Bootloader shell (aic@tinySPL #): aicupg usb 0 (software entry, no reset)
- RT-Thread shell (aic />): aicupg (fastest, auto-reset into BROM mode)
- 29% freeze = AICUPG_SUPPORT not enabled in bootloader
Two Shell Prompts
- aic@tinySPL # = Bootloader shell (Ctrl+C to interrupt, blocking read, no fixed timeout)
- aic /> = RT-Thread Finsh shell (after app boots)
Log Convention
- [E] prefix in serial logs often = DEBUG info, NOT errors
- Common harmless [E]: spinand_register_report_bitflip_cb (duplicate callback),
USB outepcfg register print (hardware config dump) - Key to judgment: if followed by normal output the [E] is harmless
SDK Patches (7 files)
- aic_build.py: line 124 (hide serdes-boot from list), line 1589 (copy binary)
- tools/scripts/onestep/*.bat (6 files): prefer serdes-boot over bootloader
Project Layout
- Board: target/d21x/serdes-nand/ (based on demo128-nand, minimalized)
- Bootloader: application/baremetal/serdes-boot/
- App: application/rt-thread/serdes-app/
- Doc: 02_BRING_UP.md
346

被折叠的 条评论
为什么被折叠?



