sylveos

Toy Operating System
Log | Files | Refs

commit 5259c04eabc55e65a124f79b963d2f4e45d2f9e5
parent 2be6a19907a081b3489323b6bc3cb6cee478f169
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu, 12 Mar 2026 19:11:55 -0700

Merge branch 'features/spi'

Diffstat:
Api/devices/aux.zig | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Api/devices/mini-spi.zig | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Api/devices/nrf.zig | 548+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Api/devices/spi.zig | 207+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpi/register.zig | 5+++--
Mpi/root.zig | 3+++
Aprograms/nrf-client.zig | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprograms/nrf-server.zig | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprograms/nrf.zig | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 1255 insertions(+), 2 deletions(-)

diff --git a/pi/devices/aux.zig b/pi/devices/aux.zig @@ -0,0 +1,57 @@ +const std = @import("std"); + +const mem = @import("../mem.zig"); +const register = @import("../register.zig"); + +pub const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000; +const AUX_IRQ: register.Register(Aux, .ReadOnly) = .init(BASE_ADDRESS + 0x00); +const AUX_ENABLES: register.Register(Aux, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x04); + +const Aux = packed struct(u32) { + mini_uart: bool, + spi_1: bool, + spi_2: bool, + _reserved_31_3: u29, +}; + +pub fn enable_mini_uart() void { + var aux = AUX_ENABLES.get(); + aux.mini_uart = true; + AUX_ENABLES.set(aux); +} +pub fn disable_mini_uart() void { + var aux = AUX_ENABLES.get(); + aux.mini_uart = false; + AUX_ENABLES.set(aux); +} +pub fn has_irq_mini_uart() bool { + return AUX_IRQ.get().mini_uart; +} + +pub fn enable_spi_1() void { + var aux = AUX_ENABLES.get(); + aux.spi_1 = true; + AUX_ENABLES.set(aux); +} +pub fn disable_spi_1() void { + var aux = AUX_ENABLES.get(); + aux.spi_1 = false; + AUX_ENABLES.set(aux); +} +pub fn has_irq_spi_1() bool { + return AUX_IRQ.get().spi_1; +} + +pub fn enable_spi_2() void { + var aux = AUX_ENABLES.get(); + aux.spi_2 = true; + AUX_ENABLES.set(aux); +} +pub fn disable_spi_2() void { + var aux = AUX_ENABLES.get(); + aux.spi_1 = false; + AUX_ENABLES.set(aux); +} +pub fn has_irq_spi_2() bool { + return AUX_IRQ.get().spi_2; +} diff --git a/pi/devices/mini-spi.zig b/pi/devices/mini-spi.zig @@ -0,0 +1,240 @@ +const std = @import("std"); + +const aux = @import("./aux.zig"); +const gpio = @import("./gpio.zig"); +const mem = @import("../mem.zig"); +const register = @import("../register.zig"); + +const BASE_ADDRESS_1: usize = aux.BASE_ADDRESS + 0x80; +const BASE_ADDRESS_2: usize = aux.BASE_ADDRESS + 0xC0; + +const AUX_SPI1_CNTL0: register.Register(Control0, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x00); +const AUX_SPI1_CNTL1: register.Register(Control1, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x04); +const AUX_SPI1_STAT: register.Register(Status, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x08); +const AUX_SPI1_PEEK: register.Register(FiFo, .ReadOnly) = .init(BASE_ADDRESS_1 + 0x0C); +const AUX_SPI1_IO: register.Register(FiFo, .ReadModifyWrite) = .init(BASE_ADDRESS_1 + 0x20); + +const AUX_SPI2_CNTL0: register.Register(Control0, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x00); +const AUX_SPI2_CNTL1: register.Register(Control1, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x04); +const AUX_SPI2_STAT: register.Register(Status, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x08); +const AUX_SPI2_PEEK: register.Register(FiFo, .ReadOnly) = .init(BASE_ADDRESS_2 + 0x0C); +const AUX_SPI2_IO: register.Register(FiFo, .ReadModifyWrite) = .init(BASE_ADDRESS_2 + 0x20); + +// Unlike UART, only 1 pair +const SPI1_CE2 = 16; +const SPI1_CE1 = 17; +const SPI1_CE0 = 18; +const SPI1_MISO = 19; +const SPI1_MOSI = 20; +const SPI1_SCLK = 21; + +const SPI2_CE2 = 40; +const SPI2_CE1 = 41; +const SPI2_CE0 = 42; +const SPI2_MISO = 43; +const SPI2_MOSI = 44; +const SPI2_SCLK = 45; + +const Control0 = packed struct(u32) { + pub const DOutHold = enum(u2) { + None = 0b00, + @"1" = 0b01, + @"4" = 0b10, + @"7" = 0b11, + }; + pub const ChipSelect = packed struct(u3) { + @"0": bool, + @"1": bool, + @"2": bool, + }; + shift_length: u6, + shift_out_ms: bool, + invert_spi_clk: bool, + out_rising: bool, + clear_fifos: bool, + in_rising: bool, + enable: bool, + dout_hold: DOutHold, + variable_width: bool, + variable_cs: bool, + post_input: bool, + chip_select: ChipSelect, + speed: u12, +}; +const Control1 = packed struct(u32) { + keep_input: bool, + shift_in_ms: bool, + _reserved_5_2: u4 = 0, + done_irq: bool, + tx_empty_irq: bool, + cs_high_time: u3, + _reserved_31_11: u21 = 0, +}; +const Status = packed struct(u32) { + bit_count: u6, + busy: bool, + rx_empty: bool, + rx_full: bool, + tx_empty: bool, + tx_full: bool, + _reserved_15_11: u5 = 0, + rx_fill_level: u4, + tx_fill_level: u4, + _reserved_31_22: u8, +}; +const FiFo = packed struct(u32) { + data: u16, + _reserved_16_31: u16 = 0, +}; + +pub const SpiDevice = enum { + SPI1, + SPI2, +}; + +pub const SpiConfig = struct { + speed: u12, + chip_select: Control0.ChipSelect = .{ + .@"0" = false, + .@"1" = false, + .@"2" = false, + }, + shift_length: u6 = 8, + out_rising: bool = true, + in_rising: bool = false, + invert_clk: bool = false, + shift_out_ms: bool = true, + shift_in_ms: bool = false, + cs_high_time: u3 = 0, +}; + +pub fn init(device: SpiDevice, config: SpiConfig) void { + const cntl0_reg = switch (device) { + .SPI1 => AUX_SPI1_CNTL0, + .SPI2 => AUX_SPI2_CNTL0, + }; + const cntl1_reg = switch (device) { + .SPI1 => AUX_SPI1_CNTL1, + .SPI2 => AUX_SPI2_CNTL1, + }; + + switch (device) { + .SPI1 => { + gpio.fn_sel(SPI1_MISO, .alt_fn_4) catch {}; + gpio.set_pull(SPI1_MISO, .Up) catch {}; + + gpio.fn_sel(SPI1_MOSI, .alt_fn_4) catch {}; + gpio.set_pull(SPI1_MOSI, .Up) catch {}; + + gpio.fn_sel(SPI1_SCLK, .alt_fn_4) catch {}; + if (config.invert_clk) { + gpio.set_pull(SPI1_SCLK, .Up) catch {}; + } else { + gpio.set_pull(SPI1_SCLK, .Down) catch {}; + } + + gpio.fn_sel(SPI1_CE0, .alt_fn_4) catch {}; + gpio.set_pull(SPI1_CE2, .Up) catch {}; + + gpio.fn_sel(SPI1_CE1, .alt_fn_4) catch {}; + gpio.set_pull(SPI1_CE2, .Up) catch {}; + + gpio.fn_sel(SPI1_CE2, .alt_fn_4) catch {}; + gpio.set_pull(SPI1_CE2, .Up) catch {}; + + aux.enable_spi_1(); + }, + .SPI2 => { + gpio.fn_sel(SPI2_MISO, .alt_fn_4) catch {}; + gpio.set_pull(SPI2_MISO, .Up) catch {}; + + gpio.fn_sel(SPI2_MOSI, .alt_fn_4) catch {}; + gpio.set_pull(SPI2_MOSI, .Up) catch {}; + + gpio.fn_sel(SPI2_SCLK, .alt_fn_4) catch {}; + if (config.invert_clk) { + gpio.set_pull(SPI2_SCLK, .Up) catch {}; + } else { + gpio.set_pull(SPI2_SCLK, .Down) catch {}; + } + + gpio.fn_sel(SPI2_CE0, .alt_fn_4) catch {}; + gpio.set_pull(SPI2_CE2, .Up) catch {}; + + gpio.fn_sel(SPI2_CE1, .alt_fn_4) catch {}; + gpio.set_pull(SPI2_CE1, .Up) catch {}; + + gpio.fn_sel(SPI2_CE2, .alt_fn_4) catch {}; + gpio.set_pull(SPI2_CE2, .Up) catch {}; + + aux.enable_spi_2(); + }, + } + + cntl0_reg.set(.{ + .shift_length = config.shift_length, + .shift_out_ms = config.shift_out_ms, + .invert_spi_clk = config.invert_clk, + .out_rising = config.out_rising, + .clear_fifos = true, + .in_rising = config.in_rising, + .enable = true, + .dout_hold = .None, + .variable_width = false, + .variable_cs = false, + .post_input = false, + .chip_select = config.chip_select, + .speed = config.speed, + }); + + cntl1_reg.set(.{ + .keep_input = false, + .shift_in_ms = config.shift_in_ms, + .done_irq = false, + .tx_empty_irq = false, + .cs_high_time = config.cs_high_time, + }); +} + +pub fn status(device: SpiDevice) Status { + return switch (device) { + .SPI1 => AUX_SPI1_STAT.get(), + .SPI2 => AUX_SPI2_STAT.get(), + }; +} + +pub fn write_polled(device: SpiDevice, data: []const u8) void { + const io_reg = switch (device) { + .SPI1 => AUX_SPI1_IO, + .SPI2 => AUX_SPI2_IO, + }; + + for (data) |b| { + while (status(device).tx_full) {} + io_reg.set(.{ + .data = @as(u16, b), + }); + } +} + +pub fn read_polled(device: SpiDevice, data: []u8) void { + const io_reg = switch (device) { + .SPI1 => AUX_SPI1_IO, + .SPI2 => AUX_SPI2_IO, + }; + + for (0..data.len) |i| { + while (status(device).rx_empty) {} + data[i] = @truncate(io_reg.get().data); + } +} + +pub fn transfer(device: SpiDevice, src: []const u8, dst: []u8) void { + write_polled(device, src); + flush(device); + read_polled(device, dst); +} + +pub fn flush(device: SpiDevice) void { + while (status(device).busy) {} +} diff --git a/pi/devices/nrf.zig b/pi/devices/nrf.zig @@ -0,0 +1,548 @@ +// nRF24L01+ + +const std = @import("std"); +const spi = @import("./spi.zig"); +const gpio = @import("./gpio.zig"); +const clock = @import("./clock.zig"); +const uart = @import("./mini-uart.zig"); + +// 51: 8.3.1 SPI commands +const CMD_R_REGISTER: u8 = 0b0000_0000; +const CMD_W_REGISTER: u8 = 0b0010_0000; +const CMD_R_RX_PAYLOAD: u8 = 0b0110_0001; +const CMD_W_TX_PAYLOAD: u8 = 0b1010_0000; +const CMD_FLUSH_TX: u8 = 0b1110_0001; +const CMD_FLUSH_RX: u8 = 0b1110_0010; +const CMD_REUSE_TX_PL: u8 = 0b1110_0011; +const CMD_R_RX_PL_WID: u8 = 0b0110_0000; +const CMD_W_ACK_PAYLOAD: u8 = 0b1010_1000; +const CMD_W_TX_PAYLOAD_NO_ACK: u8 = 0b1011_0000; +const CMD_NOP: u8 = 0b1111_1111; + +// 57: 9.1 Register map table +const REG_CONFIG: u5 = 0x00; +const nRFConfig = packed struct(u8) { + const PrimRx = enum(u1) { + PRX = 1, + PTX = 0, + }; + const Crc = enum(u1) { + @"1" = 0, + @"2" = 1, + }; + prim_rx: PrimRx = .PTX, + pwr_up: bool = false, + crc: Crc = .@"1", + enable_crc: bool = true, + mask_max_rt: bool = false, + mask_tx_rs: bool = false, + mask_rx_dr: bool = false, + _reserved_7: u1 = 0, +}; + +// Pipes +const Pipes = packed struct(u8) { + @"0": bool = true, + @"1": bool = true, + @"2": bool = true, + @"3": bool = true, + @"4": bool = true, + @"5": bool = true, + _reserved_7_6: u2 = 0, +}; +const REG_EN_AA: u5 = 0x01; +const REG_EN_RXADDR: u5 = 0x02; + +const REG_SETUP_AW: u5 = 0x03; +const AddressWidths = packed struct(u8) { + const Widths = enum(u2) { + @"3" = 0b01, + @"4" = 0b10, + @"5" = 0b11, + }; + + width: Widths, + _reserved_7_2: u6 = 0, +}; + +const REG_SETUP_RETR: u5 = 0x04; +const SetupRetry = packed struct(u8) { + delay: u4 = 0b0000, + attempts: u4 = 0b011, +}; + +const REG_RF_CH: u5 = 0x05; +const RFChannel = packed struct(u8) { + channel: u7 = 0b0000010, + _reserved_7: u1 = 0, +}; + +const REG_RF_SETUP: u5 = 0x06; +const RFSetup = packed struct(u8) { + const OutputPower = enum(u2) { + @"-18dBm" = 0b00, + @"-12dBm" = 0b01, + @"-6dBm" = 0b10, + @"0dBm" = 0b11, + }; + const Speed = enum(u2) { + @"1Mbps" = 0b00, + @"2Mbps" = 0b01, + @"250kbps" = 0b10, + }; + + _reserved_0: u1 = 0, + output_power: OutputPower = .@"0dBm", + rf_high: bool = true, + pll_lock: bool = false, + rf_low: bool = false, + _reserved_6: u1 = 0, + continuous_transmission: bool = false, +}; + +const REG_STATUS: u5 = 0x07; +const Status = packed struct(u8) { + tx_full: bool = false, + rx_pipe_number: u3 = 0b111, + maxed_retries: bool = false, + tx_ds: bool = false, + rx_dr: bool = false, + _reserved_7: u1 = 0, +}; + +const REG_OBSERVE_TX: u5 = 0x08; +const ObserveTx = packed struct(u8) { + retransmit_count: u4 = 0, + lost_count: u4 = 0, +}; + +const REG_RPD: u5 = 0x09; +const ReceivedPowerDetector = packed struct(u8) { + rpd: bool = false, + _reserved_7_1: u7, +}; + +const REG_RX_ADDR_P0: u5 = 0x0A; +const REG_RX_ADDR_P1: u5 = 0x0B; +const REG_RX_ADDR_P2: u5 = 0x0C; +const REG_RX_ADDR_P3: u5 = 0x0D; +const REG_RX_ADDR_P4: u5 = 0x0E; +const REG_RX_ADDR_P5: u5 = 0x0F; +const REG_TX_ADDR: u5 = 0x10; + +const RxPayload = packed struct(u8) { + bytes: u6 = 0, + _reserved_7_6: u2 = 0, +}; +const REG_RX_PW_P0: u5 = 0x11; +const REG_RX_PW_P1: u5 = 0x12; +const REG_RX_PW_P2: u5 = 0x13; +const REG_RX_PW_P3: u5 = 0x14; +const REG_RX_PW_P4: u5 = 0x15; +const REG_RX_PW_P5: u5 = 0x16; + +const REG_FIFO_STATUS: u5 = 0x17; +const FiFoStatus = packed struct(u8) { + rx_empty: bool, + rx_full: bool, + _reserved_3_2: u2, + tx_empty: bool, + tx_full: bool, + tx_reuse: bool, + _reserved_7: u1 = 0, +}; + +// Pipes +const REG_DYNPD: u5 = 0x1C; + +const REG_FEATURE: u5 = 0x1D; +const Feature = packed struct(u8) { + enable_dynamic_acknowledge: bool = false, + enable_acknowledge_payload: bool = false, + enable_dynamic_payload_length: bool = false, + _reserved_7_3: u5 = 0, +}; + +// Implementation +pub const Error = error{ + InvalidPipe, + InvalidPayload, + InvalidPayloadSize, + NotReady, + PayloadTooLarge, + Timeout, +}; + +pub const Config = struct { + spi_config: spi.SpiConfig, + ce_pin: u8, + int_pin: u8, + + channel: u7 = 2, + output_power: RFSetup.OutputPower = .@"0dBm", + data_rate: RFSetup.Speed = .@"1Mbps", + crc_length: nRFConfig.Crc = .@"2", + auto_retransmit_delay: u4 = 2, // 500us + auto_retransmit_count: u4 = 15, + payload_size: u6 = 32, +}; + +pub const Device = struct { + spi_config: spi.SpiConfig, + ce_pin: u8, + int_pin: u8, + + const Self = @This(); + + pub fn init(config: Config) !Self { + var self: Self = .{ + .spi_config = config.spi_config, + .ce_pin = config.ce_pin, + .int_pin = config.int_pin, + }; + + self.spi_config.chip_select_polarity = .ActiveLow; + self.spi_config.clock_polarity = .Low; + self.spi_config.clock_phase = .Middle; + spi.init(&self.spi_config, 64); + + try gpio.fn_sel(config.ce_pin, .output); + try gpio.set_off(config.ce_pin); + + // Power on reset 100ms + clock.delay_ms(100); + + // Configure + self.write_register(nRFConfig, REG_CONFIG, .{ + .pwr_up = false, + .crc = .@"2", + .prim_rx = .PTX, + }); + + // Setup Auto Acknowledge + self.write_register(Pipes, REG_EN_AA, .{ + .@"0" = true, + .@"1" = true, + .@"2" = true, + .@"3" = true, + .@"4" = true, + .@"5" = true, + }); + + // Enable Pipes 0/1 + self.write_register(Pipes, REG_EN_RXADDR, .{ + .@"0" = true, + .@"1" = true, + .@"2" = false, + .@"3" = false, + .@"4" = false, + .@"5" = false, + }); + + // Set Address Width + self.write_register(AddressWidths, REG_SETUP_AW, .{ + .width = .@"5", + }); + + // Setup retry + self.write_register(SetupRetry, REG_SETUP_RETR, .{ + .attempts = config.auto_retransmit_count, + .delay = config.auto_retransmit_delay, + }); + + // Set channel + self.write_register(RFChannel, REG_RF_CH, .{ + .channel = config.channel, + }); + + // Set data rate and power + self.set_rf(config.output_power, config.data_rate); + + // Clear DYNPD & FEATURE + self.write_register(u8, REG_FEATURE, 0); + self.write_register(u8, REG_DYNPD, 0); + + if (config.payload_size > 32) return Error.InvalidPayloadSize; + try self.set_rx_payload_size(0, config.payload_size); + try self.set_rx_payload_size(1, config.payload_size); + + // Flush before power up + self.flush_tx(); + self.flush_rx(); + self.clear_irq(); + + // Power on (Standby-I) + self.power_up(); + + return self; + } + + fn power_up(self: *const Self) void { + var cfg = self.read_register(nRFConfig, REG_CONFIG); + cfg.pwr_up = true; + self.write_register(nRFConfig, REG_CONFIG, cfg); + clock.delay_ms(2); + } + + fn power_down(self: *const Self) void { + var cfg = self.read_register(nRFConfig, REG_CONFIG); + cfg.pwr_up = false; + self.write_register(nRFConfig, REG_CONFIG, cfg); + self.set_ce_low(); + } + + fn set_ce_high(self: *const Self) void { + gpio.set_on(self.ce_pin) catch {}; + } + + fn set_ce_low(self: *const Self) void { + gpio.set_off(self.ce_pin) catch {}; + } + + fn pulse_ce(self: *const Self) void { + self.set_ce_high(); + clock.delay(15); + self.set_ce_low(); + } + + fn set_rf(self: *const Self, power: RFSetup.OutputPower, speed: RFSetup.Speed) void { + const rate: u2 = @intFromEnum(speed); + const low = (rate & 0b10) == 0b10; + const high = (rate & 0b01) == 0b01; + + self.write_register(RFSetup, REG_RF_SETUP, .{ + .output_power = power, + .rf_low = low, + .rf_high = high, + }); + } + + // u40 = 5 byte addresses + pub fn set_tx_addr(self: *const Self, addr: u40) void { + var tx_data: [5]u8 = undefined; + std.mem.writeInt(u40, &tx_data, addr, .little); + self.write_register_n(REG_TX_ADDR, &tx_data); + } + + pub fn set_rx_addr(self: *const Self, pipe: u3, addr: u40) !void { + if (pipe > 5) return Error.InvalidPipe; + + if (pipe <= 1) { + var tx_data: [5]u8 = undefined; + std.mem.writeInt(u40, &tx_data, addr, .little); + self.write_register_n(REG_RX_ADDR_P0 + pipe, &tx_data); + } else { + const tx_data = [_]u8{@truncate(addr)}; + self.write_register_n(REG_RX_ADDR_P0 + pipe, &tx_data); + } + } + + fn set_rx_payload_size(self: *const Self, pipe: u3, size: u6) Error!void { + if (size > 32) return Error.InvalidPayloadSize; + if (pipe > 5) return Error.InvalidPipe; + + self.write_register(RxPayload, REG_RX_PW_P0 + pipe, .{ + .bytes = size, + }); + } + + fn enable_dynamic_payloads(self: *const Self) void { + var feature = self.read_register(Feature, REG_FEATURE); + feature.enable_dynamic_payload_length = true; + self.write_register(Feature, REG_FEATURE, feature); + self.write_register(Pipes, REG_DYNPD, .{ + .@"0" = true, + .@"1" = true, + .@"2" = true, + .@"3" = true, + .@"4" = true, + .@"5" = true, + }); + } + + fn disable_dynamic_payloads(self: *const Self) void { + var feature = self.read_register(Feature, REG_FEATURE); + feature.enable_dynamic_payload_length = false; + self.write_register(Feature, REG_FEATURE, feature); + self.write_register(Pipes, REG_DYNPD, .{ + .@"0" = false, + .@"1" = false, + .@"2" = false, + .@"3" = false, + .@"4" = false, + .@"5" = false, + }); + } + + pub fn connect(self: *const Self) void { + var cfg = self.read_register(nRFConfig, REG_CONFIG); + cfg.prim_rx = .PRX; + self.write_register(nRFConfig, REG_CONFIG, cfg); + + self.clear_irq(); + self.flush_rx(); + + self.set_ce_high(); + clock.delay(150); + } + + pub fn disconnect(self: *const Self) void { + self.set_ce_low(); + clock.delay(150); + + var cfg = self.read_register(nRFConfig, REG_CONFIG); + cfg.prim_rx = .PTX; + self.write_register(nRFConfig, REG_CONFIG, cfg); + + self.flush_tx(); + } + + pub fn transmit(self: *const Self, payload: []const u8, timeout_ms: u32) Error!bool { + if (payload.len == 0 or payload.len > 32) return Error.InvalidPayload; + + self.disconnect(); + self.clear_irq(); + self.flush_tx(); + + self.write_command(CMD_W_TX_PAYLOAD, payload); + + self.pulse_ce(); + + const deadline = clock.current_count() + std.time.us_per_ms * timeout_ms; + while (clock.current_count() < deadline) { + const status = self.get_status(); + + if (status.tx_ds) { + self.write_register(Status, REG_STATUS, .{ + .tx_ds = true, + }); + + return true; + } + + if (status.maxed_retries) { + self.write_register(Status, REG_STATUS, .{ + .maxed_retries = true, + }); + self.flush_tx(); + + return false; + } + + clock.delay_ms(1); + } + + return Error.Timeout; + } + + pub fn receive(self: *const Self, buffer: []u8) Error!usize { + const fifo = self.read_register(FiFoStatus, REG_FIFO_STATUS); + if (fifo.rx_empty) return 0; + + var width: usize = buffer.len; + + const feature = self.read_register(Feature, REG_FEATURE); + if (feature.enable_dynamic_payload_length) { + const w = self.read_rx_payload_width(); + if (w > 32) { + self.flush_rx(); + return Error.NotReady; + } + width = @intCast(w); + if (width > buffer.len) return Error.PayloadTooLarge; + } + + _ = self.read_command(CMD_R_RX_PAYLOAD, buffer[0..width]); + + self.write_register(Status, REG_STATUS, .{ + .rx_dr = true, + }); + + return width; + } + + fn read_register(self: *const Self, comptime T: type, reg: u5) T { + const cmd: u8 = CMD_R_REGISTER | @as(u8, reg); + const tx_data = [_]u8{ cmd, CMD_NOP }; + var rx_data: [2]u8 = undefined; + + spi.transfer(&self.spi_config, &tx_data, &rx_data); + + return @bitCast(rx_data[1]); + } + + fn read_register_n(self: *const Self, reg: u5, buffer: []u8) void { + return self.read_command(CMD_R_REGISTER | @as(u8, reg), buffer); + } + + fn read_rx_payload_width(self: *const Self) u8 { + const tx_data = [_]u8{ CMD_R_RX_PL_WID, CMD_NOP }; + var rx_data: [2]u8 = undefined; + + spi.transfer(&self.spi_config, &tx_data, &rx_data); + + return rx_data[1]; + } + + fn write_register(self: *const Self, comptime T: type, reg: u5, value: T) void { + const cmd: u8 = CMD_W_REGISTER | @as(u8, reg); + const value_byte: u8 = @bitCast(value); + const tx_data = [_]u8{ cmd, value_byte }; + + var rx_data: [2]u8 = undefined; + + spi.transfer(&self.spi_config, &tx_data, &rx_data); + } + + fn write_register_n(self: *const Self, reg: u5, buffer: []const u8) void { + return self.write_command(CMD_W_REGISTER | @as(u8, reg), buffer); + } + + fn read_command(self: *const Self, cmd: u8, result: []u8) Status { + var tx_data: [64]u8 = undefined; + var rx_data: [64]u8 = undefined; + + tx_data[0] = cmd; + @memset(tx_data[1 .. result.len + 1], CMD_NOP); + + spi.transfer(&self.spi_config, tx_data[0 .. result.len + 1], rx_data[0 .. result.len + 1]); + + @memcpy(result, rx_data[1 .. result.len + 1]); + + return @bitCast(rx_data[0]); + } + + fn write_command(self: *const Self, cmd: u8, bytes: []const u8) void { + var tx_data: [64]u8 = undefined; + var rx_data: [64]u8 = undefined; + + tx_data[0] = cmd; + @memcpy(tx_data[1 .. bytes.len + 1], bytes); + + spi.transfer(&self.spi_config, tx_data[0 .. bytes.len + 1], rx_data[0 .. bytes.len + 1]); + } + + pub fn get_status(self: *const Self) Status { + return self.read_register(Status, REG_STATUS); + } + + pub fn flush_tx(self: *const Self) void { + const tx_data = [_]u8{CMD_FLUSH_TX}; + var rx_data: [1]u8 = undefined; + spi.transfer(&self.spi_config, &tx_data, &rx_data); + } + + pub fn flush_rx(self: *const Self) void { + const tx_data = [_]u8{CMD_FLUSH_RX}; + var rx_data: [1]u8 = undefined; + spi.transfer(&self.spi_config, &tx_data, &rx_data); + } + + pub fn clear_irq(self: *const Self) void { + self.write_register(Status, REG_STATUS, .{ + .rx_dr = true, + .tx_ds = true, + .maxed_retries = true, + }); + } +}; diff --git a/pi/devices/spi.zig b/pi/devices/spi.zig @@ -0,0 +1,207 @@ +const std = @import("std"); + +const mem = @import("../mem.zig"); +const gpio = @import("./gpio.zig"); +const register = @import("../register.zig"); + +const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0020_4000; +const CS: register.Register(MasterControlStatus, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x00); +const FIFO: register.Register(u32, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x04); +const CLK: register.Register(Clk, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x08); +const DATA_LENGTH: register.Register(DataLength, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x0C); +const LTOH: register.Register(Ltoh, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x10); +const DC: register.Register(Dc, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x14); + +const MasterControlStatus = packed struct(u32) { + pub const ChipSelect = enum(u2) { + @"0" = 0b00, + @"1" = 0b01, + @"2" = 0b10, + }; + + pub const ClockPhase = enum(u1) { + Middle = 0, + Beginning = 1, + }; + + pub const ClockPolarity = enum(u1) { + Low = 0, + High = 1, + }; + + pub const FiFoClear = packed struct(u2) { + clear_tx: bool, + clear_rx: bool, + }; + + pub const ChipSelectPolarity = enum(u1) { + ActiveLow = 0, + ActiveHigh = 1, + }; + + pub const Ren = enum(u1) { + Write = 0, + Read = 1, + }; + + pub const Len = enum(u1) { + SPIMaster = 0, + LoSSIMaster = 1, + }; + + chip_select: ChipSelect = .@"0", + clock_phase: ClockPhase = .Middle, + clock_polarity: ClockPolarity = .Low, + clear_fifo: FiFoClear = .{ .clear_tx = false, .clear_rx = false }, + // elinux bcm2835 errata: + // "There is a CSPOL bit described here, wereas there are also CSPOL[0,1,2] + // described on page 153. How do these combine???" + // Linux sets both this and chip_select_N together + chip_select_polarity: ChipSelectPolarity = .ActiveLow, + transfer_active: bool = false, + dma_enable: bool = false, + interrupt_on_done: bool = false, + interrupt_on_rx: bool = false, + automatically_deassert_chip_select: bool = false, + ren: Ren = .Write, + len_lossi_enable: Len = .SPIMaster, + _unused_14: u1 = 0, + _unused_15: u1 = 0, + done: bool = false, + rxd_contains_data: bool = false, + txd_has_space: bool = true, + rxr_is_full: bool = false, + rxf_is_full: bool = false, + chip_select_0: ChipSelectPolarity = .ActiveLow, + chip_select_1: ChipSelectPolarity = .ActiveLow, + chip_select_2: ChipSelectPolarity = .ActiveLow, + dma_enable_lossi: bool = false, + lossi_write_u32: bool = false, + _reserved_31_26: u6 = 0, +}; + +const Clk = packed struct(u32) { + clock_divider: u16 = 0, + _reserved_31_16: u16 = 0, +}; + +const DataLength = packed struct(u32) { + length: u16, + _reserved_31_16: u16, +}; + +const Ltoh = packed struct(u32) { + toh: u4, + _reserved_31_4: u28, +}; + +const Dc = packed struct(u32) { + dma_write_request_threshold: u8, + dma_write_panic_threshold: u8, + dma_read_request_threshold: u8, + dma_read_panic_threshold: u8, +}; + +// Polled: +// 1. Set CS(MasterControlStatus), CPOL(chip_select_polarity), CPHA(clock_phase) +// as required and set TA(transfer_active) = 1 +// 2. Poll TXD(txd_has_space) writing bytes to SPI_FIFO(FiFo), RXD(rxd_contains_data) +// reading bytes from SPI_FIFO(FiFo) until all data is written +// 3. Poll DONE(transfer_progress) until it goes to 1 +// 4. Set TA(transfer_active) = 0 +pub const SpiConfig = struct { + chip_select: MasterControlStatus.ChipSelect, + chip_select_polarity: MasterControlStatus.ChipSelectPolarity = .ActiveLow, + clock_polarity: MasterControlStatus.ClockPolarity = .Low, + clock_phase: MasterControlStatus.ClockPhase = .Middle, + pins: enum(u8) { + Lower = 7, + Upper = 35, + }, +}; + +pub fn init(config: *const SpiConfig, clock: u16) void { + const ce1 = @intFromEnum(config.pins); + gpio.fn_sel(ce1, .alt_fn_0) catch {}; + gpio.set_pull(ce1, .Off) catch {}; + + gpio.fn_sel(ce1 + 1, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 1, .Off) catch {}; + + gpio.fn_sel(ce1 + 2, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 2, .Off) catch {}; + + gpio.fn_sel(ce1 + 3, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 3, .Off) catch {}; + + gpio.fn_sel(ce1 + 4, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 4, .Off) catch {}; + + // Set Clock + CLK.set(.{ + .clock_divider = clock, + }); +} + +pub fn get_base_config(config: *const SpiConfig) MasterControlStatus { + var cs: MasterControlStatus = .{}; + + cs.chip_select = config.chip_select; + cs.chip_select_polarity = config.chip_select_polarity; + cs.clock_polarity = config.clock_polarity; + cs.clock_phase = config.clock_phase; + + switch (config.chip_select) { + .@"0" => cs.chip_select_0 = config.chip_select_polarity, + .@"1" => cs.chip_select_1 = config.chip_select_polarity, + .@"2" => cs.chip_select_2 = config.chip_select_polarity, + } + + return cs; +} + +fn clear_fifo(config: *const SpiConfig) void { + var base = get_base_config(config); + base.clear_fifo = .{ .clear_rx = true, .clear_tx = true }; + CS.set(base); +} + +fn start_transfer(config: *const SpiConfig) void { + CS.set(get_base_config(config)); + clear_fifo(config); + var base = get_base_config(config); + base.transfer_active = true; + CS.set(base); +} + +fn end_transfer(config: *const SpiConfig) void { + var base = get_base_config(config); + base.transfer_active = false; + CS.set(base); +} + +pub fn transfer(config: *const SpiConfig, src: []const u8, dst: []u8) void { + start_transfer(config); + + for (0..@max(src.len, dst.len)) |i| { + while (!CS.get().txd_has_space) {} + + if (i < src.len) { + FIFO.set(@as(u32, src[i])); + } else { + FIFO.set(0x00); + } + + while (!CS.get().rxd_contains_data) {} + + if (i < dst.len) { + dst[i] = @truncate(FIFO.get()); + } else { + _ = FIFO.get(); + } + } + + while (!CS.get().done) {} + + end_transfer(config); +} diff --git a/pi/register.zig b/pi/register.zig @@ -1,6 +1,7 @@ // Referenced based off of Swift MMIO const std = @import("std"); +const mem = @import("./mem.zig"); const Error = error{OutOfRange}; const Kind = enum { @@ -20,11 +21,11 @@ pub fn Register(comptime T: type, comptime kind: Kind) type { const Self = @This(); inline fn get(addr_ptr: *volatile usize) T { - return @bitCast(addr_ptr.*); + return @bitCast(mem.get_u32(@volatileCast(addr_ptr))); } inline fn set(addr_ptr: *volatile usize, value: T) void { - addr_ptr.* = @bitCast(value); + return mem.put_u32(@volatileCast(addr_ptr), @bitCast(value)); } }; diff --git a/pi/root.zig b/pi/root.zig @@ -24,6 +24,9 @@ pub const devices = struct { pub const timer = @import("./devices/timer.zig"); pub const mailbox = @import("./devices/mailbox.zig"); pub const sd = @import("./devices/sd.zig"); + pub const spi = @import("./devices/spi.zig"); + pub const mini_spi = @import("./devices/mini-spi.zig"); + pub const nrf = @import("./devices/nrf.zig"); }; pub const fs = struct { diff --git a/programs/nrf-client.zig b/programs/nrf-client.zig @@ -0,0 +1,66 @@ +const std = @import("std"); +const shared = @import("shared"); +const pi = @import("pi"); + +const nrf = pi.devices.nrf; +const clock = pi.devices.clock; +const uart = pi.devices.mini_uart; + +fn wait_for_ans(client_dev: *nrf.Device) !u8 { + var buffer: [4]u8 = undefined; + + while (true) { + const bytes_collected = client_dev.receive(&buffer) catch |err| { + if (err == nrf.Error.NotReady) continue; + return err; + }; + if (bytes_collected == 4) break; + if (bytes_collected > 0) { + uart.print("read: {d}\n", .{bytes_collected}); + } + + clock.delay_ms(1); + } + + return buffer[3]; +} + +// Server: +// CS - 8 +// CE - 6 +// +// Client: +// CS - 7 +// CE - 5 +pub fn main() !void { + uart.print("creating client\n", .{}); + + const client_addr = 0xE5E5E5E5E5; + + var client_dev: nrf.Device = try .init(.{ + .spi_config = .{ + .chip_select = .@"1", + .pins = .Lower, + }, + .ce_pin = 5, + .int_pin = 22, + .payload_size = 4, + }); + uart.print("initialized client\n", .{}); + + try client_dev.set_rx_addr(0, client_addr); + client_dev.connect(); + + while (true) { + const b = try wait_for_ans(&client_dev); + + uart.print("got byte: {c}\n", .{b}); + + clock.delay_ms(10); + + if (b == '\r') break; + } + + uart.print("done!\n", .{}); + uart.flush(); +} diff --git a/programs/nrf-server.zig b/programs/nrf-server.zig @@ -0,0 +1,53 @@ +const std = @import("std"); +const shared = @import("shared"); +const pi = @import("pi"); + +const nrf = pi.devices.nrf; +const clock = pi.devices.clock; +const uart = pi.devices.mini_uart; + +// Server: +// CS - 8 +// CE - 6 +// +// Client: +// CS - 7 +// CE - 5 +pub fn main() !void { + uart.print("creating server\n", .{}); + + const client_addr = 0xE5E5E5E5E5; + + var server_dev: nrf.Device = try .init(.{ + .spi_config = .{ + .chip_select = .@"0", + .pins = .Lower, + }, + .ce_pin = 6, + .int_pin = 23, + .payload_size = 4, + }); + uart.print("initialized server\n", .{}); + + server_dev.set_tx_addr(client_addr); + try server_dev.set_rx_addr(0, client_addr); + + const msg = "Hello, World!\r"; + + for (msg) |b| { + uart.print("Sending {c}: ", .{b}); + + var buffer: [4]u8 = .{ 0, 0, 0, b }; + if (try server_dev.transmit(&buffer, 1000)) { + uart.print("transmitted\n", .{}); + } else { + uart.print("server failed to transmit\n", .{}); + continue; + } + + clock.delay_ms(10); + } + + uart.print("done!\n", .{}); + uart.flush(); +} diff --git a/programs/nrf.zig b/programs/nrf.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const shared = @import("shared"); +const pi = @import("pi"); + +const nrf = pi.devices.nrf; +const uart = pi.devices.mini_uart; + +// Server: +// CS - 8 +// CE - 6 +// +// Client: +// CS - 7 +// CE - 5 +pub fn main() !void { + uart.print("creating client/server\n", .{}); + + const client_addr = 0xE5E5E5E5E5; + // const server_addr = 0xD5D5D5; + + var client_dev: nrf.Device = try .init(.{ + .spi_config = .{ + .chip_select = .@"1", + .pins = .Lower, + }, + .ce_pin = 5, + .int_pin = 22, + .payload_size = 4, + }); + uart.print("initialized client\n", .{}); + + var server_dev: nrf.Device = try .init(.{ + .spi_config = .{ + .chip_select = .@"0", + .pins = .Lower, + }, + .ce_pin = 6, + .int_pin = 23, + .payload_size = 4, + }); + uart.print("initialized server\n", .{}); + + uart.print("running tests\n", .{}); + server_dev.set_tx_addr(client_addr); + try server_dev.set_rx_addr(0, client_addr); + try client_dev.set_rx_addr(0, client_addr); + client_dev.connect(); + + for (0..1000) |i| { + uart.print("trial {}: ", .{i}); + + { + var buffer: [4]u8 = undefined; + std.mem.writeInt(u32, &buffer, i, .little); + if (!try server_dev.transmit(&buffer, 128)) { + uart.print("server failed to transmit\n", .{}); + continue; + } + } + + { + var buffer: [4]u8 = undefined; + if (try client_dev.receive(&buffer) > 0) { + const x = std.mem.readInt(u32, &buffer, .little); + + if (x == i) { + uart.print("{} == {}\n", .{ x, i }); + } else { + uart.print("{} != {}\n", .{ x, i }); + } + } else { + uart.print("timed out\n", .{}); + } + } + } + + uart.print("done!\n", .{}); +}