diff --git a/build.zig b/build.zig index 9c518c7b9..aacc07f80 100644 --- a/build.zig +++ b/build.zig @@ -727,6 +727,16 @@ pub fn MicroBuild(port_select: PortSelect) type { .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/examples/microchip/attiny/build.zig b/examples/microchip/attiny/build.zig index 4928514b3..df2730852 100644 --- a/examples/microchip/attiny/build.zig +++ b/examples/microchip/attiny/build.zig @@ -13,7 +13,14 @@ pub fn build(b: *std.Build) void { 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| { 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..8d7807d82 --- /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.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.configure_pwm(.{ + .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.enable_result_ready_interrupt(); + hal.adc.start(); + hal.watchdog.reset(); + + const saved_level = hal.eeprom.read_byte(.from_int(0)); + const ramp_level = Ramp.get(1); + _ = saved_level; + _ = ramp_level; + + while (true) { + nop(); + } +} + +inline fn nop() void { + asm volatile ("nop"); +} 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..6c0c9e3b4 --- /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.configure_phase_correct_dynamic(.{ .top = 255, .prescaler = .clk_1 }); + hal.timer1.set_compare_a(96); + hal.timer1.set_compare_b(64); + + hal.timer0.configure_phase_correct_pwm_a(.clk_1); + hal.timer0.set_compare_a(32); + + hal.adc.configure_internal1v1(.adc6, .div64); + hal.adc.start(); + hal.watchdog.reset(); + hal.watchdog.configure(.interrupt, .ms16); + + const saved_level = hal.eeprom.read_byte(hal.eeprom.address(0)); + const gamma_level = Gamma.get(2); + _ = saved_level; + _ = gamma_level; + + while (true) { + nop(); + } +} + +inline fn nop() void { + asm volatile ("nop"); +} 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 index 959573275..3f7fceaae 100644 --- a/port/microchip/attiny/README.md +++ b/port/microchip/attiny/README.md @@ -3,6 +3,9 @@ ## Supported Chips - ATtiny85 +- ATtiny84 +- ATtiny1634 +- ATtiny1616 ## FYI: LLVM issues diff --git a/port/microchip/attiny/build.zig b/port/microchip/attiny/build.zig index 290cbab4d..1f68b9463 100644 --- a/port/microchip/attiny/build.zig +++ b/port/microchip/attiny/build.zig @@ -5,11 +5,16 @@ 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, }, }, @@ -17,6 +22,7 @@ pub fn init(dep: *std.Build.Dependency) ?Self { const b = dep.builder; const atpack = b.lazyDependency("atpack", .{}) orelse return null; + const avr25_target: std.Target.Query = .{ .cpu_arch = .avr, .cpu_model = .{ .explicit = &std.Target.avr.cpu.avr25 }, @@ -24,6 +30,20 @@ pub fn init(dep: *std.Build.Dependency) ?Self { .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, @@ -45,11 +65,84 @@ pub fn init(dep: *std.Build.Dependency) ?Self { .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 = .{ @@ -58,6 +151,13 @@ pub fn init(dep: *std.Build.Dependency) ?Self { .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"), + }, + }), }, }, }; 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/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/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/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/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 index 98a622082..258271311 100644 --- a/port/microchip/attiny/src/hals/ATtiny85.zig +++ b/port/microchip/attiny/src/hals/ATtiny85.zig @@ -1,68 +1,17 @@ -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); - } - }; +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; }; 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..9bb5275e7 --- /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.clear_bits(regs.adc0_ctrla, regs.bit(regs.adc_bits.enable)); +} + +pub fn result_ready() bool { + return (regs.read(regs.adc0_intflags) & regs.bit(regs.adc_bits.resrdy)) != 0; +} + +pub fn clear_result_ready() void { + regs.write(regs.adc0_intflags, 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 disable_result_ready_interrupt() void { + regs.clear_bits(regs.adc0_intctrl, regs.bit(regs.adc_bits.resrdy)); +} + +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 new file mode 100644 index 000000000..e169e151b --- /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 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. + // 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 set_prescaler(prescaler: Prescaler) void { + protected_write(regs.clkctrl_mclkctrlb, @intFromEnum(prescaler)); +} + +pub fn use_default20_m_hz_div2() void { + set_prescaler(.div2); +} + +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 new file mode 100644 index 000000000..c721cc755 --- /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 from_int(value: u8) Address { + return @enumFromInt(value); + } +}; + +pub fn read_byte(address: Address) u8 { + return regs.mem8(data_start + @as(u16, @intFromEnum(address))).*; +} + +pub fn write_byte(address: Address, value: u8) void { + busy_wait(); + regs.mem8(data_start + @as(u16, @intFromEnum(address))).* = value; + execute_command(command_page_erase_write); +} + +pub fn update_byte(address: Address, value: u8) void { + if (read_byte(address) != value) write_byte(address, value); +} + +pub fn read_slice(comptime len: usize, start: Address) [len]u8 { + var out: [len]u8 = undefined; + for (&out, 0..) |*byte, offset| { + byte.* = read_byte(@enumFromInt(@intFromEnum(start) + offset)); + } + return out; +} + +pub fn busy_wait() void { + while ((regs.read(regs.nvmctrl_status) & regs.bit(regs.nvm_bits.eebusy)) != 0) {} +} + +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. + // 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/gpio.zig b/port/microchip/attiny/src/hals/attiny1616/gpio.zig new file mode 100644 index 000000000..d5c42a25a --- /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 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 }, + .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 = port_registers(gpio_pin.port); + switch (direction) { + .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(port_registers(gpio_pin.port).vin) & mask(gpio_pin)) != 0; +} + +pub fn put(gpio_pin: Pin, value: bool) void { + const r = port_registers(gpio_pin.port); + if (value) { + regs.set_bits(r.vout, mask(gpio_pin)); + } else { + regs.clear_bits(r.vout, mask(gpio_pin)); + } +} + +pub fn toggle(gpio_pin: Pin) void { + 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 { + // 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(port_registers(gpio_pin.port).pinctrl + gpio_pin.index, value); +} + +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 new file mode 100644 index 000000000..52924b28a --- /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 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 new file mode 100644 index 000000000..93a3aea1e --- /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 set_bits(address: u16, mask: u8) void { + write(address, read(address) | mask); +} + +pub inline fn clear_bits(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; +}; 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..a16cbe3e1 --- /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 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 new file mode 100644 index 000000000..ca267b040 --- /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 configure_pwm(config: PwmConfig) void { + regs.write(regs.tca0_single_ctrla, 0); + 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); + 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.clear_bits(regs.tca0_single_ctrla, regs.bit(regs.tca_bits.enable)); +} + +pub fn set_top(value: u16) void { + regs.mem16(regs.tca0_single_perbuf).* = value; +} + +pub fn set_compare0(value: u16) void { + regs.mem16(regs.tca0_single_cmp0buf).* = value; +} + +pub fn set_compare1(value: u16) void { + regs.mem16(regs.tca0_single_cmp1buf).* = value; +} + +pub fn set_counter(value: u16) void { + regs.mem16(regs.tca0_single_cnt).* = value; +} + +pub fn enable_overflow_interrupt() void { + regs.set_bits(regs.tca0_single_intctrl, regs.bit(regs.tca_bits.ovf)); +} + +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 new file mode 100644 index 000000000..7f9e60eb1 --- /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.protected_write(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; +} 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..bdc153b7e --- /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 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 + 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.set_bits(regs.ADCSRA, 1 << 6); +} + +pub inline fn stop() void { + regs.clear_bits(regs.ADCSRA, 1 << 7); +} + +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 new file mode 100644 index 000000000..cf0f9c0a9 --- /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 busy_wait() void { + while ((regs.read(regs.EECR) & regs.bit(regs.eeprom_bits.eepe)) != 0) {} +} + +pub fn read_byte(addr: Address) u8 { + busy_wait(); + regs.write(regs.EEARL, @intFromEnum(addr)); + regs.write(regs.EEARH, 0); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eere)); + return regs.read(regs.EEDR); +} + +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.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/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/pcint.zig b/port/microchip/attiny/src/hals/attiny1634/pcint.zig new file mode 100644 index 000000000..3d20e41a8 --- /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 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 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.set_bits(mask_register(group), regs.bit(bit)); +} + +fn mask_register(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/registers.zig b/port/microchip/attiny/src/hals/attiny1634/registers.zig new file mode 100644 index 000000000..91f0fe470 --- /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 set_bits(address: u16, mask: u8) void { + mem8(address).* |= mask; +} + +pub inline fn clear_bits(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; +}; 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..d583ca287 --- /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 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 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 new file mode 100644 index 000000000..ebb33c384 --- /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 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))); + set_top(config.top); +} + +pub inline fn set_top(value: u16) void { + regs.mem16(regs.ICR1).* = value; +} + +pub inline fn set_compare_a(value: u16) void { + regs.mem16(regs.OCR1A).* = value; +} + +pub inline fn set_compare_b(value: u16) void { + regs.mem16(regs.OCR1B).* = value; +} + +pub inline fn set_counter(value: u16) void { + regs.mem16(regs.TCNT1).* = value; +} + +pub inline fn counter() u16 { + return regs.mem16(regs.TCNT1).*; +} 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..6a2169c44 --- /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, control_value(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 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); + 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), + }; +} 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..7779804fb --- /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 use_adc_noise_reduction_sleep() void { + sleep.set_mode(.adc_noise_reduction); +} + +pub inline fn start() void { + regs.set_bits(regs.ADCSRA, regs.bit(regs.adc_bits.adsc)); +} + +pub inline fn stop() void { + regs.clear_bits(regs.ADCSRA, regs.bit(regs.adc_bits.aden)); +} + +pub inline fn conversion_running() bool { + return (regs.read(regs.ADCSRA) & regs.bit(regs.adc_bits.adsc)) != 0; +} + +pub inline fn enable_digital_input(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.clear_bits(regs.DIDR0, mask) else regs.set_bits(regs.DIDR0, mask); +} + +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 read_raw16() u16 { + const low = regs.read(regs.ADCL); + const high = regs.read(regs.ADCH); + return (@as(u16, high) << 8) | low; +} + +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 new file mode 100644 index 000000000..be5cba4eb --- /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 is_ready() bool { + return (regs.read(regs.EECR) & regs.bit(regs.eeprom_bits.eepe)) == 0; +} + +pub inline fn busy_wait() void { + while (!is_ready()) {} +} + +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 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.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eempe)); + regs.set_bits(regs.EECR, regs.bit(regs.eeprom_bits.eepe)); +} + +pub fn update_byte(addr: Address, value: u8) void { + if (read_byte(addr) != value) write_byte(addr, value); +} + +pub fn read_slice(addr: Address, dest: []u8) void { + const base = @intFromEnum(addr); + for (dest, 0..) |*byte, i| { + byte.* = read_byte(address(base + @as(u16, @intCast(i)))); + } +} + +pub fn update_slice(addr: Address, src: []const u8) void { + const base = @intFromEnum(addr); + for (src, 0..) |byte, i| { + update_byte(address(base + @as(u16, @intCast(i))), byte); + } +} + +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/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/pcint.zig b/port/microchip/attiny/src/hals/attiny85/pcint.zig new file mode 100644 index 000000000..94260b798 --- /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 enable_pin(pin: PinChange) void { + regs.set_bits(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.set_bits(regs.GIMSK, 1 << 5); +} + +pub inline fn disable() void { + regs.clear_bits(regs.GIMSK, 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 new file mode 100644 index 000000000..0e0ba29fb --- /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 read_byte(ptr: *const u8) u8 { + return read(u8, ptr); +} + +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 new file mode 100644 index 000000000..95910326f --- /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 set_bits(comptime address: u6, mask: u8) void { + io(address).* |= mask; +} + +pub inline fn clear_bits(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; +}; 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..553d1c776 --- /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 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.set_bits(regs.MCUCR, regs.bit(regs.sleep_bits.se)); +} + +pub inline fn disable() void { + regs.clear_bits(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 { + set_mode(mode); + enable(); + cpu(); + disable(); +} + +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.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 new file mode 100644 index 000000000..25e3f9960 --- /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 set_compare_a(value: u8) void { + regs.write(regs.OCR0A, value); +} + +pub inline fn set_compare_b(value: u8) void { + regs.write(regs.OCR0B, value); +} + +pub inline fn counter() u8 { + return regs.read(regs.TCNT0); +} + +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 new file mode 100644 index 000000000..5343d0a8d --- /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 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); + + switch (config.output) { + .a => { + regs.write(regs.TCCR1, 0x40 | + (@as(u8, @intFromEnum(config.compare)) << 4) | + @as(u8, @intFromEnum(config.prescaler))); + regs.clear_bits(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 set_compare_a(value: u8) void { + regs.write(regs.OCR1A, value); +} + +pub inline fn set_compare_b(value: u8) void { + regs.write(regs.OCR1B, value); +} + +pub inline fn set_top(value: u8) void { + regs.write(regs.OCR1C, value); +} + +pub inline fn counter() u8 { + return regs.read(regs.TCNT1); +} + +pub inline fn enable_interrupt(interrupt: Interrupt) void { + regs.set_bits(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 new file mode 100644 index 000000000..3b7005ca7 --- /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.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.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 force_reset(timeout: Timeout) noreturn { + microzig.interrupt.disable_interrupts(); + regs.write(regs.WDTCR, control_value(.reset, timeout)); + microzig.interrupt.enable_interrupts(); + reset(); + while (true) asm volatile ("" ::: .{ .memory = true }); +} + +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); + 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), + }; +}