commit 901c99c6b97dcc5d737a94f18549ab023fa63e62
parent e6946351adfb85d96b5ab34a81fe9c6c234d4411
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 12 Mar 2026 14:55:35 -0700
nRF works
Diffstat:
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", .{});
+}