Skip to content

Commit

Permalink
STM32F303: First HAL fragments
Browse files Browse the repository at this point in the history
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'.)
  • Loading branch information
marnix committed Feb 1, 2025
1 parent 68b5ad1 commit 2c6bb5c
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 48 deletions.
3 changes: 3 additions & 0 deletions port/stmicro/stm32/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = .{
Expand Down
105 changes: 57 additions & 48 deletions port/stmicro/stm32/src/hals/STM32F303.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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", .{});

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 });
Expand Down
56 changes: 56 additions & 0 deletions port/stmicro/stm32/src/hals/STM32F303/gpio.zig
Original file line number Diff line number Diff line change
@@ -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);
}
};
154 changes: 154 additions & 0 deletions port/stmicro/stm32/src/hals/STM32F303/pins.zig
Original file line number Diff line number Diff line change
@@ -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;
}
};

0 comments on commit 2c6bb5c

Please sign in to comment.