From 08decbf6b0348b9d4fa56800b5ff16a8fcfcc82e Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 16:32:58 +0200 Subject: [PATCH 1/9] Update build.zig --- port/stmicro/stm32/build.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/port/stmicro/stm32/build.zig b/port/stmicro/stm32/build.zig index c3c8cece0..f152f6a8e 100644 --- a/port/stmicro/stm32/build.zig +++ b/port/stmicro/stm32/build.zig @@ -76,6 +76,10 @@ pub fn init(dep: *std.Build.Dependency) Self { .name = "STM32F429IDISCOVERY", .root_source_file = b.path("src/boards/STM32F429IDISCOVERY.zig"), }, + .hal = microzig.HardwareAbstractionLayer{ + .root_source_file = b.path("src/hals/STM32F303.zig"), + .imports = hal_imports, + }, }), }, }; From 54c76d57136992eb35e47959b40782a571a6627b Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 16:35:24 +0200 Subject: [PATCH 2/9] Update build.zig --- port/stmicro/stm32/build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/stmicro/stm32/build.zig b/port/stmicro/stm32/build.zig index f152f6a8e..859bda2a3 100644 --- a/port/stmicro/stm32/build.zig +++ b/port/stmicro/stm32/build.zig @@ -77,7 +77,7 @@ pub fn init(dep: *std.Build.Dependency) Self { .root_source_file = b.path("src/boards/STM32F429IDISCOVERY.zig"), }, .hal = microzig.HardwareAbstractionLayer{ - .root_source_file = b.path("src/hals/STM32F303.zig"), + .root_source_file = b.path("src/hals/STM32F429.zig"), .imports = hal_imports, }, }), From 9aa4bb7628e54491008bdc73af5539112022a264 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 16:48:43 +0200 Subject: [PATCH 3/9] Update STM32F429.zig --- port/stmicro/stm32/src/hals/STM32F429.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/port/stmicro/stm32/src/hals/STM32F429.zig b/port/stmicro/stm32/src/hals/STM32F429.zig index 43caee757..fa300a8cd 100644 --- a/port/stmicro/stm32/src/hals/STM32F429.zig +++ b/port/stmicro/stm32/src/hals/STM32F429.zig @@ -6,14 +6,14 @@ //! default AHB prescaler = /1 (= values 0..7): //! //! ``` -//! RCC.CFGR.modify(.{ .HPRE = 0 }); +//! RCC.CFGR.modify(.{ .HPRE = .Div1 }); //! ``` //! //! so also HCLK = 16 MHz. //! And with the default APB1 prescaler = /1: //! //! ``` -//! RCC.CFGR.modify(.{ .PPRE1 = 0 }); +//! RCC.CFGR.modify(.{ .PPRE1 = .Div1 }); //! ``` //! //! results in PCLK1 = 16 MHz. @@ -22,7 +22,7 @@ const std = @import("std"); const microzig = @import("microzig"); -const peripherals = microzig.peripherals; +const peripherals = microzig.chip.peripherals; const RCC = peripherals.RCC; pub const clock = struct { From aa25931b9a11b773640fbd1c4f5e93f03f411604 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 17:19:33 +0200 Subject: [PATCH 4/9] Update STM32F429.zig --- port/stmicro/stm32/src/hals/STM32F429.zig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/port/stmicro/stm32/src/hals/STM32F429.zig b/port/stmicro/stm32/src/hals/STM32F429.zig index fa300a8cd..18bbdd4d4 100644 --- a/port/stmicro/stm32/src/hals/STM32F429.zig +++ b/port/stmicro/stm32/src/hals/STM32F429.zig @@ -22,6 +22,7 @@ const std = @import("std"); const microzig = @import("microzig"); +const mmio = microzig.mmio; const peripherals = microzig.chip.peripherals; const RCC = peripherals.RCC; @@ -68,13 +69,16 @@ fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) voi pub const gpio = struct { pub fn set_output(comptime pin: type) void { - set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01); + const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; + const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); + set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); + set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Output); } pub fn set_input(comptime pin: type) void { - set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00); + const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; + set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); + set_reg_field(@field(pin.gpio_port, "MODER"), "MODER[" ++ pin.suffix ++ "]", .Input); } pub fn read(comptime pin: type) microzig.gpio.State { From a7e24ac9edda0f5d425e58b1b3413676e42e5864 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 17:29:42 +0200 Subject: [PATCH 5/9] Update pins_v2.zig --- .../stmicro/stm32/src/hals/common/pins_v2.zig | 320 ++++-------------- 1 file changed, 75 insertions(+), 245 deletions(-) diff --git a/port/stmicro/stm32/src/hals/common/pins_v2.zig b/port/stmicro/stm32/src/hals/common/pins_v2.zig index 198831cfd..e3b02378f 100644 --- a/port/stmicro/stm32/src/hals/common/pins_v2.zig +++ b/port/stmicro/stm32/src/hals/common/pins_v2.zig @@ -1,272 +1,102 @@ -const std = @import("std"); -const assert = std.debug.assert; -const comptimePrint = std.fmt.comptimePrint; -const StructField = std.builtin.Type.StructField; +//! For now we keep all clock settings on the chip defaults. +//! This code should work with all the STM32F42xx line +//! +//! Specifically, TIM6 is running on a 16 MHz clock, +//! HSI = 16 MHz is the SYSCLK after reset +//! default AHB prescaler = /1 (= values 0..7): +//! +//! ``` +//! RCC.CFGR.modify(.{ .HPRE = .Div1 }); +//! ``` +//! +//! so also HCLK = 16 MHz. +//! And with the default APB1 prescaler = /1: +//! +//! ``` +//! RCC.CFGR.modify(.{ .PPRE1 = .Div1 }); +//! ``` +//! +//! results in PCLK1 = 16 MHz. +//! +//! TODO: add more clock calculations when adding Uart +const std = @import("std"); const microzig = @import("microzig"); -const enums = @import("../common/enums.zig"); +const mmio = microzig.mmio; +const peripherals = microzig.chip.peripherals; +const RCC = peripherals.RCC; const Digital_IO = microzig.drivers.base.Digital_IO; -const Direction = Digital_IO.Direction; -const SetDirError = Digital_IO.SetDirError; -const SetBiasError = Digital_IO.SetBiasError; -const WriteError = Digital_IO.WriteError; -const ReadError = Digital_IO.ReadError; const State = Digital_IO.State; -const gpio_v2 = microzig.chip.types.peripherals.gpio_v2; -const PUPDR = gpio_v2.PUPDR; -const rcc = microzig.hal.rcc; - -const gpio = @import("gpio_v2.zig"); - -pub const Pin = enum { - PIN0, - PIN1, - PIN2, - PIN3, - PIN4, - PIN5, - PIN6, - PIN7, - PIN8, - PIN9, - PIN10, - PIN11, - PIN12, - PIN13, - PIN14, - PIN15, - pub const Configuration = struct { - name: ?[:0]const u8 = null, - mode: ?gpio.Mode = null, +pub const clock = struct { + pub const Domain = enum { + cpu, + ahb, + apb1, + apb2, }; }; -pub const InputGPIO = struct { - pin: gpio.Pin, - pub inline fn read(self: @This()) u1 { - const port = self.pin.get_port(); - return if (port.IDR.raw & self.pin.mask() != 0) - 1 - else - 0; - } -}; - -pub const OutputGPIO = struct { - pin: gpio.Pin, - - pub inline fn put(self: @This(), value: u1) void { - var port = self.pin.get_port(); - switch (value) { - 0 => port.BSRR.raw = @intCast(self.pin.mask() << 16), - 1 => port.BSRR.raw = self.pin.mask(), - } - } - - pub inline fn low(self: @This()) void { - self.put(0); - } - - pub inline fn high(self: @This()) void { - self.put(1); - } - - pub inline fn toggle(self: @This()) void { - var port = self.pin.get_port(); - port.ODR.raw ^= self.pin.mask(); - } -}; - -pub const AlternateFunction = struct { - // Empty on perpose it should not be used as a GPIO. +// Default clock frequencies after reset, see top comment for calculation +pub const clock_frequencies = .{ + .cpu = 16_000_000, + .ahb = 16_000_000, + .apb1 = 16_000_000, + .apb2 = 16_000_000, }; -const Analog = struct { - pin: gpio.Pin, -}; - -pub const Digital_IO_Pin = struct { - pin: gpio.Pin, - const vtable: Digital_IO.VTable = .{ - .set_direction_fn = Digital_IO_Pin.set_direction_fn, - .set_bias_fn = Digital_IO_Pin.set_bias_fn, - .write_fn = Digital_IO_Pin.write_fn, - .read_fn = Digital_IO_Pin.read_fn, - }; - pub fn set_direction_fn(ptr: *anyopaque, dir: Direction) SetDirError!void { - const self: *@This() = @ptrCast(@alignCast(ptr)); - switch (dir) { - .input => self.pin.set_moder(.Input), - .output => self.pin.set_moder(.Output), - } - } - pub fn set_bias_fn(ptr: *anyopaque, maybe_bias: ?State) SetBiasError!void { - const self: *@This() = @ptrCast(@alignCast(ptr)); +pub fn parse_pin(comptime spec: []const u8) type { + const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme."; - const pupdr: PUPDR = if (maybe_bias) |bias| switch (bias) { - .low => .PullDown, - .high => .PullUp, - } else .Floating; - self.pin.set_bias(pupdr); - } - pub fn write_fn(ptr: *anyopaque, state: State) WriteError!void { - const self: *@This() = @ptrCast(@alignCast(ptr)); - var port = self.pin.get_port(); - switch (state) { - .low => port.BSRR.raw = @intCast(self.pin.mask() << 16), - .high => port.BSRR.raw = self.pin.mask(), - } - } - pub fn read_fn(ptr: *anyopaque) ReadError!State { - const self: *@This() = @ptrCast(@alignCast(ptr)); - const port = self.pin.get_port(); - return if (port.IDR.raw & self.pin.mask() != 0) - .high - else - .low; - } + if (spec[0] != 'P') + @compileError(invalid_format_msg); + if (spec[1] < 'A' or spec[1] > 'K') + @compileError(invalid_format_msg); - pub fn digital_io(ptr: *@This()) Digital_IO { - return .{ - .ptr = ptr, - .vtable = &vtable, - }; - } -}; + const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); -pub fn GPIO(comptime mode: gpio.Mode) type { - return switch (mode) { - .input => InputGPIO, - .output => OutputGPIO, - .alternate_function => AlternateFunction, - .analog => Analog, - .digital_io => Digital_IO_Pin, + return struct { + /// 'A'...'K' + const gpio_port_name = spec[1..2]; + const gpio_port = @field(peripherals, "GPIO" ++ gpio_port_name); + const suffix = std.fmt.comptimePrint("{d}", .{pin_number}); }; } -pub fn Pins(comptime config: GlobalConfiguration) type { - comptime { - var fields: []const StructField = &.{}; - for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { - if (@field(config, port_field.name)) |port_config| { - for (@typeInfo(Port.Configuration).@"struct".fields) |field| { - if (@field(port_config, field.name)) |pin_config| { - var pin_field = StructField{ - .is_comptime = false, - .default_value_ptr = null, - - // initialized below: - .name = undefined, - .type = undefined, - .alignment = undefined, - }; - - const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; - pin_field.name = pin_config.name orelse default_name; - pin_field.type = GPIO(pin_config.mode orelse .{ .input = .{.floating} }); - pin_field.alignment = @alignOf(field.type); - - fields = fields ++ &[_]StructField{pin_field}; - } - } - } - } - - return @Type(.{ - .@"struct" = .{ - .layout = .auto, - .is_tuple = false, - .fields = fields, - .decls = &.{}, - }, - }); - } +fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) void { + var temp = reg.read(); + @field(temp, field_name) = value; + reg.write(temp); } -pub const Port = enum { - GPIOA, - GPIOB, - GPIOC, - GPIOD, - GPIOE, - GPIOF, - GPIOG, - pub const Configuration = struct { - PIN0: ?Pin.Configuration = null, - PIN1: ?Pin.Configuration = null, - PIN2: ?Pin.Configuration = null, - PIN3: ?Pin.Configuration = null, - PIN4: ?Pin.Configuration = null, - PIN5: ?Pin.Configuration = null, - PIN6: ?Pin.Configuration = null, - PIN7: ?Pin.Configuration = null, - PIN8: ?Pin.Configuration = null, - PIN9: ?Pin.Configuration = null, - PIN10: ?Pin.Configuration = null, - PIN11: ?Pin.Configuration = null, - PIN12: ?Pin.Configuration = null, - PIN13: ?Pin.Configuration = null, - PIN14: ?Pin.Configuration = null, - PIN15: ?Pin.Configuration = null, - - comptime { - const pin_field_count = @typeInfo(Pin).@"enum".fields.len; - const config_field_count = @typeInfo(Configuration).@"struct".fields.len; - if (pin_field_count != config_field_count) - @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); - } - }; -}; - -pub const GlobalConfiguration = struct { - GPIOA: ?Port.Configuration = null, - GPIOB: ?Port.Configuration = null, - GPIOC: ?Port.Configuration = null, - GPIOD: ?Port.Configuration = null, - GPIOE: ?Port.Configuration = null, - GPIOF: ?Port.Configuration = null, - GPIOG: ?Port.Configuration = null, - - comptime { - const port_field_count = @typeInfo(Port).@"enum".fields.len; - const config_field_count = @typeInfo(GlobalConfiguration).@"struct".fields.len; - if (port_field_count != config_field_count) - @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); +pub const gpio = struct { + pub fn set_output(comptime pin: type) void { + const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; + const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); + set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); + set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Output); } - pub fn apply(comptime config: GlobalConfiguration) Pins(config) { - var ret: Pins(config) = undefined; - - inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { - if (@field(config, port_field.name)) |_| { - rcc.enable_clock(@field(enums.Peripherals, port_field.name)); - } - } + pub fn set_input(comptime pin: type) void { + const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; + const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); + set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); + set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Input); + } - inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { - if (@field(config, port_field.name)) |port_config| { - inline for (@typeInfo(Port.Configuration).@"struct".fields) |field| { - if (@field(port_config, field.name)) |pin_config| { - const port = @intFromEnum(@field(Port, port_field.name)); - var pin = gpio.Pin.from_port(@enumFromInt(port), @intFromEnum(@field(Pin, field.name))); - pin.write_pin_config(pin_config.mode.?); - const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; + pub fn read(comptime pin: type) microzig.gpio.State { + const idr_reg = pin.gpio_port.IDR; + const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()? + return @as(microzig.gpio.State, @enumFromInt(reg_value)); + } - switch (pin_config.mode orelse .input) { - .input => @field(ret, pin_config.name orelse default_name) = InputGPIO{ .pin = pin }, - .output => @field(ret, pin_config.name orelse default_name) = OutputGPIO{ .pin = pin }, - .analog => @field(ret, pin_config.name orelse default_name) = Analog{}, - .alternate_function => @field(ret, pin_config.name orelse default_name) = AlternateFunction{}, - .digital_io => @field(ret, pin_config.name orelse default_name) = Digital_IO_Pin{ .pin = pin }, - } - } - } - } + pub fn write(comptime pin: type, state: State) void { + const BSRR: *volatile @TypeOf(pin.gpio_port.BSRR) = &pin.gpio_port.BSRR; + switch (state) { + .low => set_reg_field(BSRR, "BR[" ++ pin.suffix ++ "]", 1), + .high => set_reg_field(BSRR, "BS[" ++ pin.suffix ++ "]", 1), } - - return ret; } }; From 3262f4fca747f9e2440463cdb81592a0333f573e Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 19:58:47 +0200 Subject: [PATCH 6/9] Update STM32F429.zig --- port/stmicro/stm32/src/hals/STM32F429.zig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/port/stmicro/stm32/src/hals/STM32F429.zig b/port/stmicro/stm32/src/hals/STM32F429.zig index 18bbdd4d4..e3b02378f 100644 --- a/port/stmicro/stm32/src/hals/STM32F429.zig +++ b/port/stmicro/stm32/src/hals/STM32F429.zig @@ -26,6 +26,10 @@ const mmio = microzig.mmio; const peripherals = microzig.chip.peripherals; const RCC = peripherals.RCC; +const Digital_IO = microzig.drivers.base.Digital_IO; + +const State = Digital_IO.State; + pub const clock = struct { pub const Domain = enum { cpu, @@ -77,8 +81,9 @@ pub const gpio = struct { pub fn set_input(comptime pin: type) void { const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; + const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(@field(pin.gpio_port, "MODER"), "MODER[" ++ pin.suffix ++ "]", .Input); + set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Input); } pub fn read(comptime pin: type) microzig.gpio.State { @@ -87,10 +92,11 @@ pub const gpio = struct { return @as(microzig.gpio.State, @enumFromInt(reg_value)); } - pub fn write(comptime pin: type, state: microzig.gpio.State) void { + pub fn write(comptime pin: type, state: State) void { + const BSRR: *volatile @TypeOf(pin.gpio_port.BSRR) = &pin.gpio_port.BSRR; switch (state) { - .low => set_reg_field(pin.gpio_port.BSRR, "BR" ++ pin.suffix, 1), - .high => set_reg_field(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1), + .low => set_reg_field(BSRR, "BR[" ++ pin.suffix ++ "]", 1), + .high => set_reg_field(BSRR, "BS[" ++ pin.suffix ++ "]", 1), } } }; From 185e35077e25fd33ec78b9449a54293953a518a2 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Mon, 27 Apr 2026 19:59:41 +0200 Subject: [PATCH 7/9] Merge gpio_v2 and pins_v2 They were tightly coupled together, but had different types for the same concept. This introduced unneeded enum casts from and to int, and added complexity. --- .../stmicro/stm32/src/hals/common/gpio_v2.zig | 177 ------- .../stmicro/stm32/src/hals/common/pins_v2.zig | 470 +++++++++++++++--- 2 files changed, 396 insertions(+), 251 deletions(-) delete mode 100644 port/stmicro/stm32/src/hals/common/gpio_v2.zig diff --git a/port/stmicro/stm32/src/hals/common/gpio_v2.zig b/port/stmicro/stm32/src/hals/common/gpio_v2.zig deleted file mode 100644 index fba4909f6..000000000 --- a/port/stmicro/stm32/src/hals/common/gpio_v2.zig +++ /dev/null @@ -1,177 +0,0 @@ -const std = @import("std"); -const microzig = @import("microzig"); - -const assert = std.debug.assert; -pub const peripherals = microzig.chip.peripherals; - -const gpio_v2 = microzig.chip.types.peripherals.gpio_v2; -const GPIO = gpio_v2.GPIO; -const MODER = gpio_v2.MODER; -const PUPDR = gpio_v2.PUPDR; -const OSPEEDR = gpio_v2.OSPEEDR; -const OT = gpio_v2.OT; -const AFIO = microzig.chip.peripherals.AFIO; - -pub const Port = enum { - A, - B, - C, - D, - E, - F, - G, -}; - -pub const Mode = union(enum) { - input: InputMode, - output: OutputMode, - analog: AnalogMode, - alternate_function: AlternateFunction, - digital_io: Digital_IO, -}; - -pub const Digital_IO = struct {}; - -pub const InputMode = struct { - resistor: PUPDR, -}; - -pub const OutputMode = struct { - resistor: PUPDR, - o_type: OT, - o_speed: OSPEEDR = .LowSpeed, -}; - -pub const AnalogMode = struct { - resistor: PUPDR = .Floating, -}; - -pub const AF = enum(u4) { - AF0, - AF1, - AF2, - AF3, - AF4, - AF5, - AF6, - AF7, - AF8, - AF9, - AF10, - AF11, - AF12, - AF13, - AF14, - AF15, -}; - -pub const AlternateFunction = struct { - afr: AF, - resistor: PUPDR = .Floating, - o_type: OT = .PushPull, - o_speed: OSPEEDR = .HighSpeed, -}; - -// This is mostly internal to hal for writing configuration. -// Public implementation is provided in the pins.zig file. -pub const Pin = enum(usize) { - _, - - pub inline fn write_pin_config(gpio: Pin, mode: Mode) void { - switch (mode) { - .input => |imode| { - gpio.set_moder(MODER.Input); - gpio.set_bias(imode.resistor); - }, - .output => |omode| { - gpio.set_moder(MODER.Output); - gpio.set_output_type(omode.o_type); - gpio.set_bias(omode.resistor); - gpio.set_speed(omode.o_speed); - }, - .analog => |amode| { - gpio.set_moder(MODER.Analog); - gpio.set_bias(amode.resistor); - }, - .alternate_function => |afmode| { - gpio.set_moder(MODER.Alternate); - gpio.set_bias(afmode.resistor); - gpio.set_speed(afmode.o_speed); - gpio.set_output_type(afmode.o_type); - gpio.set_alternate_function(afmode.afr); - }, - .digital_io => { - // Nothing for now - }, - } - } - - pub fn mask_2bit(gpio: Pin) u32 { - const pin: u5 = @intCast(@intFromEnum(gpio) % 16); - return @as(u32, 0b11) << (pin << 1); - } - - pub fn mask(gpio: Pin) u32 { - const pin: u4 = @intCast(@intFromEnum(gpio) % 16); - return @as(u32, 1) << pin; - } - - //NOTE: should invalid pins panic or just be ignored? - pub fn get_port(gpio: Pin) *volatile GPIO { - const port: usize = @divFloor(@intFromEnum(gpio), 16); - switch (port) { - 0 => return if (@hasDecl(peripherals, "GPIOA")) peripherals.GPIOA else @panic("Invalid Pin"), - 1 => return if (@hasDecl(peripherals, "GPIOB")) peripherals.GPIOB else @panic("Invalid Pin"), - 2 => return if (@hasDecl(peripherals, "GPIOC")) peripherals.GPIOC else @panic("Invalid Pin"), - 3 => return if (@hasDecl(peripherals, "GPIOD")) peripherals.GPIOD else @panic("Invalid Pin"), - 4 => return if (@hasDecl(peripherals, "GPIOE")) peripherals.GPIOE else @panic("Invalid Pin"), - 5 => return if (@hasDecl(peripherals, "GPIOF")) peripherals.GPIOF else @panic("Invalid Pin"), - 6 => return if (@hasDecl(peripherals, "GPIOG")) peripherals.GPIOG else @panic("Invalid Pin"), - else => @panic("The STM32 only has ports 0..6 (A..G)"), - } - } - - pub inline fn set_bias(gpio: Pin, bias: PUPDR) void { - const port = gpio.get_port(); - const pin: u5 = @intCast(@intFromEnum(gpio) % 16); - const modMask: u32 = gpio.mask_2bit(); - - port.PUPDR.write_raw((port.PUPDR.raw & ~modMask) | @as(u32, @intFromEnum(bias)) << (pin << 1)); - } - - pub inline fn set_speed(gpio: Pin, speed: OSPEEDR) void { - const port = gpio.get_port(); - const pin: u5 = @intCast(@intFromEnum(gpio) % 16); - const modMask: u32 = gpio.mask_2bit(); - - port.OSPEEDR.write_raw((port.OSPEEDR.raw & ~modMask) | @as(u32, @intFromEnum(speed)) << (pin << 1)); - } - - pub inline fn set_moder(gpio: Pin, moder: MODER) void { - const port = gpio.get_port(); - const pin: u5 = @intCast(@intFromEnum(gpio) % 16); - const modMask: u32 = gpio.mask_2bit(); - - port.MODER.write_raw((port.MODER.raw & ~modMask) | @as(u32, @intFromEnum(moder)) << (pin << 1)); - } - - pub inline fn set_output_type(gpio: Pin, otype: OT) void { - const port = gpio.get_port(); - const pin: u5 = @intCast(@intFromEnum(gpio) % 16); - - port.OTYPER.write_raw((port.OTYPER.raw & ~gpio.mask()) | @as(u32, @intFromEnum(otype)) << pin); - } - - pub inline fn set_alternate_function(gpio: Pin, afr: AF) void { - const port = gpio.get_port(); - const pin: u5 = @intCast(@intFromEnum(gpio) % 16); - const afrMask: u32 = @as(u32, 0b1111) << ((pin % 8) << 2); - const register = if (pin > 7) &port.AFR[1] else &port.AFR[0]; - register.write_raw((register.raw & ~afrMask) | @as(u32, @intFromEnum(afr)) << ((pin % 8) << 2)); - } - - pub fn from_port(port: Port, pin: u4) Pin { - const value: usize = pin + (@as(usize, 16) * @intFromEnum(port)); - return @enumFromInt(value); - } -}; diff --git a/port/stmicro/stm32/src/hals/common/pins_v2.zig b/port/stmicro/stm32/src/hals/common/pins_v2.zig index e3b02378f..af9dc8d0a 100644 --- a/port/stmicro/stm32/src/hals/common/pins_v2.zig +++ b/port/stmicro/stm32/src/hals/common/pins_v2.zig @@ -1,102 +1,424 @@ -//! For now we keep all clock settings on the chip defaults. -//! This code should work with all the STM32F42xx line -//! -//! Specifically, TIM6 is running on a 16 MHz clock, -//! HSI = 16 MHz is the SYSCLK after reset -//! default AHB prescaler = /1 (= values 0..7): -//! -//! ``` -//! RCC.CFGR.modify(.{ .HPRE = .Div1 }); -//! ``` -//! -//! so also HCLK = 16 MHz. -//! And with the default APB1 prescaler = /1: -//! -//! ``` -//! RCC.CFGR.modify(.{ .PPRE1 = .Div1 }); -//! ``` -//! -//! results in PCLK1 = 16 MHz. -//! -//! TODO: add more clock calculations when adding Uart - const std = @import("std"); +const assert = std.debug.assert; +const comptimePrint = std.fmt.comptimePrint; +const StructField = std.builtin.Type.StructField; + const microzig = @import("microzig"); -const mmio = microzig.mmio; -const peripherals = microzig.chip.peripherals; -const RCC = peripherals.RCC; +const enums = @import("../common/enums.zig"); const Digital_IO = microzig.drivers.base.Digital_IO; +const Direction = Digital_IO.Direction; +const SetDirError = Digital_IO.SetDirError; +const SetBiasError = Digital_IO.SetBiasError; +const WriteError = Digital_IO.WriteError; +const ReadError = Digital_IO.ReadError; const State = Digital_IO.State; -pub const clock = struct { - pub const Domain = enum { - cpu, - ahb, - apb1, - apb2, +const gpio_v2 = microzig.chip.types.peripherals.gpio_v2; +const PUPDR = gpio_v2.PUPDR; +const MODER = gpio_v2.MODER; +const OSPEEDR = gpio_v2.OSPEEDR; +const OT = gpio_v2.OT; +const AFIO = microzig.chip.peripherals.AFIO; + +const rcc = microzig.hal.rcc; +const peripherals = microzig.chip.peripherals; + +pub const Mode = union(enum) { + input: InputMode, + output: OutputMode, + analog: AnalogMode, + alternate_function: AlternateFunctionMode, + digital_io: Digital_IOMode, +}; + +const Digital_IOMode = struct {}; + +const InputMode = struct { + resistor: PUPDR, +}; + +const OutputMode = struct { + resistor: PUPDR, + o_type: OT, + o_speed: OSPEEDR = .LowSpeed, +}; + +const AnalogMode = struct { + resistor: PUPDR = .Floating, +}; + +const AF = enum(u4) { + AF0, + AF1, + AF2, + AF3, + AF4, + AF5, + AF6, + AF7, + AF8, + AF9, + AF10, + AF11, + AF12, + AF13, + AF14, + AF15, +}; + +pub const AlternateFunctionMode = struct { + afr: AF, + resistor: PUPDR = .Floating, + o_type: OT = .PushPull, + o_speed: OSPEEDR = .HighSpeed, +}; + +pub const Pin = enum { + PIN0, + PIN1, + PIN2, + PIN3, + PIN4, + PIN5, + PIN6, + PIN7, + PIN8, + PIN9, + PIN10, + PIN11, + PIN12, + PIN13, + PIN14, + PIN15, + pub const Configuration = struct { + name: ?[:0]const u8 = null, + mode: ?Mode = null, }; }; -// Default clock frequencies after reset, see top comment for calculation -pub const clock_frequencies = .{ - .cpu = 16_000_000, - .ahb = 16_000_000, - .apb1 = 16_000_000, - .apb2 = 16_000_000, +const GPIO_Pin = struct { + pin: Pin, + port: Port, + + inline fn write_pin_config(_gpio: GPIO_Pin, mode: Mode) void { + switch (mode) { + .input => |imode| { + _gpio.set_moder(MODER.Input); + _gpio.set_bias(imode.resistor); + }, + .output => |omode| { + _gpio.set_moder(MODER.Output); + _gpio.set_output_type(omode.o_type); + _gpio.set_bias(omode.resistor); + _gpio.set_speed(omode.o_speed); + }, + .analog => |amode| { + _gpio.set_moder(MODER.Analog); + _gpio.set_bias(amode.resistor); + }, + .alternate_function => |afmode| { + _gpio.set_moder(MODER.Alternate); + _gpio.set_bias(afmode.resistor); + _gpio.set_speed(afmode.o_speed); + _gpio.set_output_type(afmode.o_type); + _gpio.set_alternate_function(afmode.afr); + }, + .digital_io => { + // Nothing for now + }, + } + } + + fn mask_2bit(_gpio: GPIO_Pin) u32 { + const pin: u4 = @intFromEnum(_gpio.pin); + return @as(u32, 0b11) << (pin << 1); + } + + fn mask(_gpio: GPIO_Pin) u32 { + const pin: u4 = @intFromEnum(_gpio.pin); + return @as(u32, 1) << pin; + } + + //NOTE: should invalid pins panic or just be ignored? + fn get_port(_gpio: GPIO_Pin) *volatile gpio_v2.GPIO { + return _gpio.port.get_port(); + } + + inline fn set_bias(_gpio: GPIO_Pin, bias: PUPDR) void { + const port = _gpio.port.get_port(); + const pin: u4 = @intFromEnum(_gpio.pin); + const modMask: u32 = _gpio.mask_2bit(); + + port.PUPDR.write_raw((port.PUPDR.raw & ~modMask) | @as(u32, @intFromEnum(bias)) << (pin << 1)); + } + + inline fn set_speed(_gpio: GPIO_Pin, speed: OSPEEDR) void { + const port = _gpio.port.get_port(); + const pin: u5 = @intFromEnum(_gpio.pin); + const modMask: u32 = _gpio.mask_2bit(); + + port.OSPEEDR.write_raw((port.OSPEEDR.raw & ~modMask) | @as(u32, @intFromEnum(speed)) << (pin << 1)); + } + + inline fn set_moder(_gpio: GPIO_Pin, moder: MODER) void { + const port = _gpio.port.get_port(); + const pin: u5 = @intFromEnum(_gpio.pin); + const modMask: u32 = _gpio.mask_2bit(); + + port.MODER.write_raw((port.MODER.raw & ~modMask) | @as(u32, @intFromEnum(moder)) << (pin << 1)); + } + + inline fn set_output_type(_gpio: GPIO_Pin, otype: OT) void { + const port = _gpio.port.get_port(); + const pin: u5 = @intFromEnum(_gpio.pin); + + port.OTYPER.write_raw((port.OTYPER.raw & ~_gpio.mask()) | @as(u32, @intFromEnum(otype)) << pin); + } + + fn from_port(port: Port, pin: Pin) GPIO_Pin { + return .{ + .port = port, + .pin = pin, + }; + } }; -pub fn parse_pin(comptime spec: []const u8) type { - const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme."; +pub const Input_GPIO = struct { + pin: GPIO_Pin, + pub inline fn read(self: @This()) u1 { + const port = self.pin.get_port(); + return if (port.IDR.raw & self.pin.mask() != 0) + 1 + else + 0; + } +}; - if (spec[0] != 'P') - @compileError(invalid_format_msg); - if (spec[1] < 'A' or spec[1] > 'K') - @compileError(invalid_format_msg); +pub const Output_GPIO = struct { + pin: GPIO_Pin, - const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); + pub inline fn put(self: @This(), value: u1) void { + var port = self.pin.get_port(); + switch (value) { + 0 => port.BSRR.raw = @intCast(self.pin.mask() << 16), + 1 => port.BSRR.raw = self.pin.mask(), + } + } + + pub inline fn low(self: @This()) void { + self.put(0); + } + + pub inline fn high(self: @This()) void { + self.put(1); + } + + pub inline fn toggle(self: @This()) void { + var port = self.pin.get_port(); + port.ODR.raw ^= self.pin.mask(); + } +}; + +pub const AlternateFunction = struct { + // Empty on perpose it should not be used as a GPIO. +}; + +const Analog = struct { + pin: GPIO_Pin, +}; - return struct { - /// 'A'...'K' - const gpio_port_name = spec[1..2]; - const gpio_port = @field(peripherals, "GPIO" ++ gpio_port_name); - const suffix = std.fmt.comptimePrint("{d}", .{pin_number}); +pub const Digital_IO_Pin = struct { + pin: GPIO_Pin, + const vtable: Digital_IO.VTable = .{ + .set_direction_fn = Digital_IO_Pin.set_direction_fn, + .set_bias_fn = Digital_IO_Pin.set_bias_fn, + .write_fn = Digital_IO_Pin.write_fn, + .read_fn = Digital_IO_Pin.read_fn, }; -} + pub fn set_direction_fn(ptr: *anyopaque, dir: Direction) SetDirError!void { + const self: *@This() = @ptrCast(@alignCast(ptr)); + switch (dir) { + .input => self.pin.set_moder(.Input), + .output => self.pin.set_moder(.Output), + } + } + pub fn set_bias_fn(ptr: *anyopaque, maybe_bias: ?State) SetBiasError!void { + const self: *@This() = @ptrCast(@alignCast(ptr)); + + const pupdr: PUPDR = if (maybe_bias) |bias| switch (bias) { + .low => .PullDown, + .high => .PullUp, + } else .Floating; + self.pin.set_bias(pupdr); + } + pub fn write_fn(ptr: *anyopaque, state: State) WriteError!void { + const self: *@This() = @ptrCast(@alignCast(ptr)); + var port = self.pin.get_port(); + switch (state) { + .low => port.BSRR.raw = @intCast(self.pin.mask() << 16), + .high => port.BSRR.raw = self.pin.mask(), + } + } + pub fn read_fn(ptr: *anyopaque) ReadError!State { + const self: *@This() = @ptrCast(@alignCast(ptr)); + const port = self.pin.get_port(); + return if (port.IDR.raw & self.pin.mask() != 0) + .high + else + .low; + } + + pub fn digital_io(ptr: *@This()) Digital_IO { + return .{ + .ptr = ptr, + .vtable = &vtable, + }; + } +}; -fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) void { - var temp = reg.read(); - @field(temp, field_name) = value; - reg.write(temp); +pub fn GPIO(comptime mode: Mode) type { + return switch (mode) { + .input => Input_GPIO, + .output => Output_GPIO, + .alternate_function => AlternateFunction, + .analog => Analog, + .digital_io => Digital_IO_Pin, + }; } -pub const gpio = struct { - pub fn set_output(comptime pin: type) void { - const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; - const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); - set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Output); +pub fn Pins(comptime config: GlobalConfiguration) type { + comptime { + var fields: []const StructField = &.{}; + for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { + if (@field(config, port_field.name)) |port_config| { + for (@typeInfo(Port.Configuration).@"struct".fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + var pin_field = StructField{ + .is_comptime = false, + .default_value_ptr = null, + + // initialized below: + .name = undefined, + .type = undefined, + .alignment = undefined, + }; + + const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; + pin_field.name = pin_config.name orelse default_name; + pin_field.type = GPIO(pin_config.mode orelse .{ .input = .{.floating} }); + pin_field.alignment = @alignOf(field.type); + + fields = fields ++ &[_]StructField{pin_field}; + } + } + } + } + + return @Type(.{ + .@"struct" = .{ + .layout = .auto, + .is_tuple = false, + .fields = fields, + .decls = &.{}, + }, + }); } +} + +pub const Port = enum { + GPIOA, + GPIOB, + GPIOC, + GPIOD, + GPIOE, + GPIOF, + GPIOG, + pub const Configuration = struct { + PIN0: ?Pin.Configuration = null, + PIN1: ?Pin.Configuration = null, + PIN2: ?Pin.Configuration = null, + PIN3: ?Pin.Configuration = null, + PIN4: ?Pin.Configuration = null, + PIN5: ?Pin.Configuration = null, + PIN6: ?Pin.Configuration = null, + PIN7: ?Pin.Configuration = null, + PIN8: ?Pin.Configuration = null, + PIN9: ?Pin.Configuration = null, + PIN10: ?Pin.Configuration = null, + PIN11: ?Pin.Configuration = null, + PIN12: ?Pin.Configuration = null, + PIN13: ?Pin.Configuration = null, + PIN14: ?Pin.Configuration = null, + PIN15: ?Pin.Configuration = null, + + comptime { + const pin_field_count = @typeInfo(Pin).@"enum".fields.len; + const config_field_count = @typeInfo(Configuration).@"struct".fields.len; + if (pin_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); + } + }; - pub fn set_input(comptime pin: type) void { - const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; - const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); - set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Input); + //NOTE: should invalid pins panic or just be ignored? + pub fn get_port(port: Port) *volatile gpio_v2.GPIO { + switch (@intFromEnum(port)) { + inline 0...7 - 1 => |_port| { + const port_name = [_]u8{"ABCDEFGHIJK"[_port]}; + return @field(peripherals, "GPIO" ++ port_name); + }, + else => @panic("STM32s only have ports 0..10 (A..K)"), + } } +}; + +pub const GlobalConfiguration = struct { + GPIOA: ?Port.Configuration = null, + GPIOB: ?Port.Configuration = null, + GPIOC: ?Port.Configuration = null, + GPIOD: ?Port.Configuration = null, + GPIOE: ?Port.Configuration = null, + GPIOF: ?Port.Configuration = null, + GPIOG: ?Port.Configuration = null, - pub fn read(comptime pin: type) microzig.gpio.State { - const idr_reg = pin.gpio_port.IDR; - const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()? - return @as(microzig.gpio.State, @enumFromInt(reg_value)); + comptime { + const port_field_count = @typeInfo(Port).@"enum".fields.len; + const config_field_count = @typeInfo(GlobalConfiguration).@"struct".fields.len; + if (port_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); } - pub fn write(comptime pin: type, state: State) void { - const BSRR: *volatile @TypeOf(pin.gpio_port.BSRR) = &pin.gpio_port.BSRR; - switch (state) { - .low => set_reg_field(BSRR, "BR[" ++ pin.suffix ++ "]", 1), - .high => set_reg_field(BSRR, "BS[" ++ pin.suffix ++ "]", 1), + pub fn apply(comptime config: GlobalConfiguration) Pins(config) { + var ret: Pins(config) = undefined; + + inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { + if (@field(config, port_field.name)) |_| { + rcc.enable_clock(@field(enums.Peripherals, port_field.name)); + } } + + inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { + if (@field(config, port_field.name)) |port_config| { + inline for (@typeInfo(Port.Configuration).@"struct".fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + const port = @field(Port, port_field.name); + var pin = GPIO_Pin.from_port(port, @field(Pin, field.name)); + pin.write_pin_config(pin_config.mode.?); + const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; + + switch (pin_config.mode orelse .input) { + .input => @field(ret, pin_config.name orelse default_name) = Input_GPIO{ .pin = pin }, + .output => @field(ret, pin_config.name orelse default_name) = Output_GPIO{ .pin = pin }, + .analog => @field(ret, pin_config.name orelse default_name) = Analog{}, + .alternate_function => @field(ret, pin_config.name orelse default_name) = AlternateFunction{}, + .digital_io => @field(ret, pin_config.name orelse default_name) = Digital_IO_Pin{ .pin = pin }, + } + } + } + } + } + + return ret; } }; From e8b0a8e5a11580348c7ebf99e6d201889b176a1c Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Wed, 29 Apr 2026 11:49:08 +0200 Subject: [PATCH 8/9] Make pins_v2.zig adaptable to multiple chips Previously pins_v2 was only available on the F303 and L47x, and only implemented for 7 GPIO ports on the later. This makes the pins api available to any number of pins (soft limited to 11 due to comptime LUT size). Importer can provide the available number of ports. This also makes the api available for stm32f429 --- .../stmicro/stm32/src/hals/STM32F303/pins.zig | 4 +- port/stmicro/stm32/src/hals/STM32F429.zig | 97 +- .../stmicro/stm32/src/hals/STM32L47X/pins.zig | 4 +- .../stmicro/stm32/src/hals/common/pins_v2.zig | 836 ++++++++++-------- 4 files changed, 510 insertions(+), 431 deletions(-) diff --git a/port/stmicro/stm32/src/hals/STM32F303/pins.zig b/port/stmicro/stm32/src/hals/STM32F303/pins.zig index f2fef522f..77b7a6f97 100644 --- a/port/stmicro/stm32/src/hals/STM32F303/pins.zig +++ b/port/stmicro/stm32/src/hals/STM32F303/pins.zig @@ -1,2 +1,4 @@ -const PinCommon = @import("../common/pins_v2.zig"); +const PinCommon = @import("../common/pins_v2.zig").get_pins(.{ + .portcount = 7, +}); pub const GlobalConfiguration = PinCommon.GlobalConfiguration; diff --git a/port/stmicro/stm32/src/hals/STM32F429.zig b/port/stmicro/stm32/src/hals/STM32F429.zig index e3b02378f..9420fca1b 100644 --- a/port/stmicro/stm32/src/hals/STM32F429.zig +++ b/port/stmicro/stm32/src/hals/STM32F429.zig @@ -30,6 +30,10 @@ const Digital_IO = microzig.drivers.base.Digital_IO; const State = Digital_IO.State; +pub const pins = @import("./common/pins_v2.zig").get_pins(.{ + .portcount = 11, +}); + pub const clock = struct { pub const Domain = enum { cpu, @@ -47,56 +51,63 @@ pub const clock_frequencies = .{ .apb2 = 16_000_000, }; -pub fn parse_pin(comptime spec: []const u8) type { - const invalid_format_msg = "The given pin '" ++ spec ++ "' has an invalid format. Pins must follow the format \"P{Port}{Pin}\" scheme."; - - if (spec[0] != 'P') - @compileError(invalid_format_msg); - if (spec[1] < 'A' or spec[1] > 'K') - @compileError(invalid_format_msg); - - const pin_number: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); - - return struct { - /// 'A'...'K' - const gpio_port_name = spec[1..2]; - const gpio_port = @field(peripherals, "GPIO" ++ gpio_port_name); - const suffix = std.fmt.comptimePrint("{d}", .{pin_number}); - }; -} +// TODO: There should be a common rcc with stuff like this, just like pins_v2.zig +pub const rcc = struct { + const util = @import("common/util.zig"); + const _rcc = microzig.chip.peripherals.RCC; -fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) void { - var temp = reg.read(); - @field(temp, field_name) = value; - reg.write(temp); -} + // Any peripheral that must be enable in RCC. + pub const Peripherals = util.create_peripheral_enum(&.{ + "GPIO", + }); -pub const gpio = struct { - pub fn set_output(comptime pin: type) void { - const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; - const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); - set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Output); + ///configure the power and clock registers before enabling the RTC + ///this function also can be called from `rtc.enable()` + pub fn enable_rtc(on: bool) void { + _rcc.BDCR.modify(.{ .RTCEN = @intFromBool(on) }); } - pub fn set_input(comptime pin: type) void { - const AHB1ENR: *volatile @TypeOf(RCC.AHB1ENR) = &RCC.AHB1ENR; - const MODER: *volatile @TypeOf(@field(pin.gpio_port, "MODER")) = &@field(pin.gpio_port, "MODER"); - set_reg_field(AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(MODER, "MODER[" ++ pin.suffix ++ "]", .Input); + pub fn set_clock(comptime peri: Peripherals, state: u1) void { + const peri_name = @tagName(peri); + const field = peri_name ++ "EN"; + if (util.match_name(peri_name, &.{"RTC"})) { + enable_rtc(state != 0); + return; + } + const rcc_register_name = comptime if (util.match_name(peri_name, &.{ + "OTGHSULPI", + "OTGHS", + "ETHMACPTP", + "ETHMACRX", + "ETHMACTX", + "ETHMAC", + "DMA2D", + "DMA2", + "DMA1", + "CCMDATARAM", + "BKPSRAM", + "CRC", + "GPIOK", + "GPIOJ", + "GPIOI", + "GPIOH", + "GPIOG", + "GPIOF", + "GPIOE", + "GPIOD", + "GPIOC", + "GPIOB", + "GPIOA", + })) "AHB1ENR" else "AHB1ENR"; + + @field(_rcc, rcc_register_name).modify_one(field, state); } - pub fn read(comptime pin: type) microzig.gpio.State { - const idr_reg = pin.gpio_port.IDR; - const reg_value = @field(idr_reg.read(), "IDR" ++ pin.suffix); // TODO extract to getRegField()? - return @as(microzig.gpio.State, @enumFromInt(reg_value)); + pub fn enable_clock(comptime peri: Peripherals) void { + set_clock(peri, 1); } - pub fn write(comptime pin: type, state: State) void { - const BSRR: *volatile @TypeOf(pin.gpio_port.BSRR) = &pin.gpio_port.BSRR; - switch (state) { - .low => set_reg_field(BSRR, "BR[" ++ pin.suffix ++ "]", 1), - .high => set_reg_field(BSRR, "BS[" ++ pin.suffix ++ "]", 1), - } + pub fn disable_clock(comptime peri: Peripherals) void { + set_clock(peri, 0); } }; diff --git a/port/stmicro/stm32/src/hals/STM32L47X/pins.zig b/port/stmicro/stm32/src/hals/STM32L47X/pins.zig index f2fef522f..eb69d0ce0 100644 --- a/port/stmicro/stm32/src/hals/STM32L47X/pins.zig +++ b/port/stmicro/stm32/src/hals/STM32L47X/pins.zig @@ -1,2 +1,4 @@ -const PinCommon = @import("../common/pins_v2.zig"); +const PinCommon = @import("../common/pins_v2.zig").get_pins(.{ + .portcount = 8, +}); pub const GlobalConfiguration = PinCommon.GlobalConfiguration; diff --git a/port/stmicro/stm32/src/hals/common/pins_v2.zig b/port/stmicro/stm32/src/hals/common/pins_v2.zig index af9dc8d0a..1c0b2eb83 100644 --- a/port/stmicro/stm32/src/hals/common/pins_v2.zig +++ b/port/stmicro/stm32/src/hals/common/pins_v2.zig @@ -1,424 +1,488 @@ -const std = @import("std"); -const assert = std.debug.assert; -const comptimePrint = std.fmt.comptimePrint; -const StructField = std.builtin.Type.StructField; - -const microzig = @import("microzig"); -const enums = @import("../common/enums.zig"); - -const Digital_IO = microzig.drivers.base.Digital_IO; -const Direction = Digital_IO.Direction; -const SetDirError = Digital_IO.SetDirError; -const SetBiasError = Digital_IO.SetBiasError; -const WriteError = Digital_IO.WriteError; -const ReadError = Digital_IO.ReadError; - -const State = Digital_IO.State; - -const gpio_v2 = microzig.chip.types.peripherals.gpio_v2; -const PUPDR = gpio_v2.PUPDR; -const MODER = gpio_v2.MODER; -const OSPEEDR = gpio_v2.OSPEEDR; -const OT = gpio_v2.OT; -const AFIO = microzig.chip.peripherals.AFIO; - -const rcc = microzig.hal.rcc; -const peripherals = microzig.chip.peripherals; - -pub const Mode = union(enum) { - input: InputMode, - output: OutputMode, - analog: AnalogMode, - alternate_function: AlternateFunctionMode, - digital_io: Digital_IOMode, +pub const config = struct { + portcount: usize, }; -const Digital_IOMode = struct {}; +pub fn get_pins(comptime cfg: config) type { + return struct { + const std = @import("std"); + const assert = std.debug.assert; + const comptimePrint = std.fmt.comptimePrint; + const StructField = std.builtin.Type.StructField; + + const microzig = @import("microzig"); + + const Digital_IO = microzig.drivers.base.Digital_IO; + const Direction = Digital_IO.Direction; + const SetDirError = Digital_IO.SetDirError; + const SetBiasError = Digital_IO.SetBiasError; + const WriteError = Digital_IO.WriteError; + const ReadError = Digital_IO.ReadError; + + const State = Digital_IO.State; + + const gpio_v2 = microzig.chip.types.peripherals.gpio_v2; + const PUPDR = gpio_v2.PUPDR; + const MODER = gpio_v2.MODER; + const OSPEEDR = gpio_v2.OSPEEDR; + const OT = gpio_v2.OT; + const AFIO = microzig.chip.peripherals.AFIO; + + const rcc = microzig.hal.rcc; + const peripherals = microzig.chip.peripherals; + + pub const Mode = union(enum) { + input: InputMode, + output: OutputMode, + analog: AnalogMode, + alternate_function: AlternateFunctionMode, + digital_io: Digital_IO_Mode, + }; -const InputMode = struct { - resistor: PUPDR, -}; + const Digital_IO_Mode = struct {}; -const OutputMode = struct { - resistor: PUPDR, - o_type: OT, - o_speed: OSPEEDR = .LowSpeed, -}; + const InputMode = struct { + resistor: PUPDR, + }; -const AnalogMode = struct { - resistor: PUPDR = .Floating, -}; + const OutputMode = struct { + resistor: PUPDR, + o_type: OT, + o_speed: OSPEEDR = .LowSpeed, + }; -const AF = enum(u4) { - AF0, - AF1, - AF2, - AF3, - AF4, - AF5, - AF6, - AF7, - AF8, - AF9, - AF10, - AF11, - AF12, - AF13, - AF14, - AF15, -}; + const AnalogMode = struct { + resistor: PUPDR = .Floating, + }; -pub const AlternateFunctionMode = struct { - afr: AF, - resistor: PUPDR = .Floating, - o_type: OT = .PushPull, - o_speed: OSPEEDR = .HighSpeed, -}; + const AF = enum(u4) { + AF0, + AF1, + AF2, + AF3, + AF4, + AF5, + AF6, + AF7, + AF8, + AF9, + AF10, + AF11, + AF12, + AF13, + AF14, + AF15, + }; -pub const Pin = enum { - PIN0, - PIN1, - PIN2, - PIN3, - PIN4, - PIN5, - PIN6, - PIN7, - PIN8, - PIN9, - PIN10, - PIN11, - PIN12, - PIN13, - PIN14, - PIN15, - pub const Configuration = struct { - name: ?[:0]const u8 = null, - mode: ?Mode = null, - }; -}; + pub const AlternateFunctionMode = struct { + afr: AF, + resistor: PUPDR = .Floating, + o_type: OT = .PushPull, + o_speed: OSPEEDR = .HighSpeed, + }; -const GPIO_Pin = struct { - pin: Pin, - port: Port, - - inline fn write_pin_config(_gpio: GPIO_Pin, mode: Mode) void { - switch (mode) { - .input => |imode| { - _gpio.set_moder(MODER.Input); - _gpio.set_bias(imode.resistor); - }, - .output => |omode| { - _gpio.set_moder(MODER.Output); - _gpio.set_output_type(omode.o_type); - _gpio.set_bias(omode.resistor); - _gpio.set_speed(omode.o_speed); - }, - .analog => |amode| { - _gpio.set_moder(MODER.Analog); - _gpio.set_bias(amode.resistor); - }, - .alternate_function => |afmode| { - _gpio.set_moder(MODER.Alternate); - _gpio.set_bias(afmode.resistor); - _gpio.set_speed(afmode.o_speed); - _gpio.set_output_type(afmode.o_type); - _gpio.set_alternate_function(afmode.afr); - }, - .digital_io => { - // Nothing for now - }, - } - } - - fn mask_2bit(_gpio: GPIO_Pin) u32 { - const pin: u4 = @intFromEnum(_gpio.pin); - return @as(u32, 0b11) << (pin << 1); - } - - fn mask(_gpio: GPIO_Pin) u32 { - const pin: u4 = @intFromEnum(_gpio.pin); - return @as(u32, 1) << pin; - } - - //NOTE: should invalid pins panic or just be ignored? - fn get_port(_gpio: GPIO_Pin) *volatile gpio_v2.GPIO { - return _gpio.port.get_port(); - } - - inline fn set_bias(_gpio: GPIO_Pin, bias: PUPDR) void { - const port = _gpio.port.get_port(); - const pin: u4 = @intFromEnum(_gpio.pin); - const modMask: u32 = _gpio.mask_2bit(); - - port.PUPDR.write_raw((port.PUPDR.raw & ~modMask) | @as(u32, @intFromEnum(bias)) << (pin << 1)); - } - - inline fn set_speed(_gpio: GPIO_Pin, speed: OSPEEDR) void { - const port = _gpio.port.get_port(); - const pin: u5 = @intFromEnum(_gpio.pin); - const modMask: u32 = _gpio.mask_2bit(); - - port.OSPEEDR.write_raw((port.OSPEEDR.raw & ~modMask) | @as(u32, @intFromEnum(speed)) << (pin << 1)); - } - - inline fn set_moder(_gpio: GPIO_Pin, moder: MODER) void { - const port = _gpio.port.get_port(); - const pin: u5 = @intFromEnum(_gpio.pin); - const modMask: u32 = _gpio.mask_2bit(); - - port.MODER.write_raw((port.MODER.raw & ~modMask) | @as(u32, @intFromEnum(moder)) << (pin << 1)); - } - - inline fn set_output_type(_gpio: GPIO_Pin, otype: OT) void { - const port = _gpio.port.get_port(); - const pin: u5 = @intFromEnum(_gpio.pin); - - port.OTYPER.write_raw((port.OTYPER.raw & ~_gpio.mask()) | @as(u32, @intFromEnum(otype)) << pin); - } - - fn from_port(port: Port, pin: Pin) GPIO_Pin { - return .{ - .port = port, - .pin = pin, + pub const Pin = enum { + PIN0, + PIN1, + PIN2, + PIN3, + PIN4, + PIN5, + PIN6, + PIN7, + PIN8, + PIN9, + PIN10, + PIN11, + PIN12, + PIN13, + PIN14, + PIN15, + pub const Configuration = struct { + name: ?[:0]const u8 = null, + mode: ?Mode = null, + }; }; - } -}; -pub const Input_GPIO = struct { - pin: GPIO_Pin, - pub inline fn read(self: @This()) u1 { - const port = self.pin.get_port(); - return if (port.IDR.raw & self.pin.mask() != 0) - 1 - else - 0; - } -}; + const GPIO_Pin = struct { + pin: Pin, + port: Port, + + inline fn write_pin_config(_gpio: GPIO_Pin, mode: Mode) void { + switch (mode) { + .input => |imode| { + _gpio.set_moder(MODER.Input); + _gpio.set_bias(imode.resistor); + }, + .output => |omode| { + _gpio.set_moder(MODER.Output); + _gpio.set_output_type(omode.o_type); + _gpio.set_bias(omode.resistor); + _gpio.set_speed(omode.o_speed); + }, + .analog => |amode| { + _gpio.set_moder(MODER.Analog); + _gpio.set_bias(amode.resistor); + }, + .alternate_function => |afmode| { + _gpio.set_moder(MODER.Alternate); + _gpio.set_bias(afmode.resistor); + _gpio.set_speed(afmode.o_speed); + _gpio.set_output_type(afmode.o_type); + _gpio.set_alternate_function(afmode.afr); + }, + .digital_io => { + // Nothing for now + }, + } + } -pub const Output_GPIO = struct { - pin: GPIO_Pin, + fn mask_2bit(_gpio: GPIO_Pin) u32 { + const pin: u4 = @intFromEnum(_gpio.pin); + return @as(u32, 0b11) << (pin << 1); + } - pub inline fn put(self: @This(), value: u1) void { - var port = self.pin.get_port(); - switch (value) { - 0 => port.BSRR.raw = @intCast(self.pin.mask() << 16), - 1 => port.BSRR.raw = self.pin.mask(), - } - } + fn mask(_gpio: GPIO_Pin) u32 { + const pin: u4 = @intFromEnum(_gpio.pin); + return @as(u32, 1) << pin; + } - pub inline fn low(self: @This()) void { - self.put(0); - } + //NOTE: should invalid pins panic or just be ignored? + fn get_port(_gpio: GPIO_Pin) *volatile gpio_v2.GPIO { + return _gpio.port.get_port(); + } - pub inline fn high(self: @This()) void { - self.put(1); - } + inline fn set_bias(_gpio: GPIO_Pin, bias: PUPDR) void { + const port = _gpio.port.get_port(); + const pin: u4 = @intFromEnum(_gpio.pin); + const modMask: u32 = _gpio.mask_2bit(); - pub inline fn toggle(self: @This()) void { - var port = self.pin.get_port(); - port.ODR.raw ^= self.pin.mask(); - } -}; + port.PUPDR.write_raw((port.PUPDR.raw & ~modMask) | @as(u32, @intFromEnum(bias)) << (pin << 1)); + } -pub const AlternateFunction = struct { - // Empty on perpose it should not be used as a GPIO. -}; + inline fn set_speed(_gpio: GPIO_Pin, speed: OSPEEDR) void { + const port = _gpio.port.get_port(); + const pin: u5 = @intFromEnum(_gpio.pin); + const modMask: u32 = _gpio.mask_2bit(); -const Analog = struct { - pin: GPIO_Pin, -}; + port.OSPEEDR.write_raw((port.OSPEEDR.raw & ~modMask) | @as(u32, @intFromEnum(speed)) << (pin << 1)); + } -pub const Digital_IO_Pin = struct { - pin: GPIO_Pin, - const vtable: Digital_IO.VTable = .{ - .set_direction_fn = Digital_IO_Pin.set_direction_fn, - .set_bias_fn = Digital_IO_Pin.set_bias_fn, - .write_fn = Digital_IO_Pin.write_fn, - .read_fn = Digital_IO_Pin.read_fn, - }; - pub fn set_direction_fn(ptr: *anyopaque, dir: Direction) SetDirError!void { - const self: *@This() = @ptrCast(@alignCast(ptr)); - switch (dir) { - .input => self.pin.set_moder(.Input), - .output => self.pin.set_moder(.Output), - } - } - pub fn set_bias_fn(ptr: *anyopaque, maybe_bias: ?State) SetBiasError!void { - const self: *@This() = @ptrCast(@alignCast(ptr)); - - const pupdr: PUPDR = if (maybe_bias) |bias| switch (bias) { - .low => .PullDown, - .high => .PullUp, - } else .Floating; - self.pin.set_bias(pupdr); - } - pub fn write_fn(ptr: *anyopaque, state: State) WriteError!void { - const self: *@This() = @ptrCast(@alignCast(ptr)); - var port = self.pin.get_port(); - switch (state) { - .low => port.BSRR.raw = @intCast(self.pin.mask() << 16), - .high => port.BSRR.raw = self.pin.mask(), - } - } - pub fn read_fn(ptr: *anyopaque) ReadError!State { - const self: *@This() = @ptrCast(@alignCast(ptr)); - const port = self.pin.get_port(); - return if (port.IDR.raw & self.pin.mask() != 0) - .high - else - .low; - } + inline fn set_moder(_gpio: GPIO_Pin, moder: MODER) void { + const port = _gpio.port.get_port(); + const pin: u5 = @intFromEnum(_gpio.pin); + const modMask: u32 = _gpio.mask_2bit(); - pub fn digital_io(ptr: *@This()) Digital_IO { - return .{ - .ptr = ptr, - .vtable = &vtable, + port.MODER.write_raw((port.MODER.raw & ~modMask) | @as(u32, @intFromEnum(moder)) << (pin << 1)); + } + + inline fn set_output_type(_gpio: GPIO_Pin, otype: OT) void { + const port = _gpio.port.get_port(); + const pin: u5 = @intFromEnum(_gpio.pin); + + port.OTYPER.write_raw((port.OTYPER.raw & ~_gpio.mask()) | @as(u32, @intFromEnum(otype)) << pin); + } + + fn from_port(port: Port, pin: Pin) GPIO_Pin { + return .{ + .port = port, + .pin = pin, + }; + } }; - } -}; -pub fn GPIO(comptime mode: Mode) type { - return switch (mode) { - .input => Input_GPIO, - .output => Output_GPIO, - .alternate_function => AlternateFunction, - .analog => Analog, - .digital_io => Digital_IO_Pin, - }; -} + pub const Input_GPIO = struct { + pin: GPIO_Pin, + pub inline fn read(self: @This()) u1 { + const port = self.pin.get_port(); + return if (port.IDR.raw & self.pin.mask() != 0) + 1 + else + 0; + } + }; -pub fn Pins(comptime config: GlobalConfiguration) type { - comptime { - var fields: []const StructField = &.{}; - for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { - if (@field(config, port_field.name)) |port_config| { - for (@typeInfo(Port.Configuration).@"struct".fields) |field| { - if (@field(port_config, field.name)) |pin_config| { - var pin_field = StructField{ - .is_comptime = false, - .default_value_ptr = null, - - // initialized below: - .name = undefined, - .type = undefined, - .alignment = undefined, - }; - - const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; - pin_field.name = pin_config.name orelse default_name; - pin_field.type = GPIO(pin_config.mode orelse .{ .input = .{.floating} }); - pin_field.alignment = @alignOf(field.type); - - fields = fields ++ &[_]StructField{pin_field}; - } + pub const Output_GPIO = struct { + pin: GPIO_Pin, + + pub inline fn put(self: @This(), value: u1) void { + var port = self.pin.get_port(); + switch (value) { + 0 => port.BSRR.raw = @intCast(self.pin.mask() << 16), + 1 => port.BSRR.raw = self.pin.mask(), } } - } - return @Type(.{ - .@"struct" = .{ - .layout = .auto, - .is_tuple = false, - .fields = fields, - .decls = &.{}, - }, - }); - } -} + pub inline fn low(self: @This()) void { + self.put(0); + } -pub const Port = enum { - GPIOA, - GPIOB, - GPIOC, - GPIOD, - GPIOE, - GPIOF, - GPIOG, - pub const Configuration = struct { - PIN0: ?Pin.Configuration = null, - PIN1: ?Pin.Configuration = null, - PIN2: ?Pin.Configuration = null, - PIN3: ?Pin.Configuration = null, - PIN4: ?Pin.Configuration = null, - PIN5: ?Pin.Configuration = null, - PIN6: ?Pin.Configuration = null, - PIN7: ?Pin.Configuration = null, - PIN8: ?Pin.Configuration = null, - PIN9: ?Pin.Configuration = null, - PIN10: ?Pin.Configuration = null, - PIN11: ?Pin.Configuration = null, - PIN12: ?Pin.Configuration = null, - PIN13: ?Pin.Configuration = null, - PIN14: ?Pin.Configuration = null, - PIN15: ?Pin.Configuration = null, - - comptime { - const pin_field_count = @typeInfo(Pin).@"enum".fields.len; - const config_field_count = @typeInfo(Configuration).@"struct".fields.len; - if (pin_field_count != config_field_count) - @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); - } - }; + pub inline fn high(self: @This()) void { + self.put(1); + } - //NOTE: should invalid pins panic or just be ignored? - pub fn get_port(port: Port) *volatile gpio_v2.GPIO { - switch (@intFromEnum(port)) { - inline 0...7 - 1 => |_port| { - const port_name = [_]u8{"ABCDEFGHIJK"[_port]}; - return @field(peripherals, "GPIO" ++ port_name); - }, - else => @panic("STM32s only have ports 0..10 (A..K)"), - } - } -}; + pub inline fn toggle(self: @This()) void { + var port = self.pin.get_port(); + port.ODR.raw ^= self.pin.mask(); + } + }; + + pub const AlternateFunction = struct { + // Empty on perpose it should not be used as a GPIO. + }; + + const Analog = struct { + pin: GPIO_Pin, + }; + + pub const Digital_IO_Pin = struct { + pin: GPIO_Pin, + const vtable: Digital_IO.VTable = .{ + .set_direction_fn = Digital_IO_Pin.set_direction_fn, + .set_bias_fn = Digital_IO_Pin.set_bias_fn, + .write_fn = Digital_IO_Pin.write_fn, + .read_fn = Digital_IO_Pin.read_fn, + }; + pub fn set_direction_fn(ptr: *anyopaque, dir: Direction) SetDirError!void { + const self: *@This() = @ptrCast(@alignCast(ptr)); + switch (dir) { + .input => self.pin.set_moder(.Input), + .output => self.pin.set_moder(.Output), + } + } + pub fn set_bias_fn(ptr: *anyopaque, maybe_bias: ?State) SetBiasError!void { + const self: *@This() = @ptrCast(@alignCast(ptr)); + + const pupdr: PUPDR = if (maybe_bias) |bias| switch (bias) { + .low => .PullDown, + .high => .PullUp, + } else .Floating; + self.pin.set_bias(pupdr); + } + pub fn write_fn(ptr: *anyopaque, state: State) WriteError!void { + const self: *@This() = @ptrCast(@alignCast(ptr)); + var port = self.pin.get_port(); + switch (state) { + .low => port.BSRR.raw = @intCast(self.pin.mask() << 16), + .high => port.BSRR.raw = self.pin.mask(), + } + } + pub fn read_fn(ptr: *anyopaque) ReadError!State { + const self: *@This() = @ptrCast(@alignCast(ptr)); + const port = self.pin.get_port(); + return if (port.IDR.raw & self.pin.mask() != 0) + .high + else + .low; + } -pub const GlobalConfiguration = struct { - GPIOA: ?Port.Configuration = null, - GPIOB: ?Port.Configuration = null, - GPIOC: ?Port.Configuration = null, - GPIOD: ?Port.Configuration = null, - GPIOE: ?Port.Configuration = null, - GPIOF: ?Port.Configuration = null, - GPIOG: ?Port.Configuration = null, - - comptime { - const port_field_count = @typeInfo(Port).@"enum".fields.len; - const config_field_count = @typeInfo(GlobalConfiguration).@"struct".fields.len; - if (port_field_count != config_field_count) - @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); - } - - pub fn apply(comptime config: GlobalConfiguration) Pins(config) { - var ret: Pins(config) = undefined; - - inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { - if (@field(config, port_field.name)) |_| { - rcc.enable_clock(@field(enums.Peripherals, port_field.name)); + pub fn digital_io(ptr: *@This()) Digital_IO { + return .{ + .ptr = ptr, + .vtable = &vtable, + }; } + }; + + pub fn GPIO(comptime mode: Mode) type { + return switch (mode) { + .input => Input_GPIO, + .output => Output_GPIO, + .alternate_function => AlternateFunction, + .analog => Analog, + .digital_io => Digital_IO_Pin, + }; } - inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { - if (@field(config, port_field.name)) |port_config| { - inline for (@typeInfo(Port.Configuration).@"struct".fields) |field| { - if (@field(port_config, field.name)) |pin_config| { - const port = @field(Port, port_field.name); - var pin = GPIO_Pin.from_port(port, @field(Pin, field.name)); - pin.write_pin_config(pin_config.mode.?); - const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; - - switch (pin_config.mode orelse .input) { - .input => @field(ret, pin_config.name orelse default_name) = Input_GPIO{ .pin = pin }, - .output => @field(ret, pin_config.name orelse default_name) = Output_GPIO{ .pin = pin }, - .analog => @field(ret, pin_config.name orelse default_name) = Analog{}, - .alternate_function => @field(ret, pin_config.name orelse default_name) = AlternateFunction{}, - .digital_io => @field(ret, pin_config.name orelse default_name) = Digital_IO_Pin{ .pin = pin }, + pub fn Pins(comptime global_config: GlobalConfiguration) type { + comptime { + var fields: []const StructField = &.{}; + for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { + if (@field(global_config, port_field.name)) |port_config| { + for (@typeInfo(Port.Configuration).@"struct".fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + var pin_field = StructField{ + .is_comptime = false, + .default_value_ptr = null, + + // initialized below: + .name = undefined, + .type = undefined, + .alignment = undefined, + }; + + const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; + pin_field.name = pin_config.name orelse default_name; + pin_field.type = GPIO(pin_config.mode orelse .{ .input = .{.floating} }); + pin_field.alignment = @alignOf(field.type); + + fields = fields ++ &[_]StructField{pin_field}; + } } } } + + return @Type(.{ + .@"struct" = .{ + .layout = .auto, + .is_tuple = false, + .fields = fields, + .decls = &.{}, + }, + }); } } - return ret; - } -}; + const PortConfig = struct { + PIN0: ?Pin.Configuration = null, + PIN1: ?Pin.Configuration = null, + PIN2: ?Pin.Configuration = null, + PIN3: ?Pin.Configuration = null, + PIN4: ?Pin.Configuration = null, + PIN5: ?Pin.Configuration = null, + PIN6: ?Pin.Configuration = null, + PIN7: ?Pin.Configuration = null, + PIN8: ?Pin.Configuration = null, + PIN9: ?Pin.Configuration = null, + PIN10: ?Pin.Configuration = null, + PIN11: ?Pin.Configuration = null, + PIN12: ?Pin.Configuration = null, + PIN13: ?Pin.Configuration = null, + PIN14: ?Pin.Configuration = null, + PIN15: ?Pin.Configuration = null, + + // TODO: Add enabled option to enable it from the start + comptime { + const pin_field_count = @typeInfo(Pin).@"enum".fields.len; + const config_field_count = @typeInfo(PortConfig).@"struct".fields.len; + if (pin_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); + } + }; + + const _Port = enum { + //NOTE: should invalid pins panic or just be ignored? + fn get_port(port: Port) *volatile gpio_v2.GPIO { + switch (@intFromEnum(port)) { + inline 0...cfg.portcount - 1 => |_port| { + const port_name = [_]u8{"ABCDEFGHIJK"[_port]}; + return @field(peripherals, "GPIO" ++ port_name); + }, + else => @panic(std.fmt.comptimePrint("STM32s only have ports 0..{any} (A..{s})", .{ cfg.portcount, [_]u8{"ABCDEFGHIJK"[cfg.portcount]} })), + } + } + }; + + const Port7 = enum { + GPIOA, + GPIOB, + GPIOC, + GPIOD, + GPIOE, + GPIOF, + GPIOG, + pub const Configuration = PortConfig; + pub const get_port = _Port.get_port; + }; + + const Port11 = enum { + GPIOA, + GPIOB, + GPIOC, + GPIOD, + GPIOE, + GPIOF, + GPIOG, + GPIOH, + GPIOI, + GPIOJ, + GPIOK, + pub const Configuration = PortConfig; + pub const get_port = _Port.get_port; + }; + + pub const Port = if (cfg.portcount == 7) + Port7 + else + Port11; + + const _GlobalConfiguration = struct { + fn apply(comptime global_config: GlobalConfiguration) Pins(global_config) { + var ret: Pins(global_config) = undefined; + + inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { + if (@field(global_config, port_field.name)) |_| { + rcc.enable_clock(@field(rcc.Peripherals, port_field.name)); + } + } + + inline for (@typeInfo(GlobalConfiguration).@"struct".fields) |port_field| { + if (@field(global_config, port_field.name)) |port_config| { + inline for (@typeInfo(Port.Configuration).@"struct".fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + const port = @field(Port, port_field.name); + var pin = GPIO_Pin.from_port(port, @field(Pin, field.name)); + pin.write_pin_config(pin_config.mode.?); + const default_name = "P" ++ port_field.name[4..5] ++ field.name[3..]; + + switch (pin_config.mode orelse .input) { + .input => @field(ret, pin_config.name orelse default_name) = Input_GPIO{ .pin = pin }, + .output => @field(ret, pin_config.name orelse default_name) = Output_GPIO{ .pin = pin }, + .analog => @field(ret, pin_config.name orelse default_name) = Analog{}, + .alternate_function => @field(ret, pin_config.name orelse default_name) = AlternateFunction{}, + .digital_io => @field(ret, pin_config.name orelse default_name) = Digital_IO_Pin{ .pin = pin }, + } + } + } + } + } + + return ret; + } + }; + + const GlobalConfiguration7 = struct { + GPIOA: ?Port.Configuration = null, + GPIOB: ?Port.Configuration = null, + GPIOC: ?Port.Configuration = null, + GPIOD: ?Port.Configuration = null, + GPIOE: ?Port.Configuration = null, + GPIOF: ?Port.Configuration = null, + GPIOG: ?Port.Configuration = null, + + comptime { + const port_field_count = @typeInfo(Port).@"enum".fields.len; + const config_field_count = @typeInfo(GlobalConfiguration).@"struct".fields.len; + if (port_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); + } + pub const apply = _GlobalConfiguration.apply; + }; + + const GlobalConfiguration11 = struct { + GPIOA: ?Port.Configuration = null, + GPIOB: ?Port.Configuration = null, + GPIOC: ?Port.Configuration = null, + GPIOD: ?Port.Configuration = null, + GPIOE: ?Port.Configuration = null, + GPIOF: ?Port.Configuration = null, + GPIOG: ?Port.Configuration = null, + GPIOH: ?Port.Configuration = null, + GPIOI: ?Port.Configuration = null, + GPIOJ: ?Port.Configuration = null, + GPIOK: ?Port.Configuration = null, + + comptime { + const port_field_count = @typeInfo(Port).@"enum".fields.len; + const config_field_count = @typeInfo(GlobalConfiguration).@"struct".fields.len; + if (port_field_count != config_field_count) + @compileError(comptimePrint("{} {}", .{ port_field_count, config_field_count })); + } + pub const apply = _GlobalConfiguration.apply; + }; + + pub const GlobalConfiguration = if (cfg.portcount == 7) + GlobalConfiguration7 + else + GlobalConfiguration11; + }; +} From e949f26bbe715a4eb9aa2d1c90c5d77bf7e51977 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Wed, 29 Apr 2026 12:25:45 +0200 Subject: [PATCH 9/9] Add blinky support for STM32F429 DISC1 --- examples/stmicro/stm32/src/blinky.zig | 7 +++++++ port/stmicro/stm32/src/boards/STM32F429IDISCOVERY.zig | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/examples/stmicro/stm32/src/blinky.zig b/examples/stmicro/stm32/src/blinky.zig index 74968ec62..e019050fb 100644 --- a/examples/stmicro/stm32/src/blinky.zig +++ b/examples/stmicro/stm32/src/blinky.zig @@ -40,6 +40,13 @@ pub fn main() !void { pins.LD10, }; break :res .{ pins, all_leds }; + } else if (comptime microzig.config.board_name != null and std.mem.eql(u8, microzig.config.board_name.?, "STM32F429IDISCOVERY")) { + const pins = board.leds_config.apply(); + const all_leds = .{ + pins.LD3, + pins.LD4, + }; + break :res .{ pins, all_leds }; } else if (comptime microzig.config.board_name != null and std.mem.eql(u8, microzig.config.board_name.?, "STM32L476DISCOVERY")) { const pins = board.leds_config.apply(); const all_leds = .{ diff --git a/port/stmicro/stm32/src/boards/STM32F429IDISCOVERY.zig b/port/stmicro/stm32/src/boards/STM32F429IDISCOVERY.zig index 034295a13..ce1449bf6 100644 --- a/port/stmicro/stm32/src/boards/STM32F429IDISCOVERY.zig +++ b/port/stmicro/stm32/src/boards/STM32F429IDISCOVERY.zig @@ -1,3 +1,7 @@ +pub const microzig = @import("microzig"); + +pub const hal = microzig.hal; + pub const cpu_frequency = 16_000_000; pub const pin_map = .{ @@ -10,3 +14,10 @@ pub const pin_map = .{ // User button .B1 = "PA0", }; + +pub const leds_config = (hal.pins.GlobalConfiguration{ + .GPIOG = .{ + .PIN13 = .{ .name = "LD3", .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } }, + .PIN14 = .{ .name = "LD4", .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } }, + }, +});