commit 028d6c9bb01216cb0faefbe9a5f0bff256b72cf1
parent 26e061b4f8959a6f009285f6ce88ccc628069065
Author: Sylvia Ivory <git@sivory.net>
Date: Tue, 20 Jan 2026 17:27:19 -0800
Add UART maybe
Diffstat:
4 files changed, 210 insertions(+), 22 deletions(-)
diff --git a/src/devices/gpio.zig b/src/devices/gpio.zig
@@ -1,34 +1,22 @@
const std = @import("std");
const mem = @import("pi").mem;
+pub const Error = error{
+ PinOutOfRange,
+};
+
// Page 90, Table 6-1: GPIO Register Assignment
const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0020_0000;
const GPFSEL0: usize = BASE_ADDRESS + 0x0000;
const GPSET0: usize = BASE_ADDRESS + 0x001C;
const GPCLR0: usize = BASE_ADDRESS + 0x0028;
const GPLEV0: usize = BASE_ADDRESS + 0x0034;
+const GPPUD: usize = BASE_ADDRESS + 0x0094;
+const GPPUDCLK0: usize = BASE_ADDRESS + 0x0098;
pub const MAX_GPIO: u8 = 53;
const GPIO_PER_FSEL: u8 = 10;
-// Page 92, Table 6-2: GPIO Alternate function select register 0
-// All function select registers reflect this bit layout
-const FunctionSelect = enum(u3) {
- input = 0b000,
- output = 0b001,
-
- alt_fn_0 = 0b100,
- alt_fn_1 = 0b101,
- alt_fn_2 = 0b110,
- alt_fn_3 = 0b111,
- alt_fn_4 = 0b011,
- alt_fn_5 = 0b010,
-};
-
-pub const Error = error{
- PinOutOfRange,
-};
-
fn gpio_check(pin: u8) Error!void {
if (pin > MAX_GPIO) return Error.PinOutOfRange;
}
@@ -43,6 +31,20 @@ fn get_address(pin: u8, offset: usize, chunk: ?u32) Error!*u32 {
return address;
}
+// Page 92, Table 6-2: GPIO Alternate function select register 0
+// All function select registers reflect this bit layout
+pub const FunctionSelect = enum(u3) {
+ input = 0b000,
+ output = 0b001,
+
+ alt_fn_0 = 0b100,
+ alt_fn_1 = 0b101,
+ alt_fn_2 = 0b110,
+ alt_fn_3 = 0b111,
+ alt_fn_4 = 0b011,
+ alt_fn_5 = 0b010,
+};
+
pub fn fn_sel(pin: u8, sel: FunctionSelect) Error!void {
const address = try get_address(pin, GPFSEL0, GPIO_PER_FSEL);
@@ -107,3 +109,39 @@ pub fn write(pin: u8, state: bool) Error!void {
try set_off(pin);
}
}
+
+pub const PullMode = enum(u2) {
+ Off = 0b00,
+ Down = 0b01,
+ Up = 0b10,
+};
+
+fn wait(count: usize) void {
+ var c = count;
+ while (c != 0) {
+ c -= 1;
+ // does this cook us?
+ std.atomic.spinLoopHint();
+ }
+}
+
+pub fn set_pull(pin: u8, pull: PullMode) Error!void {
+ try gpio_check(pin);
+
+ // Set GPPUD
+ mem.put_u32(@ptrFromInt(GPPUD), @intFromEnum(pull));
+
+ // Wait 150 cycles
+ wait(150);
+
+ // Set GPPUDCLK
+ const GPPUDCLK = try get_address(pin, GPPUDCLK0, null);
+ addr_bitset(pin, GPPUDCLK);
+
+ // Wait 150 cycles
+ wait(150);
+
+ // Clear GPPUD & GPPUDCLK
+ mem.put_u32(GPPUDCLK, 0);
+ mem.put_u32(@ptrFromInt(GPPUDCLK0), 0);
+}
diff --git a/src/devices/mini-uart.zig b/src/devices/mini-uart.zig
@@ -0,0 +1,145 @@
+const std = @import("std");
+const mem = @import("pi").mem;
+
+const gpio = @import("./gpio.zig");
+const timer = @import("./timer.zig");
+
+pub const Error = error{AlreadyInitialized} || gpio.Error;
+
+// Page 8: Auxiliary peripherals Register Map
+const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000;
+const AUX_ENABLES: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_IO_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_IER_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_IIR_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_LCR_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_MCR_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_LSR_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_MSR_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_SCRATCH: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_CNTL_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_STAT_REG: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_BAUD_REG: usize = BASE_ADDRESS + 0x0004;
+
+const CLOCK_FREQ = 250_000_000;
+
+const TxPin = enum(u8) {
+ Gpio14 = 14,
+ Gpio32 = 32,
+ Gpio36 = 36,
+};
+const RxPin = enum(u8) {
+ Gpio15 = 15,
+ Gpio33 = 33,
+ Gpio37 = 37,
+};
+
+var is_initialized = false;
+
+pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
+ if (is_initialized) return Error.AlreadyInitialized;
+
+ // Set GPIO pins first (as specified by manual)
+ // Page 102 specifies which alt mode
+ const tx_fn: gpio.FunctionSelect = switch (tx_pin) {
+ .Gpio14 => .alt_fn_0,
+ .Gpio32 => .alt_fn_3,
+ .Gpio36 => .alt_fn_2,
+ };
+ const rx_fn: gpio.FunctionSelect = switch (rx_pin) {
+ .Gpio15 => .alt_fn_0,
+ .Gpio33 => .alt_fn_3,
+ .Gpio37 => .alt_fn_2,
+ };
+
+ try gpio.fn_sel(@intFromEnum(tx_pin), tx_fn);
+ try gpio.fn_sel(@intFromEnum(rx_pin), rx_fn);
+
+ try gpio.set_pull(@intFromEnum(tx_pin), .Off);
+ try gpio.set_pull(@intFromEnum(rx_pin), .Off);
+
+ // AUX_ENABLES needs bit 0 set to 1
+ // Page 9: AUXENB Register
+ mem.put_u32(@ptrFromInt(AUX_ENABLES), 1);
+
+ // Disable interrupts
+ // Page 13: AUX_MU_IER_REG Register
+ // When bits 2:1 is 0, interrupts are disabled
+ mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0);
+
+ // Disable RX/TX during configuration
+ // Page 17: AUX_MU_CNTL_REG Register
+ // When bit 0/1 is 0, UART receiver/transmitter is disabled
+ mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0);
+
+ // Set data size to 8 bits
+ // Page 14: AUX_MU_LCR_REG Register
+ // When bit 0 is 1, UART is in 8-bit mode, else 7-bit
+ mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 1);
+
+ // Put RTS high (indicate request to send)
+ // Page 14: AUX_MU_MCR_REG Register
+ // When bit 1 is 0, RTS line is high, else low
+ mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0);
+
+ // Clear FIFO
+ // Page 12: AUX_MU_IER_REG Register (yes the page is mislabeled)
+ // When bit 1/2 is 1, receive/transmit will be cleared
+ mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0x6);
+
+ // Set baud rate
+ // Page 11: 2.2.1 Mini UART Implementation Details
+ // The baudrate formula is given as (system_clock_freq)/(8 * (baudrate_reg + 1))
+ mem.put_u32(@ptrFromInt(AUX_MU_BAUD_REG), CLOCK_FREQ / (8 * (baud + 1)));
+
+ // Enable RX/TX again
+ // Page 17: AUX_MU_CNTL_REG Register
+ // When bit 0/1 is 1, UART receiver/transmitter is enabled
+ mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0x3);
+
+ is_initialized = true;
+}
+
+fn can_write() bool {
+ // Check if FIFO can accept data
+ // Page 15: AUX_MU_LSR_REG Register
+ // Bit 5 is set when FIFO can accept at least 1 byte
+ return mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20 != 0;
+}
+
+fn can_read() bool {
+ // Check if FIFO has data
+ // Page 15: AUX_MU_LSR_REG Register
+ // Bit 1 is set when FIFO holds at least 1 byte
+ return mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 1;
+}
+
+pub fn write_byte(byte: u8) void {
+ // TODO; support timeout
+ while (!can_write()) {
+ std.atomic.spinLoopHint();
+ }
+
+ // Write into FIFO
+ // Page 11: AUX_MU_IO_REG Register
+ // Bits 7:0 holds data from the FIFO
+ mem.put_u32(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte));
+}
+
+pub fn read_byte() u8 {
+ // TODO; support timeout
+ while (!can_read()) {
+ std.atomic.spinLoopHint();
+ }
+
+ // Read from FIFO
+ // Page 11: AUX_MU_IO_REG Register
+ // Bits 7:0 holds data from the FIFO
+ return @truncate(mem.get_u32(AUX_MU_IO_REG));
+}
+
+pub fn write_slice(slice: []const u8) void {
+ for (slice) |b| {
+ write_byte(b);
+ }
+}
diff --git a/src/devices/timer.zig b/src/devices/timer.zig
@@ -1,9 +1,9 @@
const std = @import("std");
const mem = @import("pi").mem;
-const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x3000;
-const COUNTER_LOWER: usize = 0x04;
-const COUNTER_UPPER: usize = 0x08;
+const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0000_3000;
+const COUNTER_LOWER: usize = BASE_ADDRESS + 0x0004;
+const COUNTER_UPPER: usize = BASE_ADDRESS + 0x0008;
pub fn current_count() u64 {
const upper = mem.get_u32_barrier(COUNTER_UPPER);
diff --git a/src/main.zig b/src/main.zig
@@ -1 +1,6 @@
-pub fn main() !void {}
+const uart = @import("devices/mini-uart.zig");
+
+pub fn main() !void {
+ try uart.initialize(115200, .Gpio14, .Gpio15);
+ uart.write_slice("Hello, World!");
+}