Files
zig-doorbell/build.zig
2025-03-23 14:32:05 +01:00

241 lines
9.1 KiB
Zig

const std = @import("std");
// Choose your board
const Board =
// .pico;
.pico_w;
// .pico2;
// .pico2_w;
// Choose whether Stdio goes to USB or UART
const StdioUsb = true;
const PicoStdlibDefine = if (StdioUsb) "LIB_PICO_STDIO_USB" else "LIB_PICO_STDIO_UART";
// Pico SDK path can be specified here for your convenience
const PicoSDKPath: ?[]const u8 = null;
// arm-none-eabi toolchain path may be specified here as well
const ARMNoneEabiPath: ?[]const u8 = null;
pub fn build(b: *std.Build) anyerror!void {
// ------------------
const target = std.Target.Query{
.abi = .eabi,
.cpu_arch = .thumb,
.cpu_model = .{ .explicit = cpu_model_by_board(Board) },
.os_tag = .freestanding,
};
const optimize = b.standardOptimizeOption(.{});
const lib = b.addObject(.{
.name = "zig-pico",
.root_source_file = b.path("src/main.zig"),
.target = b.resolveTargetQuery(target),
.optimize = optimize,
});
// get and perform basic verification on the pico sdk path
// if the sdk path contains the pico_sdk_init.cmake file then we know its correct
const pico_sdk_path =
if (PicoSDKPath) |sdk_path| sdk_path else std.process.getEnvVarOwned(b.allocator, "PICO_SDK_PATH") catch null orelse {
std.log.err("The Pico SDK path must be set either through the PICO_SDK_PATH environment variable or at the top of build.zig.", .{});
return;
};
const pico_init_cmake_path = b.pathJoin(&.{ pico_sdk_path, "pico_sdk_init.cmake" });
std.fs.cwd().access(pico_init_cmake_path, .{}) catch {
std.log.err(
\\Provided Pico SDK path does not contain the file pico_sdk_init.cmake
\\Tried: {s}
\\Are you sure you entered the path correctly?"
, .{pico_init_cmake_path});
return;
};
// default arm-none-eabi includes
lib.linkLibC();
// Standard libary headers may be in different locations on different platforms
const arm_header_location = blk: {
if (ARMNoneEabiPath) |path| {
break :blk path;
}
if (std.process.getEnvVarOwned(b.allocator, "ARM_NONE_EABI_PATH") catch null) |path| {
break :blk path;
}
const unix_path = "/usr/arm-none-eabi/include";
if (std.fs.accessAbsolute(unix_path, .{})) |_| {
break :blk unix_path;
} else |err| err catch {};
break :blk error.StandardHeaderLocationNotSpecified;
} catch |err| {
err catch {};
std.log.err(
\\Could not determine ARM Toolchain include directory.
\\Please set the ARM_NONE_EABI_PATH environment variable with the correct path
\\or set the ARMNoneEabiPath variable at the top of build.zig
, .{});
return;
};
lib.addSystemIncludePath(.{ .cwd_relative = arm_header_location });
// Sort out the platform specifics
const IsRP2040 = Board == .pico or Board == .pico_w;
const Platform = comptime platform_by_board(Board);
// find the board header
const board_header = blk: {
const header_file = @tagName(Board) ++ ".h";
const _board_header = b.pathJoin(&.{ pico_sdk_path, "src/boards/include/boards", header_file });
std.fs.cwd().access(_board_header, .{}) catch {
std.log.err("Could not find the header file for board '{s}'\n", .{@tagName(Board)});
return;
};
break :blk header_file;
};
// Autogenerate the header file like the pico sdk would
const cmsys_exception_prefix = if (IsRP2040) "" else "//";
const header_str = try std.fmt.allocPrint(b.allocator,
\\#include "{s}/src/boards/include/boards/{s}"
\\{s}#include "{s}/src/rp2_common/cmsis/include/cmsis/rename_exceptions.h"
, .{ pico_sdk_path, board_header, cmsys_exception_prefix, pico_sdk_path });
// Write and include the generated header
const config_autogen_step = b.addWriteFile("pico/config_autogen.h", header_str);
lib.step.dependOn(&config_autogen_step.step);
lib.addIncludePath(config_autogen_step.getDirectory());
// requires running cmake at least once
lib.addSystemIncludePath(b.path("build/generated/pico_base"));
// PICO SDK includes
// Find all folders called include in the Pico SDK
{
const pico_sdk_src = try std.fmt.allocPrint(b.allocator, "{s}/src", .{pico_sdk_path});
var dir = try std.fs.cwd().openDir(pico_sdk_src, .{
.iterate = true,
.no_follow = true,
});
const allowed_paths = [_][]const u8{ @tagName(Platform), "rp2_common", "common" };
var walker = try dir.walk(b.allocator);
defer walker.deinit();
while (try walker.next()) |entry| {
if (std.mem.eql(u8, entry.basename, "include")) {
for (allowed_paths) |path| {
if (std.mem.indexOf(u8, entry.path, path)) |_| {
const pico_sdk_include = try std.fmt.allocPrint(b.allocator, "{s}/src/{s}", .{ pico_sdk_path, entry.path });
lib.addIncludePath(.{ .cwd_relative = pico_sdk_include });
continue;
}
}
}
}
}
// Define the platform specific macros
define_platform_specific_macros(lib, Platform);
// Define UART or USB constant for headers
lib.root_module.addCMacro(PicoStdlibDefine, "1");
// required for pico_w wifi
lib.root_module.addCMacro("PICO_CYW43_ARCH_THREADSAFE_BACKGROUND", "1");
const cyw43_include = try std.fmt.allocPrint(b.allocator, "{s}/lib/cyw43-driver/src", .{pico_sdk_path});
lib.addIncludePath(.{ .cwd_relative = cyw43_include });
// required by cyw43
const lwip_include = try std.fmt.allocPrint(b.allocator, "{s}/lib/lwip/src/include", .{pico_sdk_path});
lib.addIncludePath(.{ .cwd_relative = lwip_include });
// options headers
lib.addIncludePath(b.path("config/"));
const compiled = lib.getEmittedBin();
const install_step = b.addInstallFile(compiled, "mlem.o");
install_step.step.dependOn(&lib.step);
// create build directory
if (std.fs.cwd().makeDir("build")) |_| {} else |err| {
if (err != error.PathAlreadyExists) return err;
}
const uart_or_usb = if (StdioUsb) "-DSTDIO_USB=1" else "-DSTDIO_UART=1";
const cmake_pico_sdk_path = b.fmt("-DPICO_SDK_PATH={s}", .{pico_sdk_path});
const cmake_argv = [_][]const u8{ "cmake", "-B", "./build", "-S .", "-DPICO_BOARD=" ++ @tagName(Board), "-DPICO_PLATFORM=" ++ @tagName(Platform), cmake_pico_sdk_path, uart_or_usb };
const cmake_step = b.addSystemCommand(&cmake_argv);
cmake_step.step.dependOn(&install_step.step);
const make_argv = [_][]const u8{ "cmake", "--build", "./build", "--parallel" };
const make_step = b.addSystemCommand(&make_argv);
make_step.step.dependOn(&cmake_step.step);
const uf2_create_step = b.addInstallFile(b.path("build/mlem.uf2"), "firmware.uf2");
uf2_create_step.step.dependOn(&make_step.step);
const uf2_step = b.step("uf2", "Create firmware.uf2");
uf2_step.dependOn(&uf2_create_step.step);
const elf_create_step = b.addInstallFile(b.path("build/mlem.elf"), "firmware.elf");
elf_create_step.step.dependOn(&make_step.step);
const elf_step = b.step("elf", "Create firmware.elf");
elf_step.dependOn(&elf_create_step.step);
const copy_step = b.step("copy", "Copy firmware");
copy_step.dependOn(uf2_step);
copy_step.dependOn(elf_step);
b.default_step = copy_step;
// b.default_step = uf2_step;
}
// ------------------ Board support
pub fn cpu_model_by_board(board: @Type(.enum_literal)) *const std.Target.Cpu.Model {
return switch (board) {
.pico, .pico_w => &std.Target.arm.cpu.cortex_m0plus,
.pico2, .pico2_w => &std.Target.arm.cpu.cortex_m33,
else => @compileError("Unknown board type"),
};
}
pub fn platform_by_board(board: @Type(.enum_literal)) @Type(.enum_literal) {
return switch (board) {
.pico, .pico_w => .rp2040,
.pico2, .pico2_w => .rp2350,
else => @compileError("Unknown platform"),
};
}
pub fn define_platform_specific_macros(compile: *std.Build.Step.Compile, platform: @Type(.enum_literal)) void {
switch (platform) {
.rp2040 => {
compile.root_module.addCMacro("PICO_RP2040", "1");
compile.root_module.addCMacro("PICO_32BIT", "1");
compile.root_module.addCMacro("PICO_ARM", "1");
compile.root_module.addCMacro("PICO_CMSIS_DEVICE", "RP2040");
compile.root_module.addCMacro("PICO_DEFAULT_FLASH_SIZE_BYTES", "\"2 * 1024 * 1024\"");
},
.rp2350 => {
compile.root_module.addCMacro("PICO_RP2350", "1");
compile.root_module.addCMacro("PICO_32BIT", "1");
compile.root_module.addCMacro("PICO_ARM", "1");
compile.root_module.addCMacro("PICO_PIO_VERSION", "1");
compile.root_module.addCMacro("NUM_DOORBELLS", "1");
compile.root_module.addCMacro("PICO_CMSIS_DEVICE", "RP2350");
compile.root_module.addCMacro("PICO_DEFAULT_FLASH_SIZE_BYTES", "\"4 * 1024 * 1024\"");
},
else => @compileError("Unknown platform"),
}
}