匠芯创 D211 SerDes Kit — Bring-Up 指南(AI开发笔记)

匠芯创 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.12026-07-06zhangpc初始 bring-up:板卡配置、bootloader、app、defconfigs、OneStep 脚本,Windows 编译验证通过
v0.22026-07-07zhangpc启动链全链路文档化;image_cfg.json 七段解析;固件烧录(三种升级模式+两阶段流程+AICUPG_SUPPORT 陷阱+实测数据);D211BCX 串口输出完整记录;Shell 提示符确认与命令速查;工具链 V3.2.0;Git pull 工作流规则;AI 上下文补全
v0.32026-07-08zhangpcddr_init.json 七段分层解析(DDR 参数+Mode Register+时序参数+升级检测+JTAG);与源码一致性校验修正(Ctrl+C 阻塞等待非 2 秒超时、DRAM 基址表述修正);SPI NAND 基准性能实测
v0.42026-07-09zhangpcSD 卡双卡 bring-up(8GB+16GB,4-bit 50MHz,首次 init 重试策略,速度协商两阶段 400KHz→50MHz)
v0.52026-07-14zhangpcVS Code 终端 PowerShell 与 win_cmd.bat 不兼容问题:根因分析、两版 PowerShell 实测验证(5.1 + 7)、两种修复方式
v0.62026-07-16zhangpcSD 卡升级关闭 + 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.72026-08-03zhangpcAI 编译环境注入: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.82026-08-05zhangpc烧录 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、分区布局
  • Bootloaderapplication/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 Codewin_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 7pwsh.exe,来自 Microsoft Store明确拒绝,报 .ps1 扩展名错误64
Windows PowerShell 5.1powershell.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.8tools\env\tools\Python38
Python 2.7tools\env\tools\Python27\Scripts(旧脚本用)
ENV_ROOTtools\env
RTT_ROOTkernel\rt-thread

OneStep 命令(官方推荐工作流)

命令功能
list列出所有应用 defconfig(bootloader 自动隐藏,由系统自动匹配)
lunch通过编号或完整名称加载 defconfig(如 lunch 23lunch xxx_defconfig
m / mb编译 bootloader + app(一键命令)
mu / ms仅编译 bootloader
ma仅编译 app
c清理 bootloader 和 app
mc清理后重新编译
me / kmapp 的 menuconfig
bmbootloader 的 menuconfig
iscons --info(当前项目信息)

工具链选择

SDK 自带两套 RISC-V 工具链:

工具链Kconfig 标识路径
V2.6.1(默认)PRJ_RV_TOOLCHAIN_V2_6_1toolchain/bin/
V3.2.0PRJ_RV_TOOLCHAIN_V3_2_0toolchain/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 → 生成最终镜像。

输出文件

项目镜像
Bootloaderoutput/d21x_serdes-nand_baremetal_serdes-boot/images/bootloader.aic
RT-Thread Appoutput/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)
  • 职责
    1. 初始化 DDR 控制器(根据 ddr_init.json 中的时序参数和频率)
    2. 配置升级模式检测引脚(PA.0 拉低 = 进入 USB 刷机模式)
    3. 配置 JTAG 调试引脚(PA.8–PA.11)
    4. 从 NAND 中加载 SPL 到 DRAM,校验后跳转执行
  • 运行位置:SRAM(此时 DDR 尚未初始化完成),初始化 DDR 后切换到 DRAM
