Skip to content
Open
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
43 changes: 25 additions & 18 deletions examples/stmicro/stm32/src/blinky.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,33 @@ pub fn main() !void {
};
break :res .{ pins, all_leds };
} else if (comptime microzig.config.board_name != null and std.mem.eql(u8, microzig.config.board_name.?, "STM32F3DISCOVERY")) {
const pins = (stm32.pins.GlobalConfiguration{ .GPIOE = .{
.PE8 = .{ .mode = .{ .output = .push_pull } },
.PE9 = .{ .mode = .{ .output = .push_pull } },
.PE10 = .{ .mode = .{ .output = .push_pull } },
.PE11 = .{ .mode = .{ .output = .push_pull } },
.PE12 = .{ .mode = .{ .output = .push_pull } },
.PE13 = .{ .mode = .{ .output = .push_pull } },
.PE14 = .{ .mode = .{ .output = .push_pull } },
.PE15 = .{ .mode = .{ .output = .push_pull } },
} }).apply();
const pins = (stm32.pins.GlobalConfiguration{
.GPIOC = .{
.PIN4 = .{ .mode = .{ .alternate_function = .{ .afr = .AF7 } } },
.PIN5 = .{ .mode = .{ .alternate_function = .{ .afr = .AF7 } } },
},
.GPIOE = .{
.PIN8 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN9 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN10 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN11 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN12 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN13 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN14 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
.PIN15 = .{ .mode = .{ .output = .{ .resistor = .Floating, .o_type = .PushPull } } },
},
}).apply();
const all_leds = .{
pins.PE8,
pins.PE9,
pins.PE10,
pins.PE11,
pins.PE12,
pins.PE13,
pins.PE14,
pins.PE15,
pins.PIN8,
pins.PIN9,
pins.PIN10,
pins.PIN11,
pins.PIN12,
pins.PIN13,
pins.PIN14,
pins.PIN15,
};

break :res .{ pins, all_leds };
} else {
@compileError("blinky is not (yet?) implemented for this target");
Expand Down
35 changes: 23 additions & 12 deletions port/stmicro/stm32/src/boards/STM32F3DISCOVERY.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub const microzig = @import("microzig");

pub const cpu_frequency = 8_000_000;
pub const hal = microzig.hal;
pub const rcc = hal.rcc;
pub const pins = hal.pins;

pub const pin_map = .{
// circle of LEDs, connected to GPIOE bits 8..15
Expand All @@ -23,15 +24,25 @@ pub const pin_map = .{
.LD6 = "PE15",
};

pub fn debug_write(string: []const u8) void {
const uart1 = microzig.core.experimental.Uart(1, .{}).get_or_init(.{
.baud_rate = 9600,
.data_bits = .eight,
.parity = null,
.stop_bits = .one,
}) catch unreachable;
pub fn init() void {
hal.enable_fpu();
rcc.enable_hse(8_000_000);
rcc.enable_pll(.HSE, .Div1, .Mul6) catch {
@panic("PLL faile to enable");
};
rcc.select_pll_for_sysclk() catch {
@panic("Faile to select sysclk");
};
}

const writer = uart1.writer();
_ = writer.write(string) catch unreachable;
uart1.internal.txflush();
// Init should come first or the baud_rate would be too fast for the default HSI.
pub fn init_log() void {
_ = (pins.GlobalConfiguration{
.GPIOC = .{
.PIN4 = .{ .mode = .{ .alternate_function = .{ .afr = .AF7 } } },
.PIN5 = .{ .mode = .{ .alternate_function = .{ .afr = .AF7 } } },
},
}).apply();
const uart = try microzig.hal.uart.Uart(.UART1).init(.{ .baud_rate = 115200 });
microzig.hal.uart.init_logger(&uart);
}
244 changes: 54 additions & 190 deletions port/stmicro/stm32/src/hals/STM32F303.zig
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,21 @@ const std = @import("std");
const runtime_safety = std.debug.runtime_safety;

const microzig = @import("microzig");
pub const gpio = @import("STM32F303/gpio.zig");
pub const uart = @import("STM32F303/uart.zig");
pub const rcc = @import("STM32F303/rcc.zig");
pub const i2c = @import("STM32F303/i2c.zig");

const SPI1 = microzig.peripherals.SPI1;
const RCC = microzig.peripherals.RCC;
const USART1 = microzig.peripherals.USART1;
const RCC = microzig.chip.peripherals.RCC;

const GPIOA = microzig.peripherals.GPIOA;
const GPIOB = microzig.peripherals.GPIOB;
const GPIOC = microzig.peripherals.GPIOC;
const GPIOC = microzig.chip.peripherals.GPIOC;
const I2C1 = microzig.peripherals.I2C1;

pub const cpu = @import("cpu");

pub const clock = struct {
pub const Domain = enum {
cpu,
ahb,
apb1,
apb2,
};
};

// Default clock frequencies after reset, see top comment for calculation
pub const clock_frequencies = .{
.cpu = 8_000_000,
.ahb = 8_000_000,
.apb1 = 8_000_000,
.apb2 = 8_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.";

Expand All @@ -98,176 +86,6 @@ fn set_reg_field(reg: anytype, comptime field_name: anytype, value: anytype) voi
reg.write(temp);
}

pub const gpio = struct {
pub fn set_output(comptime pin: type) void {
set_reg_field(RCC.AHBENR, "IOP" ++ pin.gpio_port_name ++ "EN", 1);
set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b01);
}

pub fn set_input(comptime pin: type) void {
set_reg_field(RCC.AHBENR, "IOP" ++ pin.gpio_port_name ++ "EN", 1);
set_reg_field(@field(pin.gpio_port, "MODER"), "MODER" ++ pin.suffix, 0b00);
}

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 write(comptime pin: type, state: microzig.gpio.State) void {
switch (state) {
.low => set_reg_field(pin.gpio_port.BRR, "BR" ++ pin.suffix, 1),
.high => set_reg_field(pin.gpio_port.BSRR, "BS" ++ pin.suffix, 1),
}
}
};

pub const uart = struct {
pub const DataBits = enum(u4) {
seven = 7,
eight = 8,
};

/// uses the values of USART_CR2.STOP
pub const StopBits = enum(u2) {
one = 0b00,
half = 0b01,
two = 0b10,
one_and_half = 0b11,
};

/// uses the values of USART_CR1.PS
pub const Parity = enum(u1) {
even = 0,
odd = 1,
};
};

pub fn Uart(comptime index: usize, comptime source_pins: microzig.uart.Pins) type {
if (!(index == 1)) @compileError("TODO: only USART1 is currently supported");
if (source_pins.tx != null or source_pins.rx != null)
@compileError("TODO: custom pins are not currently supported");

return struct {
parity_read_mask: u8,

const Self = @This();

pub fn init(config: microzig.uart.Config) !Self {
// The following must all be written when the USART is disabled (UE=0).
if (USART1.CR1.read().UE == 1)
@panic("Trying to initialize USART1 while it is already enabled");
// LATER: Alternatively, set UE=0 at this point? Then wait for something?
// Or add a destroy() function which disables the USART?

// enable the USART1 clock
RCC.APB2ENR.modify(.{ .USART1EN = 1 });
// enable GPIOC clock
RCC.AHBENR.modify(.{ .IOPCEN = 1 });
// set PC4+PC5 to alternate function 7, USART1_TX + USART1_RX
GPIOC.MODER.modify(.{ .MODER4 = 0b10, .MODER5 = 0b10 });
GPIOC.AFRL.modify(.{ .AFRL4 = 7, .AFRL5 = 7 });

// clear USART1 configuration to its default
USART1.CR1.raw = 0;
USART1.CR2.raw = 0;
USART1.CR3.raw = 0;

// set word length
// Per the reference manual, M[1:0] means
// - 00: 8 bits (7 data + 1 parity, or 8 data), probably the chip default
// - 01: 9 bits (8 data + 1 parity)
// - 10: 7 bits (7 data)
// So M1==1 means "7-bit mode" (in which
// "the Smartcard mode, LIN master mode and Auto baud rate [...] are not supported");
// and M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'.
const m1: u1 = if (config.data_bits == .seven and config.parity == null) 1 else 0;
const m0: u1 = if (config.data_bits == .eight and config.parity != null) 1 else 0;
// Note that .padding0 = bit 28 = .M1 (.svd file bug?), and .M == .M0.
USART1.CR1.modify(.{ .padding0 = m1, .M = m0 });

// set parity
if (config.parity) |parity| {
USART1.CR1.modify(.{ .PCE = 1, .PS = @intFromEnum(parity) });
} else USART1.CR1.modify(.{ .PCE = 0 }); // no parity, probably the chip default

// set number of stop bits
USART1.CR2.modify(.{ .STOP = @intFromEnum(config.stop_bits) });

// set the baud rate
// TODO: Do not use the _board_'s frequency, but the _U(S)ARTx_ frequency
// from the chip, which can be affected by how the board configures the chip.
// In our case, these are accidentally the same at chip reset,
// if the board doesn't configure e.g. an HSE external crystal.
// TODO: Do some checks to see if the baud rate is too high (or perhaps too low)
// TODO: Do a rounding div, instead of a truncating div?
const usartdiv = @as(u16, @intCast(@divTrunc(microzig.clock.get().apb1, config.baud_rate)));
USART1.BRR.raw = usartdiv;
// Above, ignore the BRR struct fields DIV_Mantissa and DIV_Fraction,
// those seem to be for another chipset; .svd file bug?
// TODO: We assume the default OVER8=0 configuration above.

// enable USART1, and its transmitter and receiver
USART1.CR1.modify(.{ .UE = 1 });
USART1.CR1.modify(.{ .TE = 1 });
USART1.CR1.modify(.{ .RE = 1 });

// For code simplicity, at cost of one or more register reads,
// we read back the actual configuration from the registers,
// instead of using the `config` values.
return read_from_registers();
}

pub fn get_or_init(config: microzig.uart.Config) !Self {
if (USART1.CR1.read().UE == 1) {
// UART1 already enabled, don't reinitialize and disturb things;
// instead read and use the actual configuration.
return read_from_registers();
} else return init(config);
}

fn read_from_registers() Self {
const cr1 = USART1.CR1.read();
// As documented in `init()`, M0==1 means 'the 9th bit (not the 8th bit) is the parity bit'.
// So we always mask away the 9th bit, and if parity is enabled and it is in the 8th bit,
// then we also mask away the 8th bit.
return Self{ .parity_read_mask = if (cr1.PCE == 1 and cr1.M == 0) 0x7F else 0xFF };
}

pub fn can_write(self: Self) bool {
_ = self;
return switch (USART1.ISR.read().TXE) {
1 => true,
0 => false,
};
}

pub fn tx(self: Self, ch: u8) void {
while (!self.can_write()) {} // Wait for Previous transmission
USART1.TDR.modify(ch);
}

pub fn txflush(_: Self) void {
while (USART1.ISR.read().TC == 0) {}
}

pub fn can_read(self: Self) bool {
_ = self;
return switch (USART1.ISR.read().RXNE) {
1 => true,
0 => false,
};
}

pub fn rx(self: Self) u8 {
while (!self.can_read()) {} // Wait till the data is received
const data_with_parity_bit: u9 = USART1.RDR.read().RDR;
return @as(u8, @intCast(data_with_parity_bit & self.parity_read_mask));
}
};
}

const enable_stm32f303_debug = false;

fn debug_print(comptime format: []const u8, args: anytype) void {
Expand All @@ -276,6 +94,52 @@ fn debug_print(comptime format: []const u8, args: anytype) void {
}
}

// Those are missing from the cortex_m4
// Maybe a specificity from STM that have those FPU unit.
const CPACP = microzig.mmio.Mmio(packed struct(u32) {
reserved1: u20 = 0,
CP10: u2,
CP11: u2,
reserved2: u8 = 0,
});

pub const FPCCR = microzig.mmio.Mmio(packed struct(u32) {
LSPACT: u1,
USER: u1,
S: u1,
THREAD: u1,
HFRDY: u1,
MMRDY: u1,
BFRDY: u1,
SFRDY: u1,
MONRDY: u1,
SPLIMVIOL: u1,
UFRDY: u1,
reserved0: u15 = 0,
TS: u1,
CLRONRETS: u1,
CLRONRET: u1,
LSPENS: u1,
LSPEN: u1,
ASPEN: u1,
});

pub const missing_peripherals = .{
.cpacr = @as(*volatile CPACP, @ptrFromInt(0xE000ED88)),
.fpccr = @as(*volatile FPCCR, @ptrFromInt(0xE000EF34)),
};

pub fn enable_fpu() void {
missing_peripherals.cpacr.modify(.{
.CP10 = 0b11,
.CP11 = 0b11,
});
missing_peripherals.fpccr.modify(.{
.ASPEN = 1,
.LSPEN = 1,
});
}

/// This implementation does not use AUTOEND=1
pub fn I2CController(comptime index: usize, comptime source_pins: microzig.i2c.Pins) type {
if (!(index == 1)) @compileError("TODO: only I2C1 is currently supported");
Expand Down
Loading