diff --git a/build.zig b/build.zig index c63cb0f..0519e58 100644 --- a/build.zig +++ b/build.zig @@ -21,8 +21,8 @@ pub fn build(b: *std.build.Builder) !void { BuildConfig{ .name = "chips.atmega328p", .backing = Backing{ .chip = 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_test = false }, - BuildConfig{ .name = "boards.stm32f4discovery", .backing = Backing{ .board = boards.stm32f4discovery }, .supports_uart_test = false }, + BuildConfig{ .name = "boards.stm32f3discovery", .backing = Backing{ .board = boards.stm32f3discovery } }, + BuildConfig{ .name = "boards.stm32f4discovery", .backing = Backing{ .board = boards.stm32f4discovery } }, BuildConfig{ .name = "boards.stm32f429idiscovery", .backing = Backing{ .board = boards.stm32f429idiscovery }, .supports_uart_test = false }, }; diff --git a/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig b/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig index 95358ff..03a5fbb 100644 --- a/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig +++ b/src/modules/boards/mbed-lpc1768/mbed-lpc1768.zig @@ -1,6 +1,8 @@ pub const chip = @import("chip"); pub const micro = @import("microzig"); +pub const cpu_frequency: u32 = 100_000_000; // 100 MHz + pub fn debugWrite(string: []const u8) void { const clk_pin = micro.Pin("DIP5"); const dat_pin = micro.Pin("DIP6"); diff --git a/src/modules/chips/lpc1768/lpc1768.zig b/src/modules/chips/lpc1768/lpc1768.zig index 666d272..aa00f58 100644 --- a/src/modules/chips/lpc1768/lpc1768.zig +++ b/src/modules/chips/lpc1768/lpc1768.zig @@ -5,6 +5,8 @@ const regs = chip.registers; pub usingnamespace chip; +pub const cpu_frequency: u32 = 100_000_000; // 100 MHz + pub const PinTarget = enum(u2) { func00 = 0b00, func01 = 0b01, @@ -129,11 +131,11 @@ pub fn Uart(comptime index: usize) type { }, 2 => { regs.SYSCON.PCONP.modify(.{ .PCUART2 = 1 }); - regs.SYSCON.PCLKSEL0.modify(.{ .PCLK_UART2 = @enumToInt(uart.CClkDiv.four) }); + regs.SYSCON.PCLKSEL1.modify(.{ .PCLK_UART2 = @enumToInt(uart.CClkDiv.four) }); }, 3 => { regs.SYSCON.PCONP.modify(.{ .PCUART3 = 1 }); - regs.SYSCON.PCLKSEL0.modify(.{ .PCLK_UART3 = @enumToInt(uart.CClkDiv.four) }); + regs.SYSCON.PCLKSEL1.modify(.{ .PCLK_UART3 = @enumToInt(uart.CClkDiv.four) }); }, else => unreachable, } diff --git a/src/modules/chips/stm32f407/stm32f407.zig b/src/modules/chips/stm32f407/stm32f407.zig index 1619f72..1359e31 100644 --- a/src/modules/chips/stm32f407/stm32f407.zig +++ b/src/modules/chips/stm32f407/stm32f407.zig @@ -19,7 +19,18 @@ //! //! results in PCLK1 = 16 MHz. //! -//! TODO: add more clock calculations when adding Uart +//! The above default configuration makes U(S)ART2..5 +//! receive a 16 MHz clock by default. +//! +//! USART1 and USART6 use PCLK2, which uses the APB2 prescaler on HCLK, +//! default APB2 prescaler = /1: +//! +//! ``` +//! regs.RCC.CFGR.modify(.{ .PPRE2 = 0 }); +//! ``` +//! +//! and therefore USART1 and USART6 receive a 16 MHz clock. +//! const std = @import("std"); const micro = @import("microzig"); @@ -28,6 +39,12 @@ const regs = chip.registers; pub usingnamespace chip; +// Default clock frequencies after reset, see top comment for calculation +// TODO: these would need to change when we support multiple clock configurations +pub const ahb_frequency = 16_000_000; +pub const apb1_frequency = 16_000_000; +pub const apb2_frequency = 16_000_000; + pub fn parsePin(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."; @@ -77,3 +94,147 @@ pub const gpio = struct { } } }; + +pub const uart = struct { + pub const DataBits = enum { + seven, + eight, + nine, + }; + + /// 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 == 2)) @compileError("TODO: only USART2 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.USART2.CR1.read().UE == 1) + @panic("Trying to initialize USART2 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 USART2 clock + regs.RCC.APB1ENR.modify(.{ .USART2EN = 1 }); + // enable GPIOA clock + regs.RCC.AHB1ENR.modify(.{ .GPIOAEN = 1 }); + // set PA2+PA3 to alternate function 7, USART2_TX + USART2_RX + regs.GPIOA.MODER.modify(.{ .MODER2 = 0b10, .MODER3 = 0b10 }); + regs.GPIOA.AFRL.modify(.{ .AFRL2 = 7, .AFRL3 = 7 }); + + // clear USART2 configuration to its default + regs.USART2.CR1.raw = 0; + regs.USART2.CR2.raw = 0; + regs.USART2.CR3.raw = 0; + + // Return error for unsupported combinations + if (config.data_bits == .nine and config.parity != null) { + // TODO: should we consider this an unsupported word size or unsupported parity? + return error.UnsupportedWordSize; + } else if (config.data_bits == .seven and config.parity == null) { + // TODO: should we consider this an unsupported word size or unsupported parity? + return error.UnsupportedWordSize; + } + + // set word length + // Per the reference manual, M means + // - 0: 1 start bit, 8 data bits (7 data + 1 parity, or 8 data), n stop bits, the chip default + // - 1: 1 start bit, 9 data bits (8 data + 1 parity, or 9 data), n stop bits + const m: u1 = if (config.data_bits == .nine or (config.data_bits == .eight and config.parity != null)) 1 else 0; + regs.USART2.CR1.modify(.{ .M = m }); + + // set parity + if (config.parity) |parity| { + regs.USART2.CR1.modify(.{ .PCE = 1, .PS = @enumToInt(parity) }); + } // otherwise, no need to set no parity since we reset Control Registers above, and it's the default + + // set number of stop bits + regs.USART2.CR2.modify(.{ .STOP = @enumToInt(config.stop_bits) }); + + // set the baud rate + // Despite the reference manual talking about fractional calculation and other buzzwords, + // it is actually just a simple divider. Just ignore DIV_Mantissa and DIV_Fraction and + // set the result of the division as the lower 16 bits of BRR. + // TODO: We assume the default OVER8=0 configuration above (i.e. 16x oversampling). + // 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(apb1_frequency, config.baud_rate)); + regs.USART2.BRR.raw = usartdiv; + + // enable USART2, and its transmitter and receiver + regs.USART2.CR1.modify(.{ .UE = 1 }); + regs.USART2.CR1.modify(.{ .TE = 1 }); + regs.USART2.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.USART2.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.USART2.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.USART2.SR.read().TXE) { + 1 => true, + 0 => false, + }; + } + + pub fn tx(self: Self, ch: u8) void { + while (!self.canWrite()) {} // Wait for Previous transmission + regs.USART2.DR.modify(ch); + } + + pub fn txflush(_: Self) void { + while (regs.USART2.SR.read().TC == 0) {} + } + + pub fn canRead(self: Self) bool { + _ = self; + return switch (regs.USART2.SR.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.USART2.DR.read(); + return @intCast(u8, data_with_parity_bit & self.parity_read_mask); + } + }; +} diff --git a/tests/uart-sync.zig b/tests/uart-sync.zig index 2a0734e..c416c7b 100644 --- a/tests/uart-sync.zig +++ b/tests/uart-sync.zig @@ -1,120 +1,76 @@ const micro = @import("microzig"); // Configures the led_pin to a hardware pin -const uart_txd_pin = micro.Pin("P0.15"); -const uart_rxd_pin = micro.Pin("P0.16"); - -const debug_pin = micro.Pin("P0.17"); - -const led1_pin = micro.Pin("P1.18"); -const led2_pin = micro.Pin("P1.20"); -const led3_pin = micro.Pin("P1.21"); -const led4_pin = micro.Pin("P1.23"); - -pub const cpu_frequency: u32 = 100_000_000; // 100 MHz - -const PLL = struct { - fn init() void { - reset_overclocking(); +const led_pin = if (micro.config.has_board) + switch (micro.config.board_name) { + .@"Arduino Nano" => micro.Pin("D13"), + .@"mbed LPC1768" => micro.Pin("LED-1"), + .@"STM32F3DISCOVERY" => micro.Pin("LD3"), + .@"STM32F4DISCOVERY" => micro.Pin("LD5"), + .@"STM32F429IDISCOVERY" => micro.Pin("LD4"), + else => @compileError("unknown board"), } +else switch (micro.config.chip_name) { + .@"ATmega328p" => micro.Pin("PB5"), + .@"NXP LPC1768" => micro.Pin("P1.18"), + .@"STM32F103x8" => micro.Pin("PC13"), + else => @compileError("unknown chip"), +}; - fn reset_overclocking() void { - overclock_flash(5); // 5 cycles access time - overclock_pll(3); // 100 MHz - } - - fn overclock_flash(timing: u4) void { - micro.chip.registers.SYSCON.FLASHCFG.modify(.{ - .FLASHTIM = timing - 1, - }); - } - inline fn feed_pll() void { - micro.chip.registers.SYSCON.PLL0FEED.modify(.{ .PLL0FEED = 0xAA }); - micro.chip.registers.SYSCON.PLL0FEED.modify(.{ .PLL0FEED = 0x55 }); - } - - fn overclock_pll(divider: u8) void { - // PLL einrichten für RC - micro.chip.registers.SYSCON.PLL0CON.modify(.{ - .PLLE0 = 0, - .PLLC0 = 0, - }); - feed_pll(); - - micro.chip.registers.SYSCON.CLKSRCSEL.modify(.{ .CLKSRC = 0 }); // RC-Oszillator als Quelle - micro.chip.registers.SYSCON.PLL0CFG.modify(.{ - // SysClk = (4MHz / 2) * (2 * 75) = 300 MHz - .MSEL0 = 74, - .NSEL0 = 1, - }); - // CPU Takt = SysClk / divider - micro.chip.registers.SYSCON.CCLKCFG.modify(.{ .CCLKSEL = divider - 1 }); - - feed_pll(); - - micro.chip.registers.SYSCON.PLL0CON.modify(.{ .PLLE0 = 1 }); // PLL einschalten - feed_pll(); - - var i: usize = 0; - while (i < 1_000) : (i += 1) { - micro.cpu.nop(); - } - - micro.chip.registers.SYSCON.PLL0CON.modify(.{ .PLLC0 = 1 }); - feed_pll(); +// Configures the uart index +const uart_idx = if (micro.config.has_board) + switch (micro.config.board_name) { + .@"mbed LPC1768" => 1, + .@"STM32F3DISCOVERY" => 1, + .@"STM32F4DISCOVERY" => 2, + else => @compileError("unknown board"), } +else switch (micro.config.chip_name) { + .@"NXP LPC1768" => 1, + else => @compileError("unknown chip"), }; pub fn main() !void { - const gpio_init = .{ .mode = .output, .initial_state = .low }; - - const led1 = micro.Gpio(led1_pin, gpio_init); - const led2 = micro.Gpio(led2_pin, gpio_init); - const led3 = micro.Gpio(led3_pin, gpio_init); - const led4 = micro.Gpio(led4_pin, gpio_init); - - const status = micro.Gpio(debug_pin, .{ + const led = micro.Gpio(led_pin, .{ .mode = .output, - .initial_state = .high, + .initial_state = .low, }); - status.init(); - led1.init(); - led2.init(); - led3.init(); - led4.init(); + led.init(); - uart_txd_pin.route(.func01); - uart_rxd_pin.route(.func01); - - PLL.init(); - led1.setToHigh(); - - var debug_port = micro.Uart(1).init(.{ + var uart = micro.Uart(uart_idx).init(.{ .baud_rate = 9600, .stop_bits = .one, .parity = null, .data_bits = .eight, }) catch |err| { - led1.write(if (err == error.UnsupportedBaudRate) micro.gpio.State.low else .high); - led2.write(if (err == error.UnsupportedParity) micro.gpio.State.low else .high); - led3.write(if (err == error.UnsupportedStopBitCount) micro.gpio.State.low else .high); - led4.write(if (err == error.UnsupportedWordSize) micro.gpio.State.low else .high); + blinkError(led, err); micro.hang(); }; - led2.setToHigh(); - - var out = debug_port.writer(); - var in = debug_port.reader(); - _ = in; - try out.writeAll("Please enter a sentence:\r\n"); - - led3.setToHigh(); + var out = uart.writer(); while (true) { - try out.writeAll("."); - led4.toggle(); + led.setToHigh(); + try out.writeAll("Hello microzig!\r\n"); + led.setToLow(); micro.debug.busySleep(100_000); } } + +fn blinkError(led: anytype, err: micro.uart.InitError) void { + var blinks: u3 = + switch (err) { + error.UnsupportedBaudRate => 1, + error.UnsupportedParity => 2, + error.UnsupportedStopBitCount => 3, + error.UnsupportedWordSize => 4, + }; + + while (blinks > 0) : (blinks -= 1) { + led.setToHigh(); + micro.debug.busySleep(1_000_000); + led.setToLow(); + micro.debug.busySleep(1_000_000); + } +}