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:
#!/usr/bin/python3
import RPi.GPIO as GPIO # import gpio
import time #import time library
import spidev
from lib_nrf24 import NRF24 #import NRF24 library
# Loosely based on: https://circuitdigest.com/microcontroller-projects/wireless-rf-communication-between-arduino-and-raspberry-pi-using-nrf24l01
GPIO.setmode(GPIO.BCM) # set the gpio mode
# set the pipe address. this address shoeld be entered on the receiver alo
pipes = [[0xE0, 0xE0, 0xF1, 0xF1, 0xE0], [0xF1, 0xF1, 0xF0, 0xF0, 0xE0]]
radio = NRF24(GPIO, spidev.SpiDev()) # use the gpio pins
radio.begin(0, 25) # start the radio and set the ce,csn pin ce= GPIO08, csn= GPIO25
radio.setPayloadSize(32) #set the payload size as 32 bytes
radio.setChannel(0x76) # set the channel as 76 hex
radio.setDataRate(NRF24.BR_1MBPS) # set radio data rate
radio.setPALevel(NRF24.PA_MAX) # set PA level
radio.setAutoAck(True) # set acknowledgement as true
radio.enableDynamicPayloads()
radio.enableAckPayload()
radio.openWritingPipe(pipes[0]) # open the defined pipe for writing
radio.printDetails() # print basic detals of radio
sendMessage = list("Hi..Arduino UNO") #the message to be sent
while len(sendMessage) < 32:
sendMessage.append(0)
while True:
start = time.time() #start the time for checking delivery time
radio.write(sendMessage) # just write the message to radio
print("Sent the message: {}".format(sendMessage)) # print a message after succesfull send
radio.startListening() # Start listening the radio
while not radio.available(0):
time.sleep(1/100)
if time.time() - start > 2:
print("Timed out.") # print errror message if radio disconnected or not functioning anymore
break
radio.stopListening() # close radio
time.sleep(3) # give delay of 3 seconds
lib_nrf24.py:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24".
# So this is my tweak for Raspberry Pi and "Virtual GPIO" ...
# ... of Barraca's port to BeagleBone python ... (Joao Paulo Barraca <[email protected]>)
# ... of maniacbug's NRF24L01 C++ library for Arduino.
# Brian Lavery Oct 2014
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
import sys
import time
if __name__ == '__main__':
print (sys.argv[0], 'is an importable module:')
print ("... from", sys.argv[0], "import lib_nrf24")
print ("")
exit()
def _BV(x):
return 1 << x
class NRF24:
MAX_CHANNEL = 127
MAX_PAYLOAD_SIZE = 32
# PA Levels
PA_MIN = 0
PA_LOW = 1
PA_HIGH = 2
PA_MAX = 3
PA_ERROR = 4
# Bit rates
BR_1MBPS = 0
BR_2MBPS = 1
BR_250KBPS = 2
# CRC
CRC_DISABLED = 0
CRC_8 = 1
CRC_16 = 2
CRC_ENABLED = 3
# Registers
CONFIG = 0x00
EN_AA = 0x01
EN_RXADDR = 0x02
SETUP_AW = 0x03
SETUP_RETR = 0x04
RF_CH = 0x05
RF_SETUP = 0x06
STATUS = 0x07
OBSERVE_TX = 0x08
CD = 0x09
RX_ADDR_P0 = 0x0A
RX_ADDR_P1 = 0x0B
RX_ADDR_P2 = 0x0C
RX_ADDR_P3 = 0x0D
RX_ADDR_P4 = 0x0E
RX_ADDR_P5 = 0x0F
TX_ADDR = 0x10
RX_PW_P0 = 0x11
RX_PW_P1 = 0x12
RX_PW_P2 = 0x13
RX_PW_P3 = 0x14
RX_PW_P4 = 0x15
RX_PW_P5 = 0x16
FIFO_STATUS = 0x17
DYNPD = 0x1C
FEATURE = 0x1D
# Bit Mnemonics */
MASK_RX_DR = 6
MASK_TX_DS = 5
MASK_MAX_RT = 4
EN_CRC = 3
CRCO = 2
PWR_UP = 1
PRIM_RX = 0
ENAA_P5 = 5
ENAA_P4 = 4
ENAA_P3 = 3
ENAA_P2 = 2
ENAA_P1 = 1
ENAA_P0 = 0
ERX_P5 = 5
ERX_P4 = 4
ERX_P3 = 3
ERX_P2 = 2
ERX_P1 = 1
ERX_P0 = 0
AW = 0
ARD = 4
ARC = 0
PLL_LOCK = 4
RF_DR = 3
RF_PWR = 6
RX_DR = 6
TX_DS = 5
MAX_RT = 4
RX_P_NO = 1
TX_FULL = 0
PLOS_CNT = 4
ARC_CNT = 0
TX_REUSE = 6
FIFO_FULL = 5
TX_EMPTY = 4
RX_FULL = 1
RX_EMPTY = 0
DPL_P5 = 5
DPL_P4 = 4
DPL_P3 = 3
DPL_P2 = 2
DPL_P1 = 1
DPL_P0 = 0
EN_DPL = 2
EN_ACK_PAY = 1
EN_DYN_ACK = 0
# Instruction Mnemonics
R_REGISTER = 0x00
W_REGISTER = 0x20
REGISTER_MASK = 0x1F
ACTIVATE = 0x50
R_RX_PL_WID = 0x60
R_RX_PAYLOAD = 0x61
W_TX_PAYLOAD = 0xA0
W_ACK_PAYLOAD = 0xA8
FLUSH_TX = 0xE1
FLUSH_RX = 0xE2
REUSE_TX_PL = 0xE3
NOP = 0xFF
# Non-P omissions
LNA_HCURR = 0x00
# P model memory Map
RPD = 0x09
# P model bit Mnemonics
RF_DR_LOW = 5
RF_DR_HIGH = 3
RF_PWR_LOW = 1
RF_PWR_HIGH = 2
# Signal Mnemonics
LOW = 0
HIGH = 1
datarate_e_str_P = ["1MBPS", "2MBPS", "250KBPS"]
model_e_str_P = ["nRF24L01", "nRF24l01+"]
crclength_e_str_P = ["Disabled", "8 bits", "16 bits"]
pa_dbm_e_str_P = ["PA_MIN", "PA_LOW", "PA_MED", "PA_HIGH"]
child_pipe = [RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5]
child_payload_size = [RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5]
child_pipe_enable = [ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5]
GPIO = None
spidev = None
def __init__(self, gpio, spidev):
# It should be possible to instantiate multiple objects, with different GPIO / spidev
# EG on Raspberry, one could be RPI GPIO & spidev module, other could be virtual-GPIO
# On rpi, only bus 0 is supported here, not bus 1 of the model B plus
self.GPIO = gpio # the GPIO module
self.spidev = spidev # the spidev object/instance
self.channel = 76
self.data_rate = NRF24.BR_1MBPS
self.wide_band = False # 2Mbs data rate in use?
self.p_variant = False # False for RF24L01 and true for RF24L01P (nrf24l01+)
self.payload_size = 5 #*< Fixed size of payloads
self.ack_payload_available = False #*< Whether there is an ack payload waiting
self.dynamic_payloads_enabled = False #*< Whether dynamic payloads are enabled.
self.ack_payload_length = 5 #*< Dynamic size of pending ack payload.
self.pipe0_reading_address = None #*< Last address set on pipe 0 for reading.
def ce(self, level):
if self.ce_pin == 0:
return
# rf24-CE is optional. Tie to HIGH if not used. (Altho, left floating seems to read HIGH anyway??? - risky!)
# Some RF24 modes may NEED control over CE.
# non-powerdown, fixed PTX or RTX role, dynamic payload size & ack-payload: does NOT need CE.
if level == NRF24.HIGH:
self.GPIO.output(self.ce_pin, self.GPIO.HIGH)
else:
self.GPIO.output(self.ce_pin, self.GPIO.LOW)
return
def read_register(self, reg, blen=1):
buf = [NRF24.R_REGISTER | ( NRF24.REGISTER_MASK & reg )]
for col in range(blen):
buf.append(NRF24.NOP)
resp = self.spidev.xfer2(buf)
if blen == 1:
return resp[1]
return resp[1:blen + 1]
def write_register(self, reg, value, length=-1):
buf = [NRF24.W_REGISTER | ( NRF24.REGISTER_MASK & reg )]
###if isinstance(value, (int, long)): # ng for python3. but value should never be long anyway
if isinstance(value, int):
if length < 0:
length = 1
length = min(4, length)
for i in range(length):
buf.insert(1, int(value & 0xff))
value >>= 8
elif isinstance(value, list):
if length < 0:
length = len(value)
for i in range(min(len(value), length)):
buf.append(int(value[len(value) - i - 1] & 0xff))
else:
raise Exception("Value must be int or list")
return self.spidev.xfer2(buf)[0]
def write_payload(self, buf):
data_len = min(self.payload_size, len(buf))
blank_len = 0
if not self.dynamic_payloads_enabled:
blank_len = self.payload_size - data_len
txbuffer = [NRF24.W_TX_PAYLOAD]
for n in buf:
t = type(n)
if t is str:
txbuffer.append(ord(n))
elif t is int:
txbuffer.append(n)
else:
raise Exception("Only ints and chars are supported: Found " + str(t))
if blank_len != 0:
blank = [0x00 for i in range(blank_len)]
txbuffer.extend(blank)
return self.spidev.xfer2(txbuffer)
def read_payload(self, buf, buf_len=-1):
if buf_len < 0:
buf_len = self.payload_size
data_len = min(self.payload_size, buf_len)
blank_len = 0
if not self.dynamic_payloads_enabled:
blank_len = self.payload_size - data_len
txbuffer = [NRF24.NOP for i in range(0, blank_len + data_len + 1)]
txbuffer[0] = NRF24.R_RX_PAYLOAD
payload = self.spidev.xfer2(txbuffer)
del buf[:]
buf.extend(payload[1:data_len + 1])
return data_len
def flush_rx(self):
return self.spidev.xfer2([NRF24.FLUSH_RX])[0]
def flush_tx(self):
return self.spidev.xfer2([NRF24.FLUSH_TX])[0]
def get_status(self):
return self.spidev.xfer2([NRF24.NOP])[0]
def print_status(self, status):
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(
status,
1 if status & _BV(NRF24.RX_DR) else 0,
1 if status & _BV(NRF24.TX_DS) else 0,
1 if status & _BV(NRF24.MAX_RT) else 0,
((status >> NRF24.RX_P_NO) & 7),
1 if status & _BV(NRF24.TX_FULL) else 0)
print (status_str)
def print_observe_tx(self, value):
print ("Observe Tx: %02x Lost Pkts: %d Retries: %d" % (value, value >> NRF24.PLOS_CNT, value & 15))
def print_byte_register(self, name, reg, qty=1):
extra_tab = '\t' if len(name) < 8 else 0
print ("%s\t%c =" % (name, extra_tab)),
while qty > 0:
print ("0x%02x" % (self.read_register(reg))),
qty -= 1
reg += 1
print ("")
def print_address_register(self, name, reg, qty=1):
extra_tab = '\t' if len(name) < 8 else 0
print ("%s\t%c =" % (name, extra_tab)),
while qty > 0:
qty -= 1
buf = reversed(self.read_register(reg, 5))
reg += 1
sys.stdout.write(" 0x"),
for i in buf:
sys.stdout.write("%02x" % i)
print ("")
def setChannel(self, channel):
self.channel = min(max(0, channel), NRF24.MAX_CHANNEL)
self.write_register(NRF24.RF_CH, self.channel)
def getChannel(self):
return self.read_register(NRF24.RF_CH)
def setPayloadSize(self, size):
self.payload_size = min(max(size, 1), NRF24.MAX_PAYLOAD_SIZE)
def getPayloadSize(self):
return self.payload_size
def printDetails(self):
self.print_status(self.get_status())
self.print_address_register("RX_ADDR_P0-1", NRF24.RX_ADDR_P0, 2)
self.print_byte_register("RX_ADDR_P2-5", NRF24.RX_ADDR_P2, 4)
self.print_address_register("TX_ADDR", NRF24.TX_ADDR)
self.print_byte_register("RX_PW_P0-6", NRF24.RX_PW_P0, 6)
self.print_byte_register("EN_AA", NRF24.EN_AA)
self.print_byte_register("EN_RXADDR", NRF24.EN_RXADDR)
self.print_byte_register("RF_CH", NRF24.RF_CH)
self.print_byte_register("RF_SETUP", NRF24.RF_SETUP)
self.print_byte_register("CONFIG", NRF24.CONFIG)
self.print_byte_register("DYNPD/FEATURE", NRF24.DYNPD, 2)
#
print ("Data Rate\t = %s" % NRF24.datarate_e_str_P[self.getDataRate()])
print ("Model\t\t = %s" % NRF24.model_e_str_P[self.isPVariant()])
print ("CRC Length\t = %s" % NRF24.crclength_e_str_P[self.getCRCLength()])
print ("PA Power\t = %s" % NRF24.pa_dbm_e_str_P[self.getPALevel()])
def begin(self, csn_pin, ce_pin=0): # csn & ce are RF24 terminology. csn = SPI's CE!
# Initialize SPI bus..
# ce_pin is for the rx=listen or tx=trigger pin on RF24 (they call that ce !!!)
# CE optional (at least in some circumstances, eg fixed PTX PRX roles, no powerdown)
# CE seems to hold itself as (sufficiently) HIGH, but tie HIGH is safer!
self.spidev.open(0, csn_pin)
self.ce_pin = ce_pin
if ce_pin:
self.GPIO.setup(self.ce_pin, self.GPIO.OUT)
self.spidev.max_speed_hz = 8000000
time.sleep(5 / 1000000.0)
# Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier
# WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet
# sizes must never be used. See documentation for a more complete explanation.
self.write_register(NRF24.SETUP_RETR, (0b0100 << NRF24.ARD) | 0b1111)
# Restore our default PA level
self.setPALevel(NRF24.PA_MAX)
# Determine if this is a p or non-p RF24 module and then
# reset our data rate back to default value. This works
# because a non-P variant won't allow the data rate to
# be set to 250Kbps.
if self.setDataRate(NRF24.BR_250KBPS):
self.p_variant = True
# Then set the data rate to the slowest (and most reliable) speed supported by all
# hardware.
self.setDataRate(NRF24.BR_1MBPS)
# Initialize CRC and request 2-byte (16bit) CRC
self.setCRCLength(NRF24.CRC_16)
# Disable dynamic payloads, to match dynamic_payloads_enabled setting
self.write_register(NRF24.DYNPD, 0)
# Reset current status
# Notice reset and flush is the last thing we do
self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
# Set up default configuration. Callers can always change it later.
# This channel should be universally safe and not bleed over into adjacent
# spectrum.
self.setChannel(self.channel)
# Flush buffers
self.flush_rx()
self.flush_tx()
def end(self):
if self.spidev:
self.spidev.close()
self.spidev = None
def startListening(self):
self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) | _BV(NRF24.PRIM_RX))
self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
# Restore the pipe0 address, if exists
if self.pipe0_reading_address:
self.write_register(self.RX_ADDR_P0, self.pipe0_reading_address, 5)
# Go!
self.ce(NRF24.HIGH)
# wait for the radio to come up (130us actually only needed)
time.sleep(130 / 1000000.0)
def stopListening(self):
self.ce(NRF24.LOW)
self.flush_tx()
self.flush_rx()
def powerDown(self):
self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) & ~_BV(NRF24.PWR_UP))
def powerUp(self):
self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP))
time.sleep(150 / 1000000.0)
def write(self, buf):
# Begin the write
self.startWrite(buf)
timeout = self.getMaxTimeout() #s to wait for timeout
sent_at = time.time()
while True:
#status = self.read_register(NRF24.OBSERVE_TX, 1)
status = self.get_status()
if (status & (_BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))) or (time.time() - sent_at > timeout ):
break
time.sleep(10 / 1000000.0)
#obs = self.read_register(NRF24.OBSERVE_TX)
#self.print_observe_tx(obs)
#self.print_status(status)
# (for debugging)
what = self.whatHappened()
result = what['tx_ok']
if what['tx_fail']:
self.flush_tx(); # bl - dont jam up the fifo
# Handle the ack packet
if what['rx_ready']:
self.ack_payload_length = self.getDynamicPayloadSize()
self.ack_payload_available = True ## bl
return result
def startWrite(self, buf):
# Transmitter power-up
self.write_register(NRF24.CONFIG, (self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) ) & ~_BV(NRF24.PRIM_RX))
# Send the payload
self.write_payload(buf)
# Allons!
if self.ce_pin:
if self.GPIO.RPI_REVISION > 0:
self.ce(self.GPIO.HIGH)
time.sleep(10 / 1000000.0)
self.ce(self.GPIO.LOW)
else:
# virtGPIO is slower. A 10 uSec pulse is better done with pulseOut():
self.GPIO.pulseOut(self.ce_pin, self.GPIO.HIGH, 10)
def getDynamicPayloadSize(self):
return self.spidev.xfer2([NRF24.R_RX_PL_WID, NRF24.NOP])[1]
def available(self, pipe_num=None):
if not pipe_num:
pipe_num = []
status = self.get_status()
result = False
# Sometimes the radio specifies that there is data in one pipe but
# doesn't set the RX flag...
if status & _BV(NRF24.RX_DR) or (status & 0b00001110 != 0b00001110):
result = True
if result:
# If the caller wants the pipe number, include that
if len(pipe_num) >= 1:
pipe_num[0] = ( status >> NRF24.RX_P_NO ) & 0b00000111
# Clear the status bit
# ??? Should this REALLY be cleared now? Or wait until we
# actually READ the payload?
self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR))
# Handle ack payload receipt
if status & _BV(NRF24.TX_DS):
self.write_register(NRF24.STATUS, _BV(NRF24.TX_DS))
return result
def read(self, buf, buf_len=-1):
# Fetch the payload
self.read_payload(buf, buf_len)
# was this the last of the data available?
return self.read_register(NRF24.FIFO_STATUS) & _BV(NRF24.RX_EMPTY)
def whatHappened(self):
# Read the status & reset the status in one easy call
# Or is that such a good idea?
status = self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
# Report to the user what happened
tx_ok = status & _BV(NRF24.TX_DS)
tx_fail = status & _BV(NRF24.MAX_RT)
rx_ready = status & _BV(NRF24.RX_DR)
return {'tx_ok': tx_ok, "tx_fail": tx_fail, "rx_ready": rx_ready}
def openWritingPipe(self, value):
# Note that the NRF24L01(+)
# expects it LSB first.
self.write_register(NRF24.RX_ADDR_P0, value, 5)
self.write_register(NRF24.TX_ADDR, value, 5)
max_payload_size = 32
self.write_register(NRF24.RX_PW_P0, min(self.payload_size, max_payload_size))
def openReadingPipe(self, child, address):
# If this is pipe 0, cache the address. This is needed because
# openWritingPipe() will overwrite the pipe 0 address, so
# startListening() will have to restore it.
if child == 0:
self.pipe0_reading_address = address
if child <= 6:
# For pipes 2-5, only write the LSB
if child < 2:
self.write_register(NRF24.child_pipe[child], address, 5)
else:
self.write_register(NRF24.child_pipe[child], address, 1)
self.write_register(NRF24.child_payload_size[child], self.payload_size)
# Note it would be more efficient to set all of the bits for all open
# pipes at once. However, I thought it would make the calling code
# more simple to do it this way.
self.write_register(NRF24.EN_RXADDR,
self.read_register(NRF24.EN_RXADDR) | _BV(NRF24.child_pipe_enable[child]))
def closeReadingPipe(self, pipe):
self.write_register(NRF24.EN_RXADDR,
self.read_register(EN_RXADDR) & ~_BV(NRF24.child_pipe_enable[pipe]))
def toggle_features(self):
buf = [NRF24.ACTIVATE, 0x73]
self.spidev.xfer2(buf)
def enableDynamicPayloads(self):
# Enable dynamic payload throughout the system
self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL))
# If it didn't work, the features are not enabled
if not self.read_register(NRF24.FEATURE):
# So enable them and try again
self.toggle_features()
self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL))
# Enable dynamic payload on all pipes
# Not sure the use case of only having dynamic payload on certain
# pipes, so the library does not support it.
self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P5) | _BV(NRF24.DPL_P4) | _BV(
NRF24.DPL_P3) | _BV(NRF24.DPL_P2) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0))
self.dynamic_payloads_enabled = True
def enableAckPayload(self):
# enable ack payload and dynamic payload features
self.write_register(NRF24.FEATURE,
self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL))
# If it didn't work, the features are not enabled
if not self.read_register(NRF24.FEATURE):
# So enable them and try again
self.toggle_features()
self.write_register(NRF24.FEATURE,
self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL))
# Enable dynamic payload on pipes 0 & 1
self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0))
def writeAckPayload(self, pipe, buf, buf_len):
txbuffer = [NRF24.W_ACK_PAYLOAD | ( pipe & 0x7 )]
max_payload_size = 32
data_len = min(buf_len, max_payload_size)
txbuffer.extend(buf[0:data_len])
self.spidev.xfer2(txbuffer)
def isAckPayloadAvailable(self):
result = self.ack_payload_available
self.ack_payload_available = False
return result
def isPVariant(self):
return self.p_variant
def setAutoAck(self, enable):
if enable:
self.write_register(NRF24.EN_AA, 0b111111)
else:
self.write_register(NRF24.EN_AA, 0)
def setAutoAckPipe(self, pipe, enable):
if pipe <= 6:
en_aa = self.read_register(NRF24.EN_AA)
if enable:
en_aa |= _BV(pipe)
else:
en_aa &= ~_BV(pipe)
self.write_register(NRF24.EN_AA, en_aa)
def testCarrier(self):
return self.read_register(NRF24.CD) & 1
def testRPD(self):
return self.read_register(NRF24.RPD) & 1
def setPALevel(self, level):
setup = self.read_register(NRF24.RF_SETUP)
setup &= ~( _BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
# switch uses RAM (evil!)
if level == NRF24.PA_MAX:
setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
elif level == NRF24.PA_HIGH:
setup |= _BV(NRF24.RF_PWR_HIGH)
elif level == NRF24.PA_LOW:
setup |= _BV(NRF24.RF_PWR_LOW)
elif level == NRF24.PA_MIN:
nop = 0
elif level == NRF24.PA_ERROR:
# On error, go to maximum PA
setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
self.write_register(NRF24.RF_SETUP, setup)
def getPALevel(self):
power = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
if power == (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)):
return NRF24.PA_MAX
elif power == _BV(NRF24.RF_PWR_HIGH):
return NRF24.PA_HIGH
elif power == _BV(NRF24.RF_PWR_LOW):
return NRF24.PA_LOW
else:
return NRF24.PA_MIN
def setDataRate(self, speed):
result = False
setup = self.read_register(NRF24.RF_SETUP)
# HIGH and LOW '00' is 1Mbs - our default
self.wide_band = False
setup &= ~(_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH))
if speed == NRF24.BR_250KBPS:
# Must set the RF_DR_LOW to 1 RF_DR_HIGH (used to be RF_DR) is already 0
# Making it '10'.
self.wide_band = False
setup |= _BV(NRF24.RF_DR_LOW)
else:
# Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1
# Making it '01'
if speed == NRF24.BR_2MBPS:
self.wide_band = True
setup |= _BV(NRF24.RF_DR_HIGH)
else:
# 1Mbs
self.wide_band = False
self.write_register(NRF24.RF_SETUP, setup)
# Verify our result
if self.read_register(NRF24.RF_SETUP) == setup:
result = True
else:
self.wide_band = False
return result
def getDataRate(self):
dr = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH))
# Order matters in our case below
if dr == _BV(NRF24.RF_DR_LOW):
# '10' = 250KBPS
return NRF24.BR_250KBPS
elif dr == _BV(NRF24.RF_DR_HIGH):
# '01' = 2MBPS
return NRF24.BR_2MBPS
else:
# '00' = 1MBPS
return NRF24.BR_1MBPS
def setCRCLength(self, length):
config = self.read_register(NRF24.CONFIG) & ~( _BV(NRF24.CRC_16) | _BV(NRF24.CRC_ENABLED))
if length == NRF24.CRC_DISABLED:
# Do nothing, we turned it off above.
self.write_register(NRF24.CONFIG, config)
return
elif length == NRF24.CRC_8:
config |= _BV(NRF24.CRC_ENABLED)
config |= _BV(NRF24.CRC_8)
else:
config |= _BV(NRF24.CRC_ENABLED)
config |= _BV(NRF24.CRC_16)
self.write_register(NRF24.CONFIG, config)
def getCRCLength(self):
result = NRF24.CRC_DISABLED
config = self.read_register(NRF24.CONFIG) & ( _BV(NRF24.CRCO) | _BV(NRF24.EN_CRC))
if config & _BV(NRF24.EN_CRC):
if config & _BV(NRF24.CRCO):
result = NRF24.CRC_16
else:
result = NRF24.CRC_8
return result
def disableCRC(self):
disable = self.read_register(NRF24.CONFIG) & ~_BV(NRF24.EN_CRC)
self.write_register(NRF24.CONFIG, disable)
def setRetries(self, delay, count):
# see specs. Delay code below 5 can conflict with some ACK lengths
# and count should be set = 0 for non-ACK modes
self.write_register(NRF24.SETUP_RETR, (delay & 0xf) << NRF24.ARD | (count & 0xf))
def getRetries(self):
return self.read_register(NRF24.SETUP_RETR)
def getMaxTimeout(self): # seconds
retries = self.getRetries()
tout = (((250+(250*((retries& 0xf0)>>4 ))) * (retries & 0x0f)) / 1000000.0 * 2) + 0.008
# Fudged up to about double Barraca's calculation
# Was too short & was timeing out wrongly. BL
return tout
On an Arduino Nano, I had this code:
NRF24L01.ino:
#include<SPI.h>
#include<RF24.h>
// Loosely based on: https://circuitdigest.com/microcontroller-projects/wireless-rf-communication-between-arduino-and-raspberry-pi-using-nrf24l01
RF24 radio(7, 8);
int count = 0;
unsigned long lastTime = 0;
void setup()
{
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
//while (!Serial);
Serial.begin(9600) ; // start serial monitor baud rate
radio.begin();
radio.setPALevel(RF24_PA_MIN);
radio.setChannel(0x76);
radio.setPayloadSize(15);
const uint64_t pipe = 0xE0E0F1F1E0LL;
radio.openReadingPipe(0, pipe);
//radio.enableDynamicPayloads();
radio.powerUp();
radio.startListening();
}
void loop()
{
bool msgReceived = false;
digitalWrite(2, count & 1 ? HIGH : LOW);
digitalWrite(3, count & 2 ? HIGH : LOW);
digitalWrite(4, count & 4 ? HIGH : LOW);
while (lastTime + 4000 > millis())
{
if (radio.available())
{
char receivedMessage[32] = {0} ;
radio.read(receivedMessage, sizeof(receivedMessage));
radio.stopListening();
radio.startListening();
Serial.println(receivedMessage);
msgReceived = true;
}
}
if (!msgReceived)
Serial.println("No message received " + String(count));
lastTime = millis();
count++;
}
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):
using System;
using System.Text;
using System.Threading;
using System.Device.Gpio;
using System.Device.Spi;
using Iot.Device.Nrf24l01;
using System.Collections.Generic;
// See https://github.com/dotnet/iot for System.Device.Gpio source code and help
namespace RF24Lib
{
/// <summary>
/// Control NRF24L01 board
/// </summary>
public class RF24Controller
{
private SpiDevice senderDevice;
private GpioController _controller;
private bool _disposed = false;
public RF24Controller(GpioController controller)
{
_controller = controller;
}
public void Initialize()
{
// SPI0 CS0
SpiConnectionSettings senderSettings = new SpiConnectionSettings(0, 0)
{
ClockFrequency = Nrf24l01.SpiClockFrequency,
Mode = Nrf24l01.SpiMode
};
senderDevice = SpiDevice.Create(senderSettings);
}
public void SendMessage(string msg)
{
// SPI Device, CE Pin, IRQ Pin, Receive Packet Size
//using (Nrf24l01 sender = new Nrf24l01(senderDevice, 25, 22, 32, 0x76, OutputPower.N18dBm, DataRate.Rate1Mbps)) //, PinNumberingScheme.Logical, _controller, false))
Nrf24l01 sender = new Nrf24l01(senderDevice, 25, 22, 15, 0x76, OutputPower.N18dBm, DataRate.Rate1Mbps);
var addr = new byte[] { 0xE0, 0xF1, 0xF1, 0xE0, 0xE0 };
sender.Address = addr; //Encoding.UTF8.GetBytes("NRF24");
WriteRegister(0, (byte)(ReadRegister(0)[0] | 0x04)); // Set CRC to 2 bytes rather than 1 byte
WriteRegister(0x1c, 0x3f); // Set dynamic payload for all pipes
WriteRegister(0x1d, 0x04); // enable dynamic payload, disable payload with ACK, disable W_TX_PAYLOAD_NOACK command
var sendStr = new string[] { "test12345678901", "long string----", "short----------", "longer---------", "10 chars..-----", "four-----------" }; // 11 chars max
for (int i = 0; i < 6; i++)
{
DisplayRegisters();
// Set sender send address
//sender.Send(Encoding.UTF8.GetBytes("Hello! .NET Core IoT"));
sender.Send(Encoding.UTF8.GetBytes(sendStr[i]));
Thread.Sleep(1000);
}
}
internal byte[] ReadRegister(byte register, int len = 1)
{
Span<byte> writeBuf = stackalloc byte[1 + len];
Span<byte> readBuf = stackalloc byte[1 + len];
writeBuf[0] = (byte)(register);
senderDevice.TransferFullDuplex(writeBuf, readBuf);
return readBuf.Slice(1).ToArray();
}
internal void WriteRegister(byte register, byte data)
{
Span<byte> writeBuf = stackalloc byte[2]
{
(byte)(0x20 + (byte)register),
data
};
Span<byte> readBuf = stackalloc byte[2];
senderDevice.TransferFullDuplex(writeBuf, readBuf);
}
public void DisplayRegisters()
{
var registers = new List<byte>();
Span<byte> writeBuf = stackalloc byte[1 + 1];
Span<byte> readBuf = stackalloc byte[1 + 1];
for (int reg = 0; reg <= 0x1d; reg++)
{
if (reg > 0x17 && reg < 0x1c)
{
registers.Add(0);
}
else
{
registers.Add(ReadRegister((byte)reg)[0]);
}
}
var status = registers[7];
var statStr = $"RX_DR={((status & 0x40) != 0 ? 1 : 0)} TX_DS={((status & 0x20) != 0 ? 1 : 0)} MAX_RT={((status & 0x10) != 0 ? 1 : 0)}" +
$"RX_P_NO={status & 0x0e >> 1} TX_FULL={((status & 0x01) != 0 ? 1 : 0)}";
Console.WriteLine($"STATUS = 0x{status:x2} {statStr}");
// Get RX and TX addresses
var txAddrRaw = ReadRegister(0x10, 5);
var rxAddrP0Raw = ReadRegister(0x0a, 5);
var rxAddrP1Raw = ReadRegister(0x0b, 5);
var rxAddrP2Raw = ReadRegister(0x0c, 5);
var rxAddrP3Raw = ReadRegister(0x0d, 5);
var rxAddrP4Raw = ReadRegister(0x0e, 5);
var rxAddrP5Raw = ReadRegister(0x0f, 5);
long txAddr = GetAddrFromRaw(txAddrRaw);
long rxAddrP0 = GetAddrFromRaw(rxAddrP0Raw);
long rxAddrP1 = GetAddrFromRaw(rxAddrP1Raw);
long rxAddrP2 = GetAddrFromRaw(rxAddrP2Raw);
long rxAddrP3 = GetAddrFromRaw(rxAddrP3Raw);
long rxAddrP4 = GetAddrFromRaw(rxAddrP4Raw);
long rxAddrP5 = GetAddrFromRaw(rxAddrP5Raw);
//long txAddr = ((((rxAddrP0Raw[0] << 8) + txAddrRaw[1] << 8) + txAddrRaw[2] << 8) + txAddrRaw[3] << 8) + txAddrRaw[4];
Console.WriteLine($"RX_ADDR_P0-1 = 0x{rxAddrP0:x10} 0x{rxAddrP1:x10}");
Console.WriteLine($"RX_ADDR_P2-5 = 0x{rxAddrP2:x10} 0x{rxAddrP3:x10} 0x{rxAddrP4:x10} 0x{rxAddrP5:x10}");
Console.WriteLine($"TX_ADDR = 0x{txAddr:x10}");
Console.WriteLine($"EN_AA = 0x{registers[1]:x2}");
Console.WriteLine($"EN_RXADDR = 0x{registers[2]:x2}");
Console.WriteLine($"RF_CH = 0x{registers[5]:x2}");
Console.WriteLine($"RF_SETUP = 0x{registers[6]:x2}");
Console.WriteLine($"CONFIG = 0x{registers[0]:x2}");
Console.WriteLine($"DYNPD/FEATURE = 0x{registers[0x1c]:x2} 0x{registers[0x1d]:x2}");
Console.WriteLine("");
for (int reg = 0; reg < registers.Count; reg++)
{
Console.Write($"Reg {reg:x2} = 0x{registers[reg]:x2} ");
}
Console.WriteLine("");
}
private long GetAddrFromRaw(byte[] raw)
{
//return BitConverter.ToInt64(raw, 0);
return ((long)raw[0] << 32) + ((long)raw[1] << 24) + ((long)raw[2] << 16) + ((long)raw[3] << 8) + (long)raw[4];
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
senderDevice.Dispose();
}
// Free any unmanaged objects here.
_disposed = true;
}
~RF24Controller()
{
Dispose(false);
}
}
}