README.md

# Nrf24

Elixir library for receiving and sending data through the Nordic
nRF24L01+ 2.4GHz wireless transceiver module.

The library wraps Circuits.SPI and Circuits.GPIO to handle common
nRF24L01+ configuration (addresses, channels, payload sizes, ACK/CRC,
retransmits) and runtime operations (listening for RX and sending TX
payloads with CE control). It defaults to Enhanced ShockBurst with
auto-acknowledgement (ACK) and CRC enabled.

This README includes wiring guidance, setup instructions, and
step-by-step examples for Raspberry Pi and Arduino
interoperability. It also includes a brief advanced section for using
the low-level Nrf24.Transciever API directly.

Important electrical notes:
- The nRF24L01+ is a 3.3V device. Do not power it from 5V and ensure
  all logic lines are 3.3V.
- Place a decoupling capacitor (e.g., 10–47 µF electrolytic + 100 nF
  ceramic) as close as possible to the module’s VCC/GND pins to avoid
  brownouts and flaky behavior.

## Pinout

![nRF24L01 Pinout](./assets/nRF24L01-pinout.png "nRF24L01 Pinout")

## Raspberry Pi wiring

Typical Raspberry Pi (BCM numbering) wiring:

| nRF24L01+ | Raspberry Pi (GPIO) | RPi 3B+ pin no. |
|----------:|--------------------:|----------------:|
|       GND |                 GND |               6 |
|       VCC |                3.3V |               1 |
|        CE |                  17 |              11 |
|       CSN |                   8 |              24 |
|       SCK |                  11 |              23 |
|      MOSI |                  10 |              19 |
|      MISO |                   9 |              21 |

Notes:
- CE (chip enable) must be a GPIO you control from software (e.g.,
  GPIO17).
- CSN (chip select) can be wired to a GPIO. This library asserts CSN
  via GPIO during TX payload writes. Using GPIO8 (SPI0 CE0) is common;
  it is both the default SPI chip-select and an addressable GPIO. If
  your platform does not allow toggling the hardware CS as a normal
  GPIO, wire CSN to an alternate free GPIO instead and use that GPIO
  number as csn_pin.
- SPI signals use SPI0 by default: SCK = GPIO11, MOSI = GPIO10, MISO =
  GPIO9.

Raspberry Pi 3B+ pinout reference:

![Raspberry PI 3B+ Pinout](./assets/raspberry-pi-3-b-plus-pinout.png "Raspberry PI 3 B+ Pinout")

## Arduino

### Wiring

The library was tested with a second nRF24L01+ module connected to an
Arduino Nano:

| nRF24L01+ | Arduino |
|----------:|--------:|
|       GND |     GND |
|       VCC |    +3V3 |
|        CE |      D9 |
|       CSN |     D10 |
|       SCK |     D13 |
|      MOSI |     D11 |
|      MISO |     D12 |

![Arduino Nano Pinout](./assets/arduino-nano-pinout.png "Arduino Nano Pinout")

### Arduino library

Use the RF24 Arduino library and the GettingStarted example for a
quick peer to the Elixir side: https://nrf24.github.io/RF24/

Ensure both sides use:
- The same 5-byte address for TX/RX pipe 0 when auto-ack is enabled.
- The same channel and data rate.
- Matching payload sizes (unless using dynamic payloads).

## Prerequisites

- Linux SBC (e.g., Raspberry Pi) with SPI enabled.
- Elixir and Mix installed.
- SPI and GPIO accessible to the running user:
  - On Raspberry Pi OS:
    - Enable SPI: sudo raspi-config → Interface Options → SPI → Enable
    - Optional: add your user to groups: sudo usermod -aG spi,gpio $(whoami)
- Dependencies: Circuits.SPI, Circuits.GPIO (transitive via this package).

Selecting an SPI bus:
- Circuits.SPI enumerates available buses; on Raspberry Pi you’ll
  usually see ["spidev0.0", "spidev0.1"].
- spidev0.0 often corresponds to CE0, spidev0.1 to CE1.
- You can list them in IEx: Circuits.SPI.bus_names()

Circuits.SPI basics (relevant to this library):
- SPI is full-duplex: for every bit clocked out, one is clocked in.
- Circuits.SPI.transfer/2 sends a binary and returns {:ok,
  response_binary}, where the response is exactly the same size as the
  request.
- To read N bytes from a device register, the host sends a one-byte
  READ command followed by N dummy bytes (0x00 or 0xFF). The first
  received byte is typically a STATUS byte, followed by the requested
  N bytes.

This library opens the SPI bus using Circuits.SPI.open(bus_name) with
default options (SPI mode 0). nRF24L01+ requires SPI mode 0 (CPOL=0,
CPHA=0).

## Installation

Add nrf24 to your deps in mix.exs:

```elixir
def deps do
  [
    {:nrf24, "~> 2.0.0"}
  ]
end
```

Fetch deps:

```shell
mix deps.get
```

## Quick start

The Nrf24 module is a GenServer that owns the SPI handle and knows
your CE/CSN GPIOs. It provides convenience functions for common
operations.

Addresses and payload sizes:
- Pipe 0 and 1 use 5-byte addresses (e.g., "1Node",
  <<0xE7,0xE7,0xE7,0xE7,0xE7>>).
- Pipes 2..5 override only the least-significant byte of pipe 1’s
  address and take a 1-byte suffix (integer 0..255).
- Payload is 1..32 bytes unless dynamic payloads are enabled (not
  enabled by default here).

Data rate and channel must match on both peers. The examples below
use:
- Channel 0x4C (76 decimal) — free to change, but keep both ends the
  same.
- Speed :medium (1 Mbps). This setting is commonly robust with the
  nRF24L01+ modules and wiring found in hobby setups.

### Receiving data

```elixir
{:ok, nrf} =
  Nrf24.start_link(
    bus_name: "spidev0.0",
    ce_pin: 17,   # GPIO17 for CE
    csn_pin: 8,   # GPIO8 for CSN (can be any GPIO you wired to CSN)
    channel: 0x4C,
    crc_length: 2,
    speed: :medium,
    pipes: [
      [pipe_no: 0, address: "1Node", payload_size: 4, auto_ack: true]
    ]
  )

# Put the radio into RX and assert CE
:ok = Nrf24.start_listening(nrf)

# Block up to ~30s waiting for one payload (default timeout in library)
case Nrf24.receive(nrf, 4) do
  {:ok, %{pipe: pipe_no, data: <<a, b, c, d>>}} ->
    IO.puts("Received on pipe #{pipe_no}: #{inspect({a, b, c, d})}")

  {:error, :no_data} ->
    IO.puts("No data received within timeout")

  {:error, reason} ->
    IO.puts("Receive error: #{inspect(reason)}")
end

# Deassert CE and power down
:ok = Nrf24.stop_listening(nrf)
```

Tips:
- Ensure the sender is using the same channel, data rate, and the
  receiver’s pipe 0 address matches the sender’s TX/RX_P0 address when
  auto-ack is enabled.
- The payload_size passed to Nrf24.receive/2 must match the pipe’s
  configured RX_PW_Px value (unless using dynamic payloads).

### Sending data

```elixir
{:ok, nrf} =
  Nrf24.start_link(
    bus_name: "spidev0.0",
    ce_pin: 17,
    csn_pin: 8,
    channel: 0x4C,
    crc_length: 2,
    speed: :medium
  )

# 4-byte little-endian float as an example payload
data = <<9273.69::float-little-size(32)>>

# Send asynchronously to receiver address (must be 5 bytes)
# When auto-ack is on, TX_ADDR must equal the receiver's RX_ADDR_P0
Nrf24.send(nrf, "2Node", data)
```

Notes:
- send/3 is asynchronous and returns immediately. The library asserts
  CE to trigger TX and deasserts it shortly after automatically.
- Max payload is 32 bytes.
- For reliable ACKs, ensure the receiver has pipe 0 enabled with the
  same 5-byte address and that both peers share channel and data rate.

## API overview

Common operations:

```elixir
# Change RF channel (0..125 typical)
{:ok, _} = Nrf24.set_channel(nrf, 76)

# CRC length: 1 or 2 bytes
{:ok, _} = Nrf24.set_crc_length(nrf, 2)

# Power management
{:ok, _} = Nrf24.power_on(nrf)
{:ok, _} = Nrf24.power_off(nrf)

# RX/TX mode
{:ok, _} = Nrf24.set_receive_mode(nrf)
{:ok, _} = Nrf24.set_transmit_mode(nrf)

# Enable/disable auto-ack per pipe (0..5)
{:ok, _} = Nrf24.ack_on(nrf, 0)
{:ok, _} = Nrf24.ack_off(nrf, 0)

# Enable/disable a pipe
{:ok, _} = Nrf24.enable_pipe(nrf, 0)
{:ok, _} = Nrf24.disable_pipe(nrf, 0)

# Configure a pipe in one go
{:ok, nil} =
  Nrf24.set_pipe(nrf, 1,
    address: "Rcvr1",
    payload_size: 6,
    auto_acknowledgement: true
  )

# Retransmit tuning
{:ok, _} = Nrf24.set_retransmit_delay(nrf, 2)  # 2->750µs (see datasheet mapping)
{:ok, _} = Nrf24.set_retransmit_count(nrf, 15) # up to 15 retries

# Low-level register access
{:ok, _} = Nrf24.write_register(nrf, :rf_ch, 76)
val = Nrf24.read_register(nrf, :rf_ch)
addr_p1 = Nrf24.read_register(nrf, :rx_addr_p1, 5)

# Reset device to a known baseline configuration
{:ok, _} = Nrf24.reset_device(nrf)
```

Notes on speed:
- The RF data rate setting is library-dependent. The examples use
  speed: :medium (1 Mbps), which is the most common and robust. Other
  speed atoms may vary by version. If unsure, prefer :medium.

## Advanced: using the low-level Transceiver API

If you want full control or to script SPI operations directly in IEx,
use Nrf24.Transciever.

Example (TX) using direct SPI:

```elixir
alias Circuits.SPI
alias Circuits.GPIO
alias Nrf24.Transciever

# Choose your SPI bus from Circuits.SPI.bus_names()
{:ok, spi} = SPI.open("spidev0.0")

# Configure
Transciever.reset(spi)  # Optional: baseline
Transciever.set_channel(spi, 76)
Transciever.set_crc_length(spi, 2)
# Data rate configuration is library-dependent;
# :medium is a safe default path via Nrf24 GenServer.

# Prepare TX: sets TX mode, programs TX_ADDR and RX_ADDR_P0
# to same 5-byte value, enables ACK on P0, clears IRQ, powers up
Transciever.start_sending(spi, "2Node")

# Send: CSN is toggled via a GPIO you supply, CE toggled via another GPIO
csn_pin = 8
ce_pin = 17
payload = <<"Hi!">>
{:ok, ce} = Transciever.send(spi, payload, csn_pin, ce_pin)

# Finish TX
Transciever.stop_sending(ce)
```

Example (RX) using direct SPI:

```elixir
alias Circuits.SPI
alias Nrf24.Transciever

{:ok, spi} = SPI.open("spidev0.0")

Transciever.reset(spi) # Optional
Transciever.set_channel(spi, 76)
Transciever.set_pipe(
  spi,
  0,
  address: "1Node",
  payload_size: 4,
  auto_acknowledgement: true)

# Start listening (CE high while in RX mode)
Transciever.start_listening(spi, 17)

# Poll for a single payload (4 bytes)
case Transciever.receive(spi, 4) do
  {:ok, %{pipe: p, data: <<a, b, c, d>>}} ->
    IO.puts("RX pipe #{p}: #{inspect({a, b, c, d})}")

  {:error, :no_data} ->
    IO.puts("No data available")

  {:error, reason} ->
    IO.puts("RX error: #{inspect(reason)}")
end

# Stop listening (CE low, power down)
Transciever.stop_listening(spi, 17)
```

Debug/inspection helpers:
- Transciever.print_details/1 prints a concise summary of the radio
  state (STATUS, addresses, payload widths, CONFIG, EN_AA, EN_RXADDR,
  RF_CH, RF_SETUP, FIFO_STATUS).
- You can also read any register with Transciever.read_register/3.

## Tips and troubleshooting

- Power and decoupling:
  - Use a stable 3.3V rail capable of sourcing the burst current the
    radio needs (at least 100 mA headroom recommended).
  - If communication is unreliable add capacitors near the module
    (e.g., 10–47 µF electrolytic and 100 nF ceramic) soldered directly
    to VCC and GND pins.
- Addressing and ACK:
  - For auto-ack to work, the sender’s TX_ADDR must equal the
    receiver’s RX_ADDR_P0.
  - Ensure the receiver has the corresponding pipe enabled and payload
    width configured.

- Channel and speed:
  - Both peers must use the same channel and RF data rate.
  - 1 Mbps is a good default for reliability with basic wiring.
- CE/CSN:
  - CE must be driven high in RX to receive. In TX, CE high triggers
    transmission after the payload is written.
  - This library asserts CSN via a GPIO when writing the TX
    payload. If CSN is instead wired strictly to a hardware
    chip-select line that’s not available as a GPIO, you may need to
    rework wiring or remove manual CSN control in the code path you
    use.
- SPI bus:
  - Verify SPI devices are present: Circuits.SPI.bus_names()
  - Ensure SPI is enabled in the OS and the running user has
    permissions to /dev/spidevX.Y and GPIO.
- No data conditions:
  - If you get {:error, :no_data}, confirm the peer is actually
    sending to your address/pipe and that CE is asserted in RX.
  - Check STATUS and FIFO registers to diagnose conditions. Using
    Transciever.print_details/1 on a direct SPI handle can be helpful.
- Range and interference:
  - Choose a channel away from congested 2.4 GHz bands (e.g., avoid
    Wi-Fi channels if possible).
  - Consider lower data rates for longer range or noisy environments.