From 92a2922742499f514ef4c29fefc78504aef6022e Mon Sep 17 00:00:00 2001 From: Marnix Klooster Date: Thu, 10 Mar 2022 22:42:31 +0100 Subject: [PATCH] stm32f3discovery/stm32f303 Initial UART1 support, including @panic() (#20) * build.zig: Trivial rename around UART test * mmio: Add writeRaw() to set a full register * UART: Add TODO for auto baud rate detection * STM32F30x Initial USART1 output/transmit support All code assumes default chip clock configuration. Code assumes STM32F303xB / STM32F3030xC. Code supports only 8 data bits, 1 stop bit. * stm32f3discovery @panic() to UART1 This is done by implementing `debugWrite()` for the board, which only initializes UART1 if that was not yet done, and flushes afterwards to make sure the host receives all. * stm32f303: Support UART1 reader This is done by implementing `rx()` and `canRead()`. * stm32f303 UART1 correctly support 7 and 8 bits This includes correctly masking the parity bit on reads. * stm32f3 UART1 support 0.5/1.5/2 stop bits * stm32f303 UART1 simplify parity code * Make work with regz-generated registers.zig change * After #23 repair Reset on stm32, lpc1768 --- build.zig | 6 +- src/core/mmio.zig | 6 +- src/core/uart.zig | 13 ++ .../stm32f3discovery/stm32f3discovery.zig | 14 ++ src/modules/chips/stm32f303/stm32f303.zig | 179 ++++++++++++++++++ 5 files changed, 214 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index bb380ae..e570737 100644 --- a/build.zig +++ b/build.zig @@ -14,14 +14,14 @@ pub fn build(b: *std.build.Builder) !void { const test_step = b.step("test", "Builds and runs the library test suite"); - const BuildConfig = struct { name: []const u8, backing: Backing, supports_uart: bool = true }; + const BuildConfig = struct { name: []const u8, backing: Backing, supports_uart_test: bool = true }; const all_backings = [_]BuildConfig{ //BuildConfig{ .name = "boards.arduino_nano", .backing = Backing{ .board = boards.arduino_nano } }, BuildConfig{ .name = "boards.mbed_lpc1768", .backing = Backing{ .board = boards.mbed_lpc1768 } }, //BuildConfig{ .name = "chips.atmega328p", .backing = Backing{ .chip = pkgs.chips.atmega328p } }, BuildConfig{ .name = "chips.lpc1768", .backing = Backing{ .chip = chips.lpc1768 } }, //BuildConfig{ .name = "chips.stm32f103x8", .backing = Backing{ .chip = chips.stm32f103x8 } }, - BuildConfig{ .name = "boards.stm32f3discovery", .backing = Backing{ .board = boards.stm32f3discovery }, .supports_uart = false }, + BuildConfig{ .name = "boards.stm32f3discovery", .backing = Backing{ .board = boards.stm32f3discovery }, .supports_uart_test = false }, }; const Test = struct { name: []const u8, source: []const u8, uses_uart: bool = false }; @@ -38,7 +38,7 @@ pub fn build(b: *std.build.Builder) !void { inline for (all_backings) |cfg| { inline for (all_tests) |tst| { - if (tst.uses_uart and !cfg.supports_uart) continue; + if (tst.uses_uart and !cfg.supports_uart_test) continue; const exe = try microzig.addEmbeddedExecutable( b, diff --git a/src/core/mmio.zig b/src/core/mmio.zig index 4df5ef4..70ba1f7 100644 --- a/src/core/mmio.zig +++ b/src/core/mmio.zig @@ -31,7 +31,11 @@ pub fn MMIO(comptime size: u8, comptime PackedT: type) type { // This is a workaround for a compiler bug related to miscompilation // If the tmp var is not used, result location will fuck things up var tmp = @bitCast(IntT, val); - addr.raw = tmp; + addr.writeRaw(tmp); + } + + pub fn writeRaw(addr: *volatile Self, val: IntT) void { + addr.raw = val; } pub fn modify(addr: *volatile Self, fields: anytype) void { diff --git a/src/core/uart.zig b/src/core/uart.zig index 1cf8e41..f84d2dc 100644 --- a/src/core/uart.zig +++ b/src/core/uart.zig @@ -17,6 +17,18 @@ pub fn Uart(comptime index: usize) type { }; } + /// If the UART is already initialized, try to return a handle to it, + /// else initialize with the given config. + pub fn getOrInit(config: Config) InitError!Self { + if (!@hasDecl(SystemUart, "getOrInit")) { + // fallback to reinitializing the UART + return init(config); + } + return Self{ + .internal = try SystemUart.getOrInit(config), + }; + } + pub fn canRead(self: Self) bool { return self.internal.canRead(); } @@ -54,6 +66,7 @@ pub fn Uart(comptime index: usize) type { /// A UART configuration. The config defaults to the *8N1* setting, so "8 data bits, no parity, 1 stop bit" which is the /// most common serial format. pub const Config = struct { + /// TODO: Make this optional, to support STM32F303 et al. auto baud-rate detection? baud_rate: u32, stop_bits: StopBits = .one, parity: ?Parity = null, diff --git a/src/modules/boards/stm32f3discovery/stm32f3discovery.zig b/src/modules/boards/stm32f3discovery/stm32f3discovery.zig index 8f2aa47..995e8a5 100644 --- a/src/modules/boards/stm32f3discovery/stm32f3discovery.zig +++ b/src/modules/boards/stm32f3discovery/stm32f3discovery.zig @@ -1,4 +1,5 @@ pub const chip = @import("chip"); +pub const micro = @import("microzig"); pub const cpu_frequency = 8_000_000; @@ -22,3 +23,16 @@ pub const pin_map = .{ // W green .@"LD6" = "PE15", }; + +pub fn debugWrite(string: []const u8) void { + const uart1 = micro.Uart(1).getOrInit(.{ + .baud_rate = 9600, + .data_bits = .eight, + .parity = null, + .stop_bits = .one, + }) catch unreachable; + + const writer = uart1.writer(); + _ = writer.write(string) catch unreachable; + uart1.internal.txflush(); +} diff --git a/src/modules/chips/stm32f303/stm32f303.zig b/src/modules/chips/stm32f303/stm32f303.zig index 91f25cd..97122b1 100644 --- a/src/modules/chips/stm32f303/stm32f303.zig +++ b/src/modules/chips/stm32f303/stm32f303.zig @@ -1,3 +1,39 @@ +//! 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): +//! +//! ``` +//! regs.RCC.CFGR.modify(.{ .HPRE = 0 }); +//! ``` +//! +//! so also HCLK = 8 MHz. +//! And with the default APB1 prescaler = /2: +//! +//! ``` +//! regs.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: +//! +//! ``` +//! regs.RCC.CFGR.modify(.{ .PPRE2 = 0 }); +//! ``` +//! +//! and therefore USART1 runs on 8 MHz. + const std = @import("std"); const micro = @import("microzig"); const chip = @import("registers.zig"); @@ -55,3 +91,146 @@ pub const gpio = struct { } } }; + +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) type { + if (!(index == 1)) @compileError("TODO: only USART1 is currently supported"); + + return struct { + parity_read_mask: u8, + + const Self = @This(); + + pub fn init(config: micro.uart.Config) !Self { + // The following must all be written when the USART is disabled (UE=0). + if (regs.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 + regs.RCC.APB2ENR.modify(.{ .USART1EN = 1 }); + // enable GPIOC clock + regs.RCC.AHBENR.modify(.{ .IOPCEN = 1 }); + // set PC4+PC5 to alternate function 7, USART1_TX + USART1_RX + regs.GPIOC.MODER.modify(.{ .MODER4 = 0b10, .MODER5 = 0b10 }); + regs.GPIOC.AFRL.modify(.{ .AFRL4 = 7, .AFRL5 = 7 }); + + // clear USART1 configuration to its default + regs.USART1.CR1.raw = 0; + regs.USART1.CR2.raw = 0; + regs.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. + regs.USART1.CR1.modify(.{ .padding0 = m1, .M = m0 }); + + // set parity + if (config.parity) |parity| { + regs.USART1.CR1.modify(.{ .PCE = 1, .PS = @enumToInt(parity) }); + } else regs.USART1.CR1.modify(.{ .PCE = 0 }); // no parity, probably the chip default + + // set number of stop bits + regs.USART1.CR2.modify(.{ .STOP = @enumToInt(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 = @intCast(u16, @divTrunc(micro.board.cpu_frequency, config.baud_rate)); + regs.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 + regs.USART1.CR1.modify(.{ .UE = 1 }); + regs.USART1.CR1.modify(.{ .TE = 1 }); + regs.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 readFromRegisters(); + } + + pub fn getOrInit(config: micro.uart.Config) !Self { + if (regs.USART1.CR1.read().UE == 1) { + // UART1 already enabled, don't reinitialize and disturb things; + // instead read and use the actual configuration. + return readFromRegisters(); + } else return init(config); + } + + fn readFromRegisters() Self { + const cr1 = regs.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 canWrite(self: Self) bool { + _ = self; + return switch (regs.USART1.ISR.read().TXE) { + 1 => true, + 0 => false, + }; + } + + pub fn tx(self: Self, ch: u8) void { + while (!self.canWrite()) {} // Wait for Previous transmission + regs.USART1.TDR.modify(ch); + } + + pub fn txflush(_: Self) void { + while (regs.USART1.ISR.read().TC == 0) {} + } + + pub fn canRead(self: Self) bool { + _ = self; + return switch (regs.USART1.ISR.read().RXNE) { + 1 => true, + 0 => false, + }; + } + + pub fn rx(self: Self) u8 { + while (!self.canRead()) {} // Wait till the data is received + const data_with_parity_bit: u9 = regs.USART1.RDR.read().RDR; + return @intCast(u8, data_with_parity_bit & self.parity_read_mask); + } + }; +}