☰
Zig 嵌入式底层库选择与 build.zig 组织指南
2026/10/6 12:39:35 网站建设 项目流程

Zig 嵌入式底层库选择与 build.zig 组织指南

适用于 STM32、GD32 等 ARM Cortex-M MCU。Zig 不会像 ARM GCC / Keil / IAR 那样自动为不同 MCU 挑选厂商 HAL 或 C 运行库;底层依赖由target、build.zig、芯片包、链接脚本、启动文件共同决定。5

1. 决定链

层级谁决定实际作用
目标三元组target/b.standardTargetOptions决定架构、ABI、是否有 libc,例如thumb-freestanding-eabi
构建脚本build.zig决定是否链接 libc、入口点、链接脚本、启动文件
芯片包stmicro-stm32、microzig等按芯片型号选择 startup、寄存器定义、内存布局
链接脚本.ld决定 Flash/RAM 地址、段布局、栈顶符号
启动文件startup_*.s决定向量表、栈初始化、data/bss 初始化、跳转到 main

2. 如何判断当前工程是否使用了 C 库

  1. 看 target 是否是 freestanding
  2. 如果是 freestanding,Zig 默认不会自动链接 libc。
  3. 看 build.zig 是否显式开启 libc
  4. 没有这一行,通常就是纯裸机工程。
  5. 看芯片包如何按型号选文件
  6. 例如根据-Dchip=stm32f103c8t6选择:
    • startup_stm32f103.s
    • stm32f103.ld
    • 寄存器定义
  7. 看链接脚本和启动文件是否匹配芯片
  8. 选错会导致链接报错、烧录后不运行、HardFault、中断异常等问题。

3. 纯 Zig 裸机工程

const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.resolveTargetQuery(.{ .cpu_arch = .thumb, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m3 }, .os_tag = .freestanding, .abi = .eabi, }); const optimize = b.standardOptimizeOption(.{ .default = .ReleaseSmall }); const exe = b.addExecutable(.{ .name = "firmware", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // 链接脚本:决定 Flash / RAM 布局 exe.setLinkerScript(b.path("linker/stm32f103.ld")); // 启动文件:向量表、栈初始化、跳转到 main exe.addAssemblyFile(b.path("src/startup_stm32f103.s")); // 链接阶段回收未使用段 exe.link_gc_sections = true; exe.link_data_sections = true; // 生成 .bin const bin = b.addObjCopy(exe, .{ .format = .bin }); bin.step.dependOn(&exe.step); b.installArtifact(bin); b.installArtifact(exe); }

这里没有任何 C 库,属于纯 freestanding 固件。

4. 加入 C 源文件

如果底层驱动是 C 写的,例如drivers/uart.c:

exe.addCSourceFile(.{ .file = b.path("drivers/uart.c"), .flags = &.{ "-mcpu=cortex-m3", "-mthumb", "-ffreestanding", "-Wall", }, });

addCSourceFile只是把 C 文件交给 Zig 内置的 Clang 前端编译,并不会自动引入完整 C 运行库。

5. 引入厂商寄存器头文件

exe.addIncludePath(b.path("include"));

C 文件里可以写:

#include"stm32f10x.h"

Zig 代码里如果想直接调用 C 头文件里的符号,可以用:

const c = @cImport({ @cInclude("stm32f10x.h"); });

注意:@cImport通常要求目标已经链接 libc。

6. 开启 C 运行时

exe.linkLibC();

这会让 Zig 根据 target 选择可用的 C 库实现。

但在 STM32 这种freestanding目标上,linkLibC()并不等于自动拿到 ST HAL、CMSIS 或 newlib-nano。很多嵌入式工程仍然需要自己提供:

  • startup_*.s
  • *.ld
  • SystemInit()
  • 寄存器访问封装

所以更常见的做法是:不开**linkLibC()**,而是手动引入启动文件、链接脚本和寄存器头文件。

7. 完整混合工程示例

const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.resolveTargetQuery(.{ .cpu_arch = .thumb, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m3 }, .os_tag = .freestanding, .abi = .eabi, }); const optimize = b.standardOptimizeOption(.{ .default = .ReleaseSmall }); const exe = b.addExecutable(.{ .name = "firmware", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // 链接脚本 exe.setLinkerScript(b.path("linker/stm32f103.ld")); // 启动汇编 exe.addAssemblyFile(b.path("src/startup_stm32f103.s")); // C 驱动源文件 exe.addCSourceFile(.{ .file = b.path("drivers/uart.c"), .flags = &.{ "-mcpu=cortex-m3", "-mthumb", "-ffreestanding", "-Wall", }, }); // 寄存器头文件路径 exe.addIncludePath(b.path("include")); // 如果确实需要 C 运行时,再开启: // exe.linkLibC(); exe.link_gc_sections = true; exe.link_data_sections = true; const bin = b.addObjCopy(exe, .{ .format = .bin }); bin.step.dependOn(&exe.step); b.installArtifact(bin); b.installArtifact(exe); }

8. 判断当前工程用了哪一层

你想确认什么在 build.zig 里看什么
是否裸机os_tag = .freestanding
是否链接 C 库是否有linkLibC()
是否引入 C 文件是否有addCSourceFile/addCSourceFiles
是否引入寄存器头是否有addIncludePath
是否使用芯片启动文件是否有addAssemblyFile
是否配置 Flash/RAM是否有setLinkerScript

9. 按芯片型号自动切换底层文件

const std = @import("std"); const ChipInfo = struct { name: []const u8, ld: []const u8, startup: []const u8, cpu_model: std.Target.arm.CpuModel, }; const chips = std.StaticStringMap(ChipInfo).initComptime(.{ .{ "stm32f103c8t6", .{ .name = "stm32f103c8t6", .ld = "linker/stm32f103.ld", .startup = "src/startup_stm32f103.s", .cpu_model = std.Target.arm.cpu.cortex_m3, } }, .{ "stm32f401re", .{ .name = "stm32f401re", .ld = "linker/stm32f401.ld", .startup = "src/startup_stm32f401.s", .cpu_model = std.Target.arm.cpu.cortex_m4, } }, }); pub fn build(b: *std.Build) void { const chip_name = b.option([]const u8, "chip", "芯片型号") orelse "stm32f103c8t6"; const chip = chips.get(chip_name) orelse { std.log.err("未知芯片: {s}", .{chip_name}); return; }; const target = b.resolveTargetQuery(.{ .cpu_arch = .thumb, .cpu_model = .{ .explicit = &chip.cpu_model }, .os_tag = .freestanding, .abi = .eabi, }); const optimize = b.standardOptimizeOption(.{ .default = .ReleaseSmall }); const exe = b.addExecutable(.{ .name = "firmware", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); exe.setLinkerScript(b.path(chip.ld)); exe.addAssemblyFile(b.path(chip.startup)); exe.link_gc_sections = true; exe.link_data_sections = true; const bin = b.addObjCopy(exe, .{ .format = .bin }); bin.step.dependOn(&exe.step); b.installArtifact(bin); b.installArtifact(exe); }

构建命令:

zig build-Dchip=stm32f103c8t6 zig build-Dchip=stm32f401re

这样不同 MCU 的底层文件就由chips表统一管理,切换芯片时不需要手动改build.zig。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询