mod config;
mod registers;
pub use config::{
Nrf24AddressWidth, Nrf24Channel, Nrf24Config, Nrf24CrcMode, Nrf24DataRate, Nrf24Power,
};
pub use registers::Register;
use crate::error::{Error, Result};
use crate::rtps::Locator;
use crate::transport::Transport;
pub const MAX_PAYLOAD_SIZE: usize = 32;
pub const DEFAULT_ADDRESS: [u8; 5] = [0xE7, 0xE7, 0xE7, 0xE7, 0xE7];
pub trait SpiDevice {
fn transfer(&mut self, data: &mut [u8]) -> Result<()>;
}
pub trait CePin {
fn set_high(&mut self);
fn set_low(&mut self);
}
pub struct Nrf24<SPI: SpiDevice, CE: CePin> {
spi: SPI,
ce: CE,
config: Nrf24Config,
tx_address: [u8; 5],
rx_address: [u8; 5],
mode: Nrf24Mode,
last_rssi: Option<i16>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Nrf24Mode {
#[default]
PowerDown,
Standby,
Rx,
Tx,
}
impl<SPI: SpiDevice, CE: CePin> Nrf24<SPI, CE> {
pub fn new(spi: SPI, ce: CE, config: Nrf24Config) -> Self {
Self {
spi,
ce,
config,
tx_address: DEFAULT_ADDRESS,
rx_address: DEFAULT_ADDRESS,
mode: Nrf24Mode::PowerDown,
last_rssi: None,
}
}
pub fn init(&mut self) -> Result<()> {
self.ce.set_low();
for _ in 0..10 {
let _ = self.read_register(Register::CONFIG);
}
self.flush_tx()?;
self.flush_rx()?;
self.write_register(Register::STATUS, 0x70)?;
self.write_register(Register::SETUP_AW, self.config.address_width.to_register())?;
self.write_register(Register::RF_CH, self.config.channel.0)?;
let rf_setup = self.config.data_rate.to_register() | self.config.power.to_register();
self.write_register(Register::RF_SETUP, rf_setup)?;
let setup_retr = (self.config.retry_delay << 4) | (self.config.retry_count & 0x0F);
self.write_register(Register::SETUP_RETR, setup_retr)?;
self.write_register(Register::EN_AA, 0x01)?;
self.write_register(Register::EN_RXADDR, 0x01)?;
self.write_register(Register::RX_PW_P0, MAX_PAYLOAD_SIZE as u8)?;
let config_reg = self.config.crc.to_register() | 0x02; self.write_register(Register::CONFIG, config_reg)?;
self.set_tx_address(&self.tx_address.clone())?;
self.set_rx_address(0, &self.rx_address.clone())?;
self.mode = Nrf24Mode::Standby;
Ok(())
}
pub fn set_tx_address(&mut self, address: &[u8; 5]) -> Result<()> {
self.tx_address = *address;
self.write_register_multi(Register::TX_ADDR, address)?;
self.write_register_multi(Register::RX_ADDR_P0, address)?;
Ok(())
}
pub fn set_rx_address(&mut self, pipe: u8, address: &[u8; 5]) -> Result<()> {
if pipe > 5 {
return Err(Error::InvalidParameter);
}
if pipe == 0 {
self.rx_address = *address;
self.write_register_multi(Register::RX_ADDR_P0, address)?;
} else if pipe == 1 {
self.write_register_multi(Register::RX_ADDR_P1, address)?;
} else {
let reg = match pipe {
2 => Register::RX_ADDR_P2,
3 => Register::RX_ADDR_P3,
4 => Register::RX_ADDR_P4,
5 => Register::RX_ADDR_P5,
_ => unreachable!(),
};
self.write_register(reg, address[0])?;
}
Ok(())
}
pub fn start_listening(&mut self) -> Result<()> {
let config = self.read_register(Register::CONFIG)?;
self.write_register(Register::CONFIG, config | 0x03)?;
self.write_register(Register::STATUS, 0x70)?;
self.flush_rx()?;
self.ce.set_high();
self.mode = Nrf24Mode::Rx;
Ok(())
}
pub fn stop_listening(&mut self) -> Result<()> {
self.ce.set_low();
let config = self.read_register(Register::CONFIG)?;
self.write_register(Register::CONFIG, config & !0x01)?;
self.mode = Nrf24Mode::Standby;
Ok(())
}
pub fn data_available(&mut self) -> Result<bool> {
let status = self.read_register(Register::STATUS)?;
let rx_empty = self.read_register(Register::FIFO_STATUS)? & 0x01;
Ok((status & 0x40) != 0 || rx_empty == 0)
}
pub fn read_payload(&mut self, buf: &mut [u8]) -> Result<usize> {
if buf.len() < MAX_PAYLOAD_SIZE {
return Err(Error::BufferTooSmall);
}
let mut cmd = [0u8; 33];
cmd[0] = 0x61; self.spi.transfer(&mut cmd)?;
buf[..MAX_PAYLOAD_SIZE].copy_from_slice(&cmd[1..33]);
self.write_register(Register::STATUS, 0x40)?;
Ok(MAX_PAYLOAD_SIZE)
}
pub fn send_payload(&mut self, data: &[u8]) -> Result<bool> {
if data.len() > MAX_PAYLOAD_SIZE {
return Err(Error::InvalidParameter);
}
if self.mode == Nrf24Mode::Rx {
self.stop_listening()?;
}
self.flush_tx()?;
let mut cmd = [0u8; 33];
cmd[0] = 0xA0; cmd[1..1 + data.len()].copy_from_slice(data);
self.spi.transfer(&mut cmd)?;
self.ce.set_high();
for _ in 0..100 {
core::hint::spin_loop();
}
self.ce.set_low();
let mut attempts = 0;
loop {
let status = self.read_register(Register::STATUS)?;
if (status & 0x20) != 0 {
self.write_register(Register::STATUS, 0x20)?;
let observe = self.read_register(Register::OBSERVE_TX)?;
let retries = observe & 0x0F;
self.last_rssi = Some(-50 - (retries as i16 * 5));
return Ok(true);
}
if (status & 0x10) != 0 {
self.write_register(Register::STATUS, 0x10)?;
self.flush_tx()?;
self.last_rssi = Some(-100);
return Ok(false);
}
attempts += 1;
if attempts > 1000 {
self.flush_tx()?;
return Err(Error::Timeout);
}
}
}
pub fn flush_tx(&mut self) -> Result<()> {
let mut cmd = [0xE1u8];
self.spi.transfer(&mut cmd)?;
Ok(())
}
pub fn flush_rx(&mut self) -> Result<()> {
let mut cmd = [0xE2u8];
self.spi.transfer(&mut cmd)?;
Ok(())
}
pub fn power_down(&mut self) -> Result<()> {
self.ce.set_low();
let config = self.read_register(Register::CONFIG)?;
self.write_register(Register::CONFIG, config & !0x02)?;
self.mode = Nrf24Mode::PowerDown;
Ok(())
}
fn read_register(&mut self, reg: Register) -> Result<u8> {
let mut buf = [reg as u8, 0];
self.spi.transfer(&mut buf)?;
Ok(buf[1])
}
fn write_register(&mut self, reg: Register, value: u8) -> Result<()> {
let mut buf = [0x20 | (reg as u8), value];
self.spi.transfer(&mut buf)?;
Ok(())
}
fn write_register_multi(&mut self, reg: Register, data: &[u8]) -> Result<()> {
let mut buf = [0u8; 6];
buf[0] = 0x20 | (reg as u8);
let len = data.len().min(5);
buf[1..1 + len].copy_from_slice(&data[..len]);
self.spi.transfer(&mut buf[..1 + len])?;
Ok(())
}
pub fn mode(&self) -> Nrf24Mode {
self.mode
}
pub fn config(&self) -> &Nrf24Config {
&self.config
}
pub fn last_rssi(&self) -> Option<i16> {
self.last_rssi
}
}
pub struct Nrf24Transport<SPI: SpiDevice, CE: CePin> {
radio: Nrf24<SPI, CE>,
local_locator: Locator,
#[allow(dead_code)]
rx_buf: [u8; MAX_PAYLOAD_SIZE],
#[allow(dead_code)]
rx_len: usize,
}
impl<SPI: SpiDevice, CE: CePin> Nrf24Transport<SPI, CE> {
pub fn new(spi: SPI, ce: CE, config: Nrf24Config) -> Self {
Self {
radio: Nrf24::new(spi, ce, config),
local_locator: Locator::udpv4([0, 0, 0, 1], 2400), rx_buf: [0u8; MAX_PAYLOAD_SIZE],
rx_len: 0,
}
}
pub fn radio_mut(&mut self) -> &mut Nrf24<SPI, CE> {
&mut self.radio
}
pub fn radio(&self) -> &Nrf24<SPI, CE> {
&self.radio
}
}
impl<SPI: SpiDevice, CE: CePin> Transport for Nrf24Transport<SPI, CE> {
fn init(&mut self) -> Result<()> {
self.radio.init()?;
self.radio.start_listening()?;
Ok(())
}
fn send(&mut self, data: &[u8], _dest: &Locator) -> Result<usize> {
if data.len() > MAX_PAYLOAD_SIZE {
return Err(Error::InvalidParameter);
}
let mut payload = [0u8; MAX_PAYLOAD_SIZE];
payload[..data.len()].copy_from_slice(data);
let success = self.radio.send_payload(&payload)?;
self.radio.start_listening()?;
if success {
Ok(data.len())
} else {
Err(Error::TransportError)
}
}
fn recv(&mut self, buf: &mut [u8]) -> Result<(usize, Locator)> {
loop {
if self.radio.data_available()? {
let len = self.radio.read_payload(buf)?;
return Ok((len, self.local_locator));
}
}
}
fn try_recv(&mut self, buf: &mut [u8]) -> Result<(usize, Locator)> {
if self.radio.data_available()? {
let len = self.radio.read_payload(buf)?;
Ok((len, self.local_locator))
} else {
Err(Error::ResourceExhausted)
}
}
fn local_locator(&self) -> Locator {
self.local_locator
}
fn mtu(&self) -> usize {
MAX_PAYLOAD_SIZE
}
fn last_rssi(&self) -> Option<i16> {
self.radio.last_rssi()
}
fn shutdown(&mut self) -> Result<()> {
self.radio.power_down()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockSpi {
registers: [u8; 32],
}
impl MockSpi {
fn new() -> Self {
Self { registers: [0; 32] }
}
}
impl SpiDevice for MockSpi {
fn transfer(&mut self, data: &mut [u8]) -> Result<()> {
if data.is_empty() {
return Ok(());
}
let cmd = data[0];
if cmd < 0x20 {
let reg = cmd as usize;
if reg < 32 && data.len() > 1 {
data[1] = self.registers[reg];
}
} else if cmd < 0x40 {
let reg = (cmd & 0x1F) as usize;
if reg < 32 && data.len() > 1 {
self.registers[reg] = data[1];
}
}
Ok(())
}
}
struct MockCe {
high: bool,
}
impl MockCe {
fn new() -> Self {
Self { high: false }
}
}
impl CePin for MockCe {
fn set_high(&mut self) {
self.high = true;
}
fn set_low(&mut self) {
self.high = false;
}
}
#[test]
fn test_nrf24_creation() {
let spi = MockSpi::new();
let ce = MockCe::new();
let config = Nrf24Config::default();
let radio = Nrf24::new(spi, ce, config);
assert_eq!(radio.mode(), Nrf24Mode::PowerDown);
}
#[test]
fn test_nrf24_init() {
let spi = MockSpi::new();
let ce = MockCe::new();
let config = Nrf24Config::default();
let mut radio = Nrf24::new(spi, ce, config);
radio.init().unwrap();
assert_eq!(radio.mode(), Nrf24Mode::Standby);
}
#[test]
fn test_nrf24_transport_creation() {
let spi = MockSpi::new();
let ce = MockCe::new();
let config = Nrf24Config::default();
let transport = Nrf24Transport::new(spi, ce, config);
assert_eq!(transport.mtu(), MAX_PAYLOAD_SIZE);
}
#[test]
fn test_nrf24_config_defaults() {
let config = Nrf24Config::default();
assert_eq!(config.channel.0, 76);
assert_eq!(config.data_rate, Nrf24DataRate::Rate1Mbps);
assert_eq!(config.power, Nrf24Power::Max);
}
#[test]
fn test_nrf24_mode_transitions() {
let spi = MockSpi::new();
let ce = MockCe::new();
let config = Nrf24Config::default();
let mut radio = Nrf24::new(spi, ce, config);
assert_eq!(radio.mode(), Nrf24Mode::PowerDown);
radio.init().unwrap();
assert_eq!(radio.mode(), Nrf24Mode::Standby);
radio.start_listening().unwrap();
assert_eq!(radio.mode(), Nrf24Mode::Rx);
radio.stop_listening().unwrap();
assert_eq!(radio.mode(), Nrf24Mode::Standby);
radio.power_down().unwrap();
assert_eq!(radio.mode(), Nrf24Mode::PowerDown);
}
}