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
wch-ch32v003
Marnix Klooster 3 years ago committed by GitHub
parent d4120f9c99
commit 92a2922742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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,

@ -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 {

@ -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,

@ -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();
}

@ -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);
}
};
}

Loading…
Cancel
Save