sylveos

Toy Operating System
Log | Files | Refs

commit 901c99c6b97dcc5d737a94f18549ab023fa63e62
parent e6946351adfb85d96b5ab34a81fe9c6c234d4411
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu, 12 Mar 2026 14:55:35 -0700

nRF works

Diffstat:
Mpi/devices/mini-spi.zig | 15+++++++++++----
Api/devices/nrf.zig | 548+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpi/devices/spi.zig | 179++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mpi/register.zig | 5+++--
Mpi/root.zig | 1+
Aprograms/nrf.zig | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 726 insertions(+), 100 deletions(-)

diff --git a/pi/devices/mini-spi.zig b/pi/devices/mini-spi.zig @@ -68,7 +68,7 @@ const Control1 = packed struct(u32) { done_irq: bool, tx_empty_irq: bool, cs_high_time: u3, - _reserved_31_11: u11 = 0, + _reserved_31_11: u21 = 0, }; const Status = packed struct(u32) { bit_count: u6, @@ -77,9 +77,10 @@ const Status = packed struct(u32) { rx_full: bool, tx_empty: bool, tx_full: bool, - _reserved_15_5: u16 = 0, + _reserved_15_11: u5 = 0, rx_fill_level: u4, tx_fill_level: u4, + _reserved_31_22: u8, }; const FiFo = packed struct(u32) { data: u16, @@ -196,10 +197,10 @@ pub fn init(device: SpiDevice, config: SpiConfig) void { } pub fn status(device: SpiDevice) Status { - switch (device) { + return switch (device) { .SPI1 => AUX_SPI1_STAT.get(), .SPI2 => AUX_SPI2_STAT.get(), - } + }; } pub fn write_polled(device: SpiDevice, data: []const u8) void { @@ -228,6 +229,12 @@ pub fn read_polled(device: SpiDevice, data: []u8) void { } } +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 = 1, // 500us + auto_retransmit_count: u4 = 3, + 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: *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 @@ -1,11 +1,12 @@ 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(FiFo, .ReadModifyWrite) = .init(BASE_ADDRESS + 0x04); +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); @@ -34,8 +35,8 @@ const MasterControlStatus = packed struct(u32) { }; pub const ChipSelectPolarity = enum(u1) { - SelectActiveLow = 0, - SelectActiveHigh = 1, + ActiveLow = 0, + ActiveHigh = 1, }; pub const Ren = enum(u1) { @@ -45,23 +46,18 @@ const MasterControlStatus = packed struct(u32) { pub const Len = enum(u1) { SPIMaster = 0, - LoSSIMasster = 1, - }; - - pub const TransferProgress = enum(u1) { - InProgress = 0, - Complete = 1, + LoSSIMaster = 1, }; chip_select: ChipSelect = .@"0", clock_phase: ClockPhase = .Middle, clock_polarity: ClockPolarity = .Low, - clear_fifo_clear: FiFoClear = .{ .clear_tx = false, .clear_rx = false }, + 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 = .SelectActiveLow, + chip_select_polarity: ChipSelectPolarity = .ActiveLow, transfer_active: bool = false, dma_enable: bool = false, interrupt_on_done: bool = false, @@ -71,22 +67,17 @@ const MasterControlStatus = packed struct(u32) { len_lossi_enable: Len = .SPIMaster, _unused_14: u1 = 0, _unused_15: u1 = 0, - transfer_progress: TransferProgress = .InProgress, + 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 = .SelectActiveLow, - chip_select_1: ChipSelectPolarity = .SelectActiveLow, - chip_select_2: ChipSelectPolarity = .SelectActiveLow, + 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, -}; - -const FiFo = packed union { - bytes: [@sizeOf(u32) / @sizeOf(u8)]u8, - long: u32, + _reserved_31_26: u6 = 0, }; const Clk = packed struct(u32) { @@ -95,8 +86,8 @@ const Clk = packed struct(u32) { }; const DataLength = packed struct(u32) { - length: u32, - _reserved_31_16: u32, + length: u16, + _reserved_31_16: u16, }; const Ltoh = packed struct(u32) { @@ -118,99 +109,99 @@ const Dc = packed struct(u32) { // 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 { +pub const SpiConfig = struct { chip_select: MasterControlStatus.ChipSelect, - chip_select_polarity: MasterControlStatus.ChipSelectPolarity, - clock_polarity: MasterControlStatus.ClockPolarity, - clock_phase: MasterControlStatus.ClockPhase, + 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 set_config(config: SPIConfig) void { - const cs: MasterControlStatus = .{ - .chip_select = config.chip_select, - .chip_select_polarity = config.chip_select_polarity, - .clock_polarity = config.clock_polarity, - .clock_phase = config.clock_phase, - }; - - switch (config.chip_select) { - .@"0" => cs.chip_select_0 = config.chip_select, - .@"1" => cs.chip_select_1 = config.chip_select, - .@"2" => cs.chip_select_2 = config.chip_select, - } +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 {}; - CS.set(cs); -} + gpio.fn_sel(ce1 + 1, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 1, .Off) catch {}; -pub fn write_polled(bytes: []const u8) void { - { - var cs = CS.get(); - cs.transfer_active = true; - CS.set(cs); - } + gpio.fn_sel(ce1 + 2, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 2, .Off) catch {}; - for (bytes) |byte| { - while (!CS.get().txd_has_space) {} - FIFO.set(@as(u32, byte)); - } + gpio.fn_sel(ce1 + 3, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 3, .Off) catch {}; - // We need to flush before clearing TA - flush(); + gpio.fn_sel(ce1 + 4, .alt_fn_0) catch {}; + gpio.set_pull(ce1 + 4, .Off) catch {}; - { - var cs = CS.get(); - cs.transfer_active = false; - CS.set(cs); - } + // Set Clock + CLK.set(.{ + .clock_divider = clock, + }); } -pub fn read_polled(dst: []u8) void { - for (0..dst.len) |i| { - while (!CS.get().rxd_contains_data) {} - dst[i] = @truncate(FIFO.get()); - } -} +pub fn get_base_config(config: *const SpiConfig) MasterControlStatus { + var cs: MasterControlStatus = .{}; -pub fn flush() void { - while (CS.get().transfer_progress == .InProgress) {} -} + 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; -fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { - // We don't care about getting the "parent" - _ = io_w; - _ = splat; + 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, + } - write_polled(data[0]); + return cs; +} - return data[0].len; +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 writer_flush(io_w: *std.Io.Writer) !void { - // We don't care about getting the "parent" - _ = io_w; +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); +} - flush(); +fn end_transfer(config: *const SpiConfig) void { + var base = get_base_config(config); + base.transfer_active = false; + CS.set(base); } -const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush }; -pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable }; +pub fn transfer(config: *const SpiConfig, src: []const u8, dst: []u8) void { + start_transfer(config); -fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usize { - // We don't care about getting the "parent" - _ = io_r; + for (0..@max(src.len, dst.len)) |i| { + while (!CS.get().txd_has_space) {} - if (limit.toInt()) |max| { - for (0..max) |_| { - var buffer: [1]u8 = .{0}; - read_polled(&buffer); - try io_w.writeByte(buffer[0]); + if (i < src.len) { + FIFO.set(@as(u32, src[i])); + } else { + FIFO.set(0x00); } - return max; - } else { - return std.Io.Reader.StreamError.ReadFailed; + while (!CS.get().rxd_contains_data) {} + + if (i < dst.len) { + dst[i] = @truncate(FIFO.get()); + } else { + _ = FIFO.get(); + } } -} -const reader_vtable: std.Io.Reader.VTable = .{ .stream = stream }; -pub var reader = std.Io.Reader{ .buffer = undefined, .seek = 0, .end = 0, .vtable = &reader_vtable }; + 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 @@ -25,6 +25,7 @@ pub const devices = struct { pub const mailbox = @import("./devices/mailbox.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 export const STACK_ADDRESS: usize = 0x8000000; 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", .{}); +}