NRF24L01 on Raspberry Pi
OK, so just for the record, I’ve tried using the NRF24L01 on the Raspberry Pi (via tha GPIO SPI pins) and I don’t recommend it.
So the following is just a record of what I did, in case I ever change my mind :).

NRF24L01 Side Wire Raspberry Pi Side
------------- ---- -----------------
MISO White GPIO 9 (SPI_MISO)
SCK Grey GPIO 11 (SPI_SCLK)
CE Orange GPIO 25
GND Black GND (PIN 20)
IRQ Brown GPIO 22
MOSI Purple GPIO 10 (SPI_MOSI)
CSN Yellow GPIO 8 (SPI_CE0)
VCC Red 3.3V (PIN 1)
The Raspberry Pi code consisted of two python files which had to be in the same folder:
nrf24TestSend.py:
1#!/usr/bin/python3
2import RPi.GPIO as GPIO # import gpio
3import time #import time library
4import spidev
5from lib_nrf24 import NRF24 #import NRF24 library
6
7# Loosely based on: https://circuitdigest.com/microcontroller-projects/wireless-rf-communication-between-arduino-and-raspberry-pi-using-nrf24l01
8
9GPIO.setmode(GPIO.BCM) # set the gpio mode
10
11 # set the pipe address. this address shoeld be entered on the receiver alo
12pipes = [[0xE0, 0xE0, 0xF1, 0xF1, 0xE0], [0xF1, 0xF1, 0xF0, 0xF0, 0xE0]]
13radio = NRF24(GPIO, spidev.SpiDev()) # use the gpio pins
14radio.begin(0, 25) # start the radio and set the ce,csn pin ce= GPIO08, csn= GPIO25
15radio.setPayloadSize(32) #set the payload size as 32 bytes
16radio.setChannel(0x76) # set the channel as 76 hex
17radio.setDataRate(NRF24.BR_1MBPS) # set radio data rate
18radio.setPALevel(NRF24.PA_MAX) # set PA level
19
20radio.setAutoAck(True) # set acknowledgement as true
21radio.enableDynamicPayloads()
22radio.enableAckPayload()
23
24radio.openWritingPipe(pipes[0]) # open the defined pipe for writing
25radio.printDetails() # print basic detals of radio
26
27sendMessage = list("Hi..Arduino UNO") #the message to be sent
28while len(sendMessage) < 32:
29 sendMessage.append(0)
30
31while True:
32 start = time.time() #start the time for checking delivery time
33 radio.write(sendMessage) # just write the message to radio
34 print("Sent the message: {}".format(sendMessage)) # print a message after succesfull send
35 radio.startListening() # Start listening the radio
36
37 while not radio.available(0):
38 time.sleep(1/100)
39 if time.time() - start > 2:
40 print("Timed out.") # print errror message if radio disconnected or not functioning anymore
41 break
42
43 radio.stopListening() # close radio
44 time.sleep(3) # give delay of 3 seconds
lib_nrf24.py:
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3#
4
5
6# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24".
7
8# So this is my tweak for Raspberry Pi and "Virtual GPIO" ...
9# ... of Barraca's port to BeagleBone python ... (Joao Paulo Barraca <[email protected]>)
10# ... of maniacbug's NRF24L01 C++ library for Arduino.
11# Brian Lavery Oct 2014
12
13
14
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25
26
27import sys
28import time
29
30if __name__ == '__main__':
31 print (sys.argv[0], 'is an importable module:')
32 print ("... from", sys.argv[0], "import lib_nrf24")
33 print ("")
34
35 exit()
36
37def _BV(x):
38 return 1 << x
39
40
41class NRF24:
42 MAX_CHANNEL = 127
43 MAX_PAYLOAD_SIZE = 32
44
45 # PA Levels
46 PA_MIN = 0
47 PA_LOW = 1
48 PA_HIGH = 2
49 PA_MAX = 3
50 PA_ERROR = 4
51
52 # Bit rates
53 BR_1MBPS = 0
54 BR_2MBPS = 1
55 BR_250KBPS = 2
56
57 # CRC
58 CRC_DISABLED = 0
59 CRC_8 = 1
60 CRC_16 = 2
61 CRC_ENABLED = 3
62
63 # Registers
64 CONFIG = 0x00
65 EN_AA = 0x01
66 EN_RXADDR = 0x02
67 SETUP_AW = 0x03
68 SETUP_RETR = 0x04
69 RF_CH = 0x05
70 RF_SETUP = 0x06
71 STATUS = 0x07
72 OBSERVE_TX = 0x08
73 CD = 0x09
74 RX_ADDR_P0 = 0x0A
75 RX_ADDR_P1 = 0x0B
76 RX_ADDR_P2 = 0x0C
77 RX_ADDR_P3 = 0x0D
78 RX_ADDR_P4 = 0x0E
79 RX_ADDR_P5 = 0x0F
80 TX_ADDR = 0x10
81 RX_PW_P0 = 0x11
82 RX_PW_P1 = 0x12
83 RX_PW_P2 = 0x13
84 RX_PW_P3 = 0x14
85 RX_PW_P4 = 0x15
86 RX_PW_P5 = 0x16
87 FIFO_STATUS = 0x17
88 DYNPD = 0x1C
89 FEATURE = 0x1D
90
91
92 # Bit Mnemonics */
93 MASK_RX_DR = 6
94 MASK_TX_DS = 5
95 MASK_MAX_RT = 4
96 EN_CRC = 3
97 CRCO = 2
98 PWR_UP = 1
99 PRIM_RX = 0
100 ENAA_P5 = 5
101 ENAA_P4 = 4
102 ENAA_P3 = 3
103 ENAA_P2 = 2
104 ENAA_P1 = 1
105 ENAA_P0 = 0
106 ERX_P5 = 5
107 ERX_P4 = 4
108 ERX_P3 = 3
109 ERX_P2 = 2
110 ERX_P1 = 1
111 ERX_P0 = 0
112 AW = 0
113 ARD = 4
114 ARC = 0
115 PLL_LOCK = 4
116 RF_DR = 3
117 RF_PWR = 6
118 RX_DR = 6
119 TX_DS = 5
120 MAX_RT = 4
121 RX_P_NO = 1
122 TX_FULL = 0
123 PLOS_CNT = 4
124 ARC_CNT = 0
125 TX_REUSE = 6
126 FIFO_FULL = 5
127 TX_EMPTY = 4
128 RX_FULL = 1
129 RX_EMPTY = 0
130 DPL_P5 = 5
131 DPL_P4 = 4
132 DPL_P3 = 3
133 DPL_P2 = 2
134 DPL_P1 = 1
135 DPL_P0 = 0
136 EN_DPL = 2
137 EN_ACK_PAY = 1
138 EN_DYN_ACK = 0
139
140 # Instruction Mnemonics
141 R_REGISTER = 0x00
142 W_REGISTER = 0x20
143 REGISTER_MASK = 0x1F
144 ACTIVATE = 0x50
145 R_RX_PL_WID = 0x60
146 R_RX_PAYLOAD = 0x61
147 W_TX_PAYLOAD = 0xA0
148 W_ACK_PAYLOAD = 0xA8
149 FLUSH_TX = 0xE1
150 FLUSH_RX = 0xE2
151 REUSE_TX_PL = 0xE3
152 NOP = 0xFF
153
154
155 # Non-P omissions
156 LNA_HCURR = 0x00
157
158 # P model memory Map
159 RPD = 0x09
160
161 # P model bit Mnemonics
162 RF_DR_LOW = 5
163 RF_DR_HIGH = 3
164 RF_PWR_LOW = 1
165 RF_PWR_HIGH = 2
166
167 # Signal Mnemonics
168 LOW = 0
169 HIGH = 1
170
171 datarate_e_str_P = ["1MBPS", "2MBPS", "250KBPS"]
172 model_e_str_P = ["nRF24L01", "nRF24l01+"]
173 crclength_e_str_P = ["Disabled", "8 bits", "16 bits"]
174 pa_dbm_e_str_P = ["PA_MIN", "PA_LOW", "PA_MED", "PA_HIGH"]
175 child_pipe = [RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5]
176
177 child_payload_size = [RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5]
178 child_pipe_enable = [ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5]
179
180 GPIO = None
181 spidev = None
182
183 def __init__(self, gpio, spidev):
184 # It should be possible to instantiate multiple objects, with different GPIO / spidev
185 # EG on Raspberry, one could be RPI GPIO & spidev module, other could be virtual-GPIO
186 # On rpi, only bus 0 is supported here, not bus 1 of the model B plus
187 self.GPIO = gpio # the GPIO module
188 self.spidev = spidev # the spidev object/instance
189 self.channel = 76
190 self.data_rate = NRF24.BR_1MBPS
191 self.wide_band = False # 2Mbs data rate in use?
192 self.p_variant = False # False for RF24L01 and true for RF24L01P (nrf24l01+)
193 self.payload_size = 5 #*< Fixed size of payloads
194 self.ack_payload_available = False #*< Whether there is an ack payload waiting
195 self.dynamic_payloads_enabled = False #*< Whether dynamic payloads are enabled.
196 self.ack_payload_length = 5 #*< Dynamic size of pending ack payload.
197 self.pipe0_reading_address = None #*< Last address set on pipe 0 for reading.
198
199 def ce(self, level):
200 if self.ce_pin == 0:
201 return
202 # rf24-CE is optional. Tie to HIGH if not used. (Altho, left floating seems to read HIGH anyway??? - risky!)
203 # Some RF24 modes may NEED control over CE.
204 # non-powerdown, fixed PTX or RTX role, dynamic payload size & ack-payload: does NOT need CE.
205 if level == NRF24.HIGH:
206 self.GPIO.output(self.ce_pin, self.GPIO.HIGH)
207 else:
208 self.GPIO.output(self.ce_pin, self.GPIO.LOW)
209 return
210
211
212
213 def read_register(self, reg, blen=1):
214 buf = [NRF24.R_REGISTER | ( NRF24.REGISTER_MASK & reg )]
215 for col in range(blen):
216 buf.append(NRF24.NOP)
217
218 resp = self.spidev.xfer2(buf)
219 if blen == 1:
220 return resp[1]
221
222 return resp[1:blen + 1]
223
224 def write_register(self, reg, value, length=-1):
225 buf = [NRF24.W_REGISTER | ( NRF24.REGISTER_MASK & reg )]
226 ###if isinstance(value, (int, long)): # ng for python3. but value should never be long anyway
227 if isinstance(value, int):
228 if length < 0:
229 length = 1
230
231 length = min(4, length)
232 for i in range(length):
233 buf.insert(1, int(value & 0xff))
234 value >>= 8
235
236 elif isinstance(value, list):
237 if length < 0:
238 length = len(value)
239
240 for i in range(min(len(value), length)):
241 buf.append(int(value[len(value) - i - 1] & 0xff))
242 else:
243 raise Exception("Value must be int or list")
244
245 return self.spidev.xfer2(buf)[0]
246
247
248 def write_payload(self, buf):
249 data_len = min(self.payload_size, len(buf))
250 blank_len = 0
251 if not self.dynamic_payloads_enabled:
252 blank_len = self.payload_size - data_len
253
254 txbuffer = [NRF24.W_TX_PAYLOAD]
255 for n in buf:
256 t = type(n)
257 if t is str:
258 txbuffer.append(ord(n))
259 elif t is int:
260 txbuffer.append(n)
261 else:
262 raise Exception("Only ints and chars are supported: Found " + str(t))
263
264 if blank_len != 0:
265 blank = [0x00 for i in range(blank_len)]
266 txbuffer.extend(blank)
267
268 return self.spidev.xfer2(txbuffer)
269
270 def read_payload(self, buf, buf_len=-1):
271 if buf_len < 0:
272 buf_len = self.payload_size
273 data_len = min(self.payload_size, buf_len)
274 blank_len = 0
275 if not self.dynamic_payloads_enabled:
276 blank_len = self.payload_size - data_len
277
278 txbuffer = [NRF24.NOP for i in range(0, blank_len + data_len + 1)]
279 txbuffer[0] = NRF24.R_RX_PAYLOAD
280
281 payload = self.spidev.xfer2(txbuffer)
282 del buf[:]
283 buf.extend(payload[1:data_len + 1])
284 return data_len
285
286 def flush_rx(self):
287 return self.spidev.xfer2([NRF24.FLUSH_RX])[0]
288
289 def flush_tx(self):
290 return self.spidev.xfer2([NRF24.FLUSH_TX])[0]
291
292 def get_status(self):
293 return self.spidev.xfer2([NRF24.NOP])[0]
294
295 def print_status(self, status):
296 status_str = "STATUS\t = 0x{0:02x} RX_DR={1:x} TX_DS={2:x} MAX_RT={3:x} RX_P_NO={4:x} TX_FULL={5:x}".format(
297 status,
298 1 if status & _BV(NRF24.RX_DR) else 0,
299 1 if status & _BV(NRF24.TX_DS) else 0,
300 1 if status & _BV(NRF24.MAX_RT) else 0,
301 ((status >> NRF24.RX_P_NO) & 7),
302 1 if status & _BV(NRF24.TX_FULL) else 0)
303
304 print (status_str)
305
306 def print_observe_tx(self, value):
307 print ("Observe Tx: %02x Lost Pkts: %d Retries: %d" % (value, value >> NRF24.PLOS_CNT, value & 15))
308
309
310 def print_byte_register(self, name, reg, qty=1):
311 extra_tab = '\t' if len(name) < 8 else 0
312 print ("%s\t%c =" % (name, extra_tab)),
313 while qty > 0:
314 print ("0x%02x" % (self.read_register(reg))),
315 qty -= 1
316 reg += 1
317
318 print ("")
319
320 def print_address_register(self, name, reg, qty=1):
321 extra_tab = '\t' if len(name) < 8 else 0
322 print ("%s\t%c =" % (name, extra_tab)),
323
324 while qty > 0:
325 qty -= 1
326 buf = reversed(self.read_register(reg, 5))
327 reg += 1
328 sys.stdout.write(" 0x"),
329 for i in buf:
330 sys.stdout.write("%02x" % i)
331
332 print ("")
333
334
335 def setChannel(self, channel):
336 self.channel = min(max(0, channel), NRF24.MAX_CHANNEL)
337 self.write_register(NRF24.RF_CH, self.channel)
338
339 def getChannel(self):
340 return self.read_register(NRF24.RF_CH)
341
342 def setPayloadSize(self, size):
343 self.payload_size = min(max(size, 1), NRF24.MAX_PAYLOAD_SIZE)
344
345 def getPayloadSize(self):
346 return self.payload_size
347
348 def printDetails(self):
349 self.print_status(self.get_status())
350 self.print_address_register("RX_ADDR_P0-1", NRF24.RX_ADDR_P0, 2)
351 self.print_byte_register("RX_ADDR_P2-5", NRF24.RX_ADDR_P2, 4)
352 self.print_address_register("TX_ADDR", NRF24.TX_ADDR)
353
354 self.print_byte_register("RX_PW_P0-6", NRF24.RX_PW_P0, 6)
355 self.print_byte_register("EN_AA", NRF24.EN_AA)
356 self.print_byte_register("EN_RXADDR", NRF24.EN_RXADDR)
357 self.print_byte_register("RF_CH", NRF24.RF_CH)
358 self.print_byte_register("RF_SETUP", NRF24.RF_SETUP)
359 self.print_byte_register("CONFIG", NRF24.CONFIG)
360 self.print_byte_register("DYNPD/FEATURE", NRF24.DYNPD, 2)
361
362 #
363 print ("Data Rate\t = %s" % NRF24.datarate_e_str_P[self.getDataRate()])
364 print ("Model\t\t = %s" % NRF24.model_e_str_P[self.isPVariant()])
365 print ("CRC Length\t = %s" % NRF24.crclength_e_str_P[self.getCRCLength()])
366 print ("PA Power\t = %s" % NRF24.pa_dbm_e_str_P[self.getPALevel()])
367
368 def begin(self, csn_pin, ce_pin=0): # csn & ce are RF24 terminology. csn = SPI's CE!
369 # Initialize SPI bus..
370 # ce_pin is for the rx=listen or tx=trigger pin on RF24 (they call that ce !!!)
371 # CE optional (at least in some circumstances, eg fixed PTX PRX roles, no powerdown)
372 # CE seems to hold itself as (sufficiently) HIGH, but tie HIGH is safer!
373 self.spidev.open(0, csn_pin)
374 self.ce_pin = ce_pin
375
376 if ce_pin:
377 self.GPIO.setup(self.ce_pin, self.GPIO.OUT)
378
379 self.spidev.max_speed_hz = 8000000
380
381 time.sleep(5 / 1000000.0)
382
383 # Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier
384 # WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet
385 # sizes must never be used. See documentation for a more complete explanation.
386 self.write_register(NRF24.SETUP_RETR, (0b0100 << NRF24.ARD) | 0b1111)
387
388 # Restore our default PA level
389 self.setPALevel(NRF24.PA_MAX)
390
391 # Determine if this is a p or non-p RF24 module and then
392 # reset our data rate back to default value. This works
393 # because a non-P variant won't allow the data rate to
394 # be set to 250Kbps.
395 if self.setDataRate(NRF24.BR_250KBPS):
396 self.p_variant = True
397
398 # Then set the data rate to the slowest (and most reliable) speed supported by all
399 # hardware.
400 self.setDataRate(NRF24.BR_1MBPS)
401
402 # Initialize CRC and request 2-byte (16bit) CRC
403 self.setCRCLength(NRF24.CRC_16)
404
405 # Disable dynamic payloads, to match dynamic_payloads_enabled setting
406 self.write_register(NRF24.DYNPD, 0)
407
408 # Reset current status
409 # Notice reset and flush is the last thing we do
410 self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
411
412 # Set up default configuration. Callers can always change it later.
413 # This channel should be universally safe and not bleed over into adjacent
414 # spectrum.
415 self.setChannel(self.channel)
416
417 # Flush buffers
418 self.flush_rx()
419 self.flush_tx()
420
421 def end(self):
422 if self.spidev:
423 self.spidev.close()
424 self.spidev = None
425
426 def startListening(self):
427 self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) | _BV(NRF24.PRIM_RX))
428 self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
429
430 # Restore the pipe0 address, if exists
431 if self.pipe0_reading_address:
432 self.write_register(self.RX_ADDR_P0, self.pipe0_reading_address, 5)
433
434 # Go!
435 self.ce(NRF24.HIGH)
436
437 # wait for the radio to come up (130us actually only needed)
438 time.sleep(130 / 1000000.0)
439
440 def stopListening(self):
441 self.ce(NRF24.LOW)
442 self.flush_tx()
443 self.flush_rx()
444
445 def powerDown(self):
446 self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) & ~_BV(NRF24.PWR_UP))
447
448 def powerUp(self):
449 self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP))
450 time.sleep(150 / 1000000.0)
451
452 def write(self, buf):
453 # Begin the write
454 self.startWrite(buf)
455
456 timeout = self.getMaxTimeout() #s to wait for timeout
457 sent_at = time.time()
458
459 while True:
460 #status = self.read_register(NRF24.OBSERVE_TX, 1)
461 status = self.get_status()
462 if (status & (_BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))) or (time.time() - sent_at > timeout ):
463 break
464 time.sleep(10 / 1000000.0)
465 #obs = self.read_register(NRF24.OBSERVE_TX)
466 #self.print_observe_tx(obs)
467 #self.print_status(status)
468 # (for debugging)
469
470 what = self.whatHappened()
471
472 result = what['tx_ok']
473 if what['tx_fail']:
474 self.flush_tx(); # bl - dont jam up the fifo
475 # Handle the ack packet
476 if what['rx_ready']:
477 self.ack_payload_length = self.getDynamicPayloadSize()
478 self.ack_payload_available = True ## bl
479
480 return result
481
482 def startWrite(self, buf):
483 # Transmitter power-up
484 self.write_register(NRF24.CONFIG, (self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) ) & ~_BV(NRF24.PRIM_RX))
485
486 # Send the payload
487 self.write_payload(buf)
488
489 # Allons!
490 if self.ce_pin:
491 if self.GPIO.RPI_REVISION > 0:
492 self.ce(self.GPIO.HIGH)
493 time.sleep(10 / 1000000.0)
494 self.ce(self.GPIO.LOW)
495 else:
496 # virtGPIO is slower. A 10 uSec pulse is better done with pulseOut():
497 self.GPIO.pulseOut(self.ce_pin, self.GPIO.HIGH, 10)
498
499
500
501 def getDynamicPayloadSize(self):
502 return self.spidev.xfer2([NRF24.R_RX_PL_WID, NRF24.NOP])[1]
503
504 def available(self, pipe_num=None):
505 if not pipe_num:
506 pipe_num = []
507
508 status = self.get_status()
509 result = False
510
511 # Sometimes the radio specifies that there is data in one pipe but
512 # doesn't set the RX flag...
513 if status & _BV(NRF24.RX_DR) or (status & 0b00001110 != 0b00001110):
514 result = True
515
516 if result:
517 # If the caller wants the pipe number, include that
518 if len(pipe_num) >= 1:
519 pipe_num[0] = ( status >> NRF24.RX_P_NO ) & 0b00000111
520
521 # Clear the status bit
522
523 # ??? Should this REALLY be cleared now? Or wait until we
524 # actually READ the payload?
525 self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR))
526
527 # Handle ack payload receipt
528 if status & _BV(NRF24.TX_DS):
529 self.write_register(NRF24.STATUS, _BV(NRF24.TX_DS))
530
531 return result
532
533 def read(self, buf, buf_len=-1):
534 # Fetch the payload
535 self.read_payload(buf, buf_len)
536
537 # was this the last of the data available?
538 return self.read_register(NRF24.FIFO_STATUS) & _BV(NRF24.RX_EMPTY)
539
540 def whatHappened(self):
541 # Read the status & reset the status in one easy call
542 # Or is that such a good idea?
543 status = self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
544
545 # Report to the user what happened
546 tx_ok = status & _BV(NRF24.TX_DS)
547 tx_fail = status & _BV(NRF24.MAX_RT)
548 rx_ready = status & _BV(NRF24.RX_DR)
549 return {'tx_ok': tx_ok, "tx_fail": tx_fail, "rx_ready": rx_ready}
550
551 def openWritingPipe(self, value):
552 # Note that the NRF24L01(+)
553 # expects it LSB first.
554
555 self.write_register(NRF24.RX_ADDR_P0, value, 5)
556 self.write_register(NRF24.TX_ADDR, value, 5)
557
558 max_payload_size = 32
559 self.write_register(NRF24.RX_PW_P0, min(self.payload_size, max_payload_size))
560
561 def openReadingPipe(self, child, address):
562 # If this is pipe 0, cache the address. This is needed because
563 # openWritingPipe() will overwrite the pipe 0 address, so
564 # startListening() will have to restore it.
565 if child == 0:
566 self.pipe0_reading_address = address
567
568 if child <= 6:
569 # For pipes 2-5, only write the LSB
570 if child < 2:
571 self.write_register(NRF24.child_pipe[child], address, 5)
572 else:
573 self.write_register(NRF24.child_pipe[child], address, 1)
574
575 self.write_register(NRF24.child_payload_size[child], self.payload_size)
576
577 # Note it would be more efficient to set all of the bits for all open
578 # pipes at once. However, I thought it would make the calling code
579 # more simple to do it this way.
580 self.write_register(NRF24.EN_RXADDR,
581 self.read_register(NRF24.EN_RXADDR) | _BV(NRF24.child_pipe_enable[child]))
582
583
584 def closeReadingPipe(self, pipe):
585 self.write_register(NRF24.EN_RXADDR,
586 self.read_register(EN_RXADDR) & ~_BV(NRF24.child_pipe_enable[pipe]))
587
588
589 def toggle_features(self):
590 buf = [NRF24.ACTIVATE, 0x73]
591 self.spidev.xfer2(buf)
592
593 def enableDynamicPayloads(self):
594 # Enable dynamic payload throughout the system
595 self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL))
596
597 # If it didn't work, the features are not enabled
598 if not self.read_register(NRF24.FEATURE):
599 # So enable them and try again
600 self.toggle_features()
601 self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL))
602
603 # Enable dynamic payload on all pipes
604
605 # Not sure the use case of only having dynamic payload on certain
606 # pipes, so the library does not support it.
607 self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P5) | _BV(NRF24.DPL_P4) | _BV(
608 NRF24.DPL_P3) | _BV(NRF24.DPL_P2) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0))
609
610 self.dynamic_payloads_enabled = True
611
612
613 def enableAckPayload(self):
614 # enable ack payload and dynamic payload features
615 self.write_register(NRF24.FEATURE,
616 self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL))
617
618 # If it didn't work, the features are not enabled
619 if not self.read_register(NRF24.FEATURE):
620 # So enable them and try again
621 self.toggle_features()
622 self.write_register(NRF24.FEATURE,
623 self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL))
624
625 # Enable dynamic payload on pipes 0 & 1
626 self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0))
627
628 def writeAckPayload(self, pipe, buf, buf_len):
629 txbuffer = [NRF24.W_ACK_PAYLOAD | ( pipe & 0x7 )]
630
631 max_payload_size = 32
632 data_len = min(buf_len, max_payload_size)
633 txbuffer.extend(buf[0:data_len])
634
635 self.spidev.xfer2(txbuffer)
636
637 def isAckPayloadAvailable(self):
638 result = self.ack_payload_available
639 self.ack_payload_available = False
640 return result
641
642 def isPVariant(self):
643 return self.p_variant
644
645 def setAutoAck(self, enable):
646 if enable:
647 self.write_register(NRF24.EN_AA, 0b111111)
648 else:
649 self.write_register(NRF24.EN_AA, 0)
650
651 def setAutoAckPipe(self, pipe, enable):
652 if pipe <= 6:
653 en_aa = self.read_register(NRF24.EN_AA)
654 if enable:
655 en_aa |= _BV(pipe)
656 else:
657 en_aa &= ~_BV(pipe)
658
659 self.write_register(NRF24.EN_AA, en_aa)
660
661 def testCarrier(self):
662 return self.read_register(NRF24.CD) & 1
663
664 def testRPD(self):
665 return self.read_register(NRF24.RPD) & 1
666
667 def setPALevel(self, level):
668 setup = self.read_register(NRF24.RF_SETUP)
669 setup &= ~( _BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
670 # switch uses RAM (evil!)
671 if level == NRF24.PA_MAX:
672 setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
673 elif level == NRF24.PA_HIGH:
674 setup |= _BV(NRF24.RF_PWR_HIGH)
675 elif level == NRF24.PA_LOW:
676 setup |= _BV(NRF24.RF_PWR_LOW)
677 elif level == NRF24.PA_MIN:
678 nop = 0
679 elif level == NRF24.PA_ERROR:
680 # On error, go to maximum PA
681 setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
682
683 self.write_register(NRF24.RF_SETUP, setup)
684
685
686 def getPALevel(self):
687 power = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
688
689 if power == (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)):
690 return NRF24.PA_MAX
691 elif power == _BV(NRF24.RF_PWR_HIGH):
692 return NRF24.PA_HIGH
693 elif power == _BV(NRF24.RF_PWR_LOW):
694 return NRF24.PA_LOW
695 else:
696 return NRF24.PA_MIN
697
698 def setDataRate(self, speed):
699 result = False
700 setup = self.read_register(NRF24.RF_SETUP)
701
702 # HIGH and LOW '00' is 1Mbs - our default
703 self.wide_band = False
704 setup &= ~(_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH))
705
706 if speed == NRF24.BR_250KBPS:
707 # Must set the RF_DR_LOW to 1 RF_DR_HIGH (used to be RF_DR) is already 0
708 # Making it '10'.
709 self.wide_band = False
710 setup |= _BV(NRF24.RF_DR_LOW)
711 else:
712 # Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1
713 # Making it '01'
714 if speed == NRF24.BR_2MBPS:
715 self.wide_band = True
716 setup |= _BV(NRF24.RF_DR_HIGH)
717 else:
718 # 1Mbs
719 self.wide_band = False
720
721 self.write_register(NRF24.RF_SETUP, setup)
722
723 # Verify our result
724 if self.read_register(NRF24.RF_SETUP) == setup:
725 result = True
726 else:
727 self.wide_band = False
728 return result
729
730 def getDataRate(self):
731 dr = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH))
732 # Order matters in our case below
733 if dr == _BV(NRF24.RF_DR_LOW):
734 # '10' = 250KBPS
735 return NRF24.BR_250KBPS
736 elif dr == _BV(NRF24.RF_DR_HIGH):
737 # '01' = 2MBPS
738 return NRF24.BR_2MBPS
739 else:
740 # '00' = 1MBPS
741 return NRF24.BR_1MBPS
742
743
744 def setCRCLength(self, length):
745 config = self.read_register(NRF24.CONFIG) & ~( _BV(NRF24.CRC_16) | _BV(NRF24.CRC_ENABLED))
746
747 if length == NRF24.CRC_DISABLED:
748 # Do nothing, we turned it off above.
749 self.write_register(NRF24.CONFIG, config)
750 return
751 elif length == NRF24.CRC_8:
752 config |= _BV(NRF24.CRC_ENABLED)
753 config |= _BV(NRF24.CRC_8)
754 else:
755 config |= _BV(NRF24.CRC_ENABLED)
756 config |= _BV(NRF24.CRC_16)
757
758 self.write_register(NRF24.CONFIG, config)
759
760 def getCRCLength(self):
761 result = NRF24.CRC_DISABLED
762 config = self.read_register(NRF24.CONFIG) & ( _BV(NRF24.CRCO) | _BV(NRF24.EN_CRC))
763
764 if config & _BV(NRF24.EN_CRC):
765 if config & _BV(NRF24.CRCO):
766 result = NRF24.CRC_16
767 else:
768 result = NRF24.CRC_8
769
770 return result
771
772 def disableCRC(self):
773 disable = self.read_register(NRF24.CONFIG) & ~_BV(NRF24.EN_CRC)
774 self.write_register(NRF24.CONFIG, disable)
775
776 def setRetries(self, delay, count):
777 # see specs. Delay code below 5 can conflict with some ACK lengths
778 # and count should be set = 0 for non-ACK modes
779 self.write_register(NRF24.SETUP_RETR, (delay & 0xf) << NRF24.ARD | (count & 0xf))
780
781 def getRetries(self):
782 return self.read_register(NRF24.SETUP_RETR)
783
784 def getMaxTimeout(self): # seconds
785 retries = self.getRetries()
786 tout = (((250+(250*((retries& 0xf0)>>4 ))) * (retries & 0x0f)) / 1000000.0 * 2) + 0.008
787 # Fudged up to about double Barraca's calculation
788 # Was too short & was timeing out wrongly. BL
789 return tout
On an Arduino Nano, I had this code:
NRF24L01.ino:
1#include<SPI.h>
2#include<RF24.h>
3
4// Loosely based on: https://circuitdigest.com/microcontroller-projects/wireless-rf-communication-between-arduino-and-raspberry-pi-using-nrf24l01
5
6
7RF24 radio(7, 8);
8
9int count = 0;
10unsigned long lastTime = 0;
11
12void setup()
13{
14 pinMode(2, OUTPUT);
15 pinMode(3, OUTPUT);
16 pinMode(4, OUTPUT);
17
18 //while (!Serial);
19 Serial.begin(9600) ; // start serial monitor baud rate
20
21 radio.begin();
22 radio.setPALevel(RF24_PA_MIN);
23 radio.setChannel(0x76);
24 radio.setPayloadSize(15);
25 const uint64_t pipe = 0xE0E0F1F1E0LL;
26 radio.openReadingPipe(0, pipe);
27 //radio.enableDynamicPayloads();
28 radio.powerUp();
29 radio.startListening();
30}
31
32void loop()
33{
34 bool msgReceived = false;
35 digitalWrite(2, count & 1 ? HIGH : LOW);
36 digitalWrite(3, count & 2 ? HIGH : LOW);
37 digitalWrite(4, count & 4 ? HIGH : LOW);
38
39 while (lastTime + 4000 > millis())
40 {
41 if (radio.available())
42 {
43 char receivedMessage[32] = {0} ;
44 radio.read(receivedMessage, sizeof(receivedMessage));
45 radio.stopListening();
46 radio.startListening();
47 Serial.println(receivedMessage);
48 msgReceived = true;
49 }
50 }
51
52 if (!msgReceived)
53 Serial.println("No message received " + String(count));
54
55 lastTime = millis();
56 count++;
57}
Arduino Nano Photos:

