烧录与调试:probe-rs、RTT 与日志查看
本篇是《Ariel OS 深度实战:Rust 库式 IoT 操作系统全解析》系列第 6 篇。上一篇:板卡描述体系:boards yaml 与结构化配置,下一篇:日志系统:defmt 与 ariel-os-log,目录见文末。
烧录与调试:probe-rs、RTT 与日志查看
导读
编译产物只是一个 ELF 文件。要让它真正跑起来,需要把字节写进 flash;要让它「可观察」,需要一条从目标到主机的数据通道。这两件事构成了嵌入式开发的内循环,也是新手最容易卡住的地方:烧录失败、日志乱码、attach 上去什么都没有、GDB 连不上。
Ariel OS 用同一套 laze 任务抽象了这两件事:run、flash、reset、attach、debug、flash-erase-all。但同名任务在不同板卡上由不同工具实现:nRF/STM32/RP 走 probe-rs,ESP32 默认走 espflash。理解这个映射,比记住命令更重要。
这一篇以 ESP32-C6-DevKitC-1 为主线,同时对照 nRF52840-DK,把四条链路讲清楚:调试接口协议(JTAG/SWD)、烧录路径(调试口 vs bootloader)、调试通道(RTT vs semihosting)、日志传输(USB/UART/RTT)。最后给一套「烧不进去 / 连不上 / 没输出」的排查顺序。
为什么需要理解烧录链路
ELF 不等于固件
cargo build 产出的是 ELF:带符号表、调试信息、多个 section,按链接脚本布局。它不能直接丢进 flash 就完事,原因有三:
- 地址映射:ELF 里的虚拟地址必须和芯片的 flash 映射对上,烧录工具要知道哪些段写到哪个偏移;
- 启动头:ESP32 需要 bootloader 和分区表;STM32 可能需要向量表偏移;nRF 需要 SoftDevice 或 bootloader(如果用);
- 校验与复位:写完要校验,然后让芯片从复位向量重新开始,而不是停在调试器暂停的状态。
烧录工具就是干这三件事的。probe-rs 通过调试接口直接写内存/闪存;espflash 通过 ESP 的 ROM bootloader 走串口/USB。两条路径的能力和适用场景完全不同。
调试接口协议:JTAG 与 SWD
JTAG 是老牌标准,四/五根线(TCK、TMS、TDI、TDO、可选 TRST),协议通用,所有厂商都能用。它最初的目的其实是边界扫描(boundary scan),用来检查 PCB 焊接,后来才成为主流的调试接口。
SWD(Serial Wire Debug)是 ARM 定义的 JTAG 精简版,两根线(SWCLK、SWDIO),只支持 ARM 核。线少、速度快、占用引脚少,所以 Cortex-M 板子基本都用 SWD。
ESP32-C6 是个例外:它是 RISC-V,没有 ARM 的 SWD,但 Espressif 在芯片里集成了 USB-Serial-JTAG 外设,直接用 USB 口说 JTAG 协议。所以 C6 上 probe-rs 要 --protocol=jtag,而 nRF52840 是 swd。这个差异写在芯片 context 里:
- name: esp32c6
env:
PROBE_RS_CHIP: esp32c6
PROBE_RS_PROTOCOL: jtag
nRF 的 context 不覆盖 PROBE_RS_PROTOCOL,于是用 ariel-os 上下文里的默认值 swd。
调试探针:为什么需要一个小盒子
主机没有 JTAG/SWD 接口,所以需要一个「探针」把 USB 转成调试协议。常见的有:
- CMSIS-DAP:ARM 定义的标准,很多板载调试器(比如 nRF52840-DK 的 J-Link OB、ST-LINK)都兼容或支持;
- J-Link:SEGGER 的商业探针,性能好,价格高;
- ST-LINK:ST 板载,只能调试 STM32;
- 板载 USB-Serial-JTAG:ESP32-C6/C3/S3 等芯片内置,插一根 USB 线就能调试,不需要额外探针。
Ariel OS 统一用 probe-rs 跟这些探针打交道。probe-rs 支持 CMSIS-DAP、J-Link、ST-LINK、ESP 的 USB-Serial-JTAG 等,所以同一套命令能覆盖大部分板子。
两条烧录路径的取舍
| 维度 | 调试口(probe-rs) | Bootloader(espflash / DFU) |
|---|---|---|
| 需要额外硬件 | 是(除非芯片内置) | 否(USB/串口即可) |
| 速度 | 通常较快,可分段校验 | 取决于 bootloader 和波特率 |
| 能力 | 烧录、复位、读内存、RTT、GDB | 只能烧录/复位 |
| 自动化 | 容易(命令行参数稳定) | 需要处理端口、握手、下载模式 |
| 适用 | 开发阶段首选 | 产线、无调试口、OTA 前置 |
Ariel OS 的选择逻辑是:有调试口就用 probe-rs,没有就用 bootloader 工具。ESP32 的 context 默认 ?espflash,因为 ESP 的 ROM bootloader 足够可靠且不需要探针;要 RTT 或 GDB 时,显式 --select probe-rs 切过去。
调试通道:RTT 与 semihosting
调试接口除了读写内存,还能提供一条「调试通道」,让目标把数据传给主机。两种实现:
Semihosting:目标触发一个特定异常(通常是 breakpoint),把操作码和参数放进寄存器,调试器捕获后代替目标执行。它本质是「远程系统调用」,能读文件、写控制台、退出进程。缺点是极慢:每次操作都要停一次核、等主机响应。Ariel OS 不用它做日志传输,只用它实现 exit()。
RTT(Real Time Transfer):在目标内存里放一个控制块和若干环形缓冲区,目标只管往缓冲区写,调试探针在后台读。速度快、不打断 CPU,适合高频日志。缺点是需要探针支持(probe-rs 支持),且缓冲区满时会丢数据或阻塞。
Ariel OS 默认把 RTT 配成非阻塞 trimming 模式:缓冲区满了就丢新数据,绝不阻塞执行。probe-rs attach 时会把它切成阻塞模式,避免丢日志;但如果之后异常断开而没有恢复模式,目标可能会卡在写日志上。这是 RTT 调试的经典陷阱。
代码案例:在 C6 上烧录、附加、调试
案例一:默认路径(espflash)
# 编译 + 烧录 + 复位 + 监视日志
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 run
# 只烧录并复位
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 flash
# 只复位
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 reset
# 附加到已运行的目标,抓日志
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 attach
这四条任务都由 espflash 模块提供。run 的底层是 cargo run,runner 为:
espflash flash --monitor
flash 任务:
espflash flash --after hard-reset <out>
attach 任务:
espflash monitor --elf <out>
--elf 很关键:espflash 需要从 ELF 里读出 defmt 的字符串表,才能把二进制日志解码成可读文本。没有它,你看到的是十六进制。
案例二:probe-rs 路径(RTT + GDB)
# 用 probe-rs 烧录并跑,日志走 RTT
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 \
--select probe-rs run
# 全片擦除(会清掉所有用户数据)
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 \
--select probe-rs flash-erase-all
# 启动 GDB server
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 \
--select probe-rs debug
probe-rs 模块提供的任务:
run: cargo run(runner = probe-rs run --protocol=<...> --chip <...> --preverify)
flash: probe-rs download --chip <CHIP> <out>
probe-rs reset --chip <CHIP>
attach: probe-rs attach --chip <CHIP> <out>
debug: probe-rs gdb --chip <CHIP>
reset: probe-rs reset --chip <CHIP>
flash-erase-all 只在 probe-rs 模块里有:
probe-rs erase --chip <CHIP> --allow-erase-all
C6 默认用 espflash,所以直接跑 flash-erase-all 找不到任务;要显式切到 probe-rs。espflash 自己也有擦除子命令,但不在 Ariel OS 的任务抽象里,需要看 espflash --help。
案例三:GDB 会话
# 终端 A:启动 GDB server(会先构建)
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 \
--select probe-rs debug
# 终端 B:连接
arm-none-eabi-gdb <elf>
(gdb) target extended-remote localhost:1337
(gdb) break __start_main
(gdb) continue
这里有一个 C6 特有的坑:ariel-os 上下文里的 gdb 任务把调试器写死成 arm-none-eabi-gdb:
gdb:
cmd:
- arm-none-eabi-gdb -ex "target extended-remote localhost:1337" ${out}
C6 是 RISC-V,用 ARM 的 GDB 连上去会因为架构不匹配而无法正确解析寄存器。解决办法是用 RISC-V 版 GDB(比如 ESP 工具链里的 riscv32-esp-elf-gdb,或 gdb-multiarch)手工连接 localhost:1337,不要用 laze build gdb。这是 0.6.0 的一个粗糙处,官方还没有按架构分派 GDB 命令。
案例四:nRF52840-DK 对照
laze -C examples/hello-world build -b nrf52840dk run
laze -C examples/hello-world build -b nrf52840dk flash-erase-all
laze -C examples/hello-world build -b nrf52840dk attach
laze -C examples/hello-world build -b nrf52840dk debug
nRF 默认走 probe-rs(nrf context selects: ?probe-rs),所以 flash-erase-all 直接可用。新出厂的 nRF52840-DK 经常是锁定的,第一次烧录会报:
An operation could not be performed because it lacked the permission to do so: erase_all
跑一次 flash-erase-all 解锁即可。这是 CTRL-AP 的保护机制,不是板子坏了。
案例五:把烧录纳入 CI
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 \
--select probe-rs run
echo "exit code: $?"
hello-world 最后调用 exit(ExitCode::SUCCESS);在 probe-rs + semihosting 下,probe-rs 进程会以 0 退出,shell 拿到 0。把 SUCCESS 改成 FAILURE,退出码变 1。这让「固件跑通了吗」变成一个可脚本化的布尔值,而不是靠人看日志。
注意:这条路要求 semihosting。probe-rs 模块 selects: ?debug-console,而 debug-console 会 ?semihosting,所以在 probe-rs 下通常自动开启;用 espflash 时没有 semihosting,exit() 会死循环,CI 会一直挂着。
逐段解析
run 与 flash 的本质区别
run 在 ariel-os 上下文里定义成 build: false,命令是 cargo run。它依赖 CARGO_RUNNER 环境变量,cargo 在编译完后调用 runner。runner 既负责烧录,也负责接管输出(--monitor 或 RTT)。
flash 是模块级任务,默认会先构建(没有 build: false),然后执行烧录命令并复位。它不会接管输出,烧完就退出。
所以:
- 想「一条命令看到日志」,用
run; - 想「烧完就走,日志另开窗口」,用
flash+attach; - 想「板子已经在跑,只想看日志」,用
attach。
attach 一定是 build: false,因为它不应该触发重新编译。但 laze 有一个警告:如果你给 flash 和后续 debug 传了不同的 -D 参数,debug 可能会先重新构建,导致符号和 flash 里的固件不一致。官方文档专门提醒「相关命令要传相同设置」。
probe-rs 的 --preverify 有什么用
runner 里带 --preverify:
probe-rs run --protocol=jtag --chip esp32c6 --preverify
它的作用是:下载前先读一遍 flash,和目标镜像比对;如果内容一致就跳过写入。好处是减少不必要的擦写,节省时间,也减少 flash 磨损。对频繁 run 的开发循环很有意义。
flash 任务没用 --preverify,而是直接 download 再 reset。因为 flash 的语义是「确保写入」,不做优化判断。
为什么 flash 要分两步:download + reset
probe-rs download --chip <CHIP> <out>
probe-rs reset --chip <CHIP>
download 只负责写 flash,写完芯片还停在调试器控制的状态;reset 让它从复位向量重新启动。分成两步的好处是:如果 download 失败,不会误复位;如果只想烧录不想立刻跑(比如产线),可以只跑第一步。
espflash 用 --after hard-reset 把复位合并进烧录命令,因为它走的是 bootloader 协议,烧完必须让芯片退出 bootloader。
RTT 的控制块与缓冲区
RTT 在目标内存里有一个控制块(control block),记录每个通道的缓冲区地址、大小、读写指针。主机工具要么知道控制块地址(从 ELF 符号里读),要么扫描内存找 magic bytes。
Ariel OS 用 defmt-rtt 或 rtt-target 实现 RTT 后端:
defmt门面配defmt-rtt;log门面配rtt-target,初始化时显式设置ChannelMode::NoBlockTrim。
NoBlockTrim 就是「非阻塞 + 截断」:缓冲区满时丢弃新数据,不阻塞。probe-rs attach 后会把模式改成 blocking,这样日志不丢,但如果主机端异常退出,目标可能永远卡在下一次写日志上。解决办法是复位目标,或者重新 attach 后正常 detach。
attach 为什么会看到「历史日志」
RTT 的缓冲区在目标内存里,主机没读之前数据一直在。所以 attach 刚连上时,可能会把 attach 之前产生的日志一并打出来。这不是 bug,而是 RTT 的工作方式。反过来说,如果你 attach 后看到一大堆旧日志,别以为程序刚刚跑了那些。
ESP32 的 USB CDC-ACM 日志没有这种「历史缓冲」,因为它是流式串口;attach 之后只能看到新数据。
semihosting 为什么只用来 exit
semihosting 的每次操作都要停核、等主机,速度是微秒到毫秒级。用它打日志会让程序慢几个数量级,还会打乱实时性。所以 Ariel OS 只在 exit() 用它:这是一次性操作,慢一点无所谓。
ariel_os::debug::exit() 在三个 feature 组合下的行为,上一篇已经拆过;这里补一个实际影响:如果你用 espflash 烧录,但手工 --select semihosting,固件会认为「主机支持 semihosting」,调用 exit() 时触发异常,而 espflash 不处理,于是 panic(典型报错 Unhandled interrupt on ProCpu)。不要在不支持 semihosting 的工具链组合下开这个 feature。
espflash 的 --elf 与 defmt 解码
espflash monitor --elf <out> 需要 ELF 来解码 defmt。defmt 的日志在固件里不是字符串,而是「索引 + 参数」的二进制流,字符串表在 ELF 的特定 section 里。主机工具必须拿到同一份 ELF,才能把索引映射回文本。
这也是为什么「换了一个 ELF 去 attach」会解码错乱:字符串表对不上,索引指向错误的字符串。烧录和 attach 必须用同一次构建的产物。
GDB 会话里能做什么
break <symbol>:在函数上下断点,比如__start_main、main、某个驱动函数;monitor reset halt:让 probe-rs 复位并停在复位向量;info registers:看寄存器;x/16wx <addr>:看内存;bt:看调用栈(需要 debug 信息,Ariel OS 的 release 默认带);continue/step/next:常规控制。
对 async 代码,断点会停在 Future 的 poll 里,调用栈可能很深,符号名带泛型。建议在业务函数入口下断点,而不是在 poll 里瞎找。
产物检查三件套
laze build -b <board> size # llvm-size:text/data/bss
laze build -b <board> objdump # rust-objdump -S:反汇编 + 源码
laze build -b <board> tree # cargo tree:依赖树
再加一条原生的:
readelf --sections <elf>
readelf 能看每个 section 的地址和大小,是验证内存布局(下一篇的主题)的直接手段。比如确认 .isr_stack 是否在 RAM 起始处、.bss 有多大、堆的边界在哪。
运行现象
espflash 烧录
典型输出分三段:探测芯片(型号、MAC、flash 容量)、写入(bootloader、分区表、应用,带进度)、复位。随后进入 monitor,日志逐行出现。按 Ctrl-C 退出 monitor,目标继续运行。
如果日志是 INFO Hello World!,说明整条链路通了:编译、烧录、启动、日志传输、主机解码。
probe-rs 烧录
输出更简洁:识别芯片、下载、校验、复位,然后 RTT 日志涌出。exit() 触发后,probe-rs 打印退出信息并结束,shell 提示符回来。echo $? 为 0。
attach
laze -C examples/hello-world build -b espressif-esp32-c6-devkitc-1 attach
如果目标正在跑且日志传输是 USB/UART,你会看到新日志;如果是 RTT,可能先看到一段历史日志。如果什么都没有,先确认目标真的在跑(按复位键看有没有启动日志),再确认 attach 用的 ELF 和烧录的一致。
GDB
probe-rs gdb 启动后监听 1337 端口,GDB 连上后目标默认是 halt 状态。monitor reset halt 让它从复位开始,continue 才真正跑。断点命中时,GDB 会显示当前源码行(因为 release 带 debug 信息)。
失败时的现象对照
| 现象 | 最可能的原因 |
|---|---|
no probe found |
接错口 / 没装 udev 规则 / 探针被占用 |
Connecting... 卡住 |
芯片在深睡 / 需要进下载模式 / 波特率不对 |
| 烧录成功但无日志 | 日志传输选错 / 解码工具不对 / 程序提前 panic |
| 日志乱码 | 用普通串口工具读 defmt 二进制 |
| attach 无输出 | ELF 不匹配 / RTT 控制块没找到 / 目标没跑 |
| GDB 连上但寄存器全 0 | 架构不匹配(C6 用了 ARM GDB) |
常见问题
Q1:C6 有两个 Type-C 口,烧录到底插哪个?
成因:一个口经 USB-UART 桥接芯片接 UART0(GPIO16/17),另一个口直连芯片的 USB-Serial-JTAG。espflash 默认找前者,probe-rs 需要后者。
解决方案:用 espflash 时插 UART 口,ls /dev/cu.usb* 确认设备;用 probe-rs 时插 USB-Serial-JTAG 口,probe-rs list 应该能枚举到。两个口都插着也可以,但要知道哪条命令走哪个口。日志传输如果选 logging-over-uart,就要盯着 UART 口;选 logging-over-usb,就盯着 USB-Serial-JTAG 口。
Q2:probe-rs 报权限错误
成因:Linux 上没有 udev 规则,普通用户无法打开 USB 设备;或者设备被 ModemManager、brltty 之类的服务抢走。
解决方案:按 probe-rs 文档安装 udev 规则并重新插拔;用 sudo 临时验证是否是权限问题;停掉可能抢占串口/USB 的服务。macOS 不需要 udev,但可能需要在系统设置里允许终端访问 USB 设备。
Q3:nRF52840-DK 第一次烧录报 erase_all 权限不足
成因:新板子的 CTRL-AP 处于保护状态,禁止全片擦除。这是 Nordic 的出厂保护,不是故障。
解决方案:
laze -C examples/hello-world build -b nrf52840dk flash-erase-all
跑一次解锁,之后正常烧录。如果还不行,用 nrfjprog --recover 或 J-Link 的解锁流程。
Q4:烧录成功,但 attach 什么都看不到
成因:四种可能。一是日志走 USB/UART,不是 RTT,probe-rs attach 自然没数据;二是 attach 用的 ELF 和烧录的不一致,RTT 控制块地址对不上;三是目标没在跑(卡在 fault、sleep、bootloader);四是日志级别过滤把输出全关了。
解决方案:先跑 info-modules 确认传输;用同一次构建的 ELF;按复位键看启动日志;用 -DLOG=trace 提高级别。如果用的是 espflash,attach 要用 espflash monitor,不是 probe-rs attach。
Q5:RTT 日志丢了一大段,或者 attach 后目标卡死
成因:RTT 默认是非阻塞 trimming 模式,缓冲区满就丢;probe-rs attach 会切成阻塞模式,如果主机端处理不过来或异常断开,目标会卡在写日志上。
解决方案:降低日志频率或级别(-DLOG=warn);加大 RTT 缓冲区(Ariel OS 没有直接暴露这个配置,需要改后端);不要在高频循环里打 info!;异常断开后复位目标。生产固件不要依赖 RTT 做关键日志。
Q6:espflash monitor 显示乱码
成因:defmt 是二进制编码,普通串口监视器不认识;或者 monitor 没带 --elf,拿不到字符串表;或者波特率不对(UART 传输时)。
解决方案:用 espflash monitor --elf <elf>;或者用 defmt-print 接管串口;或者把门面换成 log(文本)。检查波特率:UART 默认 115200,SBD 里没有改的话就是它。
Q7:laze build gdb 在 C6 上连不上或寄存器异常
成因:gdb 任务硬编码 arm-none-eabi-gdb,C6 是 RISC-V,架构不匹配。这是当前版本的实现限制。
解决方案:用 laze build --select probe-rs debug 启动 GDB server,然后手工用 RISC-V GDB 连接:
riscv32-esp-elf-gdb <elf>
(gdb) target extended-remote localhost:1337
没有 ESP 工具链的话,gdb-multiarch 也可以,但要多配置 architecture。ARM 板卡上 laze build gdb 是正常的。
Q8:flash 和 debug 之间固件不一致
成因:laze 的官方警告:任务可能触发重新构建。如果 flash 用了 -DLOG=info,debug 用了 -DLOG=trace,第二次会重新编译,符号和 flash 里的机器码对不上,断点位置会错。
解决方案:相关命令传完全相同的参数;或者先 flash,再 attach(attach 不会构建);需要改配置时,重新 flash 再 debug。CI 里把构建参数写进脚本变量,避免手误。
面试考点
考点 1:JTAG 和 SWD 的区别是什么?为什么 ESP32-C6 用 JTAG?
参考答案:JTAG 是通用标准,四/五线,支持边界扫描和多器件链;SWD 是 ARM 专有的精简协议,两线,只用于 ARM 核。C6 是 RISC-V,没有 ARM 的 SWD,但 Espressif 集成了 USB-Serial-JTAG 外设,所以走 JTAG 协议,且物理层就是 USB。nRF52840 是 Cortex-M4F,走 SWD。
考点 2:RTT 和 semihosting 的机制与适用场景有什么不同?
参考答案:RTT 在目标内存里放控制块和环形缓冲区,目标写、探针后台读,不打断 CPU,适合高频日志;semihosting 通过触发异常让调试器代替目标执行操作,是远程系统调用,极慢,适合一次性操作(如 exit、读文件)。Ariel OS 用 RTT 做日志通道,用 semihosting 实现 exit()。
考点 3:RTT 的 trimming 模式和 blocking 模式有什么区别?各有什么风险?
参考答案:trimming(非阻塞)模式下缓冲区满就丢弃新数据,保证目标不被日志阻塞,风险是丢日志;blocking 模式下缓冲区满时目标等待,保证不丢,风险是主机端不读或断开时目标卡死。Ariel OS 默认 trimming,probe-rs attach 时切 blocking。
考点 4:probe-rs 的 --preverify 是什么?为什么 flash 任务不用它?
参考答案:--preverify 在下载前读取 flash 内容并与镜像比对,相同则跳过写入,减少擦写和耗时。run 的 runner 用它优化开发循环;flash 的语义是「确保写入并复位」,直接 download 更确定,也避免「内容相同但用户期望重写」的歧义。
考点 5:run、flash、attach 三个任务的职责边界是什么?
参考答案:run 编译后通过 runner 烧录并接管输出,适合开发循环;flash 编译后烧录并复位,不接管输出,适合产线或配合独立监视;attach 不编译,连接到已运行目标抓日志,适合观察现场。三者对构建的触发不同,混用时要注意参数一致。
考点 6:为什么「烧录成功」不等于「程序在跑」?
参考答案:烧录只保证字节写入 flash 并通过校验;程序是否运行取决于复位向量、时钟、链接脚本、启动代码、硬件连接。常见「烧录成功但不跑」的原因包括:复位没生效、时钟配置错误导致卡在启动、看门狗复位循环、链接脚本把代码放到了错误地址、程序 panic 后进入死循环。需要结合日志、GDB、寄存器状态判断。
小结
烧录与调试这条链路上,有四个独立的选择,任何一个选错都会表现为「没输出」:
- 物理口:UART 口还是 USB-Serial-JTAG 口;
- 烧录工具:espflash 还是 probe-rs;
- 调试通道:RTT 还是 USB/UART;
- 日志门面:defmt 还是 log。
Ariel OS 用 laze 模块把这四个选择收敛成构建期配置,好处是命令统一,坏处是行为隐式。排查时永远先跑 info-modules,确认这四项,再怀疑代码。
下一篇专门讲日志:defmt 为什么小、log 为什么通用、传输怎么选、LOG 变量怎么过滤、panic 怎么打印、自定义传输怎么写。那是「没输出」问题的另一半答案。
系列目录
- 系列总览:Ariel OS 设计理念与生态全景
- 环境搭建:Rust 工具链与 laze 构建系统
- laze 元构建系统:contexts、modules 与 builders
- 第一个应用:从 minimal 到 hello-world
- 板卡描述体系:boards yaml 与结构化配置
- 烧录与调试:probe-rs、RTT 与日志查看
- 日志系统:defmt 与 ariel-os-log
- 调试控制台:shell 与交互调试
- 异步执行器:Embassy 集成与任务宏
- 内存布局与全局分配器
- 时钟体系与启动流程
- 引脚与 GPIO:静态配置与运行时切换
- 多线程(上):抢占式线程与线程宏
- 多线程(下):Channel、Event 与互斥
- 线程与异步互操作:multicore 与定时器
- 低功耗与电源管理
- I2C 实战:总线扫描与设备通信
- SPI 与 UART 实战
- 传感器框架:sensor API 与注册表
- 随机数与熵源
- 网络栈(上):初始化、Wi-Fi 与 UDP
- 网络栈(下):TCP 客户端与回显服务
- 应用协议:HTTP 与 CoAP
- USB 实战:串口与 HID 键盘
- BLE 实战:广播与扫描
- 以太网、设备元数据与身份
- 持久化存储
- 测试:embedded-test 与硬件在环
- 原生目标、基准测试与 Ferrocene
- 收官:新增板卡支持、横向对比与路线图

浙公网安备 33010602011771号