任务声明文件
一、.vscode/tasks.json 是什么
它是 VS Code 的任务声明文件(不是 CMake 的一部分),作用是把一串 shell 命令包装成可在 Cmd+Shift+P → Tasks: Run Task 或 Cmd+Shift+B 里一键执行的操作。它本身不编译任何东西,真正的编译由它调用的 cmake 完成。
1.1 顶层字段
1 | { |
version: "2.0.0"—— tasks.json 的 schema 版本(注意:与CmakePresets.json里的"version": 3是两个完全不相干的东西)。tasks[]—— 五个任务,下面逐个拆。
1.2 各字段通用含义
| 字段 | 含义 |
|---|---|
label |
任务名,被 launch.json 的 preLaunchTask 引用 |
type: "shell" |
交给系统 shell(zsh)执行 |
command / args |
实际命令行 |
options.cwd |
工作目录,这里统一是 ${workspaceFolder} |
group.kind: "build" + isDefault: true |
绑定到 Cmd+Shift+B |
problemMatcher |
把终端输出解析成”问题”面板条目,支持点击跳转 |
isBackground |
长驻进程,配合 beginsPattern/endsPattern 判断启动完成 |
1.3 任务 ①:J-Link: Flash (Debug)(test 组默认)
1 | "command": "printf 'device FR8029D\nsi SWD\nspeed auto\nconnect\nloadfile \"${workspaceFolder}/build/debug/ble_hzpc.elf\"\nr\ng\nexit\n' > \"${workspaceFolder}\"/build/debug/flash.jlink && JLinkExe -device FR8029D -if SWD -speed auto -autoconnect 1 -CommanderScript \"${workspaceFolder}\"/build/debug/flash.jlink", |
两步:先用 printf 动态拼出一个 J-Link Commander 脚本,再让 JLinkExe 执行它。脚本内容是:
1 | device FR8029D # 目标芯片 |
关键点:**烧的是 .elf 而不是 .bin**。loadfile 能读 ELF 里的段地址,自动按链接脚本的地址(0x10002000)写入,不需要手工指定偏移。
1.4 任务 ②:Build (Debug)(build 组默认,Cmd+Shift+B)
1 | "label": "Build (Debug)", |
这是唯一真正产出可执行文件的任务。它把构建完全委托给 CMake + Ninja,--preset arm-gcc-debug 从 CmakePresets.json 取参数。
它的问题匹配器值得注意:
1 | "pattern": { |
把 GCC 的 文件:行:列: 级别: 信息 抽成可点击条目(捕获组含 note)。
1.5 任务 ③:Build & Flash (Debug)
1 | "command": "cmake --build --preset arm-gcc-debug && printf 'device FR8029D\nsi SWD\nspeed auto\nconnect\nloadfile \"${workspaceFolder}/build/debug/ble_hzpc.elf\"\nr\ng\nexit\n' > \"${workspaceFolder}\"/build/debug/flash.jlink && JLinkExe -device FR8029D -if SWD -speed auto -autoconnect 1 -CommanderScript \"${workspaceFolder}\"/build/debug/flash.jlink", |
&& 串联 ② 和 ① —— 编译成功才烧录。
1.6 任务 ④:J-Link: Erase Chip (全片擦除)
1 | "command": "printf 'device FR8029D\nsi SWD\nspeed auto\nconnect\nerase 0x10000000 0x10100000\nexit\n' > \"${workspaceFolder}\"/build/erase_chip.jlink && JLinkExe -device FR8029D -if SWD -speed auto -autoconnect 1 -CommanderScript \"${workspaceFolder}\"/build/erase_chip.jlink", |
detail 里说明了为什么必须给显式地址范围:J-Link 没有 FR8029D 的 flash loader,只能按地址区间擦。范围 0x10000000~0x10100000 覆盖 bootloader + app 共 1MB,擦完必须重新烧录。
1.7 任务 ⑤:J-Link: Start GDB Server(后台任务)
1 | "isBackground": true, |
GDB Server 不会退出,所以标 isBackground。VS Code 靠 endsPattern(等到出现 Waiting for GDB connection...)判断”服务已就绪”,之后才允许依赖它的调试配置启动。端口:GDB 2331 / SWO 2332 / Telnet 2333 / RTT 2334,正好对应 launch.json 里第二个配置的 miDebuggerServerAddress: localhost:2331。
1.8 与 launch.json 的联动
两个调试配置都写了 "preLaunchTask": "Build (Debug)",按 F5 会先跑 ② 再调试:
1 | "executable": "${workspaceFolder}/build/debug/ble_hzpc.elf", |
二、可执行文件是怎么生成的
tasks.json 只是”按钮”,真正的流水线是 CMake Presets → 交叉编译 → 链接 → 后处理。分 5 个阶段。
阶段 0:配置(configure)
CmakePresets.json 定义了两个预设:
1 | { |
generator: Ninja→ 增量编译靠 Ninja。binaryDir: build/debug→ 所有产物落在这里(实测目录里确实有ble_hzpc.elf/.bin/.hex/.lst/.map)。CMAKE_TRY_COMPILE_TARGET_TYPE: STATIC_LIBRARY→ 让 CMake 的编译器探测只编译不链接(裸机没有可用的链接测试目标)。
工具链文件设定交叉编译器与产物后缀:
1 | set(CMAKE_SYSTEM_NAME Generic) |
CMAKE_EXECUTABLE_SUFFIX = .elf 就是最终文件名带 .elf 的来源。目标名写成 ble_hzpc.elf、后缀也是 .elf,CMake 不会重复追加 —— 目录里的实际产物就是 ble_hzpc.elf。
阶段 1:编译(compile)
CMakeLists.txt 手工枚举了约 90 个源文件,分 8 组汇成 ALL_SOURCES:
1 | set(ALL_SOURCES |
编译选项:
1 | set(CPU_FLAGS |
1 | target_compile_options(${CMAKE_PROJECT_NAME}.elf PRIVATE |
-ffunction-sections/-fdata-sections 是为链接期的 --gc-sections 做铺垫(每个函数独立成段,未用到的整段丢弃)。
阶段 2:链接(link)
1 | add_executable(${CMAKE_PROJECT_NAME}.elf ${ALL_SOURCES}) |
1 | target_link_options(${CMAKE_PROJECT_NAME}.elf PRIVATE |
要点:
- 两个链接脚本:
-T ldscript.ld(主)+-T syscall_gcc.txt(BLE 库的 syscall 表)。 nano.specs/nosys.specs:用精简 newlib-nano,省体积。- 静态库:
libbtdm_host.a、libbtdm_controller.a(闭源 BLE 协议栈)。 - 内存布局决定一切:
1 | MEMORY |
前 8K(0x10000000~0x10002000)留给镜像头 + 标志区,所以 app 从 0x10002000 起。链接脚本还埋了两个链接期断言,防止 RAM 静默踩踏:
1 | ASSERT(ABSOLUTE(_estack) - ABSOLUTE(_sstack) >= _Min_Stack_Size, |
一个容易忽略但很重要的修复:
1 | # 改了链接脚本必须重新链接(ninja 默认不把 -T 的脚本当依赖,否则 ASSERT 之类 |
阶段 3:后处理(POST_BUILD)→ 生成 .bin / .hex / .lst + 体积校验
这是”可执行文件变成可烧录文件”的地方:
1 | add_custom_command(TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD |
产物一览:
| 产物 | 工具 | 用途 |
|---|---|---|
ble_hzpc.elf |
链接器 | 调试/J-Link loadfile 烧录(自带地址信息) |
ble_hzpc.bin |
objcopy -O binary -S |
裸二进制,-S 剥掉符号 |
ble_hzpc.hex |
objcopy -O ihex -S |
Intel HEX,串口 ISP/编程器用 |
ble_hzpc.lst |
objdump --source |
源码+汇编交叉对照,排查硬 fault |
ble_hzpc.map |
-Wl,-Map |
符号地址/段大小分布 |
最后一步调用独立的 CMake 脚本做硬校验,超限直接 FATAL_ERROR 让编译失败:
1 | if(_image_size GREATER _limit) |
上限来自这里(332K):
1 | set(FLASHENV_OFFSET 0x55000) |
阶段 4:串口烧录文件(可选,产线用)
要串口烧录时,README 给出的命令:
1 | > cd components/toolchain |
它在 .bin 前面拼一个 4K 的 image_info_t 头 + 4K 的标志区:
1 | f_out.write(struct.pack('I', version)) |
随后 info_crc + magic + 0x00008020 + 0xFF 填到 4K,再写 4K 全 0x00 的搬运进度区,最后追加原始 bin。产物是 ble_hzpc_burn.bin。默认 exec_offset = 0x2000,与 APP_BASE_OFFSET 一致 —— ROM bootloader 依据这个头把镜像搬进执行区。
阶段 5:资源包(独立于固件,烧外部 SPI Flash)
字模/图片不进固件,由 Python 脚本单独产出 res/res_pack.bin,烧到外部 flash 0x700000。README 强调两者必须同步更新。这条链路完全不经过 CMake。
三、整体流程图
1 | flowchart TD |
四、几个值得注意的点
没有 configure 任务。
tasks.json里只有--build,首次克隆或改了CMakeLists.txt结构后需手动跑一次cmake --preset arm-gcc-debug(或靠 CMake Tools 扩展)。实测build/debug/里已有CMakeCache.txt和build.ninja,说明已配置过。预设文件名大小写:工程里叫
CmakePresets.json,而 CMake / VS Code CMake Tools 按惯例查找CMakePresets.json。macOS 默认分区大小写不敏感所以能work,但换到大小写敏感的环境(或 Linux CI)会找不到预设。arm-gcc-release预设没人用:只有 debug 被三个任务引用,release 目前是”定义了但没接按钮”。Flash 任务假设
build/debug/已存在:命令直接往${workspaceFolder}/build/debug/flash.jlink重定向,若目录没建会失败(当前目录已存在,所以没问题)。build/根目录有一批旧产物(ble_hzpc.elf/.bin/.hex/.lst/.map),是早前binaryDir直接设为build时留下的,与现在的build/debug无关,容易混淆。332K 上限的三处联动:
CMakeLists.txt的FLASHENV_OFFSET、Inc/fal_cfg.h的FAL_PART_TABLE偏移、common/ota/ota.c的 OTA B 槽保底值,改一处必须同步另外两处,否则会静默覆盖参数区。gcc/Makefile是历史遗留路径,CMake 才是当前主用;两者源列表已不完全一致,维护时以CMakeLists.txt为准。
需要我进一步展开哪一块?例如:
- 逐行讲
post_process.py生成的 48 字节镜像头每个字段的含义; - 梳理内部 Flash 1MB 的完整分区表(A 镜像 / FlashEnv / OTA B 槽 / 余量);
- 或者给
tasks.json补一个Clean/CMake: Configure任务(这需要切到 craft 模式才能落盘)。