Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STM32F303: First HAL fragments (depends on #371) #375

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
};
Loading