4.2.3 SPL(Secondary Program Loader,二级程序加载器)
  • 位置:存储在 NAND 的 spl 分区,紧接 PBP 之后
  • 运行的是d21x.bin 的早期初始化阶段(即 Bootloader 本体的一部分)
  • 二进制封装bootloader.aic 中的 loader.aic 部分(由 image_cfg.json 定义组装规则)
  • 职责
    1. 初始化更多硬件(PLL、时钟树、更多外设)
    2. 从 NAND 中读取完整的 bootloader 到 DRAM
    3. 校验 bootloader 完整性(CRC / 签名)
    4. 跳转到 bootloader 入口地址(CONFIG_AIC_BOOTLOADER_TEXT_BASE,即 0x47B80100
  • 和 PBP 的关系:PBP 是最小化的硬件初始化(仅 DDR),SPL 在 DDR 可用的前提下做更多初始化。两者共同构成 bootloader.aic 镜像
4.2.4 Bootloader(serdes-boot,裸机引导程序)
  • 位置:存储在 NAND 的 spl 分区,由 SPL 加载到 DRAM 运行
  • Kernelbaremetal(无 RTOS,直接操作硬件寄存器)
  • 源码application/baremetal/serdes-boot/main.c
  • defconfigd21x_serdes-nand_baremetal_serdes-boot_defconfig
  • 职责
    1. 初始化 UART0 控制台(115200 8N1)
    2. 显示 banner:"SerDes Kit Boot [Built on ...]"
    3. 进入交互式 shell(aic /> 提示符),可用命令包括:
      • nand_boot — 从 NAND 加载 OS 镜像到 DRAM 并跳转
      • spinand — SPI NAND 读写/擦除
      • mtd — MTD 分区操作
      • part — 分区表查询
    4. 收到 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 命令加载
  • Kernelrt-thread(RTOS 内核)
  • 镜像文件d21x_os.itb(FIT Image Tree Blob,包含内核 + device tree + 可选 ramdisk)
  • 源码application/rt-thread/serdes-app/main.c
  • defconfigd21x_serdes-nand_rt-thread_serdes-app_defconfig
  • 职责
    1. RT-Thread 内核初始化(调度器、内存管理、设备框架)
    2. 调用 main() 打印 "SerDes Kit App [Built on ...]"
    3. 进入 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 各阶段运行文件对照
阶段运行文件来源生成方式配置文件
PBPd21x.pbpSDK 预编译,bsp/artinchip/sys/d21x/无需编译,mk_image.py 直接复制ddr_init.json
SPL + Bootloaderbootloader.aic编译 serdes-boot 源码scons 编译 → objcopy → AIC 包装 → 与 PBP 拼接,共 6 步bootloader defconfigimage_cfg.json
Appd21x_os.itb编译 serdes-app 源码scons 编译 → elf_parse.py 生成 .itsmk_image.py 打包app defconfigimage_cfg.json
4.3.2 Bootloader 镜像生成流水线(d21x.binbootloader.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/*.cd21x.elfriscv64-unknown-elf-gccC 源码编译 + 链接,按链接脚本(d21x_bootloader_gcc.ld)指定内存布局。编译哪些 .c 文件由 Kconfig/defconfig 决定🔧 板卡配置决定
2. DDR 参数转换ddr_init.jsonddr_init.binmk_private_resource.pyJSON 配置转为二进制格式,供 PBP 和 loader 使用。DDR 类型(DDR3)、频率(600M)、容量(128M) 从 ddr_init.json 读取🔧 板卡配置决定
3. 去头d21x.elfd21x.binobjcopy -O binary剥离 ELF 头、节区表、符号表,保留纯机器码。所有板都一样✅ 所有板卡相同
4. 拷贝d21x.binbootloader.binaic_build.py 自动脚本复制到 pack/ 目录,供 mk_image.py 读取。aic_build.py 通过命名匹配决定路径(我们的 SDK 补丁就在这里)🔧 板卡配置决定
5. AIC 包装bootloader.bin + ddr_init.binloader.aicmk_image.pyaicimage包裹 AIC 头(加载地址/入口点从 Kconfig 取值),嵌入 DDR 参数(用于热复位场景)🔧 板卡配置决定
5b. AIC 包装d21x.pbp + ddr_init.binpbp_ext.aicmk_image.pyaicimagePBP 预编译二进制 + DDR 参数(ddr_init.json 转换)。内嵌的 ddr_init.bin 是板卡专属🔧 板卡配置决定
6. 拼接pbp_ext.aic + loader.aicbootloader.aicconcatenate按顺序拼接两个 AIC 文件为单一镜像。拼接顺序对所有板都一样✅ 所有板卡相同
App 编译serdes-app/*.cd21x_os.itbsconself_parse.pymk_image.py编译 + 生成 .its + 打包为 FIT Image。内核/外设配置全部由 serdes-app 的 defconfig 决定🔧 板卡配置决定
最终 .img各分区镜像*.imgmk_image.py分区布局、镜像组件列表由 image_cfg.json 决定(product: "serdes_nand"),文件名包含 NAND 几何参数🔧 板卡配置决定
4.3.3 涉及文件一览
文件类型说明
d21x.pbpSDK 预编译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。具体机制:

  1. aic_build.py 扫描 target/configs/ 下所有 {chip}_{board}_baremetal_*_defconfig
  2. 排除 serdes-boot(我们在第 5.1 节打的补丁)和 bootloader
  3. 第一个匹配的 baremetal defconfig 自动作为 bootloader 编译
  4. 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-nandimagepre-process

4.5.1 spi-nand — 存储设备与分区定义

定义 NAND flash 的物理参数和分区布局,每个分区有明确的用途。

分区大小用途
spl1 MB存放 Bootloader 镜像(bootloader.aic),芯片上电后 ROM/PBP/SPL 从这里加载
env256 KBU-Boot 环境变量 A 区,存储启动参数(AB 系统选择、bootlimit 等)
env_r256 KBU-Boot 环境变量 B 区(冗余备份),A 区损坏时自动切换
os4 MBRT-Thread OS 镜像 A 区(d21x_os.itb),正常启动时加载
os_r4 MBOS 镜像 B 区(冗余备份),升级失败时自动回退到此处
rodata12 MB只读数据 A 区(字体、图片等静态资源),挂载为 FATFS
rodata_r12 MB只读数据 B 区(冗余备份)
data40 MB用户数据 A 区,通过 NFTL 管理(坏块替换+磨损均衡),挂载为 FATFS
data_r40 MB用户数据 B 区(冗余备份)
refresh1 MBNAND 刷新元数据区,记录各块刷新状态,防止长期不刷新导致读干扰

分区总计约 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_rrodata/rodata_rdata/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 参数
        ]
    }
}

platformproductversion 三个字段直接决定最终 .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此组件可选,文件不存在时跳过不报错

写入规则

  • splos 标记为 required——缺一不可,否则系统无法启动
  • envenv_r 写入同一个 env.bin 文件——env_r 是其冗余副本。env.bin 内容来自 pre-process.uboot_env 步骤
  • rodata(只读数据)和 data(用户数据)是 optional——当前最小配置下 .fatfs 文件尚未构建(无文件系统镜像),打包时自动跳过
  • data 使用 block 属性(NFTL),其他分区使用 mtd(直接写)——因为 data 分区需要坏块管理和磨损均衡

AB 冗余分区的自动处理:分区列表定义了 os_rrodata_rdata_r 三个冗余分区,但它们没有对应的 target 条目——mk_image.py 会根据 AB 系统环境变量自动将同一个文件写入 A 和 B 两套分区。同时还有 refresh 分区(1MB),存储 NAND 刷新元数据,防止同一位置长期不刷新导致读干扰。

最小配置下的实际写入:当前 defconfig 未启用文件系统镜像和 rodata/data 构建,因此打包时实际写入的分区为:

分区文件状态
splbootloader.aicrequired — 必须存在
env / env_renv.binoptional — 存在,写入
osd21x_os.itbrequired — 必须存在
rodata / rodata_roptional — 跳过
data / data_roptional — 跳过
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.aicUSB 升级:仅初始化 DDR❌ 仅 PBP + DDR 参数
pbp_ext.aic拼接组件:PBP + DDR❌ 仅 PBP + DDR 参数
loader.aic拼接组件:Bootloader 本体✅ bootloader.bin

load addressentry 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=5osAB_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
字段来源当前值
platforminfo.platformd21x
productinfo.productserdes_nand
versioninfo.version1.0.0
pagemedia.array_organization[0].page2k
blockmedia.array_organization[0].block128k

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 的处理流程:

  1. defconfig 复制为 .config
  2. defconfig.py 检查 Kconfig 依赖:depends on 不满足的强制改 n
  3. defconfig 未写的配置取 Kconfig default
  4. select 链传导:你开了 A,A 的 select B 自动把 B 设为 y
  5. 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_ENABLEAIC_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_ENABLEn关闭 SD 卡升级自动检测/挂载
AICUPG_MMC_ARTINCHIPn关闭写 MMC 设备
AIC_BOOTLOADER_MMC_SUPPORTy保留 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=10248192

现象:插入 SD 卡后,App 自动检测到卡(SD card capacity 15558144 KB),50MHz 速度切换完成后立即 CPU Exception: NO.1mcause=1),mepc 低 32 位恰好等于 sp(返回地址被栈数据覆盖)。

根因:RT-Thread mmcsd 框架的 mmcsd_detect 线程栈仅 1024 字节kernel/rt-thread/components/drivers/sdio/mmcsd_core.c:26),而 drv_sdmc.caic_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

或直接修改 defconfigCONFIG_RT_MMCSD_STACK_SIZE=10248192(与其他 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.batmake_boot_and_app.batmake_boot.batmake_app.batapp_menuconfig.batboot_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

时钟频率来源
CPU600 MHzPLL_INT0
AXI0240 MHz
AHB0240 MHz
APB0100 MHz
PLL_INT0600 MHz
PLL_INT11200 MHz
PLL_FRA1491.52 MHz(音频)
PLL_FRA2840 MHz(显示)

PLL_FRA0(DDR2/DDR3)被有意跳过 — DDR 由 PBP 在 bootloader 运行前初始化。

6.2 引脚复用(pinmux.c

D21X 全系列芯片共享同一套 pinmux 寄存器映射。所有 D21X 板卡的 pinmux.c 使用相同的引脚功能码,差异仅在于各板启用的外设不同。demo128-nand 的 pinmux 是最完整版本(227 个条目),其他板是其子集。

功能引脚条件编译开关
UART0 TX/RXPA.0 / PA.1始终启用
QSPI0(SPI NAND)PB.0–PB.5AIC_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 控制器参数

分为 ddr2ddr3 两个子段,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
    }
}
字段说明
type0x03DDR 类型:0x02=DDR2, 0x03=DDR3
memsize0x8000000128MB,PBP 写 DRAM 控制器地址范围
freq600000000600MHz(芯片标称 672MHz,降频兼容)
zq0x80005d5dZQ 阻抗校准,影响 DDR IO 引脚驱动能力
odt0x00ODT 终结电阻,SIP 封装通常关
para10x000030DA数据位宽、列地址宽度、BANK 数等
para20x02000000AXI 接口配置
6.3.2 mr0mr6 — Mode Register 配置

DDR3 芯片有 7 个模式寄存器,PBP 通过 MRS 命令写入。

寄存器含义
mr00x1C70CAS Latency=12, Burst Length=8, DLL Reset
mr10x0040DLL Enable, ODT Rtt_Nom=disabled
mr20x0018CAS Write Latency=8, Auto Self-Refresh=normal
mr30x0000MPR (Multi-Purpose Register) 关闭
mr40x0000未使用
mr50x0400CA Parity 关闭,ODT 输入缓冲使能
mr60x0848VrefDQ 训练范围 = 60.0%
6.3.3 tpr0tpr18 — 时序参数寄存器

19 个 Timing Parameter Register,控制 DDR3 PHY 的时序窗口。每个 tpr 对应 DDR 控制器的一个硬件寄存器。

关键 tpr控制内容
tpr00x0048A192tRAS/tRC/tRCD 等基础时序
tpr10x01B1A94BtRRD/tFAW/tWTR 等 Bank 间时序
tpr20x00061043tXP/tCKE/tXSDLL 等 CKE 时序
tpr30x78787896DQS 门控训练参数
tpr130x0001FC01PHY 训练控制

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 字节。

分区大小内容
spl1 MBbootloader.aic
env256 KBenv.bin
env_r256 KBenv.bin(冗余)
os4 MBd21x_os.itb
os_r4 MBd21x_os.itb(冗余)
rodata12 MBrodata.fatfs
rodata_r12 MBrodata.fatfs(冗余)
data40 MBdata.fatfs(NFTL)
data_r40 MBdata.fatfs(NFTL,冗余)
refresh1 MB(保留)

6.5 SPI NAND 性能

QSPI0 接口 100MHz,4 线模式(CLK + CS + D0-D3)。spinand_benchmark 0 实测:

操作速度说明
块擦除(128KB)237,036 KB/s540μs/块
页写入(2KB)3,745 KB/s534μs/页
页读取(2KB)7,193 KB/s278μ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-62div 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)
DDRSIP DDR3 128 MB @ 600 MHz
NANDW25N01GVZEIG(128 MB,页 2K,块 128K,OOB 64)
NAND IDefaa2100(启动时 SPI NAND 驱动自动识别)
CPU 时钟600 MHz
控制台UART0,115200 baud,8N1
USBDevice 模式(ID 0),控制器为 USB0;无 Host
SD 卡SDMC1 接口

8. 故障排除

构建错误

症状原因修复方法
SyntaxError 路径被截断工作区路径含空格使用 SerDes_Kit 而非 SerDes Kit
Error in calling command:lsMSYS 工具不在 PATH 中使用 win_cmd.bat(见第 1 节)
undefined reference to mtd_probe缺少 MTD 裸机驱动在 bootloader defconfig 中添加 CONFIG_AIC_MTD_BARE_TEST=y
region DRAM_CMA overflowedCMA 大小未设置在 app defconfig 中添加 CONFIG_AIC_DRAM_CMA_SIZE=0x2000000
undefined reference to aic_memheap_mallocboard.c 缺少 memheap 代码确保 board.c#ifdef RT_USING_MEMHEAP 下有 memheap 函数
File bootloader.bin is not existApp 名称不是 “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 lostBootloader 未编译 USB 升级模块(AICUPG_SUPPORT 默认 n)bootloader defconfig 添加 CONFIG_AICUPG_SUPPORT=y,见第 10.4 节

无害警告

警告说明
scons: warning: No version of Visual Studio compiler foundSCons 默认检测 MSVC。本项目使用 riscv64-unknown-elf-gcc,不使用 Visual Studio。
cc1.exe: warning: is shorter than expectedRISC-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 blockBurn 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 100MHzQSPI0 总线频率,加载速度 ~7 MB/s
[E]spinand_register...无害。Bootloader 和 App 各自初始化 SPI NAND 时重复注册 refresh 回调,第二次报已存在,不影响功能
CRC32 verify OKOS 镜像完整性校验通过
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 loadedSD 卡驱动加载成功
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 ShellApp 启动完成后自动进入线程管理、内存查看、设备列表、RT-Thread 命令

提示符格式来自 baremetal/serdes-boot 中的 Tiny Shell 实现。aic 为板级前缀,tinySPL 为 Shell 名称。D211BCX 和 D211DCX 均实测一致。

Bootloader Shell(aic@tinySPL #)可用命令
命令功能
help列出所有 bootloader 命令
nand_boot从 NAND 加载 OS 镜像并跳转执行
spinandSPI NAND 读写/擦除/信息查看
mtdMTD 分区读写操作
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 --aicupgCLI调用 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 损坏
2Bootloader 命令aicupg usb 0aic@tinySPL #正常启动时 Ctrl+C 中断进入(阻塞等待,非固定延时)
3App 命令aicupgaic />系统已运行,最快方式
方式一:硬件触发

上电时 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 CSWno 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-nandserdes-boot(修复前)serdes-boot(修复后)
SPL 大小391,440 B168,720 B316,176 B
Bootloader 是否含 USB 升级
USB 重连✅ ~1.5s❌ 10s 超时✅ ~1.5s
烧写分区6 个4 个
镜像总大小12.5 MB677 KB972 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=yAICUPG_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 lostBootloader 未编译 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 imagescons --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 onOriginal 声明
  • 注释与 SDK demo 保持相同详细程度,不过度注释
  • 注释使用英文

11.2 构建产物完整清理

scons -c(或 c 命令)仅清除当前工程的 .o 和输出镜像。以下清单需手动删除(完整命令见第 2 节「完全重置」):

产物位置
output/所有编译输出
.log/OneStep 构建日志
*.sconsign.dbliteSCons 增量编译缓存
rtconfig.h, cconfig.h, partition_table.h, d21x_bootloader_gcc.ld编译生成的头文件和链接脚本
.config, .config.old, .defconfig, .Kconfig.prjKconfig 配置状态
packages/third-party/mbedtls/ports/src/tls_certificate.cmbedTLS 编译生成
target/d21x/serdes-nand/pack/.image_cfg.json.tmpbootloader 编译生成(Kconfig 宏展开)
target/d21x/serdes-nand/pack/bootloader.binbootloader 编译生成

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值