//! Demo application: prove the toolchain, then blink. //! //! Nothing here is framework code. There is no FreeRTOS, no `app_main`, no libc and no //! `compiler_rt`: `_start` is the reset entry the bootloader jumps to, the peripherals are the //! comptime register model in soc.zig, and `printf` is a mask-ROM address. const std = @import("std"); const config = @import("config"); const soc = @import("soc"); const led: u6 = @intCast(config.led_pin); /// Zero-initialised statics live in L2MEM, which the bootloader does not clear. `_start` clears /// them, and this array exists so the board can prove it happened. export var bss_probe: [64]u32 = @splat(0); export fn zig_main() noreturn { // readback: enable the pad's input buffer too, so the pin can be sampled while it is driven - // which is what makes the blink self-verifying rather than hopeful. soc.gpio.configureOutput(led, .{ .readback = true }); soc.rom.print("\r\nMARK ZIG_P4 toolchain=zig-only, no cmake, no esptool, no external linker\r\n", .{}); soc.rom.print("MARK ZIG_P4_ROM ets_printf@0x%x reached from zig\r\n", .{@as(u32, @intFromPtr(&soc.rom.ets_printf))}); var bss_or: u32 = 0; for (&bss_probe) |*w| bss_or |= @as(*volatile u32, w).*; soc.rom.print("MARK ZIG_P4_BSS or-of-64-words=0x%08x expect=0x00000000\r\n", .{bss_or}); // A comptime hash of a compile-time string, checked against a value computed on the host: // if the ABI or the linker script were wrong, this would not match. const fnv = comptime fnv1a("0x4200.cafe"); soc.rom.print("MARK ZIG_P4_FNV fnv1a(0x4200.cafe)=0x%08x expect=0x68440dea\r\n", .{fnv}); // A float the compiler cannot fold: loaded through a volatile pointer, so the arithmetic really // happens at run time as fcvt.s.wu/fmul.s/fadd.s. Without the mstatus.FS write in _start this // line is an unhandled illegal instruction - and with a constant the compiler would fold it // away and the check would pass on a board whose FPU is still off. var seven: u32 = 7; var fx: f32 = @floatFromInt(@as(*volatile u32, &seven).*); fx = fx * 1.5 + 0.25; soc.rom.print("MARK ZIG_P4_FPU 7*1.5+0.25=%u.%u expect=10.75\r\n", .{ @as(u32, @intFromFloat(fx)), @as(u32, @intFromFloat((fx - @trunc(fx)) * 100)), }); const t0 = soc.cycles(); soc.rom.ets_delay_us(1000); const per_ms = soc.cycles() - t0; soc.rom.print("MARK ZIG_P4_CLOCK %u cycles per ms\r\n", .{@as(u32, @intCast(per_ms))}); var beat: u32 = 0; while (true) : (beat += 1) { soc.gpio.setHigh(led); const high = soc.gpio.getLevel(led); // sampled while driven high, so it proves the toggle soc.rom.ets_delay_us(500_000); soc.gpio.setLow(led); const low = soc.gpio.getLevel(led); soc.rom.ets_delay_us(500_000); if (beat % 4 == 0) { soc.rom.print("MARK ZIG_P4_ALIVE beat=%u gpio%u high=%u low=%u\r\n", .{ beat, @as(u32, led), @as(u32, high), @as(u32, low), }); } } } fn fnv1a(comptime s: []const u8) u32 { var h: u32 = 0x811c9dc5; for (s) |b| { h ^= b; h = h *% 0x0100_0193; } return h; } /// Reset entry. The bootloader hands over with an unspecified stack pointer and with the FPU /// switched off, so: enable the F extension (mstatus.FS = Initial - the target includes `f`, and /// ESP-IDF only ever enables the unit lazily from a trap handler, which this image does not have), /// establish a stack in L2MEM, clear .bss, then call into Zig proper. export fn _start() linksection(".text.entry") callconv(.naked) noreturn { asm volatile ( \\ li t0, 1 << 13 \\ csrs mstatus, t0 \\ la sp, __stack_top \\ mv fp, sp \\ la t0, __bss_start \\ la t1, __bss_end \\ bgeu t0, t1, 2f \\1: \\ sw zero, 0(t0) \\ addi t0, t0, 4 \\ bltu t0, t1, 1b \\2: \\ j zig_main ); } /// The current spelling: std wraps a bare three-argument `panic` in a compatibility shim whose own /// comment calls it deprecated. pub const panic = std.debug.FullPanic(struct { fn call(msg: []const u8, _: ?usize) noreturn { soc.rom.print("MARK ZIG_P4_PANIC %s\r\n", .{msg.ptr}); while (true) {} } }.call);