nrf.zig (15059B)
1 // nRF24L01+ 2 3 const std = @import("std"); 4 const spi = @import("./spi.zig"); 5 const gpio = @import("./gpio.zig"); 6 const clock = @import("./clock.zig"); 7 const uart = @import("./mini-uart.zig"); 8 9 // 51: 8.3.1 SPI commands 10 const CMD_R_REGISTER: u8 = 0b0000_0000; 11 const CMD_W_REGISTER: u8 = 0b0010_0000; 12 const CMD_R_RX_PAYLOAD: u8 = 0b0110_0001; 13 const CMD_W_TX_PAYLOAD: u8 = 0b1010_0000; 14 const CMD_FLUSH_TX: u8 = 0b1110_0001; 15 const CMD_FLUSH_RX: u8 = 0b1110_0010; 16 const CMD_REUSE_TX_PL: u8 = 0b1110_0011; 17 const CMD_R_RX_PL_WID: u8 = 0b0110_0000; 18 const CMD_W_ACK_PAYLOAD: u8 = 0b1010_1000; 19 const CMD_W_TX_PAYLOAD_NO_ACK: u8 = 0b1011_0000; 20 const CMD_NOP: u8 = 0b1111_1111; 21 22 // 57: 9.1 Register map table 23 const REG_CONFIG: u5 = 0x00; 24 const nRFConfig = packed struct(u8) { 25 const PrimRx = enum(u1) { 26 PRX = 1, 27 PTX = 0, 28 }; 29 const Crc = enum(u1) { 30 @"1" = 0, 31 @"2" = 1, 32 }; 33 prim_rx: PrimRx = .PTX, 34 pwr_up: bool = false, 35 crc: Crc = .@"1", 36 enable_crc: bool = true, 37 mask_max_rt: bool = false, 38 mask_tx_rs: bool = false, 39 mask_rx_dr: bool = false, 40 _reserved_7: u1 = 0, 41 }; 42 43 // Pipes 44 const Pipes = packed struct(u8) { 45 @"0": bool = true, 46 @"1": bool = true, 47 @"2": bool = true, 48 @"3": bool = true, 49 @"4": bool = true, 50 @"5": bool = true, 51 _reserved_7_6: u2 = 0, 52 }; 53 const REG_EN_AA: u5 = 0x01; 54 const REG_EN_RXADDR: u5 = 0x02; 55 56 const REG_SETUP_AW: u5 = 0x03; 57 const AddressWidths = packed struct(u8) { 58 const Widths = enum(u2) { 59 @"3" = 0b01, 60 @"4" = 0b10, 61 @"5" = 0b11, 62 }; 63 64 width: Widths, 65 _reserved_7_2: u6 = 0, 66 }; 67 68 const REG_SETUP_RETR: u5 = 0x04; 69 const SetupRetry = packed struct(u8) { 70 delay: u4 = 0b0000, 71 attempts: u4 = 0b011, 72 }; 73 74 const REG_RF_CH: u5 = 0x05; 75 const RFChannel = packed struct(u8) { 76 channel: u7 = 0b0000010, 77 _reserved_7: u1 = 0, 78 }; 79 80 const REG_RF_SETUP: u5 = 0x06; 81 const RFSetup = packed struct(u8) { 82 const OutputPower = enum(u2) { 83 @"-18dBm" = 0b00, 84 @"-12dBm" = 0b01, 85 @"-6dBm" = 0b10, 86 @"0dBm" = 0b11, 87 }; 88 const Speed = enum(u2) { 89 @"1Mbps" = 0b00, 90 @"2Mbps" = 0b01, 91 @"250kbps" = 0b10, 92 }; 93 94 _reserved_0: u1 = 0, 95 output_power: OutputPower = .@"0dBm", 96 rf_high: bool = true, 97 pll_lock: bool = false, 98 rf_low: bool = false, 99 _reserved_6: u1 = 0, 100 continuous_transmission: bool = false, 101 }; 102 103 const REG_STATUS: u5 = 0x07; 104 const Status = packed struct(u8) { 105 tx_full: bool = false, 106 rx_pipe_number: u3 = 0b111, 107 maxed_retries: bool = false, 108 tx_ds: bool = false, 109 rx_dr: bool = false, 110 _reserved_7: u1 = 0, 111 }; 112 113 const REG_OBSERVE_TX: u5 = 0x08; 114 const ObserveTx = packed struct(u8) { 115 retransmit_count: u4 = 0, 116 lost_count: u4 = 0, 117 }; 118 119 const REG_RPD: u5 = 0x09; 120 const ReceivedPowerDetector = packed struct(u8) { 121 rpd: bool = false, 122 _reserved_7_1: u7, 123 }; 124 125 const REG_RX_ADDR_P0: u5 = 0x0A; 126 const REG_RX_ADDR_P1: u5 = 0x0B; 127 const REG_RX_ADDR_P2: u5 = 0x0C; 128 const REG_RX_ADDR_P3: u5 = 0x0D; 129 const REG_RX_ADDR_P4: u5 = 0x0E; 130 const REG_RX_ADDR_P5: u5 = 0x0F; 131 const REG_TX_ADDR: u5 = 0x10; 132 133 const RxPayload = packed struct(u8) { 134 bytes: u6 = 0, 135 _reserved_7_6: u2 = 0, 136 }; 137 const REG_RX_PW_P0: u5 = 0x11; 138 const REG_RX_PW_P1: u5 = 0x12; 139 const REG_RX_PW_P2: u5 = 0x13; 140 const REG_RX_PW_P3: u5 = 0x14; 141 const REG_RX_PW_P4: u5 = 0x15; 142 const REG_RX_PW_P5: u5 = 0x16; 143 144 const REG_FIFO_STATUS: u5 = 0x17; 145 const FiFoStatus = packed struct(u8) { 146 rx_empty: bool, 147 rx_full: bool, 148 _reserved_3_2: u2, 149 tx_empty: bool, 150 tx_full: bool, 151 tx_reuse: bool, 152 _reserved_7: u1 = 0, 153 }; 154 155 // Pipes 156 const REG_DYNPD: u5 = 0x1C; 157 158 const REG_FEATURE: u5 = 0x1D; 159 const Feature = packed struct(u8) { 160 enable_dynamic_acknowledge: bool = false, 161 enable_acknowledge_payload: bool = false, 162 enable_dynamic_payload_length: bool = false, 163 _reserved_7_3: u5 = 0, 164 }; 165 166 // Implementation 167 pub const Error = error{ 168 InvalidPipe, 169 InvalidPayload, 170 InvalidPayloadSize, 171 NotReady, 172 PayloadTooLarge, 173 Timeout, 174 }; 175 176 pub const Config = struct { 177 spi_config: spi.SpiConfig, 178 ce_pin: u8, 179 int_pin: u8, 180 181 channel: u7 = 2, 182 output_power: RFSetup.OutputPower = .@"0dBm", 183 data_rate: RFSetup.Speed = .@"1Mbps", 184 crc_length: nRFConfig.Crc = .@"2", 185 auto_retransmit_delay: u4 = 2, // 500us 186 auto_retransmit_count: u4 = 15, 187 payload_size: u6 = 32, 188 }; 189 190 pub const Device = struct { 191 spi_config: spi.SpiConfig, 192 ce_pin: u8, 193 int_pin: u8, 194 195 const Self = @This(); 196 197 pub fn init(config: Config) !Self { 198 var self: Self = .{ 199 .spi_config = config.spi_config, 200 .ce_pin = config.ce_pin, 201 .int_pin = config.int_pin, 202 }; 203 204 self.spi_config.chip_select_polarity = .ActiveLow; 205 self.spi_config.clock_polarity = .Low; 206 self.spi_config.clock_phase = .Middle; 207 spi.init(&self.spi_config, 64); 208 209 try gpio.fn_sel(config.ce_pin, .output); 210 try gpio.set_off(config.ce_pin); 211 212 // Power on reset 100ms 213 clock.delay_ms(100); 214 215 // Configure 216 self.write_register(nRFConfig, REG_CONFIG, .{ 217 .pwr_up = false, 218 .crc = .@"2", 219 .prim_rx = .PTX, 220 }); 221 222 // Setup Auto Acknowledge 223 self.write_register(Pipes, REG_EN_AA, .{ 224 .@"0" = true, 225 .@"1" = true, 226 .@"2" = true, 227 .@"3" = true, 228 .@"4" = true, 229 .@"5" = true, 230 }); 231 232 // Enable Pipes 0/1 233 self.write_register(Pipes, REG_EN_RXADDR, .{ 234 .@"0" = true, 235 .@"1" = true, 236 .@"2" = false, 237 .@"3" = false, 238 .@"4" = false, 239 .@"5" = false, 240 }); 241 242 // Set Address Width 243 self.write_register(AddressWidths, REG_SETUP_AW, .{ 244 .width = .@"5", 245 }); 246 247 // Setup retry 248 self.write_register(SetupRetry, REG_SETUP_RETR, .{ 249 .attempts = config.auto_retransmit_count, 250 .delay = config.auto_retransmit_delay, 251 }); 252 253 // Set channel 254 self.write_register(RFChannel, REG_RF_CH, .{ 255 .channel = config.channel, 256 }); 257 258 // Set data rate and power 259 self.set_rf(config.output_power, config.data_rate); 260 261 // Clear DYNPD & FEATURE 262 self.write_register(u8, REG_FEATURE, 0); 263 self.write_register(u8, REG_DYNPD, 0); 264 265 if (config.payload_size > 32) return Error.InvalidPayloadSize; 266 try self.set_rx_payload_size(0, config.payload_size); 267 try self.set_rx_payload_size(1, config.payload_size); 268 269 // Flush before power up 270 self.flush_tx(); 271 self.flush_rx(); 272 self.clear_irq(); 273 274 // Power on (Standby-I) 275 self.power_up(); 276 277 return self; 278 } 279 280 fn power_up(self: *const Self) void { 281 var cfg = self.read_register(nRFConfig, REG_CONFIG); 282 cfg.pwr_up = true; 283 self.write_register(nRFConfig, REG_CONFIG, cfg); 284 clock.delay_ms(2); 285 } 286 287 fn power_down(self: *const Self) void { 288 var cfg = self.read_register(nRFConfig, REG_CONFIG); 289 cfg.pwr_up = false; 290 self.write_register(nRFConfig, REG_CONFIG, cfg); 291 self.set_ce_low(); 292 } 293 294 fn set_ce_high(self: *const Self) void { 295 gpio.set_on(self.ce_pin) catch {}; 296 } 297 298 fn set_ce_low(self: *const Self) void { 299 gpio.set_off(self.ce_pin) catch {}; 300 } 301 302 fn pulse_ce(self: *const Self) void { 303 self.set_ce_high(); 304 clock.delay(15); 305 self.set_ce_low(); 306 } 307 308 fn set_rf(self: *const Self, power: RFSetup.OutputPower, speed: RFSetup.Speed) void { 309 const rate: u2 = @intFromEnum(speed); 310 const low = (rate & 0b10) == 0b10; 311 const high = (rate & 0b01) == 0b01; 312 313 self.write_register(RFSetup, REG_RF_SETUP, .{ 314 .output_power = power, 315 .rf_low = low, 316 .rf_high = high, 317 }); 318 } 319 320 // u40 = 5 byte addresses 321 pub fn set_tx_addr(self: *const Self, addr: u40) void { 322 var tx_data: [5]u8 = undefined; 323 std.mem.writeInt(u40, &tx_data, addr, .little); 324 self.write_register_n(REG_TX_ADDR, &tx_data); 325 } 326 327 pub fn set_rx_addr(self: *const Self, pipe: u3, addr: u40) !void { 328 if (pipe > 5) return Error.InvalidPipe; 329 330 if (pipe <= 1) { 331 var tx_data: [5]u8 = undefined; 332 std.mem.writeInt(u40, &tx_data, addr, .little); 333 self.write_register_n(REG_RX_ADDR_P0 + pipe, &tx_data); 334 } else { 335 const tx_data = [_]u8{@truncate(addr)}; 336 self.write_register_n(REG_RX_ADDR_P0 + pipe, &tx_data); 337 } 338 } 339 340 fn set_rx_payload_size(self: *const Self, pipe: u3, size: u6) Error!void { 341 if (size > 32) return Error.InvalidPayloadSize; 342 if (pipe > 5) return Error.InvalidPipe; 343 344 self.write_register(RxPayload, REG_RX_PW_P0 + pipe, .{ 345 .bytes = size, 346 }); 347 } 348 349 fn enable_dynamic_payloads(self: *const Self) void { 350 var feature = self.read_register(Feature, REG_FEATURE); 351 feature.enable_dynamic_payload_length = true; 352 self.write_register(Feature, REG_FEATURE, feature); 353 self.write_register(Pipes, REG_DYNPD, .{ 354 .@"0" = true, 355 .@"1" = true, 356 .@"2" = true, 357 .@"3" = true, 358 .@"4" = true, 359 .@"5" = true, 360 }); 361 } 362 363 fn disable_dynamic_payloads(self: *const Self) void { 364 var feature = self.read_register(Feature, REG_FEATURE); 365 feature.enable_dynamic_payload_length = false; 366 self.write_register(Feature, REG_FEATURE, feature); 367 self.write_register(Pipes, REG_DYNPD, .{ 368 .@"0" = false, 369 .@"1" = false, 370 .@"2" = false, 371 .@"3" = false, 372 .@"4" = false, 373 .@"5" = false, 374 }); 375 } 376 377 pub fn connect(self: *const Self) void { 378 var cfg = self.read_register(nRFConfig, REG_CONFIG); 379 cfg.prim_rx = .PRX; 380 self.write_register(nRFConfig, REG_CONFIG, cfg); 381 382 self.clear_irq(); 383 self.flush_rx(); 384 385 self.set_ce_high(); 386 clock.delay(150); 387 } 388 389 pub fn disconnect(self: *const Self) void { 390 self.set_ce_low(); 391 clock.delay(150); 392 393 var cfg = self.read_register(nRFConfig, REG_CONFIG); 394 cfg.prim_rx = .PTX; 395 self.write_register(nRFConfig, REG_CONFIG, cfg); 396 397 self.flush_tx(); 398 } 399 400 pub fn transmit(self: *const Self, payload: []const u8, timeout_ms: u32) Error!bool { 401 if (payload.len == 0 or payload.len > 32) return Error.InvalidPayload; 402 403 self.disconnect(); 404 self.clear_irq(); 405 self.flush_tx(); 406 407 self.write_command(CMD_W_TX_PAYLOAD, payload); 408 409 self.pulse_ce(); 410 411 const deadline = clock.current_count() + std.time.us_per_ms * timeout_ms; 412 while (clock.current_count() < deadline) { 413 const status = self.get_status(); 414 415 if (status.tx_ds) { 416 self.write_register(Status, REG_STATUS, .{ 417 .tx_ds = true, 418 }); 419 420 return true; 421 } 422 423 if (status.maxed_retries) { 424 self.write_register(Status, REG_STATUS, .{ 425 .maxed_retries = true, 426 }); 427 self.flush_tx(); 428 429 return false; 430 } 431 432 clock.delay_ms(1); 433 } 434 435 return Error.Timeout; 436 } 437 438 pub fn receive(self: *const Self, buffer: []u8) Error!usize { 439 const fifo = self.read_register(FiFoStatus, REG_FIFO_STATUS); 440 if (fifo.rx_empty) return 0; 441 442 var width: usize = buffer.len; 443 444 const feature = self.read_register(Feature, REG_FEATURE); 445 if (feature.enable_dynamic_payload_length) { 446 const w = self.read_rx_payload_width(); 447 if (w > 32) { 448 self.flush_rx(); 449 return Error.NotReady; 450 } 451 width = @intCast(w); 452 if (width > buffer.len) return Error.PayloadTooLarge; 453 } 454 455 _ = self.read_command(CMD_R_RX_PAYLOAD, buffer[0..width]); 456 457 self.write_register(Status, REG_STATUS, .{ 458 .rx_dr = true, 459 }); 460 461 return width; 462 } 463 464 fn read_register(self: *const Self, comptime T: type, reg: u5) T { 465 const cmd: u8 = CMD_R_REGISTER | @as(u8, reg); 466 const tx_data = [_]u8{ cmd, CMD_NOP }; 467 var rx_data: [2]u8 = undefined; 468 469 spi.transfer(&self.spi_config, &tx_data, &rx_data); 470 471 return @bitCast(rx_data[1]); 472 } 473 474 fn read_register_n(self: *const Self, reg: u5, buffer: []u8) void { 475 return self.read_command(CMD_R_REGISTER | @as(u8, reg), buffer); 476 } 477 478 fn read_rx_payload_width(self: *const Self) u8 { 479 const tx_data = [_]u8{ CMD_R_RX_PL_WID, CMD_NOP }; 480 var rx_data: [2]u8 = undefined; 481 482 spi.transfer(&self.spi_config, &tx_data, &rx_data); 483 484 return rx_data[1]; 485 } 486 487 fn write_register(self: *const Self, comptime T: type, reg: u5, value: T) void { 488 const cmd: u8 = CMD_W_REGISTER | @as(u8, reg); 489 const value_byte: u8 = @bitCast(value); 490 const tx_data = [_]u8{ cmd, value_byte }; 491 492 var rx_data: [2]u8 = undefined; 493 494 spi.transfer(&self.spi_config, &tx_data, &rx_data); 495 } 496 497 fn write_register_n(self: *const Self, reg: u5, buffer: []const u8) void { 498 return self.write_command(CMD_W_REGISTER | @as(u8, reg), buffer); 499 } 500 501 fn read_command(self: *const Self, cmd: u8, result: []u8) Status { 502 var tx_data: [64]u8 = undefined; 503 var rx_data: [64]u8 = undefined; 504 505 tx_data[0] = cmd; 506 @memset(tx_data[1 .. result.len + 1], CMD_NOP); 507 508 spi.transfer(&self.spi_config, tx_data[0 .. result.len + 1], rx_data[0 .. result.len + 1]); 509 510 @memcpy(result, rx_data[1 .. result.len + 1]); 511 512 return @bitCast(rx_data[0]); 513 } 514 515 fn write_command(self: *const Self, cmd: u8, bytes: []const u8) void { 516 var tx_data: [64]u8 = undefined; 517 var rx_data: [64]u8 = undefined; 518 519 tx_data[0] = cmd; 520 @memcpy(tx_data[1 .. bytes.len + 1], bytes); 521 522 spi.transfer(&self.spi_config, tx_data[0 .. bytes.len + 1], rx_data[0 .. bytes.len + 1]); 523 } 524 525 pub fn get_status(self: *const Self) Status { 526 return self.read_register(Status, REG_STATUS); 527 } 528 529 pub fn flush_tx(self: *const Self) void { 530 const tx_data = [_]u8{CMD_FLUSH_TX}; 531 var rx_data: [1]u8 = undefined; 532 spi.transfer(&self.spi_config, &tx_data, &rx_data); 533 } 534 535 pub fn flush_rx(self: *const Self) void { 536 const tx_data = [_]u8{CMD_FLUSH_RX}; 537 var rx_data: [1]u8 = undefined; 538 spi.transfer(&self.spi_config, &tx_data, &rx_data); 539 } 540 541 pub fn clear_irq(self: *const Self) void { 542 self.write_register(Status, REG_STATUS, .{ 543 .rx_dr = true, 544 .tx_ds = true, 545 .maxed_retries = true, 546 }); 547 } 548 };