Because the Nano is 5V, I used a NRF24L01 adapter module.
It did work, but I wanted to use C# and .Net and the NRF24L01 driver in the dotnet iot project didn’t work without modification:
.Net Code (using dotnet iot library):
1using System;
2using System.Text;
3using System.Threading;
4using System.Device.Gpio;
5using System.Device.Spi;
6using Iot.Device.Nrf24l01;
7using System.Collections.Generic;
8
9// See https://github.com/dotnet/iot for System.Device.Gpio source code and help
10
11namespace RF24Lib
12{
13 /// <summary>
14 /// Control NRF24L01 board
15 /// </summary>
16 public class RF24Controller
17 {
18 private SpiDevice senderDevice;
19 private GpioController _controller;
20 private bool _disposed = false;
21
22 public RF24Controller(GpioController controller)
23 {
24 _controller = controller;
25 }
26
27 public void Initialize()
28 {
29 // SPI0 CS0
30 SpiConnectionSettings senderSettings = new SpiConnectionSettings(0, 0)
31 {
32 ClockFrequency = Nrf24l01.SpiClockFrequency,
33 Mode = Nrf24l01.SpiMode
34 };
35 senderDevice = SpiDevice.Create(senderSettings);
36 }
37
38 public void SendMessage(string msg)
39 {
40 // SPI Device, CE Pin, IRQ Pin, Receive Packet Size
41 //using (Nrf24l01 sender = new Nrf24l01(senderDevice, 25, 22, 32, 0x76, OutputPower.N18dBm, DataRate.Rate1Mbps)) //, PinNumberingScheme.Logical, _controller, false))
42 Nrf24l01 sender = new Nrf24l01(senderDevice, 25, 22, 15, 0x76, OutputPower.N18dBm, DataRate.Rate1Mbps);
43 var addr = new byte[] { 0xE0, 0xF1, 0xF1, 0xE0, 0xE0 };
44 sender.Address = addr; //Encoding.UTF8.GetBytes("NRF24");
45 WriteRegister(0, (byte)(ReadRegister(0)[0] | 0x04)); // Set CRC to 2 bytes rather than 1 byte
46 WriteRegister(0x1c, 0x3f); // Set dynamic payload for all pipes
47 WriteRegister(0x1d, 0x04); // enable dynamic payload, disable payload with ACK, disable W_TX_PAYLOAD_NOACK command
48
49 var sendStr = new string[] { "test12345678901", "long string----", "short----------", "longer---------", "10 chars..-----", "four-----------" }; // 11 chars max
50
51 for (int i = 0; i < 6; i++)
52 {
53 DisplayRegisters();
54
55 // Set sender send address
56 //sender.Send(Encoding.UTF8.GetBytes("Hello! .NET Core IoT"));
57 sender.Send(Encoding.UTF8.GetBytes(sendStr[i]));
58
59 Thread.Sleep(1000);
60 }
61 }
62
63 internal byte[] ReadRegister(byte register, int len = 1)
64 {
65 Span<byte> writeBuf = stackalloc byte[1 + len];
66 Span<byte> readBuf = stackalloc byte[1 + len];
67
68 writeBuf[0] = (byte)(register);
69
70 senderDevice.TransferFullDuplex(writeBuf, readBuf);
71
72 return readBuf.Slice(1).ToArray();
73 }
74
75 internal void WriteRegister(byte register, byte data)
76 {
77 Span<byte> writeBuf = stackalloc byte[2]
78 {
79 (byte)(0x20 + (byte)register),
80 data
81 };
82 Span<byte> readBuf = stackalloc byte[2];
83
84 senderDevice.TransferFullDuplex(writeBuf, readBuf);
85 }
86
87 public void DisplayRegisters()
88 {
89 var registers = new List<byte>();
90
91 Span<byte> writeBuf = stackalloc byte[1 + 1];
92 Span<byte> readBuf = stackalloc byte[1 + 1];
93
94 for (int reg = 0; reg <= 0x1d; reg++)
95 {
96 if (reg > 0x17 && reg < 0x1c)
97 {
98 registers.Add(0);
99 }
100 else
101 {
102 registers.Add(ReadRegister((byte)reg)[0]);
103 }
104 }
105
106 var status = registers[7];
107 var statStr = $"RX_DR={((status & 0x40) != 0 ? 1 : 0)} TX_DS={((status & 0x20) != 0 ? 1 : 0)} MAX_RT={((status & 0x10) != 0 ? 1 : 0)}" +
108 $"RX_P_NO={status & 0x0e >> 1} TX_FULL={((status & 0x01) != 0 ? 1 : 0)}";
109 Console.WriteLine($"STATUS = 0x{status:x2} {statStr}");
110
111 // Get RX and TX addresses
112 var txAddrRaw = ReadRegister(0x10, 5);
113 var rxAddrP0Raw = ReadRegister(0x0a, 5);
114 var rxAddrP1Raw = ReadRegister(0x0b, 5);
115 var rxAddrP2Raw = ReadRegister(0x0c, 5);
116 var rxAddrP3Raw = ReadRegister(0x0d, 5);
117 var rxAddrP4Raw = ReadRegister(0x0e, 5);
118 var rxAddrP5Raw = ReadRegister(0x0f, 5);
119
120 long txAddr = GetAddrFromRaw(txAddrRaw);
121 long rxAddrP0 = GetAddrFromRaw(rxAddrP0Raw);
122 long rxAddrP1 = GetAddrFromRaw(rxAddrP1Raw);
123 long rxAddrP2 = GetAddrFromRaw(rxAddrP2Raw);
124 long rxAddrP3 = GetAddrFromRaw(rxAddrP3Raw);
125 long rxAddrP4 = GetAddrFromRaw(rxAddrP4Raw);
126 long rxAddrP5 = GetAddrFromRaw(rxAddrP5Raw);
127
128 //long txAddr = ((((rxAddrP0Raw[0] << 8) + txAddrRaw[1] << 8) + txAddrRaw[2] << 8) + txAddrRaw[3] << 8) + txAddrRaw[4];
129
130 Console.WriteLine($"RX_ADDR_P0-1 = 0x{rxAddrP0:x10} 0x{rxAddrP1:x10}");
131 Console.WriteLine($"RX_ADDR_P2-5 = 0x{rxAddrP2:x10} 0x{rxAddrP3:x10} 0x{rxAddrP4:x10} 0x{rxAddrP5:x10}");
132 Console.WriteLine($"TX_ADDR = 0x{txAddr:x10}");
133
134 Console.WriteLine($"EN_AA = 0x{registers[1]:x2}");
135 Console.WriteLine($"EN_RXADDR = 0x{registers[2]:x2}");
136 Console.WriteLine($"RF_CH = 0x{registers[5]:x2}");
137 Console.WriteLine($"RF_SETUP = 0x{registers[6]:x2}");
138 Console.WriteLine($"CONFIG = 0x{registers[0]:x2}");
139 Console.WriteLine($"DYNPD/FEATURE = 0x{registers[0x1c]:x2} 0x{registers[0x1d]:x2}");
140 Console.WriteLine("");
141
142 for (int reg = 0; reg < registers.Count; reg++)
143 {
144 Console.Write($"Reg {reg:x2} = 0x{registers[reg]:x2} ");
145 }
146 Console.WriteLine("");
147 }
148
149 private long GetAddrFromRaw(byte[] raw)
150 {
151 //return BitConverter.ToInt64(raw, 0);
152 return ((long)raw[0] << 32) + ((long)raw[1] << 24) + ((long)raw[2] << 16) + ((long)raw[3] << 8) + (long)raw[4];
153 }
154
155 public void Dispose()
156 {
157 Dispose(true);
158 GC.SuppressFinalize(this);
159 }
160
161 protected virtual void Dispose(bool disposing)
162 {
163 if (_disposed) return;
164 if (disposing)
165 {
166 senderDevice.Dispose();
167 }
168
169 // Free any unmanaged objects here.
170 _disposed = true;
171 }
172
173 ~RF24Controller()
174 {
175 Dispose(false);
176 }
177 }
178}