我接到了一个GD32F503的zephyr RTOS移植任务,通过几天的努力,已经基本实现了,现在总结如下:
目录
1. 参考了哪些文档(source-of-truth 清单)
移植过程中引用为权威依据的文档,按可获取性排:
1.1 厂商原始资料(vendor)
| D:\GD\gd32F503\docs\GD32F50x_用户手册_Rev1.4.pdf(829 页) | Ground truth:内存映射、时钟树、Cortex-M33 中断表、AF SEL 范围、PLL 公式 | §阶段 2 时钟配置、阶段 3 DTS、阶段 5 linker |
| D:\GD\gd32F503\GD32F50x_Demo_Suites_V1.0.4\...\ | Ground truth:哪些 pin/AF 在实际 demo 被用过(vendor 自己保证「能跑」) | 阶段 4 pinctrl YAML、附录 B pin 速查表 |
| D:\GD\gd32F503\GD32F50x_Demo_Suites_V1.0.4\...\Utilities\gd32f503v_eval.{h,c} | Ground truth:板子 LED/KEY/COM 完整定义 | 阶段 4 board DTS |
| D:\GD\gd32F503\GD32F50x_Firmware_Library_V1.0.3\...\GD32F50x_Firmware_Library\ | Ground truth:SPL CMSIS、标准外设库 | 阶段 1 HAL 拷贝;gd32f50x.h 里 _BASE 系列宏被 Bug #1 引用为真值 |
| D:\GD\gd32F503\GD32F50x_Firmware_Library_V1.0.3\...\CMSIS\GD\GD32F50x\Source\Templates\gd32f503xK.ld | Ground truth:F503 启动代码、链接脚本 MEMORY 段 | 阶段 5 linker |
| 板子原理图(在 Demo Suites V1.0.4 Docs/Schematic/ 下) | Ground truth:哪些 pin 接 LED、按键、COM | LED 极性 → Bug #3 衍生 |
1.2 Zephyr 既有移植参考
| D:\luglZephyrproject\zephyr\soc\gd\gd32\gd32f4xx\ | F4xx SOC 层:拿来套 F50x(同厂商兄弟) |
| D:\luglZephyrproject\zephyr\dts\arm\gd\gd32f4xx\ | F4xx DTS:拿来抄 RCU 时钟 ID 宏 |
| D:\luglZephyrproject\zephyr\boards\gd\gd32f450i_eval\ | F450I EVAL 板:拿来套 GD32F503V-EVAL(同封装档) |
| D:\luglZephyrproject\zephyr\drivers\gpio\gpio_gd32.c | 上游 GPIO driver:Bug #2 / Bug #3 全部在这里发生 |
| D:\luglZephyrproject\zephyr\subsys\shell\Kconfig、Kconfig.backends | shell 启动顺序、DEVICE_DT_GET 模板 |
| D:\luglZephyrproject\zephyr\include\zephyr\sys\util_macro.h | BIT macro 定义(Bug #4) |
| D:\luglZephyrproject\zephyr\include\zephyr\dt-bindings\clock\gd32f50x-clocks.h | 自己写的 clock ID 表,对照 demo suites 写对 |
1.3 工具
| Zephyr SDK | D:\luglZephyrproject\zephyr-sdk-1.0.1\zephyr-sdk-1.0.1 | west build / arm-zephyr-eabi-* 工具链 |
| west | 1.5.0 | west build -b <board> . |
| dtc | Zephyr SDK 自带 | DTS 编译检查 |
| SEGGER Ozone | V3.50 | 故障 halt + 反汇编 + 外设寄存器 Watch |
| J-Link | — | 烧录 + 调试;board_runner_args(jlink "--device=GD32F503RE") |
| PuTTY / Tera Term / minicom | — | Zephyr shell 串口交互 |
2. 文件全图(provenance)
2.1 复制的文件(vendor SPL / Demo Suites → Zephyr HAL)
原则:Zephyr HAL 是 vendor 库的「翻译层」。vendor 头文件是 ground truth,我们不改 vendor 源码——HAL 层通过 #include <gd32f50x.h> 直接接入,但所有改动都集中在 compat shim 里(gd32f50x_compat.h)。
| D:\GD\gd32F503\GD32F50x_Firmware_Library_V1.0.3\...\Firmware\CMSIS\GD\GD32F50x\Include\ | modules/hal/gigadevice/gd32f50x/cmsis/gd/gd32f50x/Include/ | 4 个 .h(gd32f50x.h, core_cm33.h, system_gd32f50x.h, gd32f50x_err_report.h) |
| D:\GD\gd32F503\GD32F50x_Firmware_Library_V1.0.3\...\Firmware\CMSIS\GD\GD32F50x\Source\ | modules/hal/gigadevice/gd32f50x/cmsis/gd/gd32f50x/Source/ | gd32f50x_err_report.c, system_gd32f50x.c |
| 同上 Source\GCC\ | 同上 Source\GCC\ | SPL 启动汇编(SPL 内核启动代码,我们不用作 Zephyr 的 Reset_Handler——Zephyr 自带 vector_table.S) |
| 同上 Source\GCC\Ld\ | 同上 Source\GCC\Ld\ | F50x linker scripts(参考用,实际 board 用的 linker 由 Zephyr template 生成) |
| D:\GD\gd32F503\GD32F50x_Firmware_Library_V1.0.3\...\Firmware\GD32F50x_standard_peripheral\Include\ | modules/hal/gigadevice/gd32f50x/standard_peripheral/Include/ | 27 个 外设头(gd32f50x_gpio.h, gd32f50x_usart.h, ...) |
| 同上 Source\ | 同上 Source/ | 27 个 外设 .c 实现 |
| D:\GD\gd32F503\GD32F50x_Firmware_Library_V1.0.3\...\Firmware\GD32F50x_usbfs_library\ | 未拷 | USB FS 库,这次不需要(CONFIG_USB=n) |
| D:\GD\gd32F503\GD32F50x_Demo_Suites_V1.0.4\...\Utilities\ | 只参考不拷 | gd32f503v_eval.{h,c} 内 LED/KEY/COM 定义在 board DTS 里重新写(不能直接 include 它,那是 STM32-style HAL) |
口子提示:vendor 头文件里 BIT / REG32 / BITS 三大基础设施宏如果直接拷过来而不加 #ifndef 保护,会和 Zephyr util_macro.h 撞名,→ Bug #4。这次手动加了 #ifndef BIT。
2.2 改写的 vendor 源(patch)
原则:vendor 源码尽量不动。但 GCC 在某些版本 / 配置下做静态分析会误报,而 SPL 厂商的代码写得很「乐观」(默认所有路径都被覆盖,不写 default: 初值)。这两类「误报且无人调用」的函数,可以直接 patch。
| modules/hal/gigadevice/gd32f50x/cmsis/gd/gd32f50x/include/gd32f50x.h | 218 | BIT(x) 加 #ifndef BIT ... #endif 保护 | Zephyr util_macro.h:36-46 已经先 define 了 BIT,没保护会撞名 |
| modules/hal/gigadevice/gd32f50x/standard_peripheral/source/gd32f50x_rcu.c | 1264 | cksys_freq, ahb_freq, apb1_freq, apb2_freq, pll0_freq 加 = 0U | -Wmaybe-uninitialized 警告(Bug #5) |
| modules/hal/gigadevice/gd32f50x/standard_peripheral/source/gd32f50x_rcu.c | 1265 | pll0sel, pll1sel, prediv0, pll0mf, prediv1, pll1mf, ck_src, idx, clk_exp 加 = 0U | 同上 |
| modules/hal/gigadevice/gd32f50x/cmsis/gd/gd32f50x/source/system_gd32f50x.c | 966 | ck_src, idx, clk_exp 加 = 0U | -Wmaybe-uninitialized(Bug #6) |
| modules/hal/gigadevice/gd32f50x/cmsis/gd/gd32f50x/source/system_gd32f50x.c | 969 | pll0sel, pll1sel, prediv0, pll0mf, prediv1, pll1mf, pll0div 加 = 0U | 同上 |
可重复:这些 patch 都是「加了等号 0U」这种纯增量改动,可以用一个 shell 脚本一次性给所有 GD 系列 HAL 应用。
2.3 新生成的文件
按层划:
HAL 模块层(modules/hal/gigadevice/gd32f50x/)| include/gd32f50x_compat.h | compat shim:屏蔽 F4xx vs F50x 在 SPL API 上的差异(单/双 CTL、AFIO/SYSCFG、有/无 toggle register) |
| pinconfigs/gd32f503xx.yml | F503 系列 pinmux AF 表,从 demo suites 实际用法 + 手册 §11 整理 |
| Kconfig | SOC 系列定义(SOC_SERIES_GD32F50X、SOC_GD32F503)+ select GD32_HAS_AFIO_PINMUX 等 |
| Kconfig.soc | SOC 子选项(默认 CPU / 工具链等) |
| Kconfig.defconfig.gd32f503 | 时钟频率默认(252 MHz)、IRQ 数(88)、NUM_IRQ_PRIO_BITS=4 |
| gd32_regs.h | 寄存器偏移 + 桥基址的统一描述(下面有详细注释) |
| soc.c / soc.h | SystemInit、clock 初始化钩子、SOC_EARLY_INIT_HOOK / SOC_RESET_HOOK |
| gd32f50x.dtsi | 通用:cpus 节点、armv8-m 包含、外设基址骨架 |
| gd32f503.dtsi | F503 全部外设节点:RCU、GPIOA-E、USART0/1/2、SPI0/1/2、I2C0/1、TIMER0/1..15/16、ADC0/1/2、DAC、EXTI、FMC、SYSCFG、AFIO、TRIGSEL、CTC、CMP、PMU、RTC、WWDGT/FWDGT、CAN0/1、USBFS、CAU/HAU/TRNG、DMA0/1、DMAMUX |
| gd32f503xk.dtsi | 1MB flash + 128KB SRAM(最大存储变体) |
| gd32f503xe.dtsi | 512KB flash + 64KB SRAM |
| gd32f503xc.dtsi | 256KB flash + 64KB SRAM |
| Kconfig.board | BOARD_GD32F503V_EVAL 入口 |
| Kconfig.gd32f503v_eval | Board 内部 Kconfig 钩子 |
| board.cmake | 烧录器相关(J-Link 配 GD32F503RE) |
| board.yml | Twister 元数据 |
| gd32f503v_eval_defconfig | board 默认 Kconfig 集合 |
| gd32f503v_eval.dts | 板上 LED / 按键 / chosen (zephyr,console = &usart0) |
| gd32f503v_eval-pinctrl.dtsi | USART0/I2C0/SPI0/PWM7 等 pinmux 映射 |
| support/post_build.cmake | 烧录钩子 |
| prj.conf | 用户级开关:CONFIG_GPIO=y、CONFIG_SERIAL=y、CONFIG_CONSOLE=y、CONFIG_UART_CONSOLE=y、CONFIG_SHELL=y 等 |
| src/main.c | 三灯流水 + printk 自检 |
| boards/gd32f503v_eval.overlay | 用户 overlay(当前为空) |
| D:\GD\gd32F503\zephyt_docs\GD32F503_Zephyr_Porting_Plan.md | 移植前写的方案 |
| D:\GD\gd32F503\zephyt_docs\GD32F503_Actual_Porting_Report.md | 移植后的事后报告 |
| D:\GD\gd32F503\zephyt_docs\GD_Zephyr_Port_Recipe.md | 本文:文件溯源 + 移植 recipe |
3. 完整目录树
D:\luglZephyrproject\ # Zephyr 根目录 ├── zephyr\ # Zephyr 主仓(不可改 vendor 上游) │ ├── modules\hal_gigadevice\ │ └── gd32f50x\ # 新增 HAL 模块 │ ├── module.yml # Zephyr HAL 模块描述(name: hal_gigadevice) │ │ │ ├── cmsis/gd/gd32f50x/ # ← 整包从 SPL V1.0.3 拷过来 │ │ ├── Include/ │ │ │ ├── gd32f50x.h # ← 最权威的 _BASE 真值表 │ │ │ ├── core_cm33.h │ │ │ ├── system_gd32f50x.h │ │ │ └── gd32f50x_err_report.h │ │ └── Source/ │ │ ├── gd32f50x_err_report.c │ │ ├── system_gd32f50x.c # ← patch: `= 0U` 一处(行 966/969) │ │ ├── ARM/startup_gd32f50x.s # SPL 自带启动代码(参考用,不启用) │ │ ├── GCC/ │ │ │ ├── startup_gd32f50x.S # SPL 启动代码 │ │ │ ├── newlib/syscalls.c │ │ │ └── Ld/*.ld # F50x linker scripts │ │ │ ├── standard_peripheral/ # ← 27 个外设,从 SPL V1.0.3 拷过来 │ │ ├── Include/ │ │ │ ├── gd32f50x_gpio.h # Bug #2 / #3 关键源 │ │ │ ├── gd32f50x_usart.h │ │ │ ├── gd32f50x_rcu.h # Bug #1 关键源 │ │ │ ├── gd32f50x_fmc.h │ │ │ ├── gd32f50x_adc.h │ │ │ └── ... 共 27 个 │ │ └── Source/ │ │ ├── gd32f50x_gpio.c │ │ ├── gd32f50x_usart.c │ │ ├── gd32f50x_rcu.c # ← patch: `= 0U` 两处 │ │ └── ... 共 27 个 │ │ │ ├── include/ │ │ └── gd32f50x_compat.h # ★ 自写 compat shim(**Bug #2 + #3 主战场**) │ │ │ └── pinconfigs/ │ └── gd32f503xx.yml # ★ 自写 pinctrl AF 表 │ ├── zephyr\soc\gd\gd32\gd32f50x\ # 新增 SOC 层 │ ├── Kconfig # ★ 自写 │ ├── Kconfig.soc # ★ 自写 │ ├── Kconfig.defconfig.gd32f503 # ★ 自写 │ ├── Kconfig.defconfig.series # ★ 自写 │ ├── gd32_regs.h # ★ 自写(RCU / FMC / GPIO 等 offset + 桥基址) │ ├── soc.h # ★ 自写 │ └── soc.c # ★ 自写(SystemInit 钩子) │ ├── zephyr\dts\arm\gd\gd32f50x\ # 新增 DTS 层 │ ├── gd32f50x.dtsi # ★ 自写(armv8-m + cpus) │ ├── gd32f503.dtsi # ★ 自写(所有外设节点) │ ├── gd32f503xk.dtsi # ★ 自写(1MB flash) │ ├── gd32f503xe.dtsi # ★ 自写(512KB flash) │ └── gd32f503xc.dtsi # ★ 自写(256KB flash) │ └── zephyr\boards\gd\gd32f503v_eval\ # 新增 board 层 ├── Kconfig.board # ★ 自写 ├── Kconfig.gd32f503v_eval # ★ 自写 ├── board.cmake # ★ 自写 ├── board.yml # ★ 自写 ├── gd32f503v_eval_defconfig # ★ 自写(默认 y Kconfig 集合) ├── gd32f503v_eval.dts # ★ 自写(LED + KEY + chosen) ├── gd32f503v_eval-pinctrl.dtsi # ★ 自写(USART0/I2C0/SPI0/PWM7 pinmux) ├── doc/index.rst └── support/post_build.cmake D:\GD\gd32F503\ # 用户本地工程目录 ├── GD32F50x_用户手册_Rev1.4.pdf # 厂商资料(**最高优先级 ground truth**) ├── GD32F50x_Firmware_Library_V1.0.3\ # 厂商 SPL 源码(**被拷到 HAL 模块的根**) ├── GD32F50x_Demo_Suites_V1.0.4\ # 厂商参考 demo(**板子 pin 速查 ground truth**) └── zephyt_docs\ # 移植相关文档 ├── GD32F503_Zephyr_Porting_Plan.md # 移植**前**写的方案 ├── GD32F503_Actual_Porting_Report.md # 移植**后**的事后报告 └── GD_Zephyr_Port_Recipe.md # ★ 本文:溯源 + recipe D:\luglZephyrproject\app\gd32f503_blink\ # 用户应用目录 ├── CMakeLists.txt ├── prj.conf ├── src/main.c ├── boards/ │ └── gd32f503v_eval.overlay # 当前为空 ├── build/ # west build 产物(不入版本库) └── docs/ └── gd32f503_zephyr_port.md # bug 详细技术文章(482 行)
图例:
<- 整包拷 是从 SPL 复制
★ 自写 是新写的文件
← patch 是改写的 vendor 源
4. 移植 recipe:给下一个 GD 芯片用的 checklist
这一节不绑死 F503;任何 GD32 系列(GD32F3x0 / GD32F4xx / GD32F50x / GD32E5x / GD32A50 / GD32L2x / GD32VF103)都可以拿这个流程走。
4.0 决定要不要新加 HAL vs 用兄弟 HAL
先看看已有 HAL 是否覆盖:
modules/hal/gigadevice/ ├── common_include/ # 跨系列共用 HAL 抽象(gd32_gpio.h 等) ├── gd32f3x0/ # F3x0 ├── gd32f4xx/ # F4xx ├── gd32f50x/ # F50x(**这次新建**) ├── gd32a50x/ # A50x ├── gd32e10x/ # E10x ├── gd32e50x/ # E50x ├── gd32f403/ # F403 ├── gd32l23x/ # L23x └── gd32vf103/ # VF103
如果新芯片是已有 HAL 的兄弟系列(比如 GD32F5xx 已经存在,那 GD32F503 就是它的子系列)→ 在原 HAL 里加子目录即可,不要新开 HAL 模块。
如果芯片架构 / 工具链差异大(比如 riscv-v 替代 arm)→ 新开 HAL。
4.1 移植前准备(spawn check)
4.1.1 必备文档| 用户手册 / Reference Manual | 厂商官网 + 内部 D:\GD\...docs\ | 第一 |
| SPL Vx.x / Standard Peripheral Library | 厂商官网 + 内部 SDK | 第一 |
| Demo Suites Vx.x | 厂商官网 + 内部 SDK | 第一 |
| 板子原理图 | 内部硬件组 | 第一 |
| Zephyr 主仓代码 | git clone 整套 | 第二 |
| Zephyr 同厂商的兄弟 HAL | zephyr/modules/hal/gigadevice/gd32*/ | 第二 |
| Zephyr 通用移植文档 | docs.zephyrproject.org | 第三 |
Zephyr SDK(推荐 1.0+),west CLI, SEGGER Ozone + J-Link(或厂商调试器 + pyOCD), 串口终端(PuTTY/Tera Term/minicom), DTS 编译器 dtc(SDK 自带), objdump(arm-zephyr-eabi-* 在 SDK 内), grep + 文本编辑器(有 LSP 的体验翻倍), Node / Python(用来现场算 offset 比手算快得多)。4.1.3 信息表(在动 DTS 之前一定先填)
| ARM core | 用户手册 §2.1 | Cortex-M3 / M4 / M33 / M7 → 哪个 armvX-m.dtsi |
| FPU 是否有 | 用户手册 §2.1 + SPL 启动 .fpu | 不确定就 不 开 |
| TrustZone | 用户手册 §2 | 开不开关系到 Kconfig 选 CPU_HAS_FPU 还是 TZ |
| AHB / APB 桥基址 | SPL 头 <chip>.h 的 _BUS_BASE | DT 一切地址计算的真值表 |
| APB1 / APB2 子基址 | 同上 | 用于选 AF 编号时知道哪个桥接 |
| 总 SRAM 起始 | 用户手册 §1.3 + SPL 启动代码 | SRAM0 = 0x20000000 几乎所有 STM32/GD 如此 |
| Flash 总容量(变体) | SPL linker scripts | xk/xe/xc 多版本 |
| 中断向量数(IRQ 数) | 用户手册 §3 + SPL 头 IRQn enum | Kconfig.defconfig 里 NUM_IRQS |
| IRQ 优先级 bit 数 | 用户手册 §3.1 | NUM_IRQ_PRIO_BITS |
| AF 范围 | 用户手册 §11 GPIO+AF | F4xx AF0~15,F50x 收窄到 AF0~8 |
| GPIO 寄存器布局 | 用户手册 §11 + SPL 头 | 单 CTL 还是双 CTL(影响 compat shim) |
| EXTI 源选择寄存器 | 用户手册 §3 + SPL 头 | 用 SYSCFG 还是 AFIO,区别非常大 |
| Toggle register 是否有 | 用户手册 §11 | F4xx 有 GPIO_TG,F50x 没有 |
这一步必须在动 DTS/compat shim 之前做完。否则你就跟这次的 Bug #1 / #2 / #3 一样,改了好几轮才发现「哦原来 AHB1 桥基址不是 0x40010000」。
4.2 5 层结构 + 拷贝顺序
| ① | HAL cmsis/ | modules/hal/gigadevice/<chip>/cmsis/... | 整包拷 vendor |
| ② | HAL standard_peripheral/ | modules/hal/gigadevice/<chip>/standard_peripheral/... | 整包拷 vendor |
| ③ | HAL compat shim + pinconfigs | 同上 | 自写 |
| ④ | SOC | zephyr/soc/gd/gd32/<chip>/ | 自写 |
| ⑤ | DTS | zephyr/dts/arm/gd/gd32/<chip>.dtsi | 自写(按 § 4.3 做) |
| ⑥ | Board | zephyr/boards/gd/<board>/ | 自写 |
| ⑦ | Application | app/<app>/ | 用户自写 |
| ⑧ | Debug | Ozone + J-Link | 第一次 boot 验证 |
4.3 DTS 编写清单(按步骤)
1. cpus 节点: - arm core(M-profile) - clock-frequency = <手册 §9 的 CK_SYS> - #address-cells / #size-cells - mpu 子节点(Cortex-M33 需要) 2. chosen 节点: - zephyr,sram = &sram0; - zephyr,flash = &flash0; - zephyr,console = &usart0 (或别的) - zephyr,shell-uart = &usart0 (启用 shell 才加) - zephyr,flash-controller = &fmc; 3. 通用桥节点: - rcu、gpioa/b/c/..、usart0/1、SPI0/1/2、I2C0/1、timer0..16、adc0/1/2、dac、 fmc、syscfg、exti、afio、trigsel、ctc、cmp、pmu、rtc、wwdgt/fwdgt、 can0/1、usbfs、cau/hau/trng、dma0/1、dmamux 4. 每个节点的 reg:必须从 vendor SPL `<chip>.h` 里 _BASE 真值表查 ‐ 反例:注释手算 `AHB1 + 0x9000 = 0x40019000`,却忘了 AHB1 桥基址是 0x40018000 不 0x40010000 → 整族外设基址全错 (本次 Bug #1) 5. memory@<ORIG> 节点 + sizes(flash / sram 全容量) - 不同变体(xk/xe/xc)用 #include 区分 .dtsi
4.4 必过:3 件实事验证「能跑」
Plan 阶段只验证 build pass,这是不够的。首轮 boot 实物验证要在第 1 周内发生,不然接下来几周都会在猜。
| LED 跑一次(哪怕只是常亮) | gpio_pin_configure_dt + gpio_pin_set 一轮 | 验证 DTS 把 GPIO 节点变成可访问的 struct device* |
| USART0 跑一次 console | 让 Zephyr 的 printk 落到 PC 端串口 | 验证:USART clock、pinctrl AF、TX 路径 |
| Ozone 能 halt + 看寄存器 | halt 后读 chip 关键寄存器(RCU.APBxEN、GPIO.CTL、USART.SR) | 验证 JTAG/SWD 不被大电流芯片干扰 |
任何一件过不了,回去查 DTS 里的 reg 地址——10 次有 9 次是这里错。
4.5 7 个常见坑 + 自检 checklist
这些是 F503 移植亲历 的 7 个 bug。下一个 GD 移植一开始就要把它们存档对一遍。
| 1 | DT 里 AHB1 桥基址手算 | 首轮 boot BusFault,halt 在 z_arm_bus_fault,PC 在 clock_control_* 或 pinctrl_apply_state 里 | DT 的每个外设 reg 都对照 vendor <chip>.h _BASE 真值表,不要靠 hand-computed 注释 |
| 2 | compat shim 双 deref | UsageFault UNALIGNED,PC 在 GPIO 配置函数里 | &(*X) == X 一定是单 deref lvalue;不要 (uint32_t)(cast) 包一层 |
| 3 | driver 假设 F4xx 寄存器布局 | 跑起来了但部分 pin 配置失败(pin=8 或 15 跳到 base 位 0..1) | 在 Ozone 里看 GPIO.CTL 那些 pin 的 mode 字段,是不是 OUT_PP |
| 4 | vendor 头缺 #ifndef 保护基础设施宏 | warning: "BIT" redefined | grep vendor 头里的 BIT / REG32 / BITS,加保护 |
| 5,6 | SPL vendor 源 -Wmaybe-uninitialized | 3 个警告一起出 | 通常是 switch-case 没覆盖,runtime 不调用即可;但为了清 build,加 = 0U 即可 |
| 7 | shell 提示符有但输入无响应 | 提示符来,输入不来 | 强制 CONFIG_SHELL_BACKEND_SERIAL_API_POLLING=y 绕开中断 |
4.6 验证矩阵(按 demo 对应)
| GPIO 输出 | #01 GPIO_Running_LED | 三盏 300ms 切换 |
| GPIO 输入(轮询) | #02 GPIO_Key_Polling | 按键按下读出 0 / 松开读出 1 |
| EXTI 中断 | #03 EXTI_Key_Interrupt | 按键触发 ISR |
| USART TX | #04 USART_Printf | PC 端看到 printk 输出 |
| USART RX(IRQ) | #05 USART_Echo_Interrupt | 串口回显 |
| USART TX(DMA) | #06 USART_DMA | 大块数据吞吐 |
| ADC | #07 ADC_Temperature_Vrefint | 内部温度读数 |
| I2C | #12 I2C_EEPROM | 24C02 写读一致 |
| SPI | #13 SPI_Quad_Flash | W25Q 写读一致 |
| PMU 休眠 | #20 PMU_Sleep_Wakeup | Stop → RTC / WKUP 唤醒 |
| RTC | #21 RTC_Calendar | 秒计数走起 |
| PWM | #22 TIMER_Breath_LED | PC7 渐变可见 |
| USB | #23 USB_FS | 枚举成功 |
每次过一个,把 ✅ 填进 plan § 7.2 的验收清单。
4.7 五个角色的工具脚本
4.7.1 拷 SPL(自动化拷贝)# 给新 chip 时拷 SPL 的标准脚本。
# 跑完会输出:<src> -> <dst> 行清单
SRC=$1 # 比如 D:/GD/gd32a50x/AHPL_V1.0.3
DST=$2 # 比如 modules/hal/gigadevice/gd32a50x
CHIP=$3 # 比如 gd32a50x
if [ -z "$SRC" ] || [ -z "$DST" ] || [ -z "$CHIP" ]; then
echo "Usage: $0 <src-root> <dst-root> <chip-name>"
exit 1
fi
# CMSIS
mkdir -p "$DST/cmsis/gd/$CHIP"/{Include,Source/GCC/Ld}
cp -v "$SRC/Firmware/CMSIS/GD/$CHIP/Include/"*.h "$DST/cmsis/gd/$CHIP/Include/"
cp -v "$SRC/Firmware/CMSIS/GD/$CHIP/Source/"*.c "$DST/cmsis/gd/$CHIP/Source/"
[ -d "$SRC/Firmware/CMSIS/GD/$CHIP/Source/ARM" ] && \
cp -v "$SRC/Firmware/CMSIS/GD/$CHIP/Source/ARM/"*.s "$DST/cmsis/gd/$CHIP/Source/ARM/" 2>/dev/null
[ -d "$SRC/Firmware/CMSIS/GD/$CHIP/Source/GCC" ] && \
cp -rv "$SRC/Firmware/CMSIS/GD/$CHIP/Source/GCC/" "$DST/cmsis/gd/$CHIP/Source/GCC/"
# Standard Peripheral
mkdir -p "$DST/standard_peripheral"/{Include,Source}
cp -v "$SRC/Firmware/${CHIP//a50x/A50x}_standard_peripheral/Include/"*.h \
"$DST/standard_peripheral/Include/" 2>/dev/null \
|| cp -v "$SRC/Firmware/${CHIP^^}_standard_peripheral/Include/"*.h \
"$DST/standard_peripheral/Include/" 2>/dev/null \
|| find "$SRC/Firmware/" -type d -name '*standard_peripheral*' -exec \
cp -v {}/Include/*.h "$DST/standard_peripheral/Include/" \;4.7.2 vendor 头#ifndef` 巡检脚本这个脚本不通用(路径名跟 GD32 系列 / SPL 版本绑得太死),但思路通用:先 find 找到 SPL 根,然后 cp -v 拷贝到对应位置、打日志、留 verbatim 行便于审计。
#!/usr/bin/env bash
# grep vendor 头里同 Zephyr util_macro.h 撞名的宏
# 命中后要看是不是要加 #ifndef 保护
set -e
HEADER_DIR=${1:?usage: $0 modules/hal/gigadevice/<chip>/cmsis/gd/<chip>/Include}
for sym in BIT BITS BIT_MASK REG32 REG16 REG8; do
hits=$(grep -E "^#define\s+${sym}\b" "$HEADER_DIR"/*.h | head -3)
if [ -n "$hits" ]; then
echo "==> $sym:"
echo "$hits"
fi
done4.7.3 Ozone Watch 启动套装(按 SOC 类别选)(M Series Cortex-M, GD/SPL 系列) /* === 时钟门 === */ APBxEN_BASE + 0x? ; (RCU APBxEN,per 手册 §9) /* === 控制寄存器 === */ GPIO_BASE + 0x00 (CTL/IMODE) ; 模式 GPIO_BASE + 0x14 (OCTL) ; 输出态 GPIO_BASE + 0x10 (ISTAT) ; 输入态 USART_BASE + 0x08 (CTL1) ; bit13 UE = 启用? USART_BASE + 0x00 (SR) ; bit5 RXNE / bit6 TC4.7.4 dtc 编译检查(DTS 语法)
dtc -I dts -O dtb -o /dev/null path/to/your.dts 2>&1 | tee dts.log
4.8 失败模式示例(失败先查这几件)
| west build 直接不识别 board | SOC_GD32XXX=y 在 defconfig 里?Kconfig.board 里 BOARD_<X> = y? |
| 编译通过但 .elf 没产出 zephyr.bin | linker script 出错;查 build/zephyr/linker.cmd.dep |
| 烧完完全不跑(halt 看 PC = 0x00000000 或 magic number) | reset 流程没跑通;查 __start 里的 soc_reset_hook |
| BusFault halt 在 pinctrl_apply_state 入口 | pinctrl 节点没绑对,pin 在 reg 跑出范围 |
| halt 在 clock_control_* 函数里 | DTS AHB/APB 桥基址错(Bug #1 那一类) |
| halt 在 GPIO 配置函数里、UFSR UNALIGNED bit 1 | compat shim 写错(Bug #2 那一类) |
| Pin 8-15 配置不到 OUT mode(mode 字段始终是输入态) | driver 上游假设 F4xx 双 CTL 寄存器(Bug #3 那一类);加 #ifdef AF_PINMUX select |
| shell 提示符有,但 help 没反应 | CONFIG_SHELL_BACKEND_SERIAL_API_POLLING=y;或调 SHELL_BACKEND_SERIAL_INIT_PRIORITY |
5. 本次可复用的脚本 / 模板
把这次踩的坑沉淀成 3 段 shell:
5.1 拷 SPL 到新 HAL 的模板(占位)
见 § 4.7.1。
5.2 vendor 头补丁自动加保护(占位)
见 § 4.7.2。TODO:扫 vendor 头里所有可能被 Zephyr 利用的宏(BIT, BITS, REG32, REG16, REG8, WRITE_BIT, …),批量加 #ifndef。
5.3 SPL -Wmaybe-uninitialized 自动化补丁(占位)
每个 vendor SPL 源文件有几个函数是「runtime 不会被调」的(SystemCoreClockUpdate、rcu_clock_freq_get 等)。写一个脚本:
grep 文件中的局部变量声明
对每个声明加 = 0U(如果是裸 uint32_t xxx)
风险:不是所有声明都要 patch——只 patch 被编译器 warn 的。所以是 interactive 工作,半自动最合理。
5.4 工具脚本位置建议
app/<your_app>/tools/ ├── copy_spl.sh # § 4.7.1 ├── vendor_header_audit.sh # § 4.7.2 ├── maybe_uninit_patch.sh # § 5.3 ├── ozone_watch_template.txt # § 4.7.3 └── dts_check.sh # § 4.7.4
附录 A:Zephyr 4.x symbol 改名词典
这些在 prj.conf 写错时不会立刻报「symbol not found」,而是 Kconfig warnings 然后升级成 error 让 west build 失败。提前知道,省半小时查 Kconfig grep。
| CONFIG_KERNEL_SHELL_CMD | CONFIG_KERNEL_SHELL | subsys/shell/modules/kernel_service/Kconfig |
| CONFIG_DEVICE_SHELL_CMD | CONFIG_DEVICE_SHELL | subsys/shell/modules/Kconfig |
| CONFIG_DATE_SHELL_CMD | CONFIG_DATE_SHELL | subsys/shell/modules/Kconfig |
| CONFIG_DEVMEM_SHELL_CMD | CONFIG_DEVMEM_SHELL | subsys/shell/modules/Kconfig |
| CONFIG_APP_VERSION_SHELL_CMD | CONFIG_APP_VERSION_SHELL | subsys/shell/modules/Kconfig |
| CONFIG_ZYCLICTEST_SHELL_CMD | CONFIG_ZYCLICTEST_SHELL | subsys/shell/modules/Kconfig |
| CONFIG_GPIO_SHELL_CMD | CONFIG_GPIO_SHELL | drivers/gpio/Kconfig |
使用方法:在 prj.conf 写一个新的 Kconfig 时,先 grep 一遍 subsys/shell/Kconfig* 等目标位置:
grep -rE "^config [A-Z_]+$" zephyr/subsys/shell/modules/ | head
把名字摸熟再写。
附录 B:Ozone Watch 通用模板(按 SOC 类别选地址)
第一次到一块新板子,给自己 6 个 Watch 就够定位 80% 的「寄存器层故障」。
B.1 STM32 / GD32 通用(按手册 §9「Memory map」+ §11 GPIO 章节定地址)
╔══════════════════════════════════════════════════════════════╗ ║ Watch 一栏(按 SOC 类别) ║ ╠══════════════════════════════════════════════════════════════╣ ║ Cortex-M + AHB/APB1/APB2 标准桥: ║ ║ APBxEN_BASE ; RCU_APBxEN 看 clock 门 ║ ║ GPIOA_BASE + 0x00 ; GPIOx.CTL 模式 ║ ║ GPIOx_BASE + 0x14 ; GPIOx.OCTL 输出态 ║ ║ USART0_BASE + 0x08 ; USARTx.CTL1 bit13 UE ║ ║ USART0_BASE + 0x00 ; USARTx.SR bit5 RXNE ║ ║ ║ ║ 不同桥基址按手册 §1.4 memory map 替换: ║ ║ GD32F50x: AHB1=0x40018000, APB2=0x40010000, APB1=0x40000000 ║ ║ STM32F4xx: AHB1=0x40020000, APB2=0x40010000, APB1=0x40000000 ║ ║ GD32F4xx: AHB1=0x40020000, APB2=0x40010000, APB1=0x40000000 ║ ╚══════════════════════════════════════════════════════════════╝
B.2 故障定位映射
| LED 不亮 1(pin mode 没设好) | GPIO.CTL mode bits |
| LED 不亮 2(clock 没开) | RCU.APBxEN bit |
| LED 不亮 3(active 极性错) | OCTL bit + 看板子「亮态」 |
| 串口不输出 1 | USART.CTL1 bit13 UE |
| 串口不输出 2 | USART.CTL1 bit14 TE |
| 串口不输出 3 | GPIO.CTL/OMODE pin AF 模式 |
| 串口输入无反应 | USART.SR bit5 RXNE,每次按键应该跳变 |
【验证】
编译sapmle\blink,已经成功的运行了流水灯:

我要赚赏金
