diff --git a/build.zig b/build.zig index 06c634afd..1a3159776 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,11 @@ 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.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 }, 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 + } +}; 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 }); } }; } diff --git a/examples/microchip/attiny/build.zig b/examples/microchip/attiny/build.zig new file mode 100644 index 000000000..ea5d795e2 --- /dev/null +++ b/examples/microchip/attiny/build.zig @@ -0,0 +1,45 @@ +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" }, + }; + + 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({}); + } +} diff --git a/port/microchip/attiny/README.md b/port/microchip/attiny/README.md new file mode 100644 index 000000000..61980d8dd --- /dev/null +++ b/port/microchip/attiny/README.md @@ -0,0 +1,16 @@ +# Microchip ATtiny Hardware Support Package + +## Supported Chips + +- ATtiny85 +- ATtiny84 + +## 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 b/port/microchip/attiny/build.zig new file mode 100644 index 000000000..a2b21cd5e --- /dev/null +++ b/port/microchip/attiny/build.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const microzig = @import("microzig/build-internals"); + +const Self = @This(); + +chips: struct { + attiny85: *const microzig.Target, + attiny84: *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 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, + }; + + return .{ + .chips = .{ + .attiny85 = chip_attiny85.derive(.{}), + .attiny84 = chip_attiny84.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"); +} 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", + }, +} 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", +}; 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..98a622082 --- /dev/null +++ b/port/microchip/attiny/src/hals/ATtiny85.zig @@ -0,0 +1,68 @@ +const microzig = @import("microzig"); +const cpu = microzig.cpu; + +pub const gpio = struct { + 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, + }; + + // IO addresses (data address - 0x20) for SBI/CBI instructions. + // PINB=0x16, DDRB=0x17, PORTB=0x18 + pub inline fn get_regs(port: Port) *volatile Regs { + return switch (port) { + .b => @ptrFromInt(0x16), + }; + } + }; + + 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); + } + }; +};