From 7df331f34077109da0fb89f664d7daa4be28cd1a Mon Sep 17 00:00:00 2001 From: Marnix Klooster Date: Tue, 4 Feb 2025 17:38:27 +0100 Subject: [PATCH] STM32F303: First HAL fragments (#375) Based on some initial HAL code that was inspired by the current STM32F103 code, but with some variations that seemed to give a slightly simpler result. (Also made the old unused HAL code in STM32F303.zig compile, and marked it as 'to be moved to separate files'.) --- port/stmicro/stm32/build.zig | 3 + port/stmicro/stm32/src/hals/STM32F303.zig | 105 ++++++------ .../stmicro/stm32/src/hals/STM32F303/gpio.zig | 56 +++++++ .../stmicro/stm32/src/hals/STM32F303/pins.zig | 154 ++++++++++++++++++ 4 files changed, 270 insertions(+), 48 deletions(-) create mode 100644 port/stmicro/stm32/src/hals/STM32F303/gpio.zig create mode 100644 port/stmicro/stm32/src/hals/STM32F303/pins.zig diff --git a/port/stmicro/stm32/build.zig b/port/stmicro/stm32/build.zig index a468de616..e39a34d59 100644 --- a/port/stmicro/stm32/build.zig +++ b/port/stmicro/stm32/build.zig @@ -24,6 +24,9 @@ pub fn init(dep: *std.Build.Dependency) Self { .name = "STM32F3DISCOVERY", .root_source_file = b.path("src/boards/STM32F3DISCOVERY.zig"), }, + .hal = microzig.HardwareAbstractionLayer{ + .root_source_file = b.path("src/hals/STM32F303.zig"), + }, }), .stm32f4discovery = chips.STM32F407VG.derive(.{ .board = .{ diff --git a/port/stmicro/stm32/src/hals/STM32F303.zig b/port/stmicro/stm32/src/hals/STM32F303.zig index 17c6999bc..3bf304ee3 100644 --- a/port/stmicro/stm32/src/hals/STM32F303.zig +++ b/port/stmicro/stm32/src/hals/STM32F303.zig @@ -1,38 +1,47 @@ -//! For now we keep all clock settings on the chip defaults. -//! This code currently assumes the STM32F303xB / STM32F303xC clock configuration. -//! TODO: Do something useful for other STM32f30x chips. -//! -//! Specifically, TIM6 is running on an 8 MHz clock, -//! HSI = 8 MHz is the SYSCLK after reset -//! default AHB prescaler = /1 (= values 0..7): -//! -//! ``` -//! RCC.CFGR.modify(.{ .HPRE = 0 }); -//! ``` -//! -//! so also HCLK = 8 MHz. -//! And with the default APB1 prescaler = /2: -//! -//! ``` -//! RCC.CFGR.modify(.{ .PPRE1 = 4 }); -//! ``` -//! -//! results in PCLK1, -//! and the resulting implicit factor *2 for TIM2/3/4/6/7 -//! makes TIM6 run at 8MHz/2*2 = 8 MHz. -//! -//! The above default configuration makes U(S)ART2..5 -//! (which use PCLK1 without that implicit *2 factor) -//! run at 4 MHz by default. -//! -//! USART1 uses PCLK2, which uses the APB2 prescaler on HCLK, -//! default APB2 prescaler = /1: -//! -//! ``` -//! RCC.CFGR.modify(.{ .PPRE2 = 0 }); -//! ``` -//! -//! and therefore USART1 runs on 8 MHz. +pub const pins = @import("STM32F303/pins.zig"); + +// /-------- +// | THE CODE BELOW IS OLD HAL CODE FOR UART, I2C, AND SPI +// | THAT HAS WORKED IN A PREVIOUS MICROZIG INCARNATION. +// | +// | IT SHOULD BE MOVED INTO SEPARATE FILES IN STM32F303/. +// \-------- +// +// For now we keep all clock settings on the chip defaults. +// This code currently assumes the STM32F303xB / STM32F303xC clock configuration. +// TODO: Do something useful for other STM32f30x chips. +// +// Specifically, TIM6 is running on an 8 MHz clock, +// HSI = 8 MHz is the SYSCLK after reset +// default AHB prescaler = /1 (= values 0..7): +// +// ``` +// RCC.CFGR.modify(.{ .HPRE = 0 }); +// ``` +// +// so also HCLK = 8 MHz. +// And with the default APB1 prescaler = /2: +// +// ``` +// RCC.CFGR.modify(.{ .PPRE1 = 4 }); +// ``` +// +// results in PCLK1, +// and the resulting implicit factor *2 for TIM2/3/4/6/7 +// makes TIM6 run at 8MHz/2*2 = 8 MHz. +// +// The above default configuration makes U(S)ART2..5 +// (which use PCLK1 without that implicit *2 factor) +// run at 4 MHz by default. +// +// USART1 uses PCLK2, which uses the APB2 prescaler on HCLK, +// default APB2 prescaler = /1: +// +// ``` +// RCC.CFGR.modify(.{ .PPRE2 = 0 }); +// ``` +// +// and therefore USART1 runs on 8 MHz. const std = @import("std"); const runtime_safety = std.debug.runtime_safety; @@ -135,9 +144,9 @@ pub const uart = struct { }; }; -pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type { +pub fn Uart(comptime index: usize, comptime source_pins: micro.uart.Pins) type { if (!(index == 1)) @compileError("TODO: only USART1 is currently supported"); - if (pins.tx != null or pins.rx != null) + if (source_pins.tx != null or source_pins.rx != null) @compileError("TODO: custom pins are not currently supported"); return struct { @@ -268,9 +277,9 @@ fn debug_print(comptime format: []const u8, args: anytype) void { } /// This implementation does not use AUTOEND=1 -pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type { +pub fn I2CController(comptime index: usize, comptime source_pins: micro.i2c.Pins) type { if (!(index == 1)) @compileError("TODO: only I2C1 is currently supported"); - if (pins.scl != null or pins.sda != null) + if (source_pins.scl != null or source_pins.sda != null) @compileError("TODO: custom pins are not currently supported"); return struct { @@ -286,15 +295,15 @@ pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type RCC.AHBENR.modify(.{ .IOPBEN = 1 }); debug_print("I2C1 configuration step 1 complete\r\n", .{}); // 2. Configure the I2C PINs for ALternate Functions - // a) Select Alternate Function in MODER Register + // a) Select Alternate Function in MODER Register GPIOB.MODER.modify(.{ .MODER6 = 0b10, .MODER7 = 0b10 }); - // b) Select Open Drain Output + // b) Select Open Drain Output GPIOB.OTYPER.modify(.{ .OT6 = 1, .OT7 = 1 }); - // c) Select High SPEED for the PINs + // c) Select High SPEED for the PINs GPIOB.OSPEEDR.modify(.{ .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 }); - // d) Select Pull-up for both the Pins + // d) Select Pull-up for both the Pins GPIOB.PUPDR.modify(.{ .PUPDR6 = 0b01, .PUPDR7 = 0b01 }); - // e) Configure the Alternate Function in AFR Register + // e) Configure the Alternate Function in AFR Register GPIOB.AFRL.modify(.{ .AFRL6 = 4, .AFRL7 = 4 }); debug_print("I2C1 configuration step 2 complete\r\n", .{}); @@ -492,11 +501,11 @@ pub fn SpiBus(comptime index: usize) type { RCC.AHBENR.modify(.{ .IOPAEN = 1 }); // Configure the I2C PINs for ALternate Functions - // - Select Alternate Function in MODER Register + // - Select Alternate Function in MODER Register GPIOA.MODER.modify(.{ .MODER5 = 0b10, .MODER6 = 0b10, .MODER7 = 0b10 }); - // - Select High SPEED for the PINs + // - Select High SPEED for the PINs GPIOA.OSPEEDR.modify(.{ .OSPEEDR5 = 0b11, .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 }); - // - Configure the Alternate Function in AFR Register + // - Configure the Alternate Function in AFR Register GPIOA.AFRL.modify(.{ .AFRL5 = 5, .AFRL6 = 5, .AFRL7 = 5 }); // Enable the SPI1 CLOCK @@ -566,7 +575,7 @@ pub fn SpiBus(comptime index: usize) type { debug_print("SPI1 RXNE == 1\r\n", .{}); // read - var data_read = SPI1.DR.raw; + const data_read = SPI1.DR.raw; _ = SPI1.SR.read(); // clear overrun flag const dr_lsb = @as([dr_byte_size]u8, @bitCast(data_read))[0]; debug_print("Received: {X:2} (DR = {X:8}).\r\n", .{ dr_lsb, data_read }); diff --git a/port/stmicro/stm32/src/hals/STM32F303/gpio.zig b/port/stmicro/stm32/src/hals/STM32F303/gpio.zig new file mode 100644 index 000000000..6d86810dc --- /dev/null +++ b/port/stmicro/stm32/src/hals/STM32F303/gpio.zig @@ -0,0 +1,56 @@ +const std = @import("std"); + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; + +pub const Mode = union(enum) { + input: InputMode, + output: OutputMode, +}; + +pub const InputMode = enum(u2) { + analog, + floating, + pull_up, + pull_down, +}; + +pub const OutputMode = enum(u2) { + push_pull, + open_drain, +}; + +// TODO: Add the following +// pub const Speed = enum(u2) { +// low, +// medium, +// high, +// }; + +pub const Pin = struct { + port_id: []const u8, + number_str: []const u8, + + pub fn init(port_id: []const u8, number_str: []const u8) Pin { + return Pin{ + .port_id = port_id, + .number_str = number_str, + }; + } + + pub fn configure(comptime self: @This()) void { + const port_peripheral = @field(peripherals, "GPIO" ++ self.port_id); + // TODO: Support input + port_peripheral.MODER.modify_one("MODER[" ++ self.number_str ++ "]", .Output); + // TODO: Support different modes, for input and for output + port_peripheral.OTYPER.modify_one("OT[" ++ self.number_str ++ "]", .PushPull); + // TODO: Support different speeds + port_peripheral.OSPEEDR.modify_one("OSPEEDR[" ++ self.number_str ++ "]", .LowSpeed); + // TODO: Support pull-up / pull-down + port_peripheral.PUPDR.modify_one("PUPDR[" ++ self.number_str ++ "]", .Floating); + } + + pub fn toggle(comptime self: @This()) void { + @field(peripherals, "GPIO" ++ self.port_id).ODR.toggle_one("ODR[" ++ self.number_str ++ "]", .High); + } +}; diff --git a/port/stmicro/stm32/src/hals/STM32F303/pins.zig b/port/stmicro/stm32/src/hals/STM32F303/pins.zig new file mode 100644 index 000000000..f72122476 --- /dev/null +++ b/port/stmicro/stm32/src/hals/STM32F303/pins.zig @@ -0,0 +1,154 @@ +const std = @import("std"); +const comptimePrint = std.fmt.comptimePrint; +const StructField = std.builtin.Type.StructField; + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; + +const gpio = @import("gpio.zig"); + +/// For now this is always a GPIO pin configuration +const PinConfiguration = struct { + name: ?[:0]const u8 = null, + mode: ?gpio.Mode = null, +}; + +fn GPIO(comptime port: []const u8, comptime num: []const u8, comptime mode: gpio.Mode) type { + if (mode == .input) @compileError("TODO: implement GPIO input mode"); + return switch (mode) { + .input => struct { + const pin = gpio.Pin.init(port, num); + + pub inline fn read(_: @This()) u1 { + return pin.read(); + } + }, + .output => packed struct { + const pin = gpio.Pin.init(port, num); + + pub inline fn put(_: @This(), value: u1) void { + pin.put(value); + } + + pub inline fn toggle(_: @This()) void { + pin.toggle(); + } + + fn configure(_: @This(), pin_config: PinConfiguration) void { + _ = pin_config; // Later: use for GPIO pin speed etc. + pin.configure(); + } + }, + }; +} + +/// This is a helper empty struct with comptime constants for parsing an STM32 pin name. +/// Example: PinDescription("PE9").gpio_port_id = "E" +/// Example: PinDescription("PA12").gpio_port_number_str = "12" +fn PinDescription(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] > 'F') + @compileError(invalid_format_msg); + + const gpio_pin_number_int: comptime_int = std.fmt.parseInt(u4, spec[2..], 10) catch @compileError(invalid_format_msg); + return struct { + /// 'A'...'F' + const gpio_port_id = spec[1..2]; + const gpio_pin_number_str = std.fmt.comptimePrint("{d}", .{gpio_pin_number_int}); + }; +} + +/// Based on the fields in `config`, returns a struct { +/// PE11: GPIO(...), +/// PF7: GPIO(...), +/// } +/// Later: Also support non-GPIO pins? +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(PortConfiguration()).Struct.fields) |field| { + if (@field(port_config, field.name)) |pin_config| { + const D = PinDescription(field.name); + fields = fields ++ &[_]StructField{.{ + .is_comptime = false, + .name = pin_config.name orelse field.name, + .type = GPIO(D.gpio_port_id, D.gpio_pin_number_str, pin_config.mode orelse .{ .input = .floating }), + .default_value = null, + .alignment = @alignOf(field.type), + }}; + } + } + } + } + + return @Type(.{ + .Struct = .{ + .layout = .auto, + .is_tuple = false, + .fields = fields, + .decls = &.{}, + }, + }); + } +} + +/// Returns the struct { +/// PA0: ?PinConfiguration = null, +/// ... +/// PF15: ?PinConfiguration = null, +/// } +fn PortConfiguration() type { + var fields: []const StructField = &.{}; + for ("ABCDEF") |gpio_port_id| { + for (0..16) |gpio_pin_number_int| { + fields = fields ++ &[_]StructField{.{ + .is_comptime = false, + .name = std.fmt.comptimePrint("P{c}{d}", .{ gpio_port_id, gpio_pin_number_int }), + .type = ?PinConfiguration, + .default_value = &@as(?PinConfiguration, null), + .alignment = @alignOf(?PinConfiguration), + }}; + } + } + + return @Type(.{ + .Struct = .{ + .layout = .auto, + .is_tuple = false, + .fields = fields, + .decls = &.{}, + }, + }); +} +pub const GlobalConfiguration = struct { + GPIOA: ?PortConfiguration() = null, + GPIOB: ?PortConfiguration() = null, + GPIOC: ?PortConfiguration() = null, + GPIOD: ?PortConfiguration() = null, + GPIOE: ?PortConfiguration() = null, + GPIOF: ?PortConfiguration() = null, + + pub fn apply(comptime config: @This()) Pins(config) { + const pins: Pins(config) = undefined; // Later: something seems incomplete here... + + inline for (@typeInfo(@This()).Struct.fields) |port_field| { + const gpio_port_name = port_field.name; + if (@field(config, gpio_port_name)) |port_config| { + peripherals.RCC.AHBENR.modify_one(gpio_port_name ++ "EN", 1); + + inline for (@typeInfo(PortConfiguration()).Struct.fields) |pin_field| { + if (@field(port_config, pin_field.name)) |pin_config| { + @field(pins, pin_field.name).configure(pin_config); + } + } + } + } + + return pins; + } +};