UP | HOME

Arduino zig: blink

blink is like hello world on Arduino, and here I will show how to do it in zig. First, you need to create a zig project:

mkdir zig-arduino && cd zig-arduino
zig init-exe

1. Blink(programming)

You can look the following picture from Arduino Examples to figure out how to build these handcrafts.

circuit.webp schematic.webp

Now, you can start programming(in src/main.zig). First, import arduino and arduino.gpio:

const arduino = @import("arduino");
const gpio = arduino.gpio;

@import("arduino") is special since it's not import a local package, but a global one, this need to be setup in build.zig, you can learn this in setup section. A side effect code is required:

pub const panic = arduino.start.panicHang;

Finally, came to main function. Idea is simple:

  1. initialize LED(13) as output pin
  2. loop
    1. turn on LED(set LED pin to high)
    2. delay 500ms
    3. turn off(set LED pin to low)
    4. delay 500ms
const LED: u8 = 13;

pub fn main() void {
    gpio.setMode(LED, .output);

    while (true) {
        gpio.setPin(LED, .high);
        arduino.cpu.delayMilliseconds(500);
        gpio.setPin(LED, .low);
        arduino.cpu.delayMilliseconds(500);
    }
}

All code is here:

const arduino = @import("arduino");
const gpio = arduino.gpio;

// Necessary, and has the side effect of pulling in the needed _start method
pub const panic = arduino.start.panicHang;

const LED: u8 = 13;

pub fn main() void {
    gpio.setMode(LED, .output);

    while (true) {
        gpio.setPin(LED, .high);
        arduino.cpu.delayMilliseconds(500);
        gpio.setPin(LED, .low);
        arduino.cpu.delayMilliseconds(500);
    }
}

2. Zig project setup

zig init-exe
zigmod init

zigmod is a package manager for zig, you might need to install it first. I recommended you just install it from source.

Now, you should have a tree

.
├── README.md
├── build.zig
├── src
│   └── main.zig
└── zig.mod

Add the line - src: git https://github.com/dannypsnl/avr-arduino-zig after dev_dependencies: in zig.mod. Example:

id: <your id>
name: <your project name>
license: <your license>
description: <your description>
dev_dependencies:
- src: git https://github.com/dannypsnl/avr-arduino-zig

Then you run zigmod fetch, the command creates deps.zig in the current directory, where deps.addAllTo(exe) will let @import("arduino") work. Now, you are able to modify build.zig to fit this project!

pub fn build(b: *std.build.Builder) !void {
    const uno = std.zig.CrossTarget{
        .cpu_arch = .avr,
        .cpu_model = .{ .explicit = &std.Target.avr.cpu.atmega328p },
        .os_tag = .freestanding,
        .abi = .none,
    };

    const exe = b.addExecutable("blink", "main.zig");
    deps.addAllTo(exe);
    exe.setTarget(uno);
    exe.setBuildMode(.ReleaseSmall);
    exe.bundle_compiler_rt = false;
    exe.setLinkerScriptPath(.{ .path = deps.dirs._ie76bs50j4tl ++ "/src/linker.ld" });
    exe.install();
    // ...
}

This is enough to get zig-out/bin/blink after run zig build. You can run command like avrdude -carduino -patmega328p -D -P /dev/<your device> -Uflash:w:./zig-out/bin/blink:e to upload binary and see result, so the following section is optional.

2.1. Custom command in build.zig

pub fn build(b: *std.build.Builder) !void {
    // ...
    const tty = b.option(
        []const u8,
        "tty",
        "Specify the port to which the Arduino is connected (defaults to /dev/ttyACM0)",
    ) orelse "/dev/ttyACM0";

    const bin_path = b.getInstallPath(exe.install_step.?.dest_dir, exe.out_filename);
    const flash = blk: {
        var tmp = std.ArrayList(u8).init(b.allocator);
        try tmp.appendSlice("-Uflash:w:");
        try tmp.appendSlice(bin_path);
        try tmp.appendSlice(":e");
        break :blk tmp.toOwnedSlice();
    };
    const avrdude = b.addSystemCommand(&.{
        "avrdude",
        "-carduino",
        "-patmega328p",
        "-D",
        "-P",
        tty,
        flash,
    });
    const upload = b.step("upload", "Upload the code to an Arduino device using avrdude");
    upload.dependOn(&avrdude.step);
    avrdude.step.dependOn(&exe.install_step.?.step);
}

The point is addSystemCommand, which allowed you to add arbitrary command into build script! flash is unfortunately, but we always need allocator for a runtime string.

Date: 2022-01-04 Tue 00:00
Author: Lîm Tsú-thuàn