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 :).

RPI NRF Board Wiring

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:

RPI NRF Board Wiring RPI NRF Board Wiring RPI NRF Board Wiring


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}