ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_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 {
87 tx[1 + i] = buf[i];
88 }
89 b->spi(tx, rx, (uint8_t)(n + 1), b->ctx);
90}
91
92uint8_t status(const nrf_bus *b)
93{
94 uint8_t tx[1] = {Nrf24Cmd::CMD_NOP};
95 uint8_t rx[1];
96 b->spi(tx, rx, 1, b->ctx);
97 return rx[0];
98}
99
100void cmd(const nrf_bus *b, uint8_t c)
101{
102 uint8_t tx[1] = {c};
103 uint8_t rx[1];
104 b->spi(tx, rx, 1, b->ctx);
105}
106} // namespace
107
108bool pc_nrf24_init(const nrf_bus *bus, const nrf_config *cfg)
109{
110 if (!bus || !bus->spi || !bus->ce || !cfg || !cfg->address)
111 {
112 return false;
113 }
114 bus->ce(false, bus->ctx);
115
116 reg_write(bus, Nrf24Reg::REG_CONFIG, Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO); // power down, 16-bit CRC
117 reg_write(bus, Nrf24Reg::REG_RF_CH, cfg->channel);
118 if (reg_read(bus, Nrf24Reg::REG_RF_CH) != cfg->channel)
119 {
120 return false; // written value did not read back -> no chip on the bus
121 }
122
123 reg_write(bus, Nrf24Reg::REG_SETUP_AW, 0x03); // 5-byte addresses
124 reg_write(bus, Nrf24Reg::REG_EN_RXADDR, 0x01); // enable pipe 0
125 reg_write(bus, Nrf24Reg::REG_EN_AA, 0x00); // raw mode (no auto-ack)
126 reg_write(bus, Nrf24Reg::REG_SETUP_RETR, 0x00); // no auto-retransmit
127 uint8_t dr = 0x00; // 1 Mbps
128 if (cfg->data_rate == 1)
129 {
130 dr = 0x08; // 2 Mbps
131 }
132 else if (cfg->data_rate == 2)
133 {
134 dr = 0x20; // 250 kbps
135 }
136 reg_write(bus, Nrf24Reg::REG_RF_SETUP, (uint8_t)(dr | ((cfg->tx_power & 0x03) << 1)));
137 reg_write(bus, Nrf24Reg::REG_RX_PW_P0, PC_NRF24_PAYLOAD);
138 reg_write_buf(bus, Nrf24Reg::REG_RX_ADDR_P0, cfg->address, 5);
139 reg_write_buf(bus, Nrf24Reg::REG_TX_ADDR, cfg->address, 5);
140
141 cmd(bus, Nrf24Cmd::CMD_FLUSH_RX);
142 cmd(bus, Nrf24Cmd::CMD_FLUSH_TX);
143 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_RX_DR | Nrf24Status::ST_TX_DS | 0x10); // clear all flags
144
145 reg_write(bus, Nrf24Reg::REG_CONFIG,
146 Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO | Nrf24Cfg::CFG_PWR_UP); // power up (standby)
147 return true;
148}
149
150bool pc_nrf24_send(const nrf_bus *bus, const uint8_t *data, uint8_t len)
151{
152 if (!bus || !data || len == 0 || len > PC_NRF24_PAYLOAD)
153 {
154 return false;
155 }
156 bus->ce(false, bus->ctx);
157 reg_write(bus, Nrf24Reg::REG_CONFIG,
158 Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO | Nrf24Cfg::CFG_PWR_UP); // PRIM_RX = 0 -> PTX
159
160 uint8_t tx[1 + PC_NRF24_PAYLOAD];
161 uint8_t rx[1 + PC_NRF24_PAYLOAD];
162 tx[0] = Nrf24Cmd::CMD_W_TX_PAYLOAD;
163 for (uint8_t i = 0; i < PC_NRF24_PAYLOAD; i++)
164 {
165 tx[1 + i] = (i < len) ? data[i] : 0x00; // zero-pad to the static width
166 }
167 bus->spi(tx, rx, (uint8_t)(PC_NRF24_PAYLOAD + 1), bus->ctx);
168
169 bus->ce(true, bus->ctx); // key the transmit
170 return true;
171}
172
173bool pc_nrf24_tx_done(const nrf_bus *bus)
174{
175 if (!bus)
176 {
177 return false;
178 }
179 if (status(bus) & Nrf24Status::ST_TX_DS)
180 {
181 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_TX_DS); // write-1-to-clear
182 return true;
183 }
184 return false;
185}
186
187void pc_nrf24_set_rx(const nrf_bus *bus)
188{
189 if (!bus)
190 {
191 return;
192 }
193 reg_write(bus, Nrf24Reg::REG_CONFIG,
194 Nrf24Cfg::CFG_EN_CRC | Nrf24Cfg::CFG_CRCO | Nrf24Cfg::CFG_PWR_UP | Nrf24Cfg::CFG_PRIM_RX); // PRX
195 bus->ce(true, bus->ctx);
196}
197
198int pc_nrf24_recv(const nrf_bus *bus, uint8_t *buf, uint8_t cap, uint8_t *pipe)
199{
200 if (!bus || !buf)
201 {
202 return -1;
203 }
204 uint8_t st = status(bus);
205 if (!(st & Nrf24Status::ST_RX_DR))
206 {
207 return -1; // nothing received
208 }
209 uint8_t p = (uint8_t)((st & Nrf24Status::ST_RX_P_NO) >> 1);
210 if (p > 5) // 0x07 = RX FIFO empty
211 {
212 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_RX_DR);
213 return -1;
214 }
215 uint8_t tx[1 + PC_NRF24_PAYLOAD];
216 uint8_t rx[1 + PC_NRF24_PAYLOAD];
217 tx[0] = Nrf24Cmd::CMD_R_RX_PAYLOAD;
218 for (uint8_t i = 0; i < PC_NRF24_PAYLOAD; i++)
219 {
220 tx[1 + i] = 0xFF;
221 }
222 bus->spi(tx, rx, (uint8_t)(PC_NRF24_PAYLOAD + 1), bus->ctx);
223
224 uint8_t n = (PC_NRF24_PAYLOAD < cap) ? PC_NRF24_PAYLOAD : cap;
225 for (uint8_t i = 0; i < n; i++)
226 {
227 buf[i] = rx[1 + i];
228 }
229 if (pipe)
230 {
231 *pipe = p;
232 }
233 reg_write(bus, Nrf24Reg::REG_STATUS, Nrf24Status::ST_RX_DR); // clear
234 return (int)n;
235}
236
237#endif // PC_ENABLE_NRF24
nRF24L01+ radio driver (PC_ENABLE_NRF24) - Nordic 2.4 GHz over SPI.
#define PC_NRF24_PAYLOAD
nRF24 fixed payload width in bytes (1..32; the chip's static payload size).