From a7e062578d2954538d704ed16a928c718cb65aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:17:44 -0400 Subject: [PATCH 01/22] avr: Add AVR25 CPU support --- core/build.zig | 1 + core/src/cpus/avr25.zig | 165 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 core/src/cpus/avr25.zig diff --git a/core/build.zig b/core/build.zig index a1fcce0d6..5a1d297dd 100644 --- a/core/build.zig +++ b/core/build.zig @@ -4,6 +4,7 @@ pub fn build(b: *std.Build) !void { b.addNamedLazyPath("cpu_cortex_m", b.path("src/cpus/cortex_m.zig")); b.addNamedLazyPath("cpu_riscv32", b.path("src/cpus/riscv32.zig")); b.addNamedLazyPath("cpu_avr5", b.path("src/cpus/avr5.zig")); + b.addNamedLazyPath("cpu_avr25", b.path("src/cpus/avr25.zig")); b.addNamedLazyPath("cpu_msp430", b.path("src/cpus/msp430.zig")); b.addNamedLazyPath("cpu_msp430x", b.path("src/cpus/msp430x.zig")); diff --git a/core/src/cpus/avr25.zig b/core/src/cpus/avr25.zig new file mode 100644 index 000000000..ee79a1498 --- /dev/null +++ b/core/src/cpus/avr25.zig @@ -0,0 +1,165 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +pub const interrupt = struct { + pub fn enable_interrupts() void { + asm volatile ("sei"); + } + + pub fn disable_interrupts() void { + asm volatile ("cli"); + } +}; + +/// AVR interrupt handler function type. +pub const HandlerFn = *const fn () callconv(.avr_signal) void; + +/// Complete list of interrupt values based on the chip's `interrupts` array. +pub const Interrupt = microzig.utilities.GenerateInterruptEnum(i32); + +/// Allowable `interrupt` options for microzig.options. +pub const InterruptOptions = microzig.utilities.GenerateInterruptOptions(&.{ + .{ .InterruptEnum = Interrupt, .HandlerFn = HandlerFn }, +}); + +pub inline fn sbi(comptime reg: u5, comptime bit: u3) void { + asm volatile ("sbi %[reg], %[bit]" + : + : [reg] "I" (reg), + [bit] "I" (bit), + ); +} + +pub inline fn cbi(comptime reg: u5, comptime bit: u3) void { + asm volatile ("cbi %[reg], %[bit]" + : + : [reg] "I" (reg), + [bit] "I" (bit), + ); +} + +pub const vector_table_asm = blk: { + const fields = std.meta.fields(microzig.chip.VectorTable); + std.debug.assert(std.mem.eql(u8, "RESET", fields[0].name)); + // avr25 devices use rjmp (2-byte) instead of jmp (4-byte) + var asm_str: []const u8 = "rjmp microzig_start\n"; + + const interrupt_options = microzig.options.interrupts; + + for (fields[1..]) |field| { + const handler = @field(interrupt_options, field.name); + if (handler) |func| { + const isr = make_isr_handler(field.name, func); + asm_str = asm_str ++ "rjmp " ++ isr.exported_name ++ "\n"; + } else { + asm_str = asm_str ++ "rjmp microzig_unhandled_vector\n"; + } + } + + break :blk asm_str; +}; + +fn vector_table() linksection("microzig_flash_start") callconv(.naked) noreturn { + asm volatile (vector_table_asm); +} + +// @breakpoint() on AVR is calling abort, so we export simple function that is calling hang +export fn abort() noreturn { + microzig.hang(); +} + +pub fn export_startup_logic() void { + _ = startup_logic; + @export(&vector_table, .{ + .name = "_start", + }); +} + +fn make_isr_handler(comptime name: []const u8, comptime func: anytype) type { + const calling_convention = switch (@typeInfo(@TypeOf(func))) { + .@"fn" => |info| info.calling_convention, + .pointer => |info| switch (@typeInfo(info.child)) { + .@"fn" => |fn_info| fn_info.calling_convention, + else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"), + }, + else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"), + }; + + switch (calling_convention) { + .auto, .avr_signal, .avr_interrupt => {}, + else => @compileError("Calling conventions for interrupts must be 'avr_interrupt', 'avr_signal', or unspecified. The avr_signal calling convention leaves global interrupts disabled during the ISR, where avr_interrupt enables global interrupts for nested ISRs."), + } + + return struct { + pub const exported_name = "microzig_isr_" ++ name; + + comptime { + @export(func, .{ .name = exported_name }); + } + }; +} + +pub const startup_logic = struct { + export fn microzig_unhandled_vector() callconv(.c) noreturn { + @panic("Unhandled interrupt"); + } + + extern fn microzig_main() noreturn; + + export fn microzig_start() callconv(.c) noreturn { + // At startup the stack pointer is at the end of RAM + // so, no need to set it manually! + + copy_data_to_ram(); + clear_bss(); + + microzig_main(); + } + + fn copy_data_to_ram() void { + asm volatile ( + \\ ; load Z register with the address of the data in flash + \\ ldi r30, lo8(microzig_data_load_start) + \\ ldi r31, hi8(microzig_data_load_start) + \\ ; load X register with address of the data in ram + \\ ldi r26, lo8(microzig_data_start) + \\ ldi r27, hi8(microzig_data_start) + \\ ; load address of end of the data in ram + \\ ldi r24, lo8(microzig_data_end) + \\ ldi r25, hi8(microzig_data_end) + \\ rjmp .L2 + \\ + \\.L1: + \\ lpm r18, Z+ ; copy from Z into r18 and increment Z + \\ st X+, r18 ; store r18 at location X and increment X + \\ + \\.L2: + \\ cp r26, r24 + \\ cpc r27, r25 ; check and branch if we are at the end of data + \\ brne .L1 + ); + // Probably a good idea to add clobbers here, but compiler doesn't seem to care + } + + fn clear_bss() void { + asm volatile ( + \\ ; load X register with the beginning of bss section + \\ ldi r26, lo8(microzig_bss_start) + \\ ldi r27, hi8(microzig_bss_start) + \\ ; load end of the bss in registers + \\ ldi r24, lo8(microzig_bss_end) + \\ ldi r25, hi8(microzig_bss_end) + \\ ldi r18, 0x00 + \\ rjmp .L4 + \\ + \\.L3: + \\ st X+, r18 + \\ + \\.L4: + \\ cp r26, r24 + \\ cpc r27, r25 ; check and branch if we are at the end of bss + \\ brne .L3 + ); + // Probably a good idea to add clobbers here, but compiler doesn't seem to care + } +}; From 9249ea8b33f8dfeedd77b0b38c595c0b9492e5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:17:49 -0400 Subject: [PATCH 02/22] avr5: Use generated interrupt vector table --- core/src/cpus/avr5.zig | 65 +++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/core/src/cpus/avr5.zig b/core/src/cpus/avr5.zig index 75850922e..c061c3c9c 100644 --- a/core/src/cpus/avr5.zig +++ b/core/src/cpus/avr5.zig @@ -11,6 +11,17 @@ pub const interrupt = struct { } }; +/// AVR interrupt handler function type. +pub const HandlerFn = *const fn () callconv(.avr_signal) void; + +/// Complete list of interrupt values based on the chip's `interrupts` array. +pub const Interrupt = microzig.utilities.GenerateInterruptEnum(i32); + +/// Allowable `interrupt` options for microzig.options. +pub const InterruptOptions = microzig.utilities.GenerateInterruptOptions(&.{ + .{ .InterruptEnum = Interrupt, .HandlerFn = HandlerFn }, +}); + pub inline fn sbi(comptime reg: u5, comptime bit: u3) void { asm volatile ("sbi %[reg], %[bit]" : @@ -28,31 +39,26 @@ pub inline fn cbi(comptime reg: u5, comptime bit: u3) void { } pub const vector_table_asm = blk: { - std.debug.assert(std.mem.eql(u8, "RESET", std.meta.fields(microzig.chip.VectorTable)[0].name)); - const asm_str: []const u8 = "jmp microzig_start\n"; - - //const has_interrupts = @hasDecl(root, "microzig_options"); - //for (@typeInfo(root.VectorTableOptions).@"struct".fields) |field| { - // const new_insn = if (has_interrupts) overload: { - // const interrupts = root.microzig_options.interrupts; - // if (@hasDecl(interrupts, field.name)) { - // const handler = @field(interrupts, field.name); - - // const isr = make_isr_handler(field.name, handler); - - // break :overload "jmp " ++ isr.exported_name; - // } else { - // break :overload "jmp microzig_unhandled_vector"; - // } - // } else "jmp microzig_unhandled_vector"; - - // asm_str = asm_str ++ new_insn ++ "\n"; - //} + const fields = std.meta.fields(microzig.chip.VectorTable); + std.debug.assert(std.mem.eql(u8, "RESET", fields[0].name)); + var asm_str: []const u8 = "jmp microzig_start\n"; + + const interrupt_options = microzig.options.interrupts; + + for (fields[1..]) |field| { + const handler = @field(interrupt_options, field.name); + if (handler) |func| { + const isr = make_isr_handler(field.name, func); + asm_str = asm_str ++ "jmp " ++ isr.exported_name ++ "\n"; + } else { + asm_str = asm_str ++ "jmp microzig_unhandled_vector\n"; + } + } break :blk asm_str; }; -fn vector_table() callconv(.naked) noreturn { +fn vector_table() linksection("microzig_flash_start") callconv(.naked) noreturn { asm volatile (vector_table_asm); } @@ -70,25 +76,24 @@ pub fn export_startup_logic() void { fn make_isr_handler(comptime name: []const u8, comptime func: anytype) type { const calling_convention = switch (@typeInfo(@TypeOf(func))) { - .Fn => |info| info.calling_convention, + .@"fn" => |info| info.calling_convention, + .pointer => |info| switch (@typeInfo(info.child)) { + .@"fn" => |fn_info| fn_info.calling_convention, + else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"), + }, else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"), }; switch (calling_convention) { - .Unspecified, .Signal, .Interrupt => {}, - else => @compileError("Calling conventions for interrupts must be 'Interrupt', 'Signal', or unspecified. The signal calling convention leaves global interrupts disabled during the ISR, where the interrupt calling conventions enables global interrupts for nested ISRs."), + .auto, .avr_signal, .avr_interrupt => {}, + else => @compileError("Calling conventions for interrupts must be 'avr_interrupt', 'avr_signal', or unspecified. The avr_signal calling convention leaves global interrupts disabled during the ISR, where avr_interrupt enables global interrupts for nested ISRs."), } return struct { pub const exported_name = "microzig_isr_" ++ name; - pub fn isr_vector() callconv(.Signal) void { - @call(.always_inline, func, .{}); - } - comptime { - const options = .{ .name = exported_name, .linkage = .Strong }; - @export(&isr_vector, options); + @export(func, .{ .name = exported_name }); } }; } From 1422fa98cbbe696d1d6b4fb6e4083ee72ab7a9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:17:53 -0400 Subject: [PATCH 03/22] build.zig: Register ATtiny port --- build.zig | 28 +++++++++++++++++----------- build.zig.zon | 1 + 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/build.zig b/build.zig index 06c634afd..8b05770f9 100644 --- a/build.zig +++ b/build.zig @@ -14,8 +14,6 @@ pub const LinkerScript = internals.LinkerScript; pub const Stack = internals.Stack; pub const MemoryRegion = internals.MemoryRegion; -const regz = @import("tools/regz"); - // If more ports are available, the error "error: evaluation exceeded 1000 backwards branches" may occur. // In such cases, consider increasing the argument value for @setEvalBranchQuota(). const port_list: []const struct { @@ -26,6 +24,7 @@ const port_list: []const struct { .{ .name = "gd32", .dep_name = "port/gigadevice/gd32" }, .{ .name = "samd51", .dep_name = "port/microchip/samd51" }, .{ .name = "atmega", .dep_name = "port/microchip/atmega" }, + .{ .name = "attiny", .dep_name = "port/microchip/attiny" }, .{ .name = "nrf5x", .dep_name = "port/nordic/nrf5x" }, .{ .name = "lpc", .dep_name = "port/nxp/lpc" }, .{ .name = "mcx", .dep_name = "port/nxp/mcx" }, @@ -36,15 +35,6 @@ const port_list: []const struct { .{ .name = "tm4c", .dep_name = "port/texasinstruments/tm4c" }, }; -const exe_targets: []const std.Target.Query = &.{ - .{ .cpu_arch = .aarch64, .os_tag = .macos }, - .{ .cpu_arch = .aarch64, .os_tag = .linux }, - .{ .cpu_arch = .aarch64, .os_tag = .windows }, - .{ .cpu_arch = .x86_64, .os_tag = .macos }, - .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }, - .{ .cpu_arch = .x86_64, .os_tag = .windows }, -}; - pub fn build(b: *Build) void { const optimize = b.standardOptimizeOption(.{}); @@ -81,6 +71,7 @@ pub const PortSelect = struct { gd32: bool = false, samd51: bool = false, atmega: bool = false, + attiny: bool = false, nrf5x: bool = false, lpc: bool = false, mcx: bool = false, @@ -771,6 +762,21 @@ pub fn MicroBuild(port_select: PortSelect) type { .name = "avr5", .root_source_file = mb.core_dep.namedLazyPath("cpu_avr5"), }; + } else if (std.mem.eql(u8, target.cpu.model.name, "avr25")) { + return .{ + .name = "avr25", + .root_source_file = mb.core_dep.namedLazyPath("cpu_avr25"), + }; + } else if (std.mem.eql(u8, target.cpu.model.name, "avr35")) { + return .{ + .name = "avr35", + .root_source_file = mb.core_dep.namedLazyPath("cpu_avr5"), + }; + } else if (std.mem.eql(u8, target.cpu.model.name, "avrxmega3")) { + return .{ + .name = "avrxmega3", + .root_source_file = mb.core_dep.namedLazyPath("cpu_avr5"), + }; } else if (std.mem.startsWith(u8, target.cpu.model.name, "cortex_m")) { return .{ .name = target.cpu.model.name, diff --git a/build.zig.zon b/build.zig.zon index 30dde66c6..41e32ae25 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -31,6 +31,7 @@ .@"port/espressif/esp" = .{ .path = "port/espressif/esp", .lazy = true }, .@"port/gigadevice/gd32" = .{ .path = "port/gigadevice/gd32", .lazy = true }, .@"port/microchip/atmega" = .{ .path = "port/microchip/atmega", .lazy = true }, + .@"port/microchip/attiny" = .{ .path = "port/microchip/attiny", .lazy = true }, .@"port/microchip/samd51" = .{ .path = "port/microchip/samd51", .lazy = true }, .@"port/nordic/nrf5x" = .{ .path = "port/nordic/nrf5x", .lazy = true }, .@"port/nxp/lpc" = .{ .path = "port/nxp/lpc", .lazy = true }, From 638f0f4d76326b717990b3e2d909729a8a42ba8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:17:58 -0400 Subject: [PATCH 04/22] attiny: Add package manifest and README --- port/microchip/attiny/README.md | 18 ++++++++++++++++++ port/microchip/attiny/build.zig.zon | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 port/microchip/attiny/README.md create mode 100644 port/microchip/attiny/build.zig.zon diff --git a/port/microchip/attiny/README.md b/port/microchip/attiny/README.md new file mode 100644 index 000000000..3f7fceaae --- /dev/null +++ b/port/microchip/attiny/README.md @@ -0,0 +1,18 @@ +# Microchip ATtiny Hardware Support Package + +## Supported Chips + +- ATtiny85 +- ATtiny84 +- ATtiny1634 +- ATtiny1616 + +## FYI: LLVM issues + +Currently LLVM is having trouble lowering AVR when this is built in debug mode. + +For now always build in release small: + +``` +zig build -Doptimize=ReleaseSmall +``` diff --git a/port/microchip/attiny/build.zig.zon b/port/microchip/attiny/build.zig.zon new file mode 100644 index 000000000..1a07fdf96 --- /dev/null +++ b/port/microchip/attiny/build.zig.zon @@ -0,0 +1,18 @@ +.{ + .name = .mz_port_microchip_attiny, + .fingerprint = 0x6455fe5728af26e9, + .version = "0.0.0", + .dependencies = .{ + .@"microzig/build-internals" = .{ .path = "../../../build-internals" }, + .atpack = .{ + .url = "https://atpack.microzig.tech/Atmel.ATtiny_DFP.2.0.368.atpack", + .hash = "N-V-__8AAChkfwZp4ZmDp0teMCY8Q-2clc9GmzWHeh7xiKnO", + }, + }, + .paths = .{ + "README.md", + "build.zig", + "build.zig.zon", + "src", + }, +} From 8b2a435db8a47737a187fee537bac795033b4eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:03 -0400 Subject: [PATCH 05/22] attiny: Define chip targets --- port/microchip/attiny/build.zig | 168 ++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 port/microchip/attiny/build.zig diff --git a/port/microchip/attiny/build.zig b/port/microchip/attiny/build.zig new file mode 100644 index 000000000..3998673ef --- /dev/null +++ b/port/microchip/attiny/build.zig @@ -0,0 +1,168 @@ +const std = @import("std"); +const microzig = @import("microzig/build-internals"); + +const Self = @This(); + +chips: struct { + attiny85: *const microzig.Target, + attiny84: *const microzig.Target, + attiny1634: *const microzig.Target, + attiny1616: *const microzig.Target, +}, + +boards: struct { + digispark: *const microzig.Target, + adafruit: struct { + trinket: *const microzig.Target, + gemma: *const microzig.Target, + }, +}, + +pub fn init(dep: *std.Build.Dependency) Self { + const b = dep.builder; + + const atpack = b.dependency("atpack", .{}); + + const avr25_target: std.Target.Query = .{ + .cpu_arch = .avr, + .cpu_model = .{ .explicit = &std.Target.avr.cpu.avr25 }, + .os_tag = .freestanding, + .abi = .eabi, + }; + + const avr35_target: std.Target.Query = .{ + .cpu_arch = .avr, + .cpu_model = .{ .explicit = &std.Target.avr.cpu.avr35 }, + .os_tag = .freestanding, + .abi = .eabi, + }; + + const avrxmega3_target: std.Target.Query = .{ + .cpu_arch = .avr, + .cpu_model = .{ .explicit = &std.Target.avr.cpu.avrxmega3 }, + .os_tag = .freestanding, + .abi = .eabi, + }; + + const chip_attiny85: microzig.Target = .{ + .dep = dep, + .preferred_binary_format = .hex, + .zig_target = avr25_target, + .chip = .{ + .name = "ATtiny85", + .url = "https://www.microchip.com/en-us/product/attiny85", + .register_definition = .{ + .atdf = atpack.path("atdf/ATtiny85.atdf"), + }, + .memory_regions = &.{ + .{ .tag = .flash, .offset = 0x000000, .length = 8 * 1024, .access = .rx }, + .{ .tag = .ram, .offset = 0x800060, .length = 512, .access = .rw }, + }, + }, + .hal = .{ + .root_source_file = b.path("src/hals/ATtiny85.zig"), + }, + .bundle_compiler_rt = false, + }; + + const chip_attiny84: microzig.Target = .{ + .dep = dep, + .preferred_binary_format = .hex, + .zig_target = avr25_target, + .chip = .{ + .name = "ATtiny84", + .url = "https://www.microchip.com/en-us/product/attiny84", + .register_definition = .{ + .atdf = atpack.path("atdf/ATtiny84.atdf"), + }, + .memory_regions = &.{ + .{ .tag = .flash, .offset = 0x000000, .length = 8 * 1024, .access = .rx }, + .{ .tag = .ram, .offset = 0x800060, .length = 512, .access = .rw }, + }, + }, + .hal = .{ + .root_source_file = b.path("src/hals/ATtiny84.zig"), + }, + .bundle_compiler_rt = false, + }; + + const chip_attiny1634: microzig.Target = .{ + .dep = dep, + .preferred_binary_format = .hex, + .zig_target = avr35_target, + .chip = .{ + .name = "ATtiny1634", + .url = "https://www.microchip.com/en-us/product/attiny1634", + .register_definition = .{ + .atdf = atpack.path("atdf/ATtiny1634.atdf"), + }, + .memory_regions = &.{ + .{ .tag = .flash, .offset = 0x000000, .length = 16 * 1024, .access = .rx }, + .{ .tag = .ram, .offset = 0x800100, .length = 1024, .access = .rw }, + }, + }, + .hal = .{ + .root_source_file = b.path("src/hals/ATtiny1634.zig"), + }, + .bundle_compiler_rt = false, + }; + + const chip_attiny1616: microzig.Target = .{ + .dep = dep, + .preferred_binary_format = .hex, + .zig_target = avrxmega3_target, + .chip = .{ + .name = "ATtiny1616", + .url = "https://www.microchip.com/en-us/product/attiny1616", + .register_definition = .{ + .atdf = atpack.path("atdf/ATtiny1616.atdf"), + }, + .memory_regions = &.{ + .{ .tag = .flash, .offset = 0x000000, .length = 16 * 1024, .access = .rx }, + .{ .tag = .ram, .offset = 0x803800, .length = 2048, .access = .rw }, + }, + }, + .hal = .{ + .root_source_file = b.path("src/hals/ATtiny1616.zig"), + }, + .bundle_compiler_rt = false, + }; + + return .{ + .chips = .{ + .attiny85 = chip_attiny85.derive(.{}), + .attiny84 = chip_attiny84.derive(.{}), + .attiny1634 = chip_attiny1634.derive(.{}), + .attiny1616 = chip_attiny1616.derive(.{}), + }, + .boards = .{ + .digispark = chip_attiny85.derive(.{ + .board = .{ + .name = "Digispark", + .url = "http://digistump.com/products/1", + .root_source_file = b.path("src/boards/digispark.zig"), + }, + }), + .adafruit = .{ + .trinket = chip_attiny85.derive(.{ + .board = .{ + .name = "Adafruit Trinket", + .url = "https://www.adafruit.com/product/1501", + .root_source_file = b.path("src/boards/adafruit_trinket.zig"), + }, + }), + .gemma = chip_attiny85.derive(.{ + .board = .{ + .name = "Adafruit Gemma", + .url = "https://www.adafruit.com/product/1222", + .root_source_file = b.path("src/boards/adafruit_gemma.zig"), + }, + }), + }, + }, + }; +} + +pub fn build(b: *std.Build) void { + _ = b.step("test", "Run platform agnostic unit tests"); +} From 8d3fce888f33223b4d874f69b184054b351ae807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:07 -0400 Subject: [PATCH 06/22] attiny: Add small board definitions --- .../attiny/src/boards/adafruit_gemma.zig | 13 +++++++++++++ .../attiny/src/boards/adafruit_trinket.zig | 15 +++++++++++++++ port/microchip/attiny/src/boards/digispark.zig | 17 +++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 port/microchip/attiny/src/boards/adafruit_gemma.zig create mode 100644 port/microchip/attiny/src/boards/adafruit_trinket.zig create mode 100644 port/microchip/attiny/src/boards/digispark.zig diff --git a/port/microchip/attiny/src/boards/adafruit_gemma.zig b/port/microchip/attiny/src/boards/adafruit_gemma.zig new file mode 100644 index 000000000..24da7ac8c --- /dev/null +++ b/port/microchip/attiny/src/boards/adafruit_gemma.zig @@ -0,0 +1,13 @@ +pub const chip = @import("chip"); + +pub const clock_frequencies = .{ + .cpu = 8_000_000, +}; + +pub const pin_map = .{ + .D0 = "PB0", + .D1 = "PB1", + .D2 = "PB2", + // Built-in LED on D1 (PB1) + .LED = "PB1", +}; diff --git a/port/microchip/attiny/src/boards/adafruit_trinket.zig b/port/microchip/attiny/src/boards/adafruit_trinket.zig new file mode 100644 index 000000000..afb2c3437 --- /dev/null +++ b/port/microchip/attiny/src/boards/adafruit_trinket.zig @@ -0,0 +1,15 @@ +pub const chip = @import("chip"); + +pub const clock_frequencies = .{ + .cpu = 8_000_000, +}; + +pub const pin_map = .{ + .P0 = "PB0", + .P1 = "PB1", + .P2 = "PB2", + .P3 = "PB3", + .P4 = "PB4", + // Built-in LED on P1 (PB1) + .LED = "PB1", +}; diff --git a/port/microchip/attiny/src/boards/digispark.zig b/port/microchip/attiny/src/boards/digispark.zig new file mode 100644 index 000000000..8245210fc --- /dev/null +++ b/port/microchip/attiny/src/boards/digispark.zig @@ -0,0 +1,17 @@ +pub const chip = @import("chip"); + +pub const clock_frequencies = .{ + .cpu = 16_500_000, +}; + +pub const pin_map = .{ + // Digispark pin numbering maps to PORTB + .P0 = "PB0", + .P1 = "PB1", + .P2 = "PB2", + .P3 = "PB3", + .P4 = "PB4", + .P5 = "PB5", + // Built-in LED on P1 (PB1) + .LED = "PB1", +}; From d7c1c0fb28a6bce4c20aa09f3209441e797a50bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:17 -0400 Subject: [PATCH 07/22] attiny: Add ATtiny84 and ATtiny85 HAL roots --- port/microchip/attiny/src/hals/ATtiny84.zig | 71 +++++++++++++++++++++ port/microchip/attiny/src/hals/ATtiny85.zig | 17 +++++ 2 files changed, 88 insertions(+) create mode 100644 port/microchip/attiny/src/hals/ATtiny84.zig create mode 100644 port/microchip/attiny/src/hals/ATtiny85.zig diff --git a/port/microchip/attiny/src/hals/ATtiny84.zig b/port/microchip/attiny/src/hals/ATtiny84.zig new file mode 100644 index 000000000..c88226f3e --- /dev/null +++ b/port/microchip/attiny/src/hals/ATtiny84.zig @@ -0,0 +1,71 @@ +const microzig = @import("microzig"); +const cpu = microzig.cpu; + +pub const gpio = struct { + pub const Port = enum(u1) { + a = 0, + b = 1, + + pub const Regs = extern struct { + /// Port Input Pins + PIN: u8, + /// Port Data Direction Register + DDR: u8, + /// Port Data Register + PORT: u8, + }; + + // IO addresses (data address - 0x20) for SBI/CBI instructions. + // PINB=0x16, DDRB=0x17, PORTB=0x18 + // PINA=0x19, DDRA=0x1A, PORTA=0x1B + pub inline fn get_regs(port: Port) *volatile Regs { + return switch (port) { + .b => @ptrFromInt(0x16), + .a => @ptrFromInt(0x19), + }; + } + }; + + pub fn pin(port: Port, num: u3) Pin { + return Pin{ + .port = port, + .num = num, + }; + } + + pub const Direction = enum { + input, + output, + }; + + pub const Pin = packed struct(u4) { + port: Port, + num: u3, + + pub inline fn set_direction(p: Pin, dir: Direction) void { + const dir_addr: *volatile u8 = &p.port.get_regs().DDR; + switch (dir) { + .input => cpu.cbi(@intFromPtr(dir_addr), p.num), + .output => cpu.sbi(@intFromPtr(dir_addr), p.num), + } + } + + pub inline fn read(p: Pin) u1 { + const pin_addr: *volatile u8 = &p.port.get_regs().PIN; + return @truncate(pin_addr.* >> p.num & 0x01); + } + + pub inline fn put(p: Pin, value: u1) void { + const port_addr: *volatile u8 = &p.port.get_regs().PORT; + switch (value) { + 1 => cpu.sbi(@intFromPtr(port_addr), p.num), + 0 => cpu.cbi(@intFromPtr(port_addr), p.num), + } + } + + pub inline fn toggle(p: Pin) void { + const pin_addr: *volatile u8 = &p.port.get_regs().PIN; + cpu.sbi(@intFromPtr(pin_addr), p.num); + } + }; +}; diff --git a/port/microchip/attiny/src/hals/ATtiny85.zig b/port/microchip/attiny/src/hals/ATtiny85.zig new file mode 100644 index 000000000..258271311 --- /dev/null +++ b/port/microchip/attiny/src/hals/ATtiny85.zig @@ -0,0 +1,17 @@ +pub const registers = @import("attiny85/registers.zig"); + +pub const gpio = @import("attiny85/gpio.zig"); +pub const timer0 = @import("attiny85/timer0.zig"); +pub const timer1 = @import("attiny85/timer1.zig"); +pub const adc = @import("attiny85/adc.zig"); +pub const watchdog = @import("attiny85/watchdog.zig"); +pub const sleep = @import("attiny85/sleep.zig"); +pub const pcint = @import("attiny85/pcint.zig"); +pub const eeprom = @import("attiny85/eeprom.zig"); +pub const progmem = @import("attiny85/progmem.zig"); + +pub const memory = struct { + pub const flash_size = 8 * 1024; + pub const eeprom_size = 512; + pub const sram_size = 512; +}; From ae8dbdba3128fa71f97e429984e62fad77cc1646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:23 -0400 Subject: [PATCH 08/22] attiny85: Add register and GPIO helpers --- .../attiny/src/hals/attiny85/gpio.zig | 68 ++++++++++++++ .../attiny/src/hals/attiny85/registers.zig | 92 +++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny85/gpio.zig create mode 100644 port/microchip/attiny/src/hals/attiny85/registers.zig diff --git a/port/microchip/attiny/src/hals/attiny85/gpio.zig b/port/microchip/attiny/src/hals/attiny85/gpio.zig new file mode 100644 index 000000000..ec576776a --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/gpio.zig @@ -0,0 +1,68 @@ +const cpu = @import("microzig").cpu; +const regs = @import("registers.zig"); + +pub const Port = enum(u1) { + b = 0, + + pub const Regs = extern struct { + /// Port Input Pins. + PIN: u8, + /// Port Data Direction Register. + DDR: u8, + /// Port Data Register. + PORT: u8, + }; + + // ATtiny25/45/85 datasheet section 10.4, page 63: PINB/DDRB/PORTB are + // I/O-space registers, so constant pins can use SBI/CBI. + // https://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf + pub inline fn get_regs(port: Port) *volatile Regs { + return switch (port) { + .b => @ptrFromInt(regs.PINB), + }; + } +}; + +pub const Direction = enum { + input, + output, +}; + +pub fn pin(port: Port, num: u3) Pin { + return .{ .port = port, .num = num }; +} + +pub const Pin = packed struct(u4) { + port: Port, + num: u3, + + pub inline fn set_direction(p: Pin, dir: Direction) void { + const dir_addr: *volatile u8 = &p.port.get_regs().DDR; + switch (dir) { + .input => cpu.cbi(@intFromPtr(dir_addr), p.num), + .output => cpu.sbi(@intFromPtr(dir_addr), p.num), + } + } + + pub inline fn read(p: Pin) u1 { + const pin_addr: *volatile u8 = &p.port.get_regs().PIN; + return @truncate((pin_addr.* >> p.num) & 0x01); + } + + pub inline fn put(p: Pin, value: u1) void { + const port_addr: *volatile u8 = &p.port.get_regs().PORT; + switch (value) { + 1 => cpu.sbi(@intFromPtr(port_addr), p.num), + 0 => cpu.cbi(@intFromPtr(port_addr), p.num), + } + } + + pub inline fn toggle(p: Pin) void { + const pin_addr: *volatile u8 = &p.port.get_regs().PIN; + cpu.sbi(@intFromPtr(pin_addr), p.num); + } + + pub inline fn set_pullup(p: Pin, enabled: bool) void { + p.put(@intFromBool(enabled)); + } +}; diff --git a/port/microchip/attiny/src/hals/attiny85/registers.zig b/port/microchip/attiny/src/hals/attiny85/registers.zig new file mode 100644 index 000000000..c1dce09b6 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/registers.zig @@ -0,0 +1,92 @@ +pub inline fn io(comptime address: u6) *volatile u8 { + return @ptrFromInt(address); +} + +pub inline fn read(comptime address: u6) u8 { + return io(address).*; +} + +pub inline fn write(comptime address: u6, value: u8) void { + io(address).* = value; +} + +pub inline fn setBits(comptime address: u6, mask: u8) void { + io(address).* |= mask; +} + +pub inline fn clearBits(comptime address: u6, mask: u8) void { + io(address).* &= ~mask; +} + +pub inline fn bit(comptime n: u3) u8 { + return @as(u8, 1) << n; +} + +pub const ADCSRB = 0x03; +pub const ADCL = 0x04; +pub const ADCH = 0x05; +pub const ADCSRA = 0x06; +pub const ADMUX = 0x07; +pub const DIDR0 = 0x14; +pub const PCMSK = 0x15; +pub const PINB = 0x16; +pub const DDRB = 0x17; +pub const PORTB = 0x18; +pub const EECR = 0x1C; +pub const EEDR = 0x1D; +pub const EEARL = 0x1E; +pub const EEARH = 0x1F; +pub const WDTCR = 0x21; +pub const CLKPR = 0x26; +pub const OCR0B = 0x28; +pub const OCR0A = 0x29; +pub const TCCR0A = 0x2A; +pub const OCR1B = 0x2B; +pub const GTCCR = 0x2C; +pub const OCR1C = 0x2D; +pub const OCR1A = 0x2E; +pub const TCNT1 = 0x2F; +pub const TCCR1 = 0x30; +pub const TCNT0 = 0x32; +pub const TCCR0B = 0x33; +pub const MCUSR = 0x34; +pub const MCUCR = 0x35; +pub const TIFR = 0x38; +pub const TIMSK = 0x39; +pub const GIFR = 0x3A; +pub const GIMSK = 0x3B; + +pub const adc_bits = struct { + pub const aden = 7; + pub const adsc = 6; + pub const adate = 5; + pub const adif = 4; + pub const adie = 3; +}; + +pub const eeprom_bits = struct { + pub const eepm1 = 5; + pub const eepm0 = 4; + pub const eerie = 3; + pub const eempe = 2; + pub const eepe = 1; + pub const eere = 0; +}; + +pub const watchdog_bits = struct { + pub const wdie = 6; + pub const wdp3 = 5; + pub const wdce = 4; + pub const wde = 3; + pub const wdp2 = 2; + pub const wdp1 = 1; + pub const wdp0 = 0; +}; + +pub const sleep_bits = struct { + pub const bods = 7; + pub const bodse = 2; + pub const se = 5; + pub const sm1 = 4; + pub const sm0 = 3; +}; From fdb86058fcc1853aa824554e9ed880f3da95c997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:28 -0400 Subject: [PATCH 09/22] attiny85: Add timer helpers --- .../attiny/src/hals/attiny85/timer0.zig | 60 +++++++++++++ .../attiny/src/hals/attiny85/timer1.zig | 88 +++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny85/timer0.zig create mode 100644 port/microchip/attiny/src/hals/attiny85/timer1.zig diff --git a/port/microchip/attiny/src/hals/attiny85/timer0.zig b/port/microchip/attiny/src/hals/attiny85/timer0.zig new file mode 100644 index 000000000..7741ee0cb --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/timer0.zig @@ -0,0 +1,60 @@ +const regs = @import("registers.zig"); + +pub const CompareOutput = enum(u2) { + disconnected = 0b00, + toggle = 0b01, + clear = 0b10, + set = 0b11, +}; + +pub const Waveform = enum(u3) { + normal = 0b000, + phase_correct_pwm_top_0xff = 0b001, + ctc = 0b010, + fast_pwm_top_0xff = 0b011, + phase_correct_pwm_top_ocra = 0b101, + fast_pwm_top_ocra = 0b111, +}; + +pub const Prescaler = enum(u3) { + stopped = 0b000, + clk_1 = 0b001, + clk_8 = 0b010, + clk_64 = 0b011, + clk_256 = 0b100, + clk_1024 = 0b101, + external_falling = 0b110, + external_rising = 0b111, +}; + +pub const Config = struct { + waveform: Waveform = .normal, + compare_a: CompareOutput = .disconnected, + compare_b: CompareOutput = .disconnected, + prescaler: Prescaler = .stopped, +}; + +pub fn apply(config: Config) void { + const wgm: u3 = @intFromEnum(config.waveform); + regs.write(regs.TCCR0A, (@as(u8, @intFromEnum(config.compare_a)) << 6) | + (@as(u8, @intFromEnum(config.compare_b)) << 4) | + (@as(u8, wgm) & 0b011)); + regs.write(regs.TCCR0B, ((@as(u8, wgm) & 0b100) << 1) | + @as(u8, @intFromEnum(config.prescaler))); +} + +pub inline fn setCompareA(value: u8) void { + regs.write(regs.OCR0A, value); +} + +pub inline fn setCompareB(value: u8) void { + regs.write(regs.OCR0B, value); +} + +pub inline fn counter() u8 { + return regs.read(regs.TCNT0); +} + +pub inline fn setCounter(value: u8) void { + regs.write(regs.TCNT0, value); +} diff --git a/port/microchip/attiny/src/hals/attiny85/timer1.zig b/port/microchip/attiny/src/hals/attiny85/timer1.zig new file mode 100644 index 000000000..59921aa64 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/timer1.zig @@ -0,0 +1,88 @@ +const regs = @import("registers.zig"); + +pub const Prescaler = enum(u4) { + stopped = 0b0000, + pck_1 = 0b0001, + pck_2 = 0b0010, + pck_4 = 0b0011, + pck_8 = 0b0100, + pck_16 = 0b0101, + pck_32 = 0b0110, + pck_64 = 0b0111, + pck_128 = 0b1000, + pck_256 = 0b1001, + pck_512 = 0b1010, + pck_1024 = 0b1011, + pck_2048 = 0b1100, + pck_4096 = 0b1101, + pck_8192 = 0b1110, + pck_16384 = 0b1111, +}; + +pub const CompareOutput = enum(u2) { + disconnected = 0b00, + toggle = 0b01, + clear = 0b10, + set = 0b11, +}; + +pub const PwmOutput = enum { + a, + b, +}; + +pub const Interrupt = enum(u8) { + overflow = 1 << 2, + compare_a = 1 << 6, + compare_b = 1 << 5, +}; + +pub const FastPwmConfig = struct { + output: PwmOutput, + prescaler: Prescaler = .pck_1, + compare: CompareOutput = .clear, + top: u8 = 255, +}; + +pub fn configureFastPwm(config: FastPwmConfig) void { + // ATtiny25/45/85 datasheet, sections 12.2 and 12.3: OCR1C is TOP for + // Timer/Counter1 PWM and Timer1 register writes are synchronized. + regs.write(regs.OCR1C, config.top); + + switch (config.output) { + .a => { + regs.write(regs.TCCR1, 0x40 | + (@as(u8, @intFromEnum(config.compare)) << 4) | + @as(u8, @intFromEnum(config.prescaler))); + regs.clearBits(regs.GTCCR, 0b0111_0000); + }, + .b => { + regs.write(regs.TCCR1, @as(u8, @intFromEnum(config.prescaler))); + regs.write(regs.GTCCR, 0x40 | (@as(u8, @intFromEnum(config.compare)) << 4)); + }, + } +} + +pub inline fn setCompareA(value: u8) void { + regs.write(regs.OCR1A, value); +} + +pub inline fn setCompareB(value: u8) void { + regs.write(regs.OCR1B, value); +} + +pub inline fn setTop(value: u8) void { + regs.write(regs.OCR1C, value); +} + +pub inline fn counter() u8 { + return regs.read(regs.TCNT1); +} + +pub inline fn enableInterrupt(interrupt: Interrupt) void { + regs.setBits(regs.TIMSK, @intFromEnum(interrupt)); +} + +pub inline fn disableInterrupt(interrupt: Interrupt) void { + regs.clearBits(regs.TIMSK, @intFromEnum(interrupt)); +} From 98f05e509182800b7c43c01f7ccdea0c52af7230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:35 -0400 Subject: [PATCH 10/22] attiny85: Add ADC watchdog and sleep helpers --- .../attiny/src/hals/attiny85/adc.zig | 92 +++++++++++++++++++ .../attiny/src/hals/attiny85/sleep.zig | 40 ++++++++ .../attiny/src/hals/attiny85/watchdog.zig | 64 +++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny85/adc.zig create mode 100644 port/microchip/attiny/src/hals/attiny85/sleep.zig create mode 100644 port/microchip/attiny/src/hals/attiny85/watchdog.zig diff --git a/port/microchip/attiny/src/hals/attiny85/adc.zig b/port/microchip/attiny/src/hals/attiny85/adc.zig new file mode 100644 index 000000000..a61db9ecf --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/adc.zig @@ -0,0 +1,92 @@ +const regs = @import("registers.zig"); +const sleep = @import("sleep.zig"); + +pub const Reference = enum(u3) { + vcc = 0b000, + internal_1v1 = 0b100, + internal_2v56 = 0b110, +}; + +pub const Channel = enum(u4) { + adc0 = 0b0000, + adc1 = 0b0001, + adc2 = 0b0010, + adc3 = 0b0011, + vcc_1v1 = 0b1100, + temperature = 0b1111, +}; + +pub const Prescaler = enum(u3) { + div2 = 0b001, + div4 = 0b010, + div8 = 0b011, + div16 = 0b100, + div32 = 0b101, + div64 = 0b110, + div128 = 0b111, +}; + +pub const Config = struct { + reference: Reference = .internal_1v1, + channel: Channel, + prescaler: Prescaler = .div128, + left_adjust: bool = true, + auto_trigger: bool = true, + interrupt: bool = false, +}; + +pub fn apply(config: Config) void { + // ATtiny25/45/85 datasheet, section 17.13.1: ADMUX packs reference, + // left-adjust, and channel selection in one register. + regs.write(regs.ADMUX, (@as(u8, @intFromEnum(config.reference)) << 4) | + (@as(u8, @intFromBool(config.left_adjust)) << 5) | + @as(u8, @intFromEnum(config.channel))); + + regs.write(regs.ADCSRA, regs.bit(regs.adc_bits.aden) | + (@as(u8, @intFromBool(config.auto_trigger)) << regs.adc_bits.adate) | + (@as(u8, @intFromBool(config.interrupt)) << regs.adc_bits.adie) | + @as(u8, @intFromEnum(config.prescaler))); +} + +pub inline fn useAdcNoiseReductionSleep() void { + sleep.setMode(.adc_noise_reduction); +} + +pub inline fn start() void { + regs.setBits(regs.ADCSRA, regs.bit(regs.adc_bits.adsc)); +} + +pub inline fn stop() void { + regs.clearBits(regs.ADCSRA, regs.bit(regs.adc_bits.aden)); +} + +pub inline fn conversionRunning() bool { + return (regs.read(regs.ADCSRA) & regs.bit(regs.adc_bits.adsc)) != 0; +} + +pub inline fn enableDigitalInput(channel: Channel, enabled: bool) void { + const mask: u8 = switch (channel) { + .adc0 => 1 << 5, + .adc1 => 1 << 2, + .adc2 => 1 << 4, + .adc3 => 1 << 3, + else => 0, + }; + if (enabled) regs.clearBits(regs.DIDR0, mask) else regs.setBits(regs.DIDR0, mask); +} + +pub inline fn readLeftAdjusted10() u16 { + const low = regs.read(regs.ADCL); + const high = regs.read(regs.ADCH); + return (@as(u16, high) << 2) | (@as(u16, low) >> 6); +} + +pub inline fn readRaw16() u16 { + const low = regs.read(regs.ADCL); + const high = regs.read(regs.ADCH); + return (@as(u16, high) << 8) | low; +} + +pub inline fn clearInterruptFlag() void { + regs.setBits(regs.ADCSRA, regs.bit(regs.adc_bits.adif)); +} diff --git a/port/microchip/attiny/src/hals/attiny85/sleep.zig b/port/microchip/attiny/src/hals/attiny85/sleep.zig new file mode 100644 index 000000000..6814edb2a --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/sleep.zig @@ -0,0 +1,40 @@ +const regs = @import("registers.zig"); + +pub const Mode = enum(u2) { + idle = 0b00, + adc_noise_reduction = 0b01, + power_down = 0b10, +}; + +pub inline fn setMode(mode: Mode) void { + const mask = regs.bit(regs.sleep_bits.sm1) | regs.bit(regs.sleep_bits.sm0); + regs.write(regs.MCUCR, (regs.read(regs.MCUCR) & ~mask) | + (@as(u8, @intFromEnum(mode)) << regs.sleep_bits.sm0)); +} + +pub inline fn enable() void { + regs.setBits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); +} + +pub inline fn disable() void { + regs.clearBits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); +} + +pub inline fn cpu() void { + asm volatile ("sleep" ::: .{ .memory = true }); +} + +pub inline fn enter(mode: Mode) void { + setMode(mode); + enable(); + cpu(); + disable(); +} + +pub inline fn bodDisable() void { + // ATtiny25/45/85 datasheet, section 7.2: BODS must be set and BODSE + // cleared in a timed sequence immediately before SLEEP. + const mask = regs.bit(regs.sleep_bits.bods) | regs.bit(regs.sleep_bits.bodse); + regs.setBits(regs.MCUCR, mask); + regs.write(regs.MCUCR, regs.read(regs.MCUCR) & ~regs.bit(regs.sleep_bits.bodse)); +} diff --git a/port/microchip/attiny/src/hals/attiny85/watchdog.zig b/port/microchip/attiny/src/hals/attiny85/watchdog.zig new file mode 100644 index 000000000..302593dbe --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/watchdog.zig @@ -0,0 +1,64 @@ +const microzig = @import("microzig"); +const regs = @import("registers.zig"); + +pub const Timeout = enum(u4) { + ms16 = 0b0000, + ms32 = 0b0001, + ms64 = 0b0010, + ms125 = 0b0011, + ms250 = 0b0100, + ms500 = 0b0101, + s1 = 0b0110, + s2 = 0b0111, + s4 = 0b1000, + s8 = 0b1001, +}; + +pub const Mode = enum { + interrupt, + reset, + interrupt_then_reset, +}; + +pub inline fn reset() void { + asm volatile ("wdr" ::: .{ .memory = true }); +} + +pub fn configure(mode: Mode, timeout: Timeout) void { + // ATtiny25/45/85 datasheet section 8.5.2, page 43: WDTCR writes use the + // WDCE/WDE timed sequence. This mirrors avr-libc's protected update. + // https://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf + microzig.interrupt.disable_interrupts(); + reset(); + regs.setBits(regs.WDTCR, regs.bit(regs.watchdog_bits.wdce) | regs.bit(regs.watchdog_bits.wde)); + regs.write(regs.WDTCR, controlValue(mode, timeout)); + microzig.interrupt.enable_interrupts(); +} + +pub fn stop() void { + microzig.interrupt.disable_interrupts(); + reset(); + regs.clearBits(regs.MCUSR, 1 << 3); + regs.setBits(regs.WDTCR, regs.bit(regs.watchdog_bits.wdce) | regs.bit(regs.watchdog_bits.wde)); + regs.write(regs.WDTCR, 0); + microzig.interrupt.enable_interrupts(); +} + +pub fn forceReset(timeout: Timeout) noreturn { + microzig.interrupt.disable_interrupts(); + regs.write(regs.WDTCR, controlValue(.reset, timeout)); + microzig.interrupt.enable_interrupts(); + reset(); + while (true) asm volatile ("" ::: .{ .memory = true }); +} + +fn controlValue(mode: Mode, timeout: Timeout) u8 { + const raw: u4 = @intFromEnum(timeout); + const prescaler = (@as(u8, raw & 0b0111)) | + ((@as(u8, raw >> 3) & 0x1) << regs.watchdog_bits.wdp3); + return prescaler | switch (mode) { + .interrupt => regs.bit(regs.watchdog_bits.wdie), + .reset => regs.bit(regs.watchdog_bits.wde), + .interrupt_then_reset => regs.bit(regs.watchdog_bits.wdie) | regs.bit(regs.watchdog_bits.wde), + }; +} From a38e96f79174041ab836260d4520b73fac1cebf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:41 -0400 Subject: [PATCH 11/22] attiny85: Add PCINT EEPROM and PROGMEM helpers --- .../attiny/src/hals/attiny85/eeprom.zig | 63 +++++++++++++++++++ .../attiny/src/hals/attiny85/pcint.zig | 30 +++++++++ .../attiny/src/hals/attiny85/progmem.zig | 21 +++++++ 3 files changed, 114 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny85/eeprom.zig create mode 100644 port/microchip/attiny/src/hals/attiny85/pcint.zig create mode 100644 port/microchip/attiny/src/hals/attiny85/progmem.zig diff --git a/port/microchip/attiny/src/hals/attiny85/eeprom.zig b/port/microchip/attiny/src/hals/attiny85/eeprom.zig new file mode 100644 index 000000000..782f1e1ca --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/eeprom.zig @@ -0,0 +1,63 @@ +const regs = @import("registers.zig"); + +pub const size = 512; + +// Type-safe wrapper for raw EEPROM byte offsets. +pub const Address = enum(u16) { + _, +}; + +pub inline fn address(value: u16) Address { + return @enumFromInt(value); +} + +pub inline fn isReady() bool { + return (regs.read(regs.EECR) & regs.bit(regs.eeprom_bits.eepe)) == 0; +} + +pub inline fn busyWait() void { + while (!isReady()) {} +} + +pub fn readByte(addr: Address) u8 { + busyWait(); + setAddress(addr); + regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); + return regs.read(regs.EEDR); +} + +pub fn writeByte(addr: Address, value: u8) void { + busyWait(); + setAddress(addr); + regs.write(regs.EEDR, value); + + // ATtiny25/45/85 datasheet section 5.3.3, page 22: EEMPE must be followed + // by EEPE within four cycles, matching avr-libc's byte-write primitive. + // https://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf + regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); + regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); +} + +pub fn updateByte(addr: Address, value: u8) void { + if (readByte(addr) != value) writeByte(addr, value); +} + +pub fn readSlice(addr: Address, dest: []u8) void { + const base = @intFromEnum(addr); + for (dest, 0..) |*byte, i| { + byte.* = readByte(address(base + @as(u16, @intCast(i)))); + } +} + +pub fn updateSlice(addr: Address, src: []const u8) void { + const base = @intFromEnum(addr); + for (src, 0..) |byte, i| { + updateByte(address(base + @as(u16, @intCast(i))), byte); + } +} + +fn setAddress(addr: Address) void { + const raw: u16 = @intFromEnum(addr); + regs.write(regs.EEARL, @truncate(raw)); + regs.write(regs.EEARH, @truncate(raw >> 8)); +} diff --git a/port/microchip/attiny/src/hals/attiny85/pcint.zig b/port/microchip/attiny/src/hals/attiny85/pcint.zig new file mode 100644 index 000000000..edad2d827 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/pcint.zig @@ -0,0 +1,30 @@ +const regs = @import("registers.zig"); + +pub const PinChange = enum(u3) { + pcint0 = 0, + pcint1 = 1, + pcint2 = 2, + pcint3 = 3, + pcint4 = 4, + pcint5 = 5, +}; + +pub inline fn enablePin(pin: PinChange) void { + regs.setBits(regs.PCMSK, regs.bit(@intFromEnum(pin))); +} + +pub inline fn disablePin(pin: PinChange) void { + regs.clearBits(regs.PCMSK, regs.bit(@intFromEnum(pin))); +} + +pub inline fn enable() void { + regs.setBits(regs.GIMSK, 1 << 5); +} + +pub inline fn disable() void { + regs.clearBits(regs.GIMSK, 1 << 5); +} + +pub inline fn clearFlag() void { + regs.setBits(regs.GIFR, 1 << 5); +} diff --git a/port/microchip/attiny/src/hals/attiny85/progmem.zig b/port/microchip/attiny/src/hals/attiny85/progmem.zig new file mode 100644 index 000000000..4aff06e79 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny85/progmem.zig @@ -0,0 +1,21 @@ +pub fn Table(comptime T: type, comptime len: usize, comptime values: [len]T) type { + return struct { + pub const data linksection(".progmem.data") = values; + + pub inline fn get(index: usize) T { + return read(T, &data[index]); + } + }; +} + +pub inline fn read(comptime T: type, ptr: *const T) T { + return ptr.*; +} + +pub inline fn readByte(ptr: *const u8) u8 { + return read(u8, ptr); +} + +pub inline fn readWord(ptr: *const u16) u16 { + return read(u16, ptr); +} From b4695ec76dcb8669297e194e89b0e76ec966b3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:46 -0400 Subject: [PATCH 12/22] attiny1634: Add HAL root and registers --- port/microchip/attiny/src/hals/ATtiny1634.zig | 16 ++++ .../attiny/src/hals/attiny1634/registers.zig | 88 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 port/microchip/attiny/src/hals/ATtiny1634.zig create mode 100644 port/microchip/attiny/src/hals/attiny1634/registers.zig diff --git a/port/microchip/attiny/src/hals/ATtiny1634.zig b/port/microchip/attiny/src/hals/ATtiny1634.zig new file mode 100644 index 000000000..1dbd497f1 --- /dev/null +++ b/port/microchip/attiny/src/hals/ATtiny1634.zig @@ -0,0 +1,16 @@ +pub const registers = @import("attiny1634/registers.zig"); + +pub const gpio = @import("attiny1634/gpio.zig"); +pub const timer0 = @import("attiny1634/timer0.zig"); +pub const timer1 = @import("attiny1634/timer1.zig"); +pub const adc = @import("attiny1634/adc.zig"); +pub const watchdog = @import("attiny1634/watchdog.zig"); +pub const pcint = @import("attiny1634/pcint.zig"); +pub const eeprom = @import("attiny1634/eeprom.zig"); +pub const progmem = @import("attiny85/progmem.zig"); + +pub const memory = struct { + pub const flash_size = 16 * 1024; + pub const eeprom_size = 256; + pub const sram_size = 1024; +}; diff --git a/port/microchip/attiny/src/hals/attiny1634/registers.zig b/port/microchip/attiny/src/hals/attiny1634/registers.zig new file mode 100644 index 000000000..f17adf55f --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/registers.zig @@ -0,0 +1,88 @@ +pub inline fn mem8(address: u16) *volatile u8 { + return @ptrFromInt(address); +} + +pub inline fn mem16(address: u16) *volatile u16 { + return @ptrFromInt(address); +} + +pub inline fn read(address: u16) u8 { + return mem8(address).*; +} + +pub inline fn write(address: u16, value: u8) void { + mem8(address).* = value; +} + +pub inline fn setBits(address: u16, mask: u8) void { + mem8(address).* |= mask; +} + +pub inline fn clearBits(address: u16, mask: u8) void { + mem8(address).* &= ~mask; +} + +pub inline fn bit(n: u3) u8 { + return @as(u8, 1) << n; +} + +pub const ADCL = 0x00; +pub const ADCH = 0x01; +pub const ADCSRB = 0x02; +pub const ADCSRA = 0x03; +pub const ADMUX = 0x04; +pub const PINC = 0x07; +pub const DDRC = 0x08; +pub const PORTC = 0x09; +pub const PUEC = 0x0A; +pub const PINB = 0x0B; +pub const DDRB = 0x0C; +pub const PORTB = 0x0D; +pub const PUEB = 0x0E; +pub const PINA = 0x0F; +pub const DDRA = 0x10; +pub const PORTA = 0x11; +pub const PUEA = 0x12; +pub const OCR0B = 0x17; +pub const OCR0A = 0x18; +pub const TCNT0 = 0x19; +pub const TCCR0B = 0x1A; +pub const TCCR0A = 0x1B; +pub const EECR = 0x1C; +pub const EEDR = 0x1D; +pub const EEARL = 0x1E; +pub const EEARH = 0x1F; +pub const PCMSK0 = 0x27; +pub const PCMSK1 = 0x28; +pub const PCMSK2 = 0x29; +pub const CCP = 0x2F; +pub const WDTCSR = 0x30; +pub const CLKPR = 0x33; +pub const MCUSR = 0x35; +pub const MCUCR = 0x36; +pub const TIFR = 0x39; +pub const TIMSK = 0x3A; +pub const GIMSK = 0x3C; +pub const ICR1 = 0x68; +pub const OCR1B = 0x6A; +pub const OCR1A = 0x6C; +pub const TCNT1 = 0x6E; +pub const TCCR1C = 0x70; +pub const TCCR1B = 0x71; +pub const TCCR1A = 0x72; + +pub const eeprom_bits = struct { + pub const eempe = 2; + pub const eepe = 1; + pub const eere = 0; +}; + +pub const watchdog_bits = struct { + pub const wdp0 = 0; + pub const wdp1 = 1; + pub const wdp2 = 2; + pub const wde = 3; + pub const wdp3 = 5; + pub const wdie = 6; + pub const wdif = 7; +}; From 12e71416905a857cff22be341109c5baefa7617d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:18:53 -0400 Subject: [PATCH 13/22] attiny1634: Add GPIO and PWM helpers --- .../attiny/src/hals/attiny1634/gpio.zig | 68 +++++++++++++++++++ .../attiny/src/hals/attiny1634/timer0.zig | 21 ++++++ .../attiny/src/hals/attiny1634/timer1.zig | 45 ++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny1634/gpio.zig create mode 100644 port/microchip/attiny/src/hals/attiny1634/timer0.zig create mode 100644 port/microchip/attiny/src/hals/attiny1634/timer1.zig diff --git a/port/microchip/attiny/src/hals/attiny1634/gpio.zig b/port/microchip/attiny/src/hals/attiny1634/gpio.zig new file mode 100644 index 000000000..c10cad4a5 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/gpio.zig @@ -0,0 +1,68 @@ +const cpu = @import("microzig").cpu; +const regs = @import("registers.zig"); + +pub const Port = enum(u2) { + a, + b, + c, + + pub const Regs = extern struct { + PIN: u8, + DDR: u8, + PORT: u8, + PUE: u8, + }; + + pub inline fn get_regs(port: Port) *volatile Regs { + return switch (port) { + .c => @ptrFromInt(regs.PINC), + .b => @ptrFromInt(regs.PINB), + .a => @ptrFromInt(regs.PINA), + }; + } +}; + +pub const Direction = enum { input, output }; + +pub fn pin(port: Port, num: u3) Pin { + return .{ .port = port, .num = num }; +} + +pub const Pin = packed struct(u5) { + port: Port, + num: u3, + + pub inline fn set_direction(p: Pin, dir: Direction) void { + const dir_addr: *volatile u8 = &p.port.get_regs().DDR; + switch (dir) { + .input => cpu.cbi(@intFromPtr(dir_addr), p.num), + .output => cpu.sbi(@intFromPtr(dir_addr), p.num), + } + } + + pub inline fn read(p: Pin) u1 { + const pin_addr: *volatile u8 = &p.port.get_regs().PIN; + return @truncate((pin_addr.* >> p.num) & 0x01); + } + + pub inline fn put(p: Pin, value: u1) void { + const port_addr: *volatile u8 = &p.port.get_regs().PORT; + switch (value) { + 1 => cpu.sbi(@intFromPtr(port_addr), p.num), + 0 => cpu.cbi(@intFromPtr(port_addr), p.num), + } + } + + pub inline fn toggle(p: Pin) void { + const pin_addr: *volatile u8 = &p.port.get_regs().PIN; + cpu.sbi(@intFromPtr(pin_addr), p.num); + } + + pub inline fn set_pullup(p: Pin, enabled: bool) void { + const pue_addr: *volatile u8 = &p.port.get_regs().PUE; + switch (enabled) { + true => cpu.sbi(@intFromPtr(pue_addr), p.num), + false => cpu.cbi(@intFromPtr(pue_addr), p.num), + } + } +}; diff --git a/port/microchip/attiny/src/hals/attiny1634/timer0.zig b/port/microchip/attiny/src/hals/attiny1634/timer0.zig new file mode 100644 index 000000000..89d6b7c1e --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/timer0.zig @@ -0,0 +1,21 @@ +const regs = @import("registers.zig"); + +pub const Prescaler = enum(u3) { + stopped = 0b000, + clk_1 = 0b001, + clk_8 = 0b010, + clk_64 = 0b011, + clk_256 = 0b100, + clk_1024 = 0b101, +}; + +pub fn configurePhaseCorrectPwmA(prescaler: Prescaler) void { + // ATtiny1634 datasheet section 11.7.3, page 83: phase-correct PWM on OC0A. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + regs.write(regs.TCCR0A, (1 << 7) | (1 << 0)); + regs.write(regs.TCCR0B, @intFromEnum(prescaler)); +} + +pub inline fn setCompareA(value: u8) void { + regs.write(regs.OCR0A, value); +} diff --git a/port/microchip/attiny/src/hals/attiny1634/timer1.zig b/port/microchip/attiny/src/hals/attiny1634/timer1.zig new file mode 100644 index 000000000..5f3257747 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/timer1.zig @@ -0,0 +1,45 @@ +const regs = @import("registers.zig"); + +pub const Prescaler = enum(u3) { + stopped = 0b000, + clk_1 = 0b001, + clk_8 = 0b010, + clk_64 = 0b011, + clk_256 = 0b100, + clk_1024 = 0b101, +}; + +pub const DynamicPwmConfig = struct { + top: u16 = 255, + prescaler: Prescaler = .clk_1, +}; + +pub fn configurePhaseCorrectDynamic(config: DynamicPwmConfig) void { + // ATtiny1634 datasheet section 12.9.3 and table 12-5, pages 105 and 113: + // phase-correct PWM with ICR1 TOP gives runtime-adjustable PWM resolution. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + + regs.write(regs.TCCR1A, (1 << 7) | (1 << 5) | (1 << 1)); + regs.write(regs.TCCR1B, (1 << 4) | @as(u8, @intFromEnum(config.prescaler))); + setTop(config.top); +} + +pub inline fn setTop(value: u16) void { + regs.mem16(regs.ICR1).* = value; +} + +pub inline fn setCompareA(value: u16) void { + regs.mem16(regs.OCR1A).* = value; +} + +pub inline fn setCompareB(value: u16) void { + regs.mem16(regs.OCR1B).* = value; +} + +pub inline fn setCounter(value: u16) void { + regs.mem16(regs.TCNT1).* = value; +} + +pub inline fn counter() u16 { + return regs.mem16(regs.TCNT1).*; +} From 85cfc4bc65e8124dd8da851c255b4feb14cad460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:00 -0400 Subject: [PATCH 14/22] attiny1634: Add ADC WDT PCINT and EEPROM helpers --- .../attiny/src/hals/attiny1634/adc.zig | 51 ++++++++++++++++ .../attiny/src/hals/attiny1634/eeprom.zig | 33 +++++++++++ .../attiny/src/hals/attiny1634/pcint.zig | 30 ++++++++++ .../attiny/src/hals/attiny1634/watchdog.zig | 59 +++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny1634/adc.zig create mode 100644 port/microchip/attiny/src/hals/attiny1634/eeprom.zig create mode 100644 port/microchip/attiny/src/hals/attiny1634/pcint.zig create mode 100644 port/microchip/attiny/src/hals/attiny1634/watchdog.zig diff --git a/port/microchip/attiny/src/hals/attiny1634/adc.zig b/port/microchip/attiny/src/hals/attiny1634/adc.zig new file mode 100644 index 000000000..592f0090b --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/adc.zig @@ -0,0 +1,51 @@ +const regs = @import("registers.zig"); + +pub const Channel = enum(u4) { + adc0 = 0, + adc1 = 1, + adc2 = 2, + adc3 = 3, + adc4 = 4, + adc5 = 5, + adc6 = 6, + adc7 = 7, + adc8 = 8, + adc9 = 9, + adc10 = 10, + adc11 = 11, + vcc_1v1 = 13, + temperature = 14, +}; + +pub const Prescaler = enum(u3) { + div2 = 1, + div4 = 2, + div8 = 3, + div16 = 4, + div32 = 5, + div64 = 6, + div128 = 7, +}; + +pub fn configureInternal1v1(channel: Channel, prescaler: Prescaler) void { + // ATtiny1634 datasheet section 17.13.1, page 177: ADMUX selects reference + // and channel; ADCSRA starts/enables auto-triggered conversions. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + regs.write(regs.ADMUX, 0b1000_0000 | @as(u8, @intFromEnum(channel))); + regs.write(regs.ADCSRB, 1 << 4); + regs.write(regs.ADCSRA, (1 << 7) | (1 << 5) | (1 << 3) | @as(u8, @intFromEnum(prescaler))); +} + +pub inline fn start() void { + regs.setBits(regs.ADCSRA, 1 << 6); +} + +pub inline fn stop() void { + regs.clearBits(regs.ADCSRA, 1 << 7); +} + +pub inline fn readRaw16() u16 { + const low = regs.read(regs.ADCL); + const high = regs.read(regs.ADCH); + return (@as(u16, high) << 8) | low; +} diff --git a/port/microchip/attiny/src/hals/attiny1634/eeprom.zig b/port/microchip/attiny/src/hals/attiny1634/eeprom.zig new file mode 100644 index 000000000..238175771 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/eeprom.zig @@ -0,0 +1,33 @@ +const regs = @import("registers.zig"); + +pub const size = 256; +// Type-safe wrapper for raw EEPROM byte offsets. +pub const Address = enum(u8) { _ }; + +pub inline fn address(value: u8) Address { + return @enumFromInt(value); +} + +pub inline fn busyWait() void { + while ((regs.read(regs.EECR) & regs.bit(regs.eeprom_bits.eepe)) != 0) {} +} + +pub fn readByte(addr: Address) u8 { + busyWait(); + regs.write(regs.EEARL, @intFromEnum(addr)); + regs.write(regs.EEARH, 0); + regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); + return regs.read(regs.EEDR); +} + +pub fn updateByte(addr: Address, value: u8) void { + if (readByte(addr) == value) return; + busyWait(); + regs.write(regs.EEDR, value); + + // ATtiny1634 datasheet section 5.3.4, page 23: EEMPE must be followed by + // EEPE within four cycles or the byte write is ignored. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); + regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); +} diff --git a/port/microchip/attiny/src/hals/attiny1634/pcint.zig b/port/microchip/attiny/src/hals/attiny1634/pcint.zig new file mode 100644 index 000000000..08e5fde8d --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/pcint.zig @@ -0,0 +1,30 @@ +const regs = @import("registers.zig"); + +pub const Group = enum { + pcint0_7, + pcint8_11, + pcint12_17, +}; + +pub inline fn enableGroup(group: Group) void { + regs.setBits(regs.GIMSK, switch (group) { + .pcint0_7 => 1 << 4, + .pcint8_11 => 1 << 5, + .pcint12_17 => 1 << 6, + }); +} + +pub inline fn enablePin(group: Group, bit: u3) void { + // ATtiny1634 datasheet section 9.3.6, page 53: set PCMSKn plus the + // corresponding PCIEn group bit in GIMSK to enable a pin-change source. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + regs.setBits(maskRegister(group), regs.bit(bit)); +} + +fn maskRegister(group: Group) u16 { + return switch (group) { + .pcint0_7 => regs.PCMSK0, + .pcint8_11 => regs.PCMSK1, + .pcint12_17 => regs.PCMSK2, + }; +} diff --git a/port/microchip/attiny/src/hals/attiny1634/watchdog.zig b/port/microchip/attiny/src/hals/attiny1634/watchdog.zig new file mode 100644 index 000000000..2f495a98b --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1634/watchdog.zig @@ -0,0 +1,59 @@ +const microzig = @import("microzig"); +const regs = @import("registers.zig"); + +pub const Timeout = enum(u4) { + ms16 = 0b0000, + ms32 = 0b0001, + ms64 = 0b0010, + ms125 = 0b0011, + ms250 = 0b0100, + ms500 = 0b0101, + s1 = 0b0110, + s2 = 0b0111, + s4 = 0b1000, + s8 = 0b1001, +}; + +pub const Mode = enum { + interrupt, + reset, + interrupt_then_reset, +}; + +pub inline fn reset() void { + asm volatile ("wdr" ::: .{ .memory = true }); +} + +pub fn configure(mode: Mode, timeout: Timeout) void { + microzig.interrupt.disable_interrupts(); + reset(); + + // ATtiny1634 datasheet section 8.5.2, page 44: WDTCSR changes are + // configuration-change protected and must follow the CCP signature write. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + regs.write(regs.CCP, 0xD8); + regs.write(regs.WDTCSR, controlValue(mode, timeout)); + microzig.interrupt.enable_interrupts(); +} + +pub fn stop() void { + microzig.interrupt.disable_interrupts(); + reset(); + // ATtiny1634 datasheet section 8.5.2, page 44: WDTCSR changes are + // configuration-change protected and must follow the CCP signature write. + // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf + regs.write(regs.CCP, 0xD8); + regs.write(regs.WDTCSR, 0); + microzig.interrupt.enable_interrupts(); +} + +fn controlValue(mode: Mode, timeout: Timeout) u8 { + const raw: u4 = @intFromEnum(timeout); + const prescaler = (@as(u8, raw & 0b0111)) | + ((@as(u8, raw >> 3) & 0x1) << regs.watchdog_bits.wdp3); + return prescaler | switch (mode) { + .interrupt => regs.bit(regs.watchdog_bits.wdie), + .reset => regs.bit(regs.watchdog_bits.wde), + .interrupt_then_reset => regs.bit(regs.watchdog_bits.wdie) | regs.bit(regs.watchdog_bits.wde), + }; +} From 1e2356ed1480983b5a01afe7e23a4b25c2265b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:07 -0400 Subject: [PATCH 15/22] attiny1616: Add HAL root and registers --- port/microchip/attiny/src/hals/ATtiny1616.zig | 16 ++ .../attiny/src/hals/attiny1616/registers.zig | 139 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 port/microchip/attiny/src/hals/ATtiny1616.zig create mode 100644 port/microchip/attiny/src/hals/attiny1616/registers.zig diff --git a/port/microchip/attiny/src/hals/ATtiny1616.zig b/port/microchip/attiny/src/hals/ATtiny1616.zig new file mode 100644 index 000000000..98c918eba --- /dev/null +++ b/port/microchip/attiny/src/hals/ATtiny1616.zig @@ -0,0 +1,16 @@ +pub const registers = @import("attiny1616/registers.zig"); +pub const gpio = @import("attiny1616/gpio.zig"); +pub const clock = @import("attiny1616/clock.zig"); +pub const tca0 = @import("attiny1616/tca0.zig"); +pub const rtc_pit = @import("attiny1616/rtc_pit.zig"); +pub const adc = @import("attiny1616/adc.zig"); +pub const pcint = @import("attiny1616/pcint.zig"); +pub const watchdog = @import("attiny1616/watchdog.zig"); +pub const eeprom = @import("attiny1616/eeprom.zig"); +pub const progmem = @import("attiny85/progmem.zig"); + +pub const memory = struct { + pub const flash_size = 16 * 1024; + pub const eeprom_size = 256; + pub const sram_size = 2048; +}; diff --git a/port/microchip/attiny/src/hals/attiny1616/registers.zig b/port/microchip/attiny/src/hals/attiny1616/registers.zig new file mode 100644 index 000000000..4853c75fa --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/registers.zig @@ -0,0 +1,139 @@ +pub inline fn mem8(address: u16) *volatile u8 { + return @ptrFromInt(address); +} + +pub inline fn mem16(address: u16) *volatile u16 { + return @ptrFromInt(address); +} + +pub inline fn read(address: u16) u8 { + return mem8(address).*; +} + +pub inline fn write(address: u16, value: u8) void { + mem8(address).* = value; +} + +pub inline fn setBits(address: u16, mask: u8) void { + write(address, read(address) | mask); +} + +pub inline fn clearBits(address: u16, mask: u8) void { + write(address, read(address) & ~mask); +} + +pub inline fn bit(bit_index: u3) u8 { + return @as(u8, 1) << bit_index; +} + +pub const ccp_signature = 0xD8; + +pub const vporta_dir = 0x0000; +pub const vporta_out = 0x0001; +pub const vporta_in = 0x0002; +pub const vporta_intflags = 0x0003; +pub const vportb_dir = 0x0004; +pub const vportb_out = 0x0005; +pub const vportb_in = 0x0006; +pub const vportb_intflags = 0x0007; +pub const vportc_dir = 0x0008; +pub const vportc_out = 0x0009; +pub const vportc_in = 0x000A; +pub const vportc_intflags = 0x000B; + +pub const ccp = 0x0034; +pub const rstctrl_rstfr = 0x0040; +pub const rstctrl_swrr = 0x0041; +pub const clkctrl_mclkctrla = 0x0060; +pub const clkctrl_mclkctrlb = 0x0061; +pub const clkctrl_mclkstatus = 0x0063; +pub const vref_ctrla = 0x00A0; +pub const vref_ctrlb = 0x00A1; +pub const wdt_ctrla = 0x0100; +pub const wdt_status = 0x0101; + +pub const rtc_pitctrla = 0x0150; +pub const rtc_pitstatus = 0x0151; +pub const rtc_pitintctrl = 0x0152; +pub const rtc_pitintflags = 0x0153; + +pub const porta_dir = 0x0400; +pub const porta_out = 0x0404; +pub const porta_in = 0x0408; +pub const porta_intflags = 0x0409; +pub const porta_pinctrl = 0x0410; +pub const portb_dir = 0x0420; +pub const portb_out = 0x0424; +pub const portb_in = 0x0428; +pub const portb_intflags = 0x0429; +pub const portb_pinctrl = 0x0430; +pub const portc_dir = 0x0440; +pub const portc_out = 0x0444; +pub const portc_in = 0x0448; +pub const portc_intflags = 0x0449; +pub const portc_pinctrl = 0x0450; + +pub const adc0_ctrla = 0x0600; +pub const adc0_ctrlb = 0x0601; +pub const adc0_ctrlc = 0x0602; +pub const adc0_ctrld = 0x0603; +pub const adc0_sampctrl = 0x0605; +pub const adc0_muxpos = 0x0606; +pub const adc0_command = 0x0608; +pub const adc0_intctrl = 0x060A; +pub const adc0_intflags = 0x060B; +pub const adc0_res = 0x0610; +pub const dac0_data = 0x06A1; + +pub const tca0_single_ctrla = 0x0A00; +pub const tca0_single_ctrlb = 0x0A01; +pub const tca0_single_intctrl = 0x0A0A; +pub const tca0_single_intflags = 0x0A0B; +pub const tca0_single_cnt = 0x0A20; +pub const tca0_single_per = 0x0A26; +pub const tca0_single_cmp0 = 0x0A28; +pub const tca0_single_cmp1 = 0x0A2A; +pub const tca0_single_perbuf = 0x0A36; +pub const tca0_single_cmp0buf = 0x0A38; +pub const tca0_single_cmp1buf = 0x0A3A; + +pub const nvmctrl_ctrla = 0x1000; +pub const nvmctrl_status = 0x1002; + +pub const ccp_ioreg_signature = 0xD8; +pub const ccp_spm_signature = 0x9D; + +pub const port_bits = struct { + pub const pullupen = 3; +}; + +pub const adc_bits = struct { + pub const enable = 0; + pub const freerun = 1; + pub const runstby = 7; + pub const start = 0; + pub const sample_capacitance = 6; + pub const resrdy = 0; +}; + +pub const rtc_bits = struct { + pub const piten = 0; + pub const pi = 0; + pub const ctrlbusy = 0; +}; + +pub const tca_bits = struct { + pub const enable = 0; + pub const cmp0en = 4; + pub const cmp1en = 5; + pub const ovf = 0; +}; + +pub const wdt_bits = struct { + pub const syncbusy = 0; +}; + +pub const nvm_bits = struct { + pub const fbusy = 0; + pub const eebusy = 1; +}; From d9a1a090e570361393653517dd5dc3288b0c9243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:14 -0400 Subject: [PATCH 16/22] attiny1616: Add GPIO clock and PCINT helpers --- .../attiny/src/hals/attiny1616/clock.zig | 37 ++++++ .../attiny/src/hals/attiny1616/gpio.zig | 111 ++++++++++++++++++ .../attiny/src/hals/attiny1616/pcint.zig | 11 ++ 3 files changed, 159 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny1616/clock.zig create mode 100644 port/microchip/attiny/src/hals/attiny1616/gpio.zig create mode 100644 port/microchip/attiny/src/hals/attiny1616/pcint.zig diff --git a/port/microchip/attiny/src/hals/attiny1616/clock.zig b/port/microchip/attiny/src/hals/attiny1616/clock.zig new file mode 100644 index 000000000..33d6f8ecf --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/clock.zig @@ -0,0 +1,37 @@ +const regs = @import("registers.zig"); + +pub const Prescaler = enum(u8) { + disabled = 0x00, + div2 = 0x01, + div4 = 0x03, + div6 = 0x11, + div8 = 0x05, + div10 = 0x13, + div12 = 0x15, + div16 = 0x07, + div24 = 0x17, + div32 = 0x09, + div48 = 0x19, + div64 = 0x0B, +}; + +pub fn protectedWrite(comptime address: u16, value: u8) void { + // ATtiny1614/1616/1617 DS40002204A section 8.5.7.1, page 55: + // protected I/O writes require CPU.CCP=IOREG and the register write + // within four instructions. + // https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf + regs.write(regs.ccp, regs.ccp_ioreg_signature); + regs.write(address, value); +} + +pub fn setPrescaler(prescaler: Prescaler) void { + protectedWrite(regs.clkctrl_mclkctrlb, @intFromEnum(prescaler)); +} + +pub fn useDefault20MHzDiv2() void { + setPrescaler(.div2); +} + +pub fn oscillatorChanging() bool { + return (regs.read(regs.clkctrl_mclkstatus) & regs.bit(0)) != 0; +} diff --git a/port/microchip/attiny/src/hals/attiny1616/gpio.zig b/port/microchip/attiny/src/hals/attiny1616/gpio.zig new file mode 100644 index 000000000..8b1828bd2 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/gpio.zig @@ -0,0 +1,111 @@ +const regs = @import("registers.zig"); + +pub const Port = enum(u2) { + a, + b, + c, +}; + +pub const Direction = enum { + input, + output, +}; + +pub const Sense = enum(u3) { + interrupt_disabled = 0x0, + both_edges = 0x1, + rising = 0x2, + falling = 0x3, + input_disable = 0x4, + level = 0x5, +}; + +pub const Pin = packed struct(u5) { + index: u3, + port: Port, + + pub fn init(comptime port: Port, comptime index: u3) Pin { + return .{ .port = port, .index = index }; + } + + pub inline fn set_direction(p: Pin, direction: Direction) void { + gpio.set_direction(p, direction); + } + + pub inline fn read(p: Pin) bool { + return gpio.read(p); + } + + pub inline fn put(p: Pin, value: bool) void { + gpio.put(p, value); + } + + pub inline fn toggle(p: Pin) void { + gpio.toggle(p); + } +}; + +const gpio = @This(); + +pub fn pin(comptime port: Port, comptime index: u3) Pin { + return Pin.init(port, index); +} + +const PortRegisters = struct { + vdir: u16, + vout: u16, + vin: u16, + vintflags: u16, + pinctrl: u16, +}; + +fn portRegisters(port: Port) PortRegisters { + return switch (port) { + .a => .{ .vdir = regs.vporta_dir, .vout = regs.vporta_out, .vin = regs.vporta_in, .vintflags = regs.vporta_intflags, .pinctrl = regs.porta_pinctrl }, + .b => .{ .vdir = regs.vportb_dir, .vout = regs.vportb_out, .vin = regs.vportb_in, .vintflags = regs.vportb_intflags, .pinctrl = regs.portb_pinctrl }, + .c => .{ .vdir = regs.vportc_dir, .vout = regs.vportc_out, .vin = regs.vportc_in, .vintflags = regs.vportc_intflags, .pinctrl = regs.portc_pinctrl }, + }; +} + +pub inline fn mask(gpio_pin: Pin) u8 { + return regs.bit(gpio_pin.index); +} + +pub fn set_direction(gpio_pin: Pin, direction: Direction) void { + const r = portRegisters(gpio_pin.port); + switch (direction) { + .input => regs.clearBits(r.vdir, mask(gpio_pin)), + .output => regs.setBits(r.vdir, mask(gpio_pin)), + } +} + +pub fn read(gpio_pin: Pin) bool { + return (regs.read(portRegisters(gpio_pin.port).vin) & mask(gpio_pin)) != 0; +} + +pub fn put(gpio_pin: Pin, value: bool) void { + const r = portRegisters(gpio_pin.port); + if (value) { + regs.setBits(r.vout, mask(gpio_pin)); + } else { + regs.clearBits(r.vout, mask(gpio_pin)); + } +} + +pub fn toggle(gpio_pin: Pin) void { + regs.write(portRegisters(gpio_pin.port).vout, regs.read(portRegisters(gpio_pin.port).vout) ^ mask(gpio_pin)); +} + +pub fn configure_input(gpio_pin: Pin, pullup: bool, sense: Sense) void { + // ATtiny1614/1616/1617 DS40002204A section 16.3.3.1, page 134: + // PORT PINnCTRL selects the per-pin input/sense mode used for pin interrupts. + // https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf + set_direction(gpio_pin, .input); + var value: u8 = @intFromEnum(sense); + if (pullup) value |= regs.bit(regs.port_bits.pullupen); + regs.write(portRegisters(gpio_pin.port).pinctrl + gpio_pin.index, value); +} + +pub fn clearInterrupt(gpio_pin: Pin) void { + regs.write(portRegisters(gpio_pin.port).vintflags, mask(gpio_pin)); +} diff --git a/port/microchip/attiny/src/hals/attiny1616/pcint.zig b/port/microchip/attiny/src/hals/attiny1616/pcint.zig new file mode 100644 index 000000000..9b470e5d6 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/pcint.zig @@ -0,0 +1,11 @@ +const gpio = @import("gpio.zig"); + +pub const Sense = gpio.Sense; + +pub fn configure(pin: gpio.Pin, pullup: bool, sense: Sense) void { + gpio.configure_input(pin, pullup, sense); +} + +pub fn clearFlag(pin: gpio.Pin) void { + gpio.clearInterrupt(pin); +} From d5f52a689207e1ba6ce5c107a3a83b225b9dcee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:20 -0400 Subject: [PATCH 17/22] attiny1616: Add TCA0 and RTC PIT helpers --- .../attiny/src/hals/attiny1616/rtc_pit.zig | 41 ++++++++++ .../attiny/src/hals/attiny1616/tca0.zig | 77 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig create mode 100644 port/microchip/attiny/src/hals/attiny1616/tca0.zig diff --git a/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig b/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig new file mode 100644 index 000000000..76814fcb5 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig @@ -0,0 +1,41 @@ +const regs = @import("registers.zig"); + +pub const Period = enum(u8) { + off = 0x00, + cycles4 = 0x08, + cycles8 = 0x10, + cycles16 = 0x18, + cycles32 = 0x20, + cycles64 = 0x28, + cycles128 = 0x30, + cycles256 = 0x38, + cycles512 = 0x40, + cycles1024 = 0x48, + cycles2048 = 0x50, + cycles4096 = 0x58, + cycles8192 = 0x60, + cycles16384 = 0x68, + cycles32768 = 0x70, +}; + +pub fn configure(period: Period, interrupt: bool) void { + // ATtiny1614/1616/1617 DS40002204A section 23.5.1, page 296: + // check PIT synchronization, set PI if needed, select PERIOD, then set PITEN. + // https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf + while (busy()) {} + regs.write(regs.rtc_pitctrla, @intFromEnum(period) | regs.bit(regs.rtc_bits.piten)); + regs.write(regs.rtc_pitintctrl, if (interrupt) regs.bit(regs.rtc_bits.pi) else 0); +} + +pub fn stop() void { + while (busy()) {} + regs.write(regs.rtc_pitctrla, 0); +} + +pub fn busy() bool { + return (regs.read(regs.rtc_pitstatus) & regs.bit(regs.rtc_bits.ctrlbusy)) != 0; +} + +pub fn clearInterruptFlag() void { + regs.write(regs.rtc_pitintflags, regs.bit(regs.rtc_bits.pi)); +} diff --git a/port/microchip/attiny/src/hals/attiny1616/tca0.zig b/port/microchip/attiny/src/hals/attiny1616/tca0.zig new file mode 100644 index 000000000..f853d176b --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/tca0.zig @@ -0,0 +1,77 @@ +const regs = @import("registers.zig"); + +pub const ClockSelect = enum(u8) { + div1 = 0x00, + div2 = 0x02, + div4 = 0x04, + div8 = 0x06, + div16 = 0x08, + div64 = 0x0A, + div256 = 0x0C, + div1024 = 0x0E, +}; + +pub const Waveform = enum(u8) { + normal = 0x0, + frequency = 0x1, + single_slope = 0x3, + dual_slope_top = 0x5, + dual_slope_top_bottom = 0x6, + dual_slope_bottom = 0x7, +}; + +pub const PwmConfig = struct { + top: u16 = 255, + compare0: u16 = 0, + compare1: u16 = 0, + enable_compare0: bool = true, + enable_compare1: bool = true, + waveform: Waveform = .dual_slope_bottom, + clock: ClockSelect = .div1, +}; + +// ATtiny1614/1616/1617 DS40002204A section 20.3.3.4.4, page 183: +// dual-slope PWM uses PER as TOP and CMPn as duty cycle. Buffered PER/CMP +// writes keep active PWM updates clean for dual-channel LED drivers. +// https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf +pub fn configurePwm(config: PwmConfig) void { + regs.write(regs.tca0_single_ctrla, 0); + setTop(config.top); + setCompare0(config.compare0); + setCompare1(config.compare1); + + var ctrlb: u8 = @intFromEnum(config.waveform); + if (config.enable_compare0) ctrlb |= regs.bit(regs.tca_bits.cmp0en); + if (config.enable_compare1) ctrlb |= regs.bit(regs.tca_bits.cmp1en); + + regs.write(regs.tca0_single_ctrlb, ctrlb); + regs.write(regs.tca0_single_ctrla, @intFromEnum(config.clock) | regs.bit(regs.tca_bits.enable)); +} + +pub fn stop() void { + regs.clearBits(regs.tca0_single_ctrla, regs.bit(regs.tca_bits.enable)); +} + +pub fn setTop(value: u16) void { + regs.mem16(regs.tca0_single_perbuf).* = value; +} + +pub fn setCompare0(value: u16) void { + regs.mem16(regs.tca0_single_cmp0buf).* = value; +} + +pub fn setCompare1(value: u16) void { + regs.mem16(regs.tca0_single_cmp1buf).* = value; +} + +pub fn setCounter(value: u16) void { + regs.mem16(regs.tca0_single_cnt).* = value; +} + +pub fn enableOverflowInterrupt() void { + regs.setBits(regs.tca0_single_intctrl, regs.bit(regs.tca_bits.ovf)); +} + +pub fn clearOverflowFlag() void { + regs.write(regs.tca0_single_intflags, regs.bit(regs.tca_bits.ovf)); +} From 93de1ae9af98c0d61c882ea4490513fad1883b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:26 -0400 Subject: [PATCH 18/22] attiny1616: Add ADC WDT and EEPROM helpers --- .../attiny/src/hals/attiny1616/adc.zig | 114 ++++++++++++++++++ .../attiny/src/hals/attiny1616/eeprom.zig | 50 ++++++++ .../attiny/src/hals/attiny1616/watchdog.zig | 34 ++++++ 3 files changed, 198 insertions(+) create mode 100644 port/microchip/attiny/src/hals/attiny1616/adc.zig create mode 100644 port/microchip/attiny/src/hals/attiny1616/eeprom.zig create mode 100644 port/microchip/attiny/src/hals/attiny1616/watchdog.zig diff --git a/port/microchip/attiny/src/hals/attiny1616/adc.zig b/port/microchip/attiny/src/hals/attiny1616/adc.zig new file mode 100644 index 000000000..44472da6b --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/adc.zig @@ -0,0 +1,114 @@ +const regs = @import("registers.zig"); + +pub const Channel = enum(u8) { + ain0 = 0x00, + ain1 = 0x01, + ain2 = 0x02, + ain3 = 0x03, + ain4 = 0x04, + ain5 = 0x05, + ain6 = 0x06, + ain7 = 0x07, + ain8 = 0x08, + ain9 = 0x09, + ain10 = 0x0A, + ain11 = 0x0B, + dac0 = 0x1C, + internal_reference = 0x1D, + temperature = 0x1E, + ground = 0x1F, +}; + +pub const Reference = enum(u8) { + vdd = 0x00, + internal_0v55 = 0x10, + internal_1v1 = 0x20, + internal_2v5 = 0x30, + internal_4v3 = 0x40, + internal_1v5 = 0x50, +}; + +pub const SampleCount = enum(u8) { + samples1 = 0x00, + samples2 = 0x01, + samples4 = 0x02, + samples8 = 0x03, + samples16 = 0x04, + samples32 = 0x05, + samples64 = 0x06, +}; + +pub const Prescaler = enum(u8) { + div2 = 0x00, + div4 = 0x01, + div8 = 0x02, + div16 = 0x03, + div32 = 0x04, + div64 = 0x05, + div128 = 0x06, + div256 = 0x07, +}; + +pub const InitialDelay = enum(u8) { + cycles0 = 0x00, + cycles16 = 0x20, + cycles32 = 0x40, + cycles64 = 0x60, + cycles128 = 0x80, + cycles256 = 0xA0, +}; + +pub const Config = struct { + channel: Channel = .ain0, + reference: Reference = .vdd, + sample_count: SampleCount = .samples1, + prescaler: Prescaler = .div16, + initial_delay: InitialDelay = .cycles0, + sample_capacitance: bool = false, + freerun: bool = false, + run_standby: bool = false, + sample_control: u8 = 0, +}; + +pub fn configure(config: Config) void { + regs.write(regs.adc0_ctrla, 0); + regs.write(regs.vref_ctrla, @intFromEnum(config.reference)); + regs.write(regs.adc0_muxpos, @intFromEnum(config.channel)); + regs.write(regs.adc0_ctrlb, @intFromEnum(config.sample_count)); + regs.write(regs.adc0_ctrlc, @intFromEnum(config.prescaler) | if (config.sample_capacitance) regs.bit(regs.adc_bits.sample_capacitance) else 0); + regs.write(regs.adc0_ctrld, @intFromEnum(config.initial_delay)); + regs.write(regs.adc0_sampctrl, config.sample_control); + + var ctrla = regs.bit(regs.adc_bits.enable); + if (config.freerun) ctrla |= regs.bit(regs.adc_bits.freerun); + if (config.run_standby) ctrla |= regs.bit(regs.adc_bits.runstby); + regs.write(regs.adc0_ctrla, ctrla); +} + +pub fn start() void { + regs.write(regs.adc0_command, regs.bit(regs.adc_bits.start)); +} + +pub fn stop() void { + regs.clearBits(regs.adc0_ctrla, regs.bit(regs.adc_bits.enable)); +} + +pub fn resultReady() bool { + return (regs.read(regs.adc0_intflags) & regs.bit(regs.adc_bits.resrdy)) != 0; +} + +pub fn clearResultReady() void { + regs.write(regs.adc0_intflags, regs.bit(regs.adc_bits.resrdy)); +} + +pub fn enableResultReadyInterrupt() void { + regs.setBits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); +} + +pub fn disableResultReadyInterrupt() void { + regs.clearBits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); +} + +pub fn readRaw16() u16 { + return regs.mem16(regs.adc0_res).*; +} diff --git a/port/microchip/attiny/src/hals/attiny1616/eeprom.zig b/port/microchip/attiny/src/hals/attiny1616/eeprom.zig new file mode 100644 index 000000000..6cf608888 --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/eeprom.zig @@ -0,0 +1,50 @@ +const regs = @import("registers.zig"); + +pub const size = 256; +pub const data_start: u16 = 0x1400; + +const command_page_erase_write = 0x03; + +// Type-safe wrapper for raw EEPROM byte offsets. +pub const Address = enum(u8) { + _, + + pub fn fromInt(value: u8) Address { + return @enumFromInt(value); + } +}; + +pub fn readByte(address: Address) u8 { + return regs.mem8(data_start + @as(u16, @intFromEnum(address))).*; +} + +pub fn writeByte(address: Address, value: u8) void { + busyWait(); + regs.mem8(data_start + @as(u16, @intFromEnum(address))).* = value; + executeCommand(command_page_erase_write); +} + +pub fn updateByte(address: Address, value: u8) void { + if (readByte(address) != value) writeByte(address, value); +} + +pub fn readSlice(comptime len: usize, start: Address) [len]u8 { + var out: [len]u8 = undefined; + for (&out, 0..) |*byte, offset| { + byte.* = readByte(@enumFromInt(@intFromEnum(start) + offset)); + } + return out; +} + +pub fn busyWait() void { + while ((regs.read(regs.nvmctrl_status) & regs.bit(regs.nvm_bits.eebusy)) != 0) {} +} + +fn executeCommand(command: u8) void { + // ATtiny1614/1616/1617 DS40002204A section 9.3.2.4, page 63: + // write EEPROM through the mapped page buffer, unlock NVMCTRL.CTRLA with + // CPU.CCP=SPM, then issue the command within four instructions. + // https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf + regs.write(regs.ccp, regs.ccp_spm_signature); + regs.write(regs.nvmctrl_ctrla, command); +} diff --git a/port/microchip/attiny/src/hals/attiny1616/watchdog.zig b/port/microchip/attiny/src/hals/attiny1616/watchdog.zig new file mode 100644 index 000000000..c62a2dc0f --- /dev/null +++ b/port/microchip/attiny/src/hals/attiny1616/watchdog.zig @@ -0,0 +1,34 @@ +const regs = @import("registers.zig"); +const clock = @import("clock.zig"); + +pub const Period = enum(u8) { + off = 0x00, + cycles8 = 0x01, + cycles16 = 0x02, + cycles32 = 0x03, + cycles64 = 0x04, + cycles128 = 0x05, + cycles256 = 0x06, + cycles512 = 0x07, + cycles1k = 0x08, + cycles2k = 0x09, + cycles4k = 0x0A, + cycles8k = 0x0B, +}; + +pub fn reset() void { + asm volatile ("wdr"); +} + +pub fn configure(period: Period) void { + while (busy()) {} + clock.protectedWrite(regs.wdt_ctrla, @intFromEnum(period)); +} + +pub fn stop() void { + configure(.off); +} + +pub fn busy() bool { + return (regs.read(regs.wdt_status) & regs.bit(regs.wdt_bits.syncbusy)) != 0; +} From 06fb15775081ed0c45d84918bbc02bde44d2b3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:32 -0400 Subject: [PATCH 19/22] examples: Add ATtiny blinky examples --- examples/microchip/attiny/build.zig | 47 +++++++++++++++++++ examples/microchip/attiny/build.zig.zon | 14 ++++++ examples/microchip/attiny/src/blinky.zig | 31 ++++++++++++ examples/microchip/attiny/src/blinky84.zig | 31 ++++++++++++ .../microchip/attiny/src/blinky_interrupt.zig | 23 +++++++++ 5 files changed, 146 insertions(+) create mode 100644 examples/microchip/attiny/build.zig create mode 100644 examples/microchip/attiny/build.zig.zon create mode 100644 examples/microchip/attiny/src/blinky.zig create mode 100644 examples/microchip/attiny/src/blinky84.zig create mode 100644 examples/microchip/attiny/src/blinky_interrupt.zig diff --git a/examples/microchip/attiny/build.zig b/examples/microchip/attiny/build.zig new file mode 100644 index 000000000..df2730852 --- /dev/null +++ b/examples/microchip/attiny/build.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +const MicroBuild = microzig.MicroBuild(.{ + .attiny = true, +}); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + const maybe_example = b.option([]const u8, "example", "only build matching examples"); + + const mz_dep = b.dependency("microzig", .{}); + const mb = MicroBuild.init(b, mz_dep) orelse return; + + const available_examples = [_]Example{ + .{ .target = mb.ports.attiny.boards.digispark, .name = "digispark_blinky", .file = "src/blinky.zig" }, + .{ .target = mb.ports.attiny.boards.adafruit.trinket, .name = "trinket_blinky", .file = "src/blinky.zig" }, + .{ .target = mb.ports.attiny.boards.adafruit.gemma, .name = "gemma_blinky", .file = "src/blinky.zig" }, + .{ .target = mb.ports.attiny.chips.attiny85, .name = "attiny85_blinky", .file = "src/blinky.zig" }, + .{ .target = mb.ports.attiny.chips.attiny85, .name = "attiny85_blinky_interrupt", .file = "src/blinky_interrupt.zig" }, + .{ .target = mb.ports.attiny.chips.attiny84, .name = "attiny84_blinky", .file = "src/blinky84.zig" }, + .{ .target = mb.ports.attiny.chips.attiny1634, .name = "attiny1634_pwm_adc", .file = "src/attiny1634_pwm_adc.zig" }, + .{ .target = mb.ports.attiny.chips.attiny1616, .name = "attiny1616_tca_rtc", .file = "src/attiny1616_tca_rtc.zig" }, + }; + + for (available_examples) |example| { + if (maybe_example) |selected_example| + if (!std.mem.containsAtLeast(u8, example.name, 1, selected_example)) + continue; + + const fw = mb.add_firmware(.{ + .name = example.name, + .target = example.target, + .optimize = optimize, + .root_source_file = b.path(example.file), + }); + + mb.install_firmware(fw, .{}); + mb.install_firmware(fw, .{ .format = .elf }); + } +} + +const Example = struct { + target: *const microzig.Target, + name: []const u8, + file: []const u8, +}; diff --git a/examples/microchip/attiny/build.zig.zon b/examples/microchip/attiny/build.zig.zon new file mode 100644 index 000000000..4225f34bd --- /dev/null +++ b/examples/microchip/attiny/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .examples_microchip_attiny, + .fingerprint = 0x83cbabd71b524269, + .version = "0.0.0", + .dependencies = .{ + .microzig = .{ .path = "../../.." }, + }, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/examples/microchip/attiny/src/blinky.zig b/examples/microchip/attiny/src/blinky.zig new file mode 100644 index 000000000..f04ea46f6 --- /dev/null +++ b/examples/microchip/attiny/src/blinky.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const gpio = microzig.hal.gpio; + +// ATtiny85: PB1 is the Digispark onboard LED +const led_pin = gpio.pin(.b, 1); + +pub fn main() void { + led_pin.set_direction(.output); + + while (true) { + busy_sleep(20_000); + led_pin.toggle(); + } +} + +pub fn busy_sleep(comptime limit: comptime_int) void { + if (limit <= 0) @compileError("limit must be non-negative!"); + + comptime var bits = 0; + inline while ((1 << bits) <= limit) { + bits += 1; + } + + const I = std.meta.Int(.unsigned, bits); + + var i: I = 0; + while (i < limit) : (i += 1) { + std.mem.doNotOptimizeAway(i); + } +} diff --git a/examples/microchip/attiny/src/blinky84.zig b/examples/microchip/attiny/src/blinky84.zig new file mode 100644 index 000000000..443fba4f0 --- /dev/null +++ b/examples/microchip/attiny/src/blinky84.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const gpio = microzig.hal.gpio; + +// ATtiny84: use PA0 as the LED pin +const led_pin = gpio.pin(.a, 0); + +pub fn main() void { + led_pin.set_direction(.output); + + while (true) { + busy_sleep(20_000); + led_pin.toggle(); + } +} + +pub fn busy_sleep(comptime limit: comptime_int) void { + if (limit <= 0) @compileError("limit must be non-negative!"); + + comptime var bits = 0; + inline while ((1 << bits) <= limit) { + bits += 1; + } + + const I = std.meta.Int(.unsigned, bits); + + var i: I = 0; + while (i < limit) : (i += 1) { + std.mem.doNotOptimizeAway(i); + } +} diff --git a/examples/microchip/attiny/src/blinky_interrupt.zig b/examples/microchip/attiny/src/blinky_interrupt.zig new file mode 100644 index 000000000..7034fff78 --- /dev/null +++ b/examples/microchip/attiny/src/blinky_interrupt.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const gpio = microzig.hal.gpio; + +const led_pin = gpio.pin(.b, 1); + +pub const microzig_options: microzig.Options = .{ + .interrupts = .{ + .INT0 = &my_int0_handler, + }, +}; + +fn my_int0_handler() callconv(.avr_signal) void { + led_pin.toggle(); +} + +pub fn main() void { + led_pin.set_direction(.output); + + while (true) { + std.mem.doNotOptimizeAway({}); + } +} From 6aa1258a25c1e89ddc4e4b214b51dcb509b81378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:38 -0400 Subject: [PATCH 20/22] examples: Add ATtiny1634 PWM ADC example --- .../attiny/src/attiny1634_pwm_adc.zig | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 examples/microchip/attiny/src/attiny1634_pwm_adc.zig diff --git a/examples/microchip/attiny/src/attiny1634_pwm_adc.zig b/examples/microchip/attiny/src/attiny1634_pwm_adc.zig new file mode 100644 index 000000000..adb93c862 --- /dev/null +++ b/examples/microchip/attiny/src/attiny1634_pwm_adc.zig @@ -0,0 +1,40 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; + +const ch1_pwm = hal.gpio.pin(.b, 3); +const ch2_pwm = hal.gpio.pin(.a, 6); +const fet_pwm = hal.gpio.pin(.c, 0); +const voltage = hal.gpio.pin(.b, 1); +const Gamma = hal.progmem.Table(u8, 4, .{ 0, 8, 32, 255 }); + +pub fn main() void { + ch1_pwm.set_direction(.output); + ch2_pwm.set_direction(.output); + fet_pwm.set_direction(.output); + voltage.set_direction(.input); + + hal.timer1.configurePhaseCorrectDynamic(.{ .top = 255, .prescaler = .clk_1 }); + hal.timer1.setCompareA(96); + hal.timer1.setCompareB(64); + + hal.timer0.configurePhaseCorrectPwmA(.clk_1); + hal.timer0.setCompareA(32); + + hal.adc.configureInternal1v1(.adc6, .div64); + hal.adc.start(); + hal.watchdog.reset(); + hal.watchdog.configure(.interrupt, .ms16); + + const saved_level = hal.eeprom.readByte(hal.eeprom.address(0)); + const gamma_level = Gamma.get(2); + _ = saved_level; + _ = gamma_level; + + while (true) { + nop(); + } +} + +inline fn nop() void { + asm volatile ("nop"); +} From 55f41fdd01e3ff5396df849b888accbb51f00a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:19:44 -0400 Subject: [PATCH 21/22] examples: Add ATtiny1616 TCA RTC example --- .../attiny/src/attiny1616_tca_rtc.zig | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/microchip/attiny/src/attiny1616_tca_rtc.zig diff --git a/examples/microchip/attiny/src/attiny1616_tca_rtc.zig b/examples/microchip/attiny/src/attiny1616_tca_rtc.zig new file mode 100644 index 000000000..fbabd4a97 --- /dev/null +++ b/examples/microchip/attiny/src/attiny1616_tca_rtc.zig @@ -0,0 +1,55 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; + +const pwm_cool = hal.gpio.pin(.b, 0); +const pwm_warm = hal.gpio.pin(.b, 1); +const aux_led = hal.gpio.pin(.b, 5); +const switch_pin = hal.gpio.pin(.a, 5); +const Ramp = hal.progmem.Table(u8, 4, .{ 1, 4, 16, 64 }); + +pub fn main() void { + hal.clock.useDefault20MHzDiv2(); + + pwm_cool.set_direction(.output); + pwm_warm.set_direction(.output); + aux_led.set_direction(.output); + hal.pcint.configure(switch_pin, true, .both_edges); + + hal.tca0.configurePwm(.{ + .top = 255, + .compare0 = 48, + .compare1 = 96, + .enable_compare0 = true, + .enable_compare1 = true, + .waveform = .dual_slope_bottom, + .clock = .div1, + }); + + hal.rtc_pit.configure(.cycles512, true); + hal.adc.configure(.{ + .channel = .internal_reference, + .reference = .vdd, + .sample_count = .samples4, + .prescaler = .div16, + .initial_delay = .cycles16, + .sample_capacitance = true, + .freerun = true, + .run_standby = true, + }); + hal.adc.enableResultReadyInterrupt(); + hal.adc.start(); + hal.watchdog.reset(); + + const saved_level = hal.eeprom.readByte(.fromInt(0)); + const ramp_level = Ramp.get(1); + _ = saved_level; + _ = ramp_level; + + while (true) { + nop(); + } +} + +inline fn nop() void { + asm volatile ("nop"); +} From b99de6e93cfebc669186c8775ae93b5f8d55772f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=B7=F0=9D=92=89=F0=9D=92=8A=F0=9D=92=8D?= =?UTF-8?q?=F0=9D=92=90=F0=9D=92=84=F0=9D=92=82=F0=9D=92=8D=F0=9D=92=9A?= =?UTF-8?q?=F0=9D=92=94=F0=9D=92=95?= Date: Thu, 14 May 2026 13:28:32 -0400 Subject: [PATCH 22/22] attiny: Use snake case HAL helper names --- .../attiny/src/attiny1616_tca_rtc.zig | 8 ++-- .../attiny/src/attiny1634_pwm_adc.zig | 14 +++---- .../attiny/src/hals/attiny1616/adc.zig | 16 ++++---- .../attiny/src/hals/attiny1616/clock.zig | 12 +++--- .../attiny/src/hals/attiny1616/eeprom.zig | 22 +++++------ .../attiny/src/hals/attiny1616/gpio.zig | 24 ++++++------ .../attiny/src/hals/attiny1616/pcint.zig | 4 +- .../attiny/src/hals/attiny1616/registers.zig | 4 +- .../attiny/src/hals/attiny1616/rtc_pit.zig | 2 +- .../attiny/src/hals/attiny1616/tca0.zig | 24 ++++++------ .../attiny/src/hals/attiny1616/watchdog.zig | 2 +- .../attiny/src/hals/attiny1634/adc.zig | 8 ++-- .../attiny/src/hals/attiny1634/eeprom.zig | 18 ++++----- .../attiny/src/hals/attiny1634/pcint.zig | 10 ++--- .../attiny/src/hals/attiny1634/registers.zig | 4 +- .../attiny/src/hals/attiny1634/timer0.zig | 4 +- .../attiny/src/hals/attiny1634/timer1.zig | 12 +++--- .../attiny/src/hals/attiny1634/watchdog.zig | 4 +- .../attiny/src/hals/attiny85/adc.zig | 22 +++++------ .../attiny/src/hals/attiny85/eeprom.zig | 38 +++++++++---------- .../attiny/src/hals/attiny85/pcint.zig | 16 ++++---- .../attiny/src/hals/attiny85/progmem.zig | 4 +- .../attiny/src/hals/attiny85/registers.zig | 4 +- .../attiny/src/hals/attiny85/sleep.zig | 12 +++--- .../attiny/src/hals/attiny85/timer0.zig | 6 +-- .../attiny/src/hals/attiny85/timer1.zig | 18 ++++----- .../attiny/src/hals/attiny85/watchdog.zig | 14 +++---- 27 files changed, 163 insertions(+), 163 deletions(-) diff --git a/examples/microchip/attiny/src/attiny1616_tca_rtc.zig b/examples/microchip/attiny/src/attiny1616_tca_rtc.zig index fbabd4a97..8d7807d82 100644 --- a/examples/microchip/attiny/src/attiny1616_tca_rtc.zig +++ b/examples/microchip/attiny/src/attiny1616_tca_rtc.zig @@ -8,14 +8,14 @@ const switch_pin = hal.gpio.pin(.a, 5); const Ramp = hal.progmem.Table(u8, 4, .{ 1, 4, 16, 64 }); pub fn main() void { - hal.clock.useDefault20MHzDiv2(); + hal.clock.use_default20_m_hz_div2(); pwm_cool.set_direction(.output); pwm_warm.set_direction(.output); aux_led.set_direction(.output); hal.pcint.configure(switch_pin, true, .both_edges); - hal.tca0.configurePwm(.{ + hal.tca0.configure_pwm(.{ .top = 255, .compare0 = 48, .compare1 = 96, @@ -36,11 +36,11 @@ pub fn main() void { .freerun = true, .run_standby = true, }); - hal.adc.enableResultReadyInterrupt(); + hal.adc.enable_result_ready_interrupt(); hal.adc.start(); hal.watchdog.reset(); - const saved_level = hal.eeprom.readByte(.fromInt(0)); + const saved_level = hal.eeprom.read_byte(.from_int(0)); const ramp_level = Ramp.get(1); _ = saved_level; _ = ramp_level; diff --git a/examples/microchip/attiny/src/attiny1634_pwm_adc.zig b/examples/microchip/attiny/src/attiny1634_pwm_adc.zig index adb93c862..6c0c9e3b4 100644 --- a/examples/microchip/attiny/src/attiny1634_pwm_adc.zig +++ b/examples/microchip/attiny/src/attiny1634_pwm_adc.zig @@ -13,19 +13,19 @@ pub fn main() void { fet_pwm.set_direction(.output); voltage.set_direction(.input); - hal.timer1.configurePhaseCorrectDynamic(.{ .top = 255, .prescaler = .clk_1 }); - hal.timer1.setCompareA(96); - hal.timer1.setCompareB(64); + hal.timer1.configure_phase_correct_dynamic(.{ .top = 255, .prescaler = .clk_1 }); + hal.timer1.set_compare_a(96); + hal.timer1.set_compare_b(64); - hal.timer0.configurePhaseCorrectPwmA(.clk_1); - hal.timer0.setCompareA(32); + hal.timer0.configure_phase_correct_pwm_a(.clk_1); + hal.timer0.set_compare_a(32); - hal.adc.configureInternal1v1(.adc6, .div64); + hal.adc.configure_internal1v1(.adc6, .div64); hal.adc.start(); hal.watchdog.reset(); hal.watchdog.configure(.interrupt, .ms16); - const saved_level = hal.eeprom.readByte(hal.eeprom.address(0)); + const saved_level = hal.eeprom.read_byte(hal.eeprom.address(0)); const gamma_level = Gamma.get(2); _ = saved_level; _ = gamma_level; diff --git a/port/microchip/attiny/src/hals/attiny1616/adc.zig b/port/microchip/attiny/src/hals/attiny1616/adc.zig index 44472da6b..9bb5275e7 100644 --- a/port/microchip/attiny/src/hals/attiny1616/adc.zig +++ b/port/microchip/attiny/src/hals/attiny1616/adc.zig @@ -90,25 +90,25 @@ pub fn start() void { } pub fn stop() void { - regs.clearBits(regs.adc0_ctrla, regs.bit(regs.adc_bits.enable)); + regs.clear_bits(regs.adc0_ctrla, regs.bit(regs.adc_bits.enable)); } -pub fn resultReady() bool { +pub fn result_ready() bool { return (regs.read(regs.adc0_intflags) & regs.bit(regs.adc_bits.resrdy)) != 0; } -pub fn clearResultReady() void { +pub fn clear_result_ready() void { regs.write(regs.adc0_intflags, regs.bit(regs.adc_bits.resrdy)); } -pub fn enableResultReadyInterrupt() void { - regs.setBits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); +pub fn enable_result_ready_interrupt() void { + regs.set_bits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); } -pub fn disableResultReadyInterrupt() void { - regs.clearBits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); +pub fn disable_result_ready_interrupt() void { + regs.clear_bits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); } -pub fn readRaw16() u16 { +pub fn read_raw16() u16 { return regs.mem16(regs.adc0_res).*; } diff --git a/port/microchip/attiny/src/hals/attiny1616/clock.zig b/port/microchip/attiny/src/hals/attiny1616/clock.zig index 33d6f8ecf..e169e151b 100644 --- a/port/microchip/attiny/src/hals/attiny1616/clock.zig +++ b/port/microchip/attiny/src/hals/attiny1616/clock.zig @@ -15,7 +15,7 @@ pub const Prescaler = enum(u8) { div64 = 0x0B, }; -pub fn protectedWrite(comptime address: u16, value: u8) void { +pub fn protected_write(comptime address: u16, value: u8) void { // ATtiny1614/1616/1617 DS40002204A section 8.5.7.1, page 55: // protected I/O writes require CPU.CCP=IOREG and the register write // within four instructions. @@ -24,14 +24,14 @@ pub fn protectedWrite(comptime address: u16, value: u8) void { regs.write(address, value); } -pub fn setPrescaler(prescaler: Prescaler) void { - protectedWrite(regs.clkctrl_mclkctrlb, @intFromEnum(prescaler)); +pub fn set_prescaler(prescaler: Prescaler) void { + protected_write(regs.clkctrl_mclkctrlb, @intFromEnum(prescaler)); } -pub fn useDefault20MHzDiv2() void { - setPrescaler(.div2); +pub fn use_default20_m_hz_div2() void { + set_prescaler(.div2); } -pub fn oscillatorChanging() bool { +pub fn oscillator_changing() bool { return (regs.read(regs.clkctrl_mclkstatus) & regs.bit(0)) != 0; } diff --git a/port/microchip/attiny/src/hals/attiny1616/eeprom.zig b/port/microchip/attiny/src/hals/attiny1616/eeprom.zig index 6cf608888..c721cc755 100644 --- a/port/microchip/attiny/src/hals/attiny1616/eeprom.zig +++ b/port/microchip/attiny/src/hals/attiny1616/eeprom.zig @@ -9,38 +9,38 @@ const command_page_erase_write = 0x03; pub const Address = enum(u8) { _, - pub fn fromInt(value: u8) Address { + pub fn from_int(value: u8) Address { return @enumFromInt(value); } }; -pub fn readByte(address: Address) u8 { +pub fn read_byte(address: Address) u8 { return regs.mem8(data_start + @as(u16, @intFromEnum(address))).*; } -pub fn writeByte(address: Address, value: u8) void { - busyWait(); +pub fn write_byte(address: Address, value: u8) void { + busy_wait(); regs.mem8(data_start + @as(u16, @intFromEnum(address))).* = value; - executeCommand(command_page_erase_write); + execute_command(command_page_erase_write); } -pub fn updateByte(address: Address, value: u8) void { - if (readByte(address) != value) writeByte(address, value); +pub fn update_byte(address: Address, value: u8) void { + if (read_byte(address) != value) write_byte(address, value); } -pub fn readSlice(comptime len: usize, start: Address) [len]u8 { +pub fn read_slice(comptime len: usize, start: Address) [len]u8 { var out: [len]u8 = undefined; for (&out, 0..) |*byte, offset| { - byte.* = readByte(@enumFromInt(@intFromEnum(start) + offset)); + byte.* = read_byte(@enumFromInt(@intFromEnum(start) + offset)); } return out; } -pub fn busyWait() void { +pub fn busy_wait() void { while ((regs.read(regs.nvmctrl_status) & regs.bit(regs.nvm_bits.eebusy)) != 0) {} } -fn executeCommand(command: u8) void { +fn execute_command(command: u8) void { // ATtiny1614/1616/1617 DS40002204A section 9.3.2.4, page 63: // write EEPROM through the mapped page buffer, unlock NVMCTRL.CTRLA with // CPU.CCP=SPM, then issue the command within four instructions. diff --git a/port/microchip/attiny/src/hals/attiny1616/gpio.zig b/port/microchip/attiny/src/hals/attiny1616/gpio.zig index 8b1828bd2..d5c42a25a 100644 --- a/port/microchip/attiny/src/hals/attiny1616/gpio.zig +++ b/port/microchip/attiny/src/hals/attiny1616/gpio.zig @@ -59,7 +59,7 @@ const PortRegisters = struct { pinctrl: u16, }; -fn portRegisters(port: Port) PortRegisters { +fn port_registers(port: Port) PortRegisters { return switch (port) { .a => .{ .vdir = regs.vporta_dir, .vout = regs.vporta_out, .vin = regs.vporta_in, .vintflags = regs.vporta_intflags, .pinctrl = regs.porta_pinctrl }, .b => .{ .vdir = regs.vportb_dir, .vout = regs.vportb_out, .vin = regs.vportb_in, .vintflags = regs.vportb_intflags, .pinctrl = regs.portb_pinctrl }, @@ -72,28 +72,28 @@ pub inline fn mask(gpio_pin: Pin) u8 { } pub fn set_direction(gpio_pin: Pin, direction: Direction) void { - const r = portRegisters(gpio_pin.port); + const r = port_registers(gpio_pin.port); switch (direction) { - .input => regs.clearBits(r.vdir, mask(gpio_pin)), - .output => regs.setBits(r.vdir, mask(gpio_pin)), + .input => regs.clear_bits(r.vdir, mask(gpio_pin)), + .output => regs.set_bits(r.vdir, mask(gpio_pin)), } } pub fn read(gpio_pin: Pin) bool { - return (regs.read(portRegisters(gpio_pin.port).vin) & mask(gpio_pin)) != 0; + return (regs.read(port_registers(gpio_pin.port).vin) & mask(gpio_pin)) != 0; } pub fn put(gpio_pin: Pin, value: bool) void { - const r = portRegisters(gpio_pin.port); + const r = port_registers(gpio_pin.port); if (value) { - regs.setBits(r.vout, mask(gpio_pin)); + regs.set_bits(r.vout, mask(gpio_pin)); } else { - regs.clearBits(r.vout, mask(gpio_pin)); + regs.clear_bits(r.vout, mask(gpio_pin)); } } pub fn toggle(gpio_pin: Pin) void { - regs.write(portRegisters(gpio_pin.port).vout, regs.read(portRegisters(gpio_pin.port).vout) ^ mask(gpio_pin)); + regs.write(port_registers(gpio_pin.port).vout, regs.read(port_registers(gpio_pin.port).vout) ^ mask(gpio_pin)); } pub fn configure_input(gpio_pin: Pin, pullup: bool, sense: Sense) void { @@ -103,9 +103,9 @@ pub fn configure_input(gpio_pin: Pin, pullup: bool, sense: Sense) void { set_direction(gpio_pin, .input); var value: u8 = @intFromEnum(sense); if (pullup) value |= regs.bit(regs.port_bits.pullupen); - regs.write(portRegisters(gpio_pin.port).pinctrl + gpio_pin.index, value); + regs.write(port_registers(gpio_pin.port).pinctrl + gpio_pin.index, value); } -pub fn clearInterrupt(gpio_pin: Pin) void { - regs.write(portRegisters(gpio_pin.port).vintflags, mask(gpio_pin)); +pub fn clear_interrupt(gpio_pin: Pin) void { + regs.write(port_registers(gpio_pin.port).vintflags, mask(gpio_pin)); } diff --git a/port/microchip/attiny/src/hals/attiny1616/pcint.zig b/port/microchip/attiny/src/hals/attiny1616/pcint.zig index 9b470e5d6..52924b28a 100644 --- a/port/microchip/attiny/src/hals/attiny1616/pcint.zig +++ b/port/microchip/attiny/src/hals/attiny1616/pcint.zig @@ -6,6 +6,6 @@ pub fn configure(pin: gpio.Pin, pullup: bool, sense: Sense) void { gpio.configure_input(pin, pullup, sense); } -pub fn clearFlag(pin: gpio.Pin) void { - gpio.clearInterrupt(pin); +pub fn clear_flag(pin: gpio.Pin) void { + gpio.clear_interrupt(pin); } diff --git a/port/microchip/attiny/src/hals/attiny1616/registers.zig b/port/microchip/attiny/src/hals/attiny1616/registers.zig index 4853c75fa..93a3aea1e 100644 --- a/port/microchip/attiny/src/hals/attiny1616/registers.zig +++ b/port/microchip/attiny/src/hals/attiny1616/registers.zig @@ -14,11 +14,11 @@ pub inline fn write(address: u16, value: u8) void { mem8(address).* = value; } -pub inline fn setBits(address: u16, mask: u8) void { +pub inline fn set_bits(address: u16, mask: u8) void { write(address, read(address) | mask); } -pub inline fn clearBits(address: u16, mask: u8) void { +pub inline fn clear_bits(address: u16, mask: u8) void { write(address, read(address) & ~mask); } diff --git a/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig b/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig index 76814fcb5..a16cbe3e1 100644 --- a/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig +++ b/port/microchip/attiny/src/hals/attiny1616/rtc_pit.zig @@ -36,6 +36,6 @@ pub fn busy() bool { return (regs.read(regs.rtc_pitstatus) & regs.bit(regs.rtc_bits.ctrlbusy)) != 0; } -pub fn clearInterruptFlag() void { +pub fn clear_interrupt_flag() void { regs.write(regs.rtc_pitintflags, regs.bit(regs.rtc_bits.pi)); } diff --git a/port/microchip/attiny/src/hals/attiny1616/tca0.zig b/port/microchip/attiny/src/hals/attiny1616/tca0.zig index f853d176b..ca267b040 100644 --- a/port/microchip/attiny/src/hals/attiny1616/tca0.zig +++ b/port/microchip/attiny/src/hals/attiny1616/tca0.zig @@ -34,11 +34,11 @@ pub const PwmConfig = struct { // dual-slope PWM uses PER as TOP and CMPn as duty cycle. Buffered PER/CMP // writes keep active PWM updates clean for dual-channel LED drivers. // https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf -pub fn configurePwm(config: PwmConfig) void { +pub fn configure_pwm(config: PwmConfig) void { regs.write(regs.tca0_single_ctrla, 0); - setTop(config.top); - setCompare0(config.compare0); - setCompare1(config.compare1); + set_top(config.top); + set_compare0(config.compare0); + set_compare1(config.compare1); var ctrlb: u8 = @intFromEnum(config.waveform); if (config.enable_compare0) ctrlb |= regs.bit(regs.tca_bits.cmp0en); @@ -49,29 +49,29 @@ pub fn configurePwm(config: PwmConfig) void { } pub fn stop() void { - regs.clearBits(regs.tca0_single_ctrla, regs.bit(regs.tca_bits.enable)); + regs.clear_bits(regs.tca0_single_ctrla, regs.bit(regs.tca_bits.enable)); } -pub fn setTop(value: u16) void { +pub fn set_top(value: u16) void { regs.mem16(regs.tca0_single_perbuf).* = value; } -pub fn setCompare0(value: u16) void { +pub fn set_compare0(value: u16) void { regs.mem16(regs.tca0_single_cmp0buf).* = value; } -pub fn setCompare1(value: u16) void { +pub fn set_compare1(value: u16) void { regs.mem16(regs.tca0_single_cmp1buf).* = value; } -pub fn setCounter(value: u16) void { +pub fn set_counter(value: u16) void { regs.mem16(regs.tca0_single_cnt).* = value; } -pub fn enableOverflowInterrupt() void { - regs.setBits(regs.tca0_single_intctrl, regs.bit(regs.tca_bits.ovf)); +pub fn enable_overflow_interrupt() void { + regs.set_bits(regs.tca0_single_intctrl, regs.bit(regs.tca_bits.ovf)); } -pub fn clearOverflowFlag() void { +pub fn clear_overflow_flag() void { regs.write(regs.tca0_single_intflags, regs.bit(regs.tca_bits.ovf)); } diff --git a/port/microchip/attiny/src/hals/attiny1616/watchdog.zig b/port/microchip/attiny/src/hals/attiny1616/watchdog.zig index c62a2dc0f..7f9e60eb1 100644 --- a/port/microchip/attiny/src/hals/attiny1616/watchdog.zig +++ b/port/microchip/attiny/src/hals/attiny1616/watchdog.zig @@ -22,7 +22,7 @@ pub fn reset() void { pub fn configure(period: Period) void { while (busy()) {} - clock.protectedWrite(regs.wdt_ctrla, @intFromEnum(period)); + clock.protected_write(regs.wdt_ctrla, @intFromEnum(period)); } pub fn stop() void { diff --git a/port/microchip/attiny/src/hals/attiny1634/adc.zig b/port/microchip/attiny/src/hals/attiny1634/adc.zig index 592f0090b..bdc153b7e 100644 --- a/port/microchip/attiny/src/hals/attiny1634/adc.zig +++ b/port/microchip/attiny/src/hals/attiny1634/adc.zig @@ -27,7 +27,7 @@ pub const Prescaler = enum(u3) { div128 = 7, }; -pub fn configureInternal1v1(channel: Channel, prescaler: Prescaler) void { +pub fn configure_internal1v1(channel: Channel, prescaler: Prescaler) void { // ATtiny1634 datasheet section 17.13.1, page 177: ADMUX selects reference // and channel; ADCSRA starts/enables auto-triggered conversions. // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf @@ -37,14 +37,14 @@ pub fn configureInternal1v1(channel: Channel, prescaler: Prescaler) void { } pub inline fn start() void { - regs.setBits(regs.ADCSRA, 1 << 6); + regs.set_bits(regs.ADCSRA, 1 << 6); } pub inline fn stop() void { - regs.clearBits(regs.ADCSRA, 1 << 7); + regs.clear_bits(regs.ADCSRA, 1 << 7); } -pub inline fn readRaw16() u16 { +pub inline fn read_raw16() u16 { const low = regs.read(regs.ADCL); const high = regs.read(regs.ADCH); return (@as(u16, high) << 8) | low; diff --git a/port/microchip/attiny/src/hals/attiny1634/eeprom.zig b/port/microchip/attiny/src/hals/attiny1634/eeprom.zig index 238175771..cf0f9c0a9 100644 --- a/port/microchip/attiny/src/hals/attiny1634/eeprom.zig +++ b/port/microchip/attiny/src/hals/attiny1634/eeprom.zig @@ -8,26 +8,26 @@ pub inline fn address(value: u8) Address { return @enumFromInt(value); } -pub inline fn busyWait() void { +pub inline fn busy_wait() void { while ((regs.read(regs.EECR) & regs.bit(regs.eeprom_bits.eepe)) != 0) {} } -pub fn readByte(addr: Address) u8 { - busyWait(); +pub fn read_byte(addr: Address) u8 { + busy_wait(); regs.write(regs.EEARL, @intFromEnum(addr)); regs.write(regs.EEARH, 0); - regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); return regs.read(regs.EEDR); } -pub fn updateByte(addr: Address, value: u8) void { - if (readByte(addr) == value) return; - busyWait(); +pub fn update_byte(addr: Address, value: u8) void { + if (read_byte(addr) == value) return; + busy_wait(); regs.write(regs.EEDR, value); // ATtiny1634 datasheet section 5.3.4, page 23: EEMPE must be followed by // EEPE within four cycles or the byte write is ignored. // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf - regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); - regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); } diff --git a/port/microchip/attiny/src/hals/attiny1634/pcint.zig b/port/microchip/attiny/src/hals/attiny1634/pcint.zig index 08e5fde8d..3d20e41a8 100644 --- a/port/microchip/attiny/src/hals/attiny1634/pcint.zig +++ b/port/microchip/attiny/src/hals/attiny1634/pcint.zig @@ -6,22 +6,22 @@ pub const Group = enum { pcint12_17, }; -pub inline fn enableGroup(group: Group) void { - regs.setBits(regs.GIMSK, switch (group) { +pub inline fn enable_group(group: Group) void { + regs.set_bits(regs.GIMSK, switch (group) { .pcint0_7 => 1 << 4, .pcint8_11 => 1 << 5, .pcint12_17 => 1 << 6, }); } -pub inline fn enablePin(group: Group, bit: u3) void { +pub inline fn enable_pin(group: Group, bit: u3) void { // ATtiny1634 datasheet section 9.3.6, page 53: set PCMSKn plus the // corresponding PCIEn group bit in GIMSK to enable a pin-change source. // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf - regs.setBits(maskRegister(group), regs.bit(bit)); + regs.set_bits(mask_register(group), regs.bit(bit)); } -fn maskRegister(group: Group) u16 { +fn mask_register(group: Group) u16 { return switch (group) { .pcint0_7 => regs.PCMSK0, .pcint8_11 => regs.PCMSK1, diff --git a/port/microchip/attiny/src/hals/attiny1634/registers.zig b/port/microchip/attiny/src/hals/attiny1634/registers.zig index f17adf55f..91f0fe470 100644 --- a/port/microchip/attiny/src/hals/attiny1634/registers.zig +++ b/port/microchip/attiny/src/hals/attiny1634/registers.zig @@ -14,11 +14,11 @@ pub inline fn write(address: u16, value: u8) void { mem8(address).* = value; } -pub inline fn setBits(address: u16, mask: u8) void { +pub inline fn set_bits(address: u16, mask: u8) void { mem8(address).* |= mask; } -pub inline fn clearBits(address: u16, mask: u8) void { +pub inline fn clear_bits(address: u16, mask: u8) void { mem8(address).* &= ~mask; } diff --git a/port/microchip/attiny/src/hals/attiny1634/timer0.zig b/port/microchip/attiny/src/hals/attiny1634/timer0.zig index 89d6b7c1e..d583ca287 100644 --- a/port/microchip/attiny/src/hals/attiny1634/timer0.zig +++ b/port/microchip/attiny/src/hals/attiny1634/timer0.zig @@ -9,13 +9,13 @@ pub const Prescaler = enum(u3) { clk_1024 = 0b101, }; -pub fn configurePhaseCorrectPwmA(prescaler: Prescaler) void { +pub fn configure_phase_correct_pwm_a(prescaler: Prescaler) void { // ATtiny1634 datasheet section 11.7.3, page 83: phase-correct PWM on OC0A. // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf regs.write(regs.TCCR0A, (1 << 7) | (1 << 0)); regs.write(regs.TCCR0B, @intFromEnum(prescaler)); } -pub inline fn setCompareA(value: u8) void { +pub inline fn set_compare_a(value: u8) void { regs.write(regs.OCR0A, value); } diff --git a/port/microchip/attiny/src/hals/attiny1634/timer1.zig b/port/microchip/attiny/src/hals/attiny1634/timer1.zig index 5f3257747..ebb33c384 100644 --- a/port/microchip/attiny/src/hals/attiny1634/timer1.zig +++ b/port/microchip/attiny/src/hals/attiny1634/timer1.zig @@ -14,29 +14,29 @@ pub const DynamicPwmConfig = struct { prescaler: Prescaler = .clk_1, }; -pub fn configurePhaseCorrectDynamic(config: DynamicPwmConfig) void { +pub fn configure_phase_correct_dynamic(config: DynamicPwmConfig) void { // ATtiny1634 datasheet section 12.9.3 and table 12-5, pages 105 and 113: // phase-correct PWM with ICR1 TOP gives runtime-adjustable PWM resolution. // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf regs.write(regs.TCCR1A, (1 << 7) | (1 << 5) | (1 << 1)); regs.write(regs.TCCR1B, (1 << 4) | @as(u8, @intFromEnum(config.prescaler))); - setTop(config.top); + set_top(config.top); } -pub inline fn setTop(value: u16) void { +pub inline fn set_top(value: u16) void { regs.mem16(regs.ICR1).* = value; } -pub inline fn setCompareA(value: u16) void { +pub inline fn set_compare_a(value: u16) void { regs.mem16(regs.OCR1A).* = value; } -pub inline fn setCompareB(value: u16) void { +pub inline fn set_compare_b(value: u16) void { regs.mem16(regs.OCR1B).* = value; } -pub inline fn setCounter(value: u16) void { +pub inline fn set_counter(value: u16) void { regs.mem16(regs.TCNT1).* = value; } diff --git a/port/microchip/attiny/src/hals/attiny1634/watchdog.zig b/port/microchip/attiny/src/hals/attiny1634/watchdog.zig index 2f495a98b..6a2169c44 100644 --- a/port/microchip/attiny/src/hals/attiny1634/watchdog.zig +++ b/port/microchip/attiny/src/hals/attiny1634/watchdog.zig @@ -32,7 +32,7 @@ pub fn configure(mode: Mode, timeout: Timeout) void { // configuration-change protected and must follow the CCP signature write. // https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8303-8-bit-AVR-Microcontroller-tinyAVR-ATtiny1634_Datasheet.pdf regs.write(regs.CCP, 0xD8); - regs.write(regs.WDTCSR, controlValue(mode, timeout)); + regs.write(regs.WDTCSR, control_value(mode, timeout)); microzig.interrupt.enable_interrupts(); } @@ -47,7 +47,7 @@ pub fn stop() void { microzig.interrupt.enable_interrupts(); } -fn controlValue(mode: Mode, timeout: Timeout) u8 { +fn control_value(mode: Mode, timeout: Timeout) u8 { const raw: u4 = @intFromEnum(timeout); const prescaler = (@as(u8, raw & 0b0111)) | ((@as(u8, raw >> 3) & 0x1) << regs.watchdog_bits.wdp3); diff --git a/port/microchip/attiny/src/hals/attiny85/adc.zig b/port/microchip/attiny/src/hals/attiny85/adc.zig index a61db9ecf..7779804fb 100644 --- a/port/microchip/attiny/src/hals/attiny85/adc.zig +++ b/port/microchip/attiny/src/hals/attiny85/adc.zig @@ -48,23 +48,23 @@ pub fn apply(config: Config) void { @as(u8, @intFromEnum(config.prescaler))); } -pub inline fn useAdcNoiseReductionSleep() void { - sleep.setMode(.adc_noise_reduction); +pub inline fn use_adc_noise_reduction_sleep() void { + sleep.set_mode(.adc_noise_reduction); } pub inline fn start() void { - regs.setBits(regs.ADCSRA, regs.bit(regs.adc_bits.adsc)); + regs.set_bits(regs.ADCSRA, regs.bit(regs.adc_bits.adsc)); } pub inline fn stop() void { - regs.clearBits(regs.ADCSRA, regs.bit(regs.adc_bits.aden)); + regs.clear_bits(regs.ADCSRA, regs.bit(regs.adc_bits.aden)); } -pub inline fn conversionRunning() bool { +pub inline fn conversion_running() bool { return (regs.read(regs.ADCSRA) & regs.bit(regs.adc_bits.adsc)) != 0; } -pub inline fn enableDigitalInput(channel: Channel, enabled: bool) void { +pub inline fn enable_digital_input(channel: Channel, enabled: bool) void { const mask: u8 = switch (channel) { .adc0 => 1 << 5, .adc1 => 1 << 2, @@ -72,21 +72,21 @@ pub inline fn enableDigitalInput(channel: Channel, enabled: bool) void { .adc3 => 1 << 3, else => 0, }; - if (enabled) regs.clearBits(regs.DIDR0, mask) else regs.setBits(regs.DIDR0, mask); + if (enabled) regs.clear_bits(regs.DIDR0, mask) else regs.set_bits(regs.DIDR0, mask); } -pub inline fn readLeftAdjusted10() u16 { +pub inline fn read_left_adjusted10() u16 { const low = regs.read(regs.ADCL); const high = regs.read(regs.ADCH); return (@as(u16, high) << 2) | (@as(u16, low) >> 6); } -pub inline fn readRaw16() u16 { +pub inline fn read_raw16() u16 { const low = regs.read(regs.ADCL); const high = regs.read(regs.ADCH); return (@as(u16, high) << 8) | low; } -pub inline fn clearInterruptFlag() void { - regs.setBits(regs.ADCSRA, regs.bit(regs.adc_bits.adif)); +pub inline fn clear_interrupt_flag() void { + regs.set_bits(regs.ADCSRA, regs.bit(regs.adc_bits.adif)); } diff --git a/port/microchip/attiny/src/hals/attiny85/eeprom.zig b/port/microchip/attiny/src/hals/attiny85/eeprom.zig index 782f1e1ca..be5cba4eb 100644 --- a/port/microchip/attiny/src/hals/attiny85/eeprom.zig +++ b/port/microchip/attiny/src/hals/attiny85/eeprom.zig @@ -11,52 +11,52 @@ pub inline fn address(value: u16) Address { return @enumFromInt(value); } -pub inline fn isReady() bool { +pub inline fn is_ready() bool { return (regs.read(regs.EECR) & regs.bit(regs.eeprom_bits.eepe)) == 0; } -pub inline fn busyWait() void { - while (!isReady()) {} +pub inline fn busy_wait() void { + while (!is_ready()) {} } -pub fn readByte(addr: Address) u8 { - busyWait(); - setAddress(addr); - regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); +pub fn read_byte(addr: Address) u8 { + busy_wait(); + set_address(addr); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); return regs.read(regs.EEDR); } -pub fn writeByte(addr: Address, value: u8) void { - busyWait(); - setAddress(addr); +pub fn write_byte(addr: Address, value: u8) void { + busy_wait(); + set_address(addr); regs.write(regs.EEDR, value); // ATtiny25/45/85 datasheet section 5.3.3, page 22: EEMPE must be followed // by EEPE within four cycles, matching avr-libc's byte-write primitive. // https://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf - regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); - regs.setBits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); } -pub fn updateByte(addr: Address, value: u8) void { - if (readByte(addr) != value) writeByte(addr, value); +pub fn update_byte(addr: Address, value: u8) void { + if (read_byte(addr) != value) write_byte(addr, value); } -pub fn readSlice(addr: Address, dest: []u8) void { +pub fn read_slice(addr: Address, dest: []u8) void { const base = @intFromEnum(addr); for (dest, 0..) |*byte, i| { - byte.* = readByte(address(base + @as(u16, @intCast(i)))); + byte.* = read_byte(address(base + @as(u16, @intCast(i)))); } } -pub fn updateSlice(addr: Address, src: []const u8) void { +pub fn update_slice(addr: Address, src: []const u8) void { const base = @intFromEnum(addr); for (src, 0..) |byte, i| { - updateByte(address(base + @as(u16, @intCast(i))), byte); + update_byte(address(base + @as(u16, @intCast(i))), byte); } } -fn setAddress(addr: Address) void { +fn set_address(addr: Address) void { const raw: u16 = @intFromEnum(addr); regs.write(regs.EEARL, @truncate(raw)); regs.write(regs.EEARH, @truncate(raw >> 8)); diff --git a/port/microchip/attiny/src/hals/attiny85/pcint.zig b/port/microchip/attiny/src/hals/attiny85/pcint.zig index edad2d827..94260b798 100644 --- a/port/microchip/attiny/src/hals/attiny85/pcint.zig +++ b/port/microchip/attiny/src/hals/attiny85/pcint.zig @@ -9,22 +9,22 @@ pub const PinChange = enum(u3) { pcint5 = 5, }; -pub inline fn enablePin(pin: PinChange) void { - regs.setBits(regs.PCMSK, regs.bit(@intFromEnum(pin))); +pub inline fn enable_pin(pin: PinChange) void { + regs.set_bits(regs.PCMSK, regs.bit(@intFromEnum(pin))); } -pub inline fn disablePin(pin: PinChange) void { - regs.clearBits(regs.PCMSK, regs.bit(@intFromEnum(pin))); +pub inline fn disable_pin(pin: PinChange) void { + regs.clear_bits(regs.PCMSK, regs.bit(@intFromEnum(pin))); } pub inline fn enable() void { - regs.setBits(regs.GIMSK, 1 << 5); + regs.set_bits(regs.GIMSK, 1 << 5); } pub inline fn disable() void { - regs.clearBits(regs.GIMSK, 1 << 5); + regs.clear_bits(regs.GIMSK, 1 << 5); } -pub inline fn clearFlag() void { - regs.setBits(regs.GIFR, 1 << 5); +pub inline fn clear_flag() void { + regs.set_bits(regs.GIFR, 1 << 5); } diff --git a/port/microchip/attiny/src/hals/attiny85/progmem.zig b/port/microchip/attiny/src/hals/attiny85/progmem.zig index 4aff06e79..0e0ba29fb 100644 --- a/port/microchip/attiny/src/hals/attiny85/progmem.zig +++ b/port/microchip/attiny/src/hals/attiny85/progmem.zig @@ -12,10 +12,10 @@ pub inline fn read(comptime T: type, ptr: *const T) T { return ptr.*; } -pub inline fn readByte(ptr: *const u8) u8 { +pub inline fn read_byte(ptr: *const u8) u8 { return read(u8, ptr); } -pub inline fn readWord(ptr: *const u16) u16 { +pub inline fn read_word(ptr: *const u16) u16 { return read(u16, ptr); } diff --git a/port/microchip/attiny/src/hals/attiny85/registers.zig b/port/microchip/attiny/src/hals/attiny85/registers.zig index c1dce09b6..95910326f 100644 --- a/port/microchip/attiny/src/hals/attiny85/registers.zig +++ b/port/microchip/attiny/src/hals/attiny85/registers.zig @@ -10,11 +10,11 @@ pub inline fn write(comptime address: u6, value: u8) void { io(address).* = value; } -pub inline fn setBits(comptime address: u6, mask: u8) void { +pub inline fn set_bits(comptime address: u6, mask: u8) void { io(address).* |= mask; } -pub inline fn clearBits(comptime address: u6, mask: u8) void { +pub inline fn clear_bits(comptime address: u6, mask: u8) void { io(address).* &= ~mask; } diff --git a/port/microchip/attiny/src/hals/attiny85/sleep.zig b/port/microchip/attiny/src/hals/attiny85/sleep.zig index 6814edb2a..553d1c776 100644 --- a/port/microchip/attiny/src/hals/attiny85/sleep.zig +++ b/port/microchip/attiny/src/hals/attiny85/sleep.zig @@ -6,18 +6,18 @@ pub const Mode = enum(u2) { power_down = 0b10, }; -pub inline fn setMode(mode: Mode) void { +pub inline fn set_mode(mode: Mode) void { const mask = regs.bit(regs.sleep_bits.sm1) | regs.bit(regs.sleep_bits.sm0); regs.write(regs.MCUCR, (regs.read(regs.MCUCR) & ~mask) | (@as(u8, @intFromEnum(mode)) << regs.sleep_bits.sm0)); } pub inline fn enable() void { - regs.setBits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); + regs.set_bits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); } pub inline fn disable() void { - regs.clearBits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); + regs.clear_bits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); } pub inline fn cpu() void { @@ -25,16 +25,16 @@ pub inline fn cpu() void { } pub inline fn enter(mode: Mode) void { - setMode(mode); + set_mode(mode); enable(); cpu(); disable(); } -pub inline fn bodDisable() void { +pub inline fn bod_disable() void { // ATtiny25/45/85 datasheet, section 7.2: BODS must be set and BODSE // cleared in a timed sequence immediately before SLEEP. const mask = regs.bit(regs.sleep_bits.bods) | regs.bit(regs.sleep_bits.bodse); - regs.setBits(regs.MCUCR, mask); + regs.set_bits(regs.MCUCR, mask); regs.write(regs.MCUCR, regs.read(regs.MCUCR) & ~regs.bit(regs.sleep_bits.bodse)); } diff --git a/port/microchip/attiny/src/hals/attiny85/timer0.zig b/port/microchip/attiny/src/hals/attiny85/timer0.zig index 7741ee0cb..25e3f9960 100644 --- a/port/microchip/attiny/src/hals/attiny85/timer0.zig +++ b/port/microchip/attiny/src/hals/attiny85/timer0.zig @@ -43,11 +43,11 @@ pub fn apply(config: Config) void { @as(u8, @intFromEnum(config.prescaler))); } -pub inline fn setCompareA(value: u8) void { +pub inline fn set_compare_a(value: u8) void { regs.write(regs.OCR0A, value); } -pub inline fn setCompareB(value: u8) void { +pub inline fn set_compare_b(value: u8) void { regs.write(regs.OCR0B, value); } @@ -55,6 +55,6 @@ pub inline fn counter() u8 { return regs.read(regs.TCNT0); } -pub inline fn setCounter(value: u8) void { +pub inline fn set_counter(value: u8) void { regs.write(regs.TCNT0, value); } diff --git a/port/microchip/attiny/src/hals/attiny85/timer1.zig b/port/microchip/attiny/src/hals/attiny85/timer1.zig index 59921aa64..5343d0a8d 100644 --- a/port/microchip/attiny/src/hals/attiny85/timer1.zig +++ b/port/microchip/attiny/src/hals/attiny85/timer1.zig @@ -44,7 +44,7 @@ pub const FastPwmConfig = struct { top: u8 = 255, }; -pub fn configureFastPwm(config: FastPwmConfig) void { +pub fn configure_fast_pwm(config: FastPwmConfig) void { // ATtiny25/45/85 datasheet, sections 12.2 and 12.3: OCR1C is TOP for // Timer/Counter1 PWM and Timer1 register writes are synchronized. regs.write(regs.OCR1C, config.top); @@ -54,7 +54,7 @@ pub fn configureFastPwm(config: FastPwmConfig) void { regs.write(regs.TCCR1, 0x40 | (@as(u8, @intFromEnum(config.compare)) << 4) | @as(u8, @intFromEnum(config.prescaler))); - regs.clearBits(regs.GTCCR, 0b0111_0000); + regs.clear_bits(regs.GTCCR, 0b0111_0000); }, .b => { regs.write(regs.TCCR1, @as(u8, @intFromEnum(config.prescaler))); @@ -63,15 +63,15 @@ pub fn configureFastPwm(config: FastPwmConfig) void { } } -pub inline fn setCompareA(value: u8) void { +pub inline fn set_compare_a(value: u8) void { regs.write(regs.OCR1A, value); } -pub inline fn setCompareB(value: u8) void { +pub inline fn set_compare_b(value: u8) void { regs.write(regs.OCR1B, value); } -pub inline fn setTop(value: u8) void { +pub inline fn set_top(value: u8) void { regs.write(regs.OCR1C, value); } @@ -79,10 +79,10 @@ pub inline fn counter() u8 { return regs.read(regs.TCNT1); } -pub inline fn enableInterrupt(interrupt: Interrupt) void { - regs.setBits(regs.TIMSK, @intFromEnum(interrupt)); +pub inline fn enable_interrupt(interrupt: Interrupt) void { + regs.set_bits(regs.TIMSK, @intFromEnum(interrupt)); } -pub inline fn disableInterrupt(interrupt: Interrupt) void { - regs.clearBits(regs.TIMSK, @intFromEnum(interrupt)); +pub inline fn disable_interrupt(interrupt: Interrupt) void { + regs.clear_bits(regs.TIMSK, @intFromEnum(interrupt)); } diff --git a/port/microchip/attiny/src/hals/attiny85/watchdog.zig b/port/microchip/attiny/src/hals/attiny85/watchdog.zig index 302593dbe..3b7005ca7 100644 --- a/port/microchip/attiny/src/hals/attiny85/watchdog.zig +++ b/port/microchip/attiny/src/hals/attiny85/watchdog.zig @@ -30,29 +30,29 @@ pub fn configure(mode: Mode, timeout: Timeout) void { // https://ww1.microchip.com/downloads/en/devicedoc/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf microzig.interrupt.disable_interrupts(); reset(); - regs.setBits(regs.WDTCR, regs.bit(regs.watchdog_bits.wdce) | regs.bit(regs.watchdog_bits.wde)); - regs.write(regs.WDTCR, controlValue(mode, timeout)); + regs.set_bits(regs.WDTCR, regs.bit(regs.watchdog_bits.wdce) | regs.bit(regs.watchdog_bits.wde)); + regs.write(regs.WDTCR, control_value(mode, timeout)); microzig.interrupt.enable_interrupts(); } pub fn stop() void { microzig.interrupt.disable_interrupts(); reset(); - regs.clearBits(regs.MCUSR, 1 << 3); - regs.setBits(regs.WDTCR, regs.bit(regs.watchdog_bits.wdce) | regs.bit(regs.watchdog_bits.wde)); + regs.clear_bits(regs.MCUSR, 1 << 3); + regs.set_bits(regs.WDTCR, regs.bit(regs.watchdog_bits.wdce) | regs.bit(regs.watchdog_bits.wde)); regs.write(regs.WDTCR, 0); microzig.interrupt.enable_interrupts(); } -pub fn forceReset(timeout: Timeout) noreturn { +pub fn force_reset(timeout: Timeout) noreturn { microzig.interrupt.disable_interrupts(); - regs.write(regs.WDTCR, controlValue(.reset, timeout)); + regs.write(regs.WDTCR, control_value(.reset, timeout)); microzig.interrupt.enable_interrupts(); reset(); while (true) asm volatile ("" ::: .{ .memory = true }); } -fn controlValue(mode: Mode, timeout: Timeout) u8 { +fn control_value(mode: Mode, timeout: Timeout) u8 { const raw: u4 = @intFromEnum(timeout); const prescaler = (@as(u8, raw & 0b0111)) | ((@as(u8, raw >> 3) & 0x1) << regs.watchdog_bits.wdp3);