DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
nrf24.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file nrf24.cpp
6 * @brief nRF24L01+ driver - implementation.
7 *
8 * The nRF24L01+ command protocol (Nordic datasheet): every SPI transaction is a command
9 * byte followed by data, and the STATUS register is shifted out on the first byte. Register
10 * access is R_REGISTER (0x00 | reg) / W_REGISTER (0x20 | reg); payloads use R_RX_PAYLOAD /
11 * W_TX_PAYLOAD. Host-testable with a mock; the RF link needs the module.
12 */
13
15
16#if DETWS_ENABLE_NRF24
17
18namespace
19{
20// Commands.
21struct Nrf24Cmd
22{
23 static constexpr uint8_t CMD_R_REGISTER = 0x00;
24 static constexpr uint8_t CMD_W_REGISTER = 0x20;
25 static constexpr uint8_t CMD_R_RX_PAYLOAD = 0x61;
26 static constexpr uint8_t CMD_W_TX_PAYLOAD = 0xA0;
27 static constexpr uint8_t CMD_FLUSH_TX = 0xE1;
28 static constexpr uint8_t CMD_FLUSH_RX = 0xE2;
29 static constexpr uint8_t CMD_NOP = 0xFF;
30};
31
32// Registers.
33struct Nrf24Reg
34{
35 static constexpr uint8_t REG_CONFIG = 0x00;
36 static constexpr uint8_t REG_EN_AA = 0x01;
37 static constexpr uint8_t REG_EN_RXADDR = 0x02;
38 static constexpr uint8_t REG_SETUP_AW = 0x03;
39 static constexpr uint8_t REG_SETUP_RETR = 0x04;
40 static constexpr uint8_t REG_RF_CH = 0x05;
41 static constexpr uint8_t REG_RF_SETUP = 0x06;
42 static constexpr uint8_t REG_STATUS = 0x07;
43 static constexpr uint8_t REG_RX_ADDR_P0 = 0x0A;
44 static constexpr uint8_t REG_TX_ADDR = 0x10;
45 static constexpr uint8_t REG_RX_PW_P0 = 0x11;
46};
47
48// CONFIG bits.
49struct Nrf24Cfg
50{
51 static constexpr uint8_t CFG_EN_CRC = 0x08;
52 static constexpr uint8_t CFG_CRCO = 0x04;
53 static constexpr uint8_t CFG_PWR_UP = 0x02;
54 static constexpr uint8_t CFG_PRIM_RX = 0x01;
55};
56
57// STATUS bits.
58struct Nrf24Status
59{
60 static constexpr uint8_t ST_RX_DR = 0x40;
61 static constexpr uint8_t ST_TX_DS = 0x20;
62 static constexpr uint8_t ST_RX_P_NO = 0x0E; // bits 3:1 = pipe of the payload at the RX FIFO head
63};
64
65void reg_write(const nrf_bus *b, uint8_t reg, uint8_t val)
66{
67 uint8_t tx[2] = {(uint8_t)(Nrf24Cmd::CMD_W_REGISTER | reg), val};
68 uint8_t rx[2];
69 b->spi(tx, rx, 2, b->ctx);
70}
71
72uint8_t reg_read(const nrf_bus *b, uint8_t reg)
73{
74 uint8_t tx[2] = {(uint8_t)(Nrf24Cmd::CMD_R_REGISTER | reg), 0xFF};
75 uint8_t rx[2];
76 b->spi(tx, rx, 2, b->ctx);
77 return rx[1];
78}
79
80void reg_write_buf(const nrf_bus *b, uint8_t reg, const uint8_t *buf, uint8_t n)
81{
82 uint8_t tx[6];
83 uint8_t rx[6];
84 tx[0] = (uint8_t)(Nrf24Cmd::CMD_W_REGISTER | reg);
85 for (uint8_t i = 0; i < n; i++)
86 tx[1 + i] = buf[i];
87 b->spi(tx, rx, (uint8_t)(n + 1), b->ctx);
88}
89
90uint8_t status(const nrf_bus *b)
91{
92 uint8_t tx[1] = {Nrf24Cmd::CMD_NOP};
93 uint8_t rx[1];
94 b->spi(tx, rx, 1, b->ctx);
95 return rx[0];
96}
97
98void cmd(const nrf_bus *b, uint8_t c)
99{
100 uint8_t tx[1] = {c};
101 uint8_t rx[1];
102 b->spi(tx, rx, 1, b->ctx);
103}
104} // namespace
105
106bool nrf24_init(const nrf_bus *bus, const nrf_config *cfg)
107{
108 if (!bus || !bus->spi || !bus->ce || !cfg || !cfg->address)
109 return false;
110 bus->ce(false, bus->ctx);
111
112 reg_write(bus, Nrf24Reg::REG_CONFIG, Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO); // power down, 16-bit CRC
113 reg_write(bus, Nrf24Reg::REG_RF_CH, cfg->channel);
114 if (reg_read(bus, Nrf24Reg::REG_RF_CH) != cfg->channel)
115 return false; // written value did not read back -> no chip on the bus
116
117 reg_write(bus, Nrf24Reg::REG_SETUP_AW, 0x03); // 5-byte addresses
118 reg_write(bus, Nrf24Reg::REG_EN_RXADDR, 0x01); // enable pipe 0
119 reg_write(bus, Nrf24Reg::REG_EN_AA, 0x00); // raw mode (no auto-ack)
120 reg_write(bus, Nrf24Reg::REG_SETUP_RETR, 0x00); // no auto-retransmit
121 uint8_t dr = 0x00; // 1 Mbps
122 if (cfg->data_rate == 1)
123 dr = 0x08; // 2 Mbps
124 else if (cfg->data_rate == 2)
125 dr = 0x20; // 250 kbps
126 reg_write(bus, Nrf24Reg::REG_RF_SETUP, (uint8_t)(dr | ((cfg->tx_power & 0x03) << 1)));
127 reg_write(bus, Nrf24Reg::REG_RX_PW_P0, DETWS_NRF24_PAYLOAD);
128 reg_write_buf(bus, Nrf24Reg::REG_RX_ADDR_P0, cfg->address, 5);
129 reg_write_buf(bus, Nrf24Reg::REG_TX_ADDR, cfg->address, 5);
130
131 cmd(bus, Nrf24Cmd::CMD_FLUSH_RX);
132 cmd(bus, Nrf24Cmd::CMD_FLUSH_TX);
133 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_RX_DR | Nrf24Status::ST_TX_DS | 0x10); // clear all flags
134
135 reg_write(bus, Nrf24Reg::REG_CONFIG,
136 Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO | Nrf24Cfg::CFG_PWR_UP); // power up (standby)
137 return true;
138}
139
140bool nrf24_send(const nrf_bus *bus, const uint8_t *data, uint8_t len)
141{
142 if (!bus || !data || len == 0 || len > DETWS_NRF24_PAYLOAD)
143 return false;
144 bus->ce(false, bus->ctx);
145 reg_write(bus, Nrf24Reg::REG_CONFIG,
146 Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO | Nrf24Cfg::CFG_PWR_UP); // PRIM_RX = 0 -> PTX
147
148 uint8_t tx[1 + DETWS_NRF24_PAYLOAD];
149 uint8_t rx[1 + DETWS_NRF24_PAYLOAD];
150 tx[0] = Nrf24Cmd::CMD_W_TX_PAYLOAD;
151 for (uint8_t i = 0; i < DETWS_NRF24_PAYLOAD; i++)
152 tx[1 + i] = (i < len) ? data[i] : 0x00; // zero-pad to the static width
153 bus->spi(tx, rx, (uint8_t)(DETWS_NRF24_PAYLOAD + 1), bus->ctx);
154
155 bus->ce(true, bus->ctx); // key the transmit
156 return true;
157}
158
159bool nrf24_tx_done(const nrf_bus *bus)
160{
161 if (!bus)
162 return false;
163 if (status(bus) & Nrf24Status::ST_TX_DS)
164 {
165 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_TX_DS); // write-1-to-clear
166 return true;
167 }
168 return false;
169}
170
171void nrf24_set_rx(const nrf_bus *bus)
172{
173 if (!bus)
174 return;
175 reg_write(bus, Nrf24Reg::REG_CONFIG,
176 Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO | Nrf24Cfg::CFG_PWR_UP | Nrf24Cfg::CFG_PRIM_RX); // PRX
177 bus->ce(true, bus->ctx);
178}
179
180int nrf24_recv(const nrf_bus *bus, uint8_t *buf, uint8_t cap, uint8_t *pipe)
181{
182 if (!bus || !buf)
183 return -1;
184 uint8_t st = status(bus);
185 if (!(st & Nrf24Status::ST_RX_DR))
186 return -1; // nothing received
187 uint8_t p = (uint8_t)((st & Nrf24Status::ST_RX_P_NO) >> 1);
188 if (p > 5) // 0x07 = RX FIFO empty
189 {
190 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_RX_DR);
191 return -1;
192 }
193 uint8_t tx[1 + DETWS_NRF24_PAYLOAD];
194 uint8_t rx[1 + DETWS_NRF24_PAYLOAD];
195 tx[0] = Nrf24Cmd::CMD_R_RX_PAYLOAD;
196 for (uint8_t i = 0; i < DETWS_NRF24_PAYLOAD; i++)
197 tx[1 + i] = 0xFF;
198 bus->spi(tx, rx, (uint8_t)(DETWS_NRF24_PAYLOAD + 1), bus->ctx);
199
200 uint8_t n = (DETWS_NRF24_PAYLOAD < cap) ? DETWS_NRF24_PAYLOAD : cap;
201 for (uint8_t i = 0; i < n; i++)
202 buf[i] = rx[1 + i];
203 if (pipe)
204 *pipe = p;
205 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_RX_DR); // clear
206 return (int)n;
207}
208
209#endif // DETWS_ENABLE_NRF24
#define DETWS_NRF24_PAYLOAD
nRF24 fixed payload width in bytes (1..32; the chip's static payload size).
nRF24L01+ radio driver (DETWS_ENABLE_NRF24) - Nordic 2.4 GHz over SPI.