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/build.zig b/port/stmicro/stm32/build.zig index c3c8cece0..859bda2a3 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/STM32F429.zig"), + .imports = hal_imports, + }, }), }, }; 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 } } }, + }, +}); 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 43caee757..9420fca1b 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,9 +22,18 @@ const std = @import("std"); const microzig = @import("microzig"); -const peripherals = microzig.peripherals; +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 pins = @import("./common/pins_v2.zig").get_pins(.{ + .portcount = 11, +}); + pub const clock = struct { pub const Domain = enum { cpu, @@ -42,51 +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); +// 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; - 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}); - }; -} - -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 { - set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01); + ///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 { - set_reg_field(RCC.AHB1ENR, "GPIO" ++ pin.gpio_port_name ++ "EN", 1); - set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00); + 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: microzig.gpio.State) void { - 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), - } + 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/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 198831cfd..1c0b2eb83 100644 --- a/port/stmicro/stm32/src/hals/common/pins_v2.zig +++ b/port/stmicro/stm32/src/hals/common/pins_v2.zig @@ -1,272 +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 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 config = struct { + portcount: usize, }; -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 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, + }; -pub const OutputGPIO = struct { - pin: gpio.Pin, + const Digital_IO_Mode = struct {}; - 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(), - } - } + const InputMode = struct { + resistor: PUPDR, + }; - pub inline fn low(self: @This()) void { - self.put(0); - } + const OutputMode = struct { + resistor: PUPDR, + o_type: OT, + o_speed: OSPEEDR = .LowSpeed, + }; - pub inline fn high(self: @This()) void { - self.put(1); - } + const AnalogMode = struct { + resistor: PUPDR = .Floating, + }; - pub inline fn toggle(self: @This()) void { - var port = self.pin.get_port(); - port.ODR.raw ^= self.pin.mask(); - } -}; + const AF = enum(u4) { + AF0, + AF1, + AF2, + AF3, + AF4, + AF5, + AF6, + AF7, + AF8, + AF9, + AF10, + AF11, + AF12, + AF13, + AF14, + AF15, + }; -pub const AlternateFunction = struct { - // Empty on perpose it should not be used as a GPIO. -}; + pub const AlternateFunctionMode = struct { + afr: AF, + resistor: PUPDR = .Floating, + o_type: OT = .PushPull, + o_speed: OSPEEDR = .HighSpeed, + }; -const Analog = struct { - pin: gpio.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 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; - } + 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(); + } - pub fn digital_io(ptr: *@This()) Digital_IO { - return .{ - .ptr = ptr, - .vtable = &vtable, + 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 GPIO(comptime mode: gpio.Mode) type { - return switch (mode) { - .input => InputGPIO, - .output => OutputGPIO, - .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); + } + + 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 = @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..]; - - 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 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; + }; +}