Examples

nRF24L01 Features

Simple test

Changed in version 2.0.0:

  • uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.

  • transmits an incrementing float instead of an int

Ensure your device works with this simple test.

examples/nrf24l01_simple_test.py
  4import time
  5import struct
  6import board
  7from digitalio import DigitalInOut
  8
  9# if running this on a ATSAMD21 M0 based board
 10# from circuitpython_nrf24l01.rf24_lite import RF24
 11from circuitpython_nrf24l01.rf24 import RF24
 12
 13# invalid default values for scoping
 14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 15
 16try:  # on Linux
 17    import spidev
 18
 19    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 20    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 21    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 22
 23except ImportError:  # on CircuitPython only
 24    # using board.SPI() automatically selects the MCU's
 25    # available SPI pins, board.SCK, board.MOSI, board.MISO
 26    SPI_BUS = board.SPI()  # init spi bus object
 27
 28    # change these (digital output) pins accordingly
 29    CE_PIN = DigitalInOut(board.D4)
 30    CSN_PIN = DigitalInOut(board.D5)
 31
 32
 33# initialize the nRF24L01 on the spi bus object
 34nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 35# On Linux, csn value is a bit coded
 36#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 37#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 38#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 39
 40# set the Power Amplifier level to -12 dBm since this test example is
 41# usually run with nRF24L01 transceivers in close proximity
 42nrf.pa_level = -12
 43
 44# addresses needs to be in a buffer protocol object (bytearray)
 45address = [b"1Node", b"2Node"]
 46
 47# to use different addresses on a pair of radios, we need a variable to
 48# uniquely identify which address this radio will use to transmit
 49# 0 uses address[0] to transmit, 1 uses address[1] to transmit
 50radio_number = bool(
 51    int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
 52)
 53
 54# set TX address of RX node into the TX pipe
 55nrf.open_tx_pipe(address[radio_number])  # always uses pipe 0
 56
 57# set RX address of TX node into an RX pipe
 58nrf.open_rx_pipe(1, address[not radio_number])  # using pipe 1
 59
 60# using the python keyword global is bad practice. Instead we'll use a 1 item
 61# list to store our float number for the payloads sent
 62payload = [0.0]
 63
 64# uncomment the following 3 lines for compatibility with TMRh20 library
 65# nrf.allow_ask_no_ack = False
 66# nrf.dynamic_payloads = False
 67# nrf.payload_length = 4
 68
 69
 70def master(count=5):  # count = 5 will only transmit 5 packets
 71    """Transmits an incrementing integer every second"""
 72    nrf.listen = False  # ensures the nRF24L01 is in TX mode
 73
 74    while count:
 75        # use struct.pack to packetize your data
 76        # into a usable payload
 77        buffer = struct.pack("<f", payload[0])
 78        # "<f" means a single little endian (4 byte) float value.
 79        start_timer = time.monotonic_ns()  # start timer
 80        result = nrf.send(buffer)
 81        end_timer = time.monotonic_ns()  # end timer
 82        if not result:
 83            print("send() failed or timed out")
 84        else:
 85            print(
 86                "Transmission successful! Time to Transmit:",
 87                f"{(end_timer - start_timer) / 1000} us. Sent: {payload[0]}"
 88            )
 89            payload[0] += 0.01
 90        time.sleep(1)
 91        count -= 1
 92
 93
 94def slave(timeout=6):
 95    """Polls the radio and prints the received value. This method expires
 96    after 6 seconds of no received transmission"""
 97    nrf.listen = True  # put radio into RX mode and power up
 98
 99    start = time.monotonic()
100    while (time.monotonic() - start) < timeout:
101        if nrf.available():
102            # grab information about the received payload
103            payload_size, pipe_number = (nrf.any(), nrf.pipe)
104            # fetch 1 payload from RX FIFO
105            buffer = nrf.read()  # also clears nrf.irq_dr status flag
106            # expecting a little endian float, thus the format string "<f"
107            # buffer[:4] truncates padded 0s if dynamic payloads are disabled
108            payload[0] = struct.unpack("<f", buffer[:4])[0]
109            # print details about the received packet
110            print(f"Received {payload_size} bytes on pipe {pipe_number}: {payload[0]}")
111            start = time.monotonic()
112
113    # recommended behavior is to keep in TX mode while idle
114    nrf.listen = False  # put the nRF24L01 is in TX mode
115
116

ACK Payloads Example

Changed in version 2.0.0:

  • uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.

  • changed payloads to show use of c-strings’ NULL terminating character.

This is a test to show how to use custom acknowledgment payloads.

See also

More details are found in the documentation on ack and load_ack().

examples/nrf24l01_ack_payload_test.py
  5import time
  6import board
  7from digitalio import DigitalInOut
  8
  9# if running this on a ATSAMD21 M0 based board
 10# from circuitpython_nrf24l01.rf24_lite import RF24
 11from circuitpython_nrf24l01.rf24 import RF24
 12
 13# invalid default values for scoping
 14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 15
 16try:  # on Linux
 17    import spidev
 18
 19    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 20    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 21    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 22
 23except ImportError:  # on CircuitPython only
 24    # using board.SPI() automatically selects the MCU's
 25    # available SPI pins, board.SCK, board.MOSI, board.MISO
 26    SPI_BUS = board.SPI()  # init spi bus object
 27
 28    # change these (digital output) pins accordingly
 29    CE_PIN = DigitalInOut(board.D4)
 30    CSN_PIN = DigitalInOut(board.D5)
 31
 32
 33# initialize the nRF24L01 on the spi bus object
 34nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 35# On Linux, csn value is a bit coded
 36#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 37#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 38#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 39
 40# the custom ACK payload feature is disabled by default
 41# NOTE the the custom ACK payload feature will be enabled
 42# automatically when you call load_ack() passing:
 43# a buffer protocol object (bytearray) of
 44# length ranging [1,32]. And pipe number always needs
 45# to be an int ranging [0, 5]
 46
 47# to enable the custom ACK payload feature
 48nrf.ack = True  # False disables again
 49
 50# set the Power Amplifier level to -12 dBm since this test example is
 51# usually run with nRF24L01 transceivers in close proximity
 52nrf.pa_level = -12
 53
 54# addresses needs to be in a buffer protocol object (bytearray)
 55address = [b"1Node", b"2Node"]
 56
 57# to use different addresses on a pair of radios, we need a variable to
 58# uniquely identify which address this radio will use to transmit
 59# 0 uses address[0] to transmit, 1 uses address[1] to transmit
 60radio_number = bool(
 61    int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
 62)
 63
 64# set TX address of RX node into the TX pipe
 65nrf.open_tx_pipe(address[radio_number])  # always uses pipe 0
 66
 67# set RX address of TX node into an RX pipe
 68nrf.open_rx_pipe(1, address[not radio_number])  # using pipe 1
 69
 70# using the python keyword global is bad practice. Instead we'll use a 1 item
 71# list to store our integer number for the payloads' counter
 72counter = [0]
 73
 74
 75def master(count=5):  # count = 5 will only transmit 5 packets
 76    """Transmits a payload every second and prints the ACK payload"""
 77    nrf.listen = False  # put radio in TX mode
 78
 79    while count:
 80        # construct a payload to send
 81        # add b"\0" as a c-string NULL terminating char
 82        buffer = b"Hello \0" + bytes([counter[0]])
 83        start_timer = time.monotonic_ns()  # start timer
 84        result = nrf.send(buffer)  # save the response (ACK payload)
 85        end_timer = time.monotonic_ns()  # stop timer
 86        if result:
 87            # print the received ACK that was automatically
 88            # fetched and saved to "result" via send()
 89            # print timer results upon transmission success
 90            print(
 91                "Transmission successful! Time to transmit:",
 92                f"{int((end_timer - start_timer) / 1000)} us.",
 93                "Sent: {}{}".format(buffer[:6].decode("utf-8"), counter[0]),
 94                end=" ",
 95            )
 96            if isinstance(result, bool):
 97                print("Received an empty ACK packet")
 98            else:
 99                # result[:6] truncates c-string NULL termiating char
100                # received counter is a unsigned byte, thus result[7:8][0]
101                print(
102                    "Received: {}{}".format(result[:6].decode("utf-8"), result[7:8][0])
103                )
104            counter[0] += 1  # increment payload counter
105        elif not result:
106            print("send() failed or timed out")
107        time.sleep(1)  # let the RX node prepare a new ACK payload
108        count -= 1
109
110
111def slave(timeout=6):
112    """Prints the received value and sends an ACK payload"""
113    nrf.listen = True  # put radio into RX mode, power it up
114
115    # setup the first transmission's ACK payload
116    # add b"\0" as a c-string NULL terminating char
117    buffer = b"World \0" + bytes([counter[0]])
118    # we must set the ACK payload data and corresponding
119    # pipe number [0, 5]. We'll be acknowledging pipe 1
120    nrf.load_ack(buffer, 1)  # load ACK for first response
121
122    start = time.monotonic()  # start timer
123    while (time.monotonic() - start) < timeout:
124        if nrf.available():
125            # grab information about the received payload
126            length, pipe_number = (nrf.any(), nrf.pipe)
127            # retreive the received packet's payload
128            received = nrf.read()
129            # increment counter from received payload
130            # received counter is a unsigned byte, thus result[7:8][0]
131            counter[0] = received[7:8][0] + 1
132            # the [:6] truncates the c-string NULL termiating char
133            print(
134                f"Received {length} bytes on pipe {pipe_number}:",
135                "{}{}".format(received[:6].decode("utf-8"), received[7:8][0]),
136                "Sent: {}{}".format(buffer[:6].decode("utf-8"), buffer[7:8][0]),
137            )
138            start = time.monotonic()  # reset timer
139            buffer = b"World \0" + bytes([counter[0]])  # build new ACK
140            nrf.load_ack(buffer, 1)  # load ACK for next response
141
142    # recommended behavior is to keep in TX mode while idle
143    nrf.listen = False  # put radio in TX mode
144    nrf.flush_tx()  # flush any ACK payloads that remain
145
146

Multiceiver Example

New in version 1.2.2.

Changed in version 2.0.0: no longer uses ACK payloads for responding to node 1.

This example shows how use a group of 6 nRF24L01 transceivers to transmit to 1 nRF24L01 transceiver. This technique is called “Multiceiver” in the nRF24L01 Specifications Sheet

Note

This example follows the diagram illistrated in figure 12 of section 7.7 of the nRF24L01 Specifications Sheet Please note that if auto_ack (on the base station) and arc (on the transmitting nodes) are disabled, then figure 10 of section 7.7 of the nRF24L01 Specifications Sheet would be a better illustration.

Hint

A paraphrased note from the the nRF24L01 Specifications Sheet:

Only when a data pipe receives a complete packet can other data pipes begin to receive data. When multiple [nRF24L01]s are transmitting to [one nRF24L01], the ard can be used to skew the auto retransmission so that they only block each other once.

This basically means that it might help packets get received if the ard attribute is set to various values among multiple transmitting nRF24L01 transceivers.

examples/nrf24l01_multiceiver_test.py
  5import time
  6import struct
  7import board
  8from digitalio import DigitalInOut
  9
 10# if running this on a ATSAMD21 M0 based board
 11# from circuitpython_nrf24l01.rf24_lite import RF24
 12from circuitpython_nrf24l01.rf24 import RF24
 13
 14# invalid default values for scoping
 15SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 16
 17try:  # on Linux
 18    import spidev
 19
 20    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 21    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 22    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 23
 24except ImportError:  # on CircuitPython only
 25    # using board.SPI() automatically selects the MCU's
 26    # available SPI pins, board.SCK, board.MOSI, board.MISO
 27    SPI_BUS = board.SPI()  # init spi bus object
 28
 29    # change these (digital output) pins accordingly
 30    CE_PIN = DigitalInOut(board.D4)
 31    CSN_PIN = DigitalInOut(board.D5)
 32
 33
 34# initialize the nRF24L01 on the spi bus object
 35nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 36# On Linux, csn value is a bit coded
 37#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 38#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 39#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 40
 41# set the Power Amplifier level to -12 dBm since this test example is
 42# usually run with nRF24L01 transceivers in close proximity
 43nrf.pa_level = -12
 44
 45# setup the addresses for all transmitting nRF24L01 nodes
 46addresses = [
 47    b"\x78" * 5,
 48    b"\xF1\xB6\xB5\xB4\xB3",
 49    b"\xCD\xB6\xB5\xB4\xB3",
 50    b"\xA3\xB6\xB5\xB4\xB3",
 51    b"\x0F\xB6\xB5\xB4\xB3",
 52    b"\x05\xB6\xB5\xB4\xB3",
 53]
 54
 55# uncomment the following 3 lines for compatibility with TMRh20 library
 56# nrf.allow_ask_no_ack = False
 57# nrf.dynamic_payloads = False
 58# nrf.payload_length = 8
 59
 60
 61def base(timeout=10):
 62    """Use the nRF24L01 as a base station for lisening to all nodes"""
 63    # write the addresses to all pipes.
 64    for pipe_n, addr in enumerate(addresses):
 65        nrf.open_rx_pipe(pipe_n, addr)
 66    nrf.listen = True  # put base station into RX mode
 67    start_timer = time.monotonic()  # start timer
 68    while time.monotonic() - start_timer < timeout:
 69        while not nrf.fifo(False, True):  # keep RX FIFO empty for reception
 70            # show the pipe number that received the payload
 71            # NOTE read() clears the pipe number and payload length data
 72            print("Received", nrf.any(), "on pipe", nrf.pipe, end=" ")
 73            node_id, payload_id = struct.unpack("<ii", nrf.read())
 74            print(f"from node {node_id}. PayloadID: {payload_id}")
 75            start_timer = time.monotonic()  # reset timer with every payload
 76    nrf.listen = False
 77
 78
 79def node(node_number=0, count=6):
 80    """start transmitting to the base station.
 81
 82    :param int node_number: the node's identifying index (from the
 83        the `addresses` list)
 84    :param int count: the number of times that the node will transmit
 85        to the base station.
 86    """
 87    nrf.listen = False
 88    # set the TX address to the address of the base station.
 89    nrf.open_tx_pipe(addresses[node_number])
 90    counter = 0
 91    # use the node_number to identify where the payload came from
 92    while counter < count:
 93        counter += 1
 94        # payloads will include the node_number and a payload ID character
 95        payload = struct.pack("<ii", node_number, counter)
 96        # show something to see it isn't frozen
 97        start_timer = time.monotonic_ns()
 98        report = nrf.send(payload)
 99        end_timer = time.monotonic_ns()
100        # show something to see it isn't frozen
101        if report:
102            print(
103                f"Transmission of payloadID {counter} as node {node_number} success"
104                f"full! Transmission time: {int((end_timer - start_timer) / 1000)} us"
105            )
106        else:
107            print("Transmission failed or timed out")
108        time.sleep(0.5)  # slow down the test for readability
109
110

Scanner Example

New in version 2.0.0.

This example simply scans the entire RF frquency (2.4 GHz to 2.525 GHz) and outputs a vertical graph of how many signals (per channel) were detected. This example can be used to find a frequency with the least ambient interference from other radio-emitting sources (i.e. WiFi, Bluetooth, or etc).

examples/nrf24l01_scanner_test.py
  6import time
  7import board
  8from digitalio import DigitalInOut
  9
 10# if running this on a ATSAMD21 M0 based board
 11# from circuitpython_nrf24l01.rf24_lite import RF24
 12from circuitpython_nrf24l01.rf24 import RF24, address_repr
 13
 14# invalid default values for scoping
 15SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 16
 17try:  # on Linux
 18    import spidev
 19
 20    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 21    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 22    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 23
 24except ImportError:  # on CircuitPython only
 25    # using board.SPI() automatically selects the MCU's
 26    # available SPI pins, board.SCK, board.MOSI, board.MISO
 27    SPI_BUS = board.SPI()  # init spi bus object
 28
 29    # change these (digital output) pins accordingly
 30    CE_PIN = DigitalInOut(board.D4)
 31    CSN_PIN = DigitalInOut(board.D5)
 32
 33
 34# initialize the nRF24L01 on the spi bus object
 35nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 36# On Linux, csn value is a bit coded
 37#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 38#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 39#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 40
 41# turn off RX features specific to the nRF24L01 module
 42nrf.auto_ack = False
 43nrf.dynamic_payloads = False
 44nrf.crc = 0
 45nrf.arc = 0
 46nrf.allow_ask_no_ack = False
 47
 48# use reverse engineering tactics for a better "snapshot"
 49nrf.address_length = 2
 50nrf.open_rx_pipe(1, b"\0\x55")
 51nrf.open_rx_pipe(0, b"\0\xAA")
 52
 53
 54def scan(timeout=30):
 55    """Traverse the spectrum of accessible frequencies and print any detection
 56    of ambient signals.
 57
 58    :param int timeout: The number of seconds in which scanning is performed.
 59    """
 60    # print the vertical header of channel numbers
 61    print("0" * 100 + "1" * 26)
 62    for i in range(13):
 63        print(str(i % 10) * (10 if i < 12 else 6), sep="", end="")
 64    print("")  # endl
 65    for i in range(126):
 66        print(str(i % 10), sep="", end="")
 67    print("\n" + "~" * 126)
 68
 69    signals = [0] * 126  # store the signal count for each channel
 70    curr_channel = 0
 71    start_timer = time.monotonic()  # start the timer
 72    while time.monotonic() - start_timer < timeout:
 73        nrf.channel = curr_channel
 74        if nrf.available():
 75            nrf.flush_rx()  # flush the RX FIFO because it asserts the RPD flag
 76        nrf.listen = 1  # start a RX session
 77        time.sleep(0.00013)  # wait 130 microseconds
 78        signals[curr_channel] += nrf.rpd  # if interference is present
 79        nrf.listen = 0  # end the RX session
 80        curr_channel = curr_channel + 1 if curr_channel < 125 else 0
 81
 82        # ouptut the signal counts per channel
 83        sig_cnt = signals[curr_channel]
 84        print(
 85            ("%X" % min(15, sig_cnt)) if sig_cnt else "-",
 86            sep="",
 87            end="" if curr_channel < 125 else "\r",
 88        )
 89    # finish printing results and end with a new line
 90    while curr_channel < len(signals) - 1:
 91        curr_channel += 1
 92        sig_cnt = signals[curr_channel]
 93        print(("%X" % min(15, sig_cnt)) if sig_cnt else "-", sep="", end="")
 94    print("")
 95
 96
 97def noise(timeout=1, channel=None):
 98    """print a stream of detected noise for duration of time.
 99
100    :param int timeout: The number of seconds to scan for ambiant noise.
101    :param int channel: The specific channel to focus on. If not provided, then the
102        radio's current setting is used.
103    """
104    if channel is not None:
105        nrf.channel = channel
106    nrf.listen = True
107    timeout += time.monotonic()
108    while time.monotonic() < timeout:
109        signal = nrf.read()
110        if signal:
111            print(address_repr(signal, False, " "))
112    nrf.listen = False
113    while not nrf.fifo(False, True):
114        # dump the left overs in the RX FIFO
115        print(address_repr(nrf.read(), False, " "))
116
117

Reading the scanner output

Hint

Make sure the terminal window used to run the scanner example is expanded to fit 125 characters. Otherwise the output will look weird.

The output of the scanner example is supposed to be read vertically (as columns). So, the following

000
111
789
~~~
13-

should be interpreted as

  • 1 signal detected on channel 017

  • 3 signals detected on channel 018

  • no signal (-) detected on channel 019

The ~ is just a divider between the vertical header and the signal counts.

IRQ Pin Example

Changed in version 1.2.0: uses ACK payloads to trigger all 3 IRQ events.

Changed in version 2.0.0: uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.

This is a test to show how to use nRF24L01’s interrupt pin using the non-blocking write(). Also the ack attribute is enabled to trigger the irq_dr event when the master node receives ACK payloads. Simply put, this example is the most advanced example script (in this library), and it runs very quickly.

examples/nrf24l01_interrupt_test.py
  7import time
  8import board
  9import digitalio
 10
 11# if running this on a ATSAMD21 M0 based board
 12# from circuitpython_nrf24l01.rf24_lite import RF24
 13from circuitpython_nrf24l01.rf24 import RF24
 14
 15# select your digital input pin that's connected to the IRQ pin on the nRF4L01
 16irq_pin = digitalio.DigitalInOut(board.D12)
 17irq_pin.switch_to_input()  # make sure its an input object
 18# change these (digital output) pins accordingly
 19CE_PIN = digitalio.DigitalInOut(board.D4)
 20CSN_PIN = digitalio.DigitalInOut(board.D5)
 21
 22# using board.SPI() automatically selects the MCU's
 23# available SPI pins, board.SCK, board.MOSI, board.MISO
 24SPI_BUS = board.SPI()  # init spi bus object
 25
 26# we'll be using the dynamic payload size feature (enabled by default)
 27# initialize the nRF24L01 on the spi bus object
 28nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 29
 30# this example uses the ACK payload to trigger the IRQ pin active for
 31# the "on data received" event
 32nrf.ack = True  # enable ACK payloads
 33
 34# set the Power Amplifier level to -12 dBm since this test example is
 35# usually run with nRF24L01 transceivers in close proximity
 36nrf.pa_level = -12
 37
 38# address needs to be in a buffer protocol object (bytearray is preferred)
 39address = [b"1Node", b"2Node"]
 40
 41# to use different addresses on a pair of radios, we need a variable to
 42# uniquely identify which address this radio will use to transmit
 43# 0 uses address[0] to transmit, 1 uses address[1] to transmit
 44radio_number = bool(
 45    int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
 46)
 47
 48# set TX address of RX node into the TX pipe
 49nrf.open_tx_pipe(address[radio_number])  # always uses pipe 0
 50
 51# set RX address of TX node into an RX pipe
 52nrf.open_rx_pipe(1, address[not radio_number])  # using pipe 1
 53
 54
 55def _ping_and_prompt():
 56    """transmit 1 payload, wait till irq_pin goes active, print IRQ status
 57    flags."""
 58    nrf.ce_pin = 1  # tell the nRF24L01 to prepare sending a single packet
 59    time.sleep(0.00001)  # mandatory 10 microsecond pulse starts transmission
 60    nrf.ce_pin = 0  # end 10 us pulse; use only 1 buffer from TX FIFO
 61    while irq_pin.value:  # IRQ pin is active when LOW
 62        pass
 63    print("IRQ pin went active LOW.")
 64    nrf.update()  # update irq_d? status flags
 65    print(f"\tirq_ds: {nrf.irq_ds}, irq_dr: {nrf.irq_dr}, irq_df: {nrf.irq_df}")
 66
 67
 68def master():
 69    """Transmits 3 times: successfully receive ACK payload first, successfully
 70    transmit on second, and intentionally fail transmit on the third"""
 71    nrf.listen = False  # ensures the nRF24L01 is in TX mode
 72    # NOTE nrf.write() internally calls nrf.clear_status_flags() first
 73
 74    # load 2 buffers into the TX FIFO; write_only=True leaves CE pin LOW
 75    nrf.write(b"Ping ", write_only=True)
 76    nrf.write(b"Pong ", write_only=True)
 77
 78    # on data ready test
 79    print("\nConfiguring IRQ pin to only ignore 'on data sent' event")
 80    nrf.interrupt_config(data_sent=False)
 81    print("    Pinging slave node for an ACK payload...", end=" ")
 82    _ping_and_prompt()  # CE pin is managed by this function
 83    print("\t\"on data ready\" event test {}successful".format("un" * nrf.irq_dr))
 84
 85    # on data sent test
 86    print("\nConfiguring IRQ pin to only ignore 'on data ready' event")
 87    nrf.interrupt_config(data_recv=False)
 88    print("    Pinging slave node again...             ", end=" ")
 89    _ping_and_prompt()  # CE pin is managed by this function
 90    print("\t\"on data sent\" event test {}successful".format("un" * nrf.irq_ds))
 91
 92    # trigger slave node to exit by filling the slave node's RX FIFO
 93    print("\nSending one extra payload to fill RX FIFO on slave node.")
 94    if nrf.send(b"Radio", send_only=True):
 95        # when send_only parameter is True, send() ignores RX FIFO usage
 96        if nrf.fifo(False, False):  # is RX FIFO full?
 97            print("Slave node should not be listening anymore.")
 98        else:
 99            print("transmission succeeded, " "but slave node might still be listening")
100    else:
101        print("Slave node was unresponsive.")
102
103    # on data fail test
104    print("\nConfiguring IRQ pin to go active for all events.")
105    nrf.interrupt_config()
106    print("    Sending a ping to inactive slave node...", end=" ")
107    nrf.flush_tx()  # just in case any previous tests failed
108    nrf.write(b"Dummy", write_only=True)  # CE pin is left LOW
109    _ping_and_prompt()  # CE pin is managed by this function
110    print("\t\"on data failed\" event test {}successful".format("un" * nrf.irq_df))
111    nrf.flush_tx()  # flush artifact payload in TX FIFO from last test
112    # all 3 ACK payloads received were 4 bytes each, and RX FIFO is full
113    # so, fetching 12 bytes from the RX FIFO also flushes RX FIFO
114    print("\nComplete RX FIFO:", nrf.read(12))
115
116
117def slave(timeout=6):  # will listen for 6 seconds before timing out
118    """Only listen for 3 payload from the master node"""
119    # setup radio to recieve pings, fill TX FIFO with ACK payloads
120    nrf.load_ack(b"Yak ", 1)
121    nrf.load_ack(b"Back", 1)
122    nrf.load_ack(b" ACK", 1)
123    nrf.listen = True  # start listening & clear irq_dr flag
124    start_timer = time.monotonic()  # start timer now
125    while not nrf.fifo(0, 0) and time.monotonic() - start_timer < timeout:
126        # if RX FIFO is not full and timeout is not reached, then keep going
127        pass
128    nrf.listen = False  # put nRF24L01 in Standby-I mode when idling
129    if not nrf.fifo(False, True):  # if RX FIFO is not empty
130        # all 3 payloads received were 5 bytes each, and RX FIFO is full
131        # so, fetching 15 bytes from the RX FIFO also flushes RX FIFO
132        print("Complete RX FIFO:", nrf.read(15))
133    nrf.flush_tx()  # discard any pending ACK payloads
134
135

Library-Specific Features

Stream Example

Changed in version 1.2.3: added master_fifo() to demonstrate using full TX FIFO to stream data.

Changed in version 2.0.0: uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.

This is a test to show how to stream data. The master() uses the send() function to transmit multiple payloads with 1 function call. However master() only uses 1 level of the nRF24L01’s TX FIFO. An alternate function, called master_fifo() uses all 3 levels of the nRF24L01’s TX FIFO to stream data, but it uses the write() function to do so.

examples/nrf24l01_stream_test.py
  4import time
  5import board
  6from digitalio import DigitalInOut
  7
  8# if running this on a ATSAMD21 M0 based board
  9# from circuitpython_nrf24l01.rf24_lite import RF24
 10from circuitpython_nrf24l01.rf24 import RF24
 11
 12# invalid default values for scoping
 13SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 14
 15try:  # on Linux
 16    import spidev
 17
 18    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 19    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 20    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 21
 22except ImportError:  # on CircuitPython only
 23    # using board.SPI() automatically selects the MCU's
 24    # available SPI pins, board.SCK, board.MOSI, board.MISO
 25    SPI_BUS = board.SPI()  # init spi bus object
 26
 27    # change these (digital output) pins accordingly
 28    CE_PIN = DigitalInOut(board.D4)
 29    CSN_PIN = DigitalInOut(board.D5)
 30
 31
 32# initialize the nRF24L01 on the spi bus object
 33nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 34# On Linux, csn value is a bit coded
 35#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 36#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 37#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 38
 39# set the Power Amplifier level to -12 dBm since this test example is
 40# usually run with nRF24L01 transceivers in close proximity
 41nrf.pa_level = -12
 42
 43# addresses needs to be in a buffer protocol object (bytearray)
 44address = [b"1Node", b"2Node"]
 45
 46# to use different addresses on a pair of radios, we need a variable to
 47# uniquely identify which address this radio will use to transmit
 48# 0 uses address[0] to transmit, 1 uses address[1] to transmit
 49radio_number = bool(
 50    int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
 51)
 52
 53# set TX address of RX node into the TX pipe
 54nrf.open_tx_pipe(address[radio_number])  # always uses pipe 0
 55
 56# set RX address of TX node into an RX pipe
 57nrf.open_rx_pipe(1, address[not radio_number])  # using pipe 1
 58
 59# uncomment the following 2 lines for compatibility with TMRh20 library
 60# nrf.allow_ask_no_ack = False
 61nrf.dynamic_payloads = False
 62
 63
 64def make_buffers(size=32):
 65    """return a list of payloads"""
 66    buffers = []
 67    # we'll use `size` for the number of payloads in the list and the
 68    # payloads' length
 69    for i in range(size):
 70        # prefix payload with a sequential letter to indicate which
 71        # payloads were lost (if any)
 72        buff = bytes([i + (65 if 0 <= i < 26 else 71)])
 73        for j in range(size - 1):
 74            char = j >= (size - 1) / 2 + abs((size - 1) / 2 - i)
 75            char |= j < (size - 1) / 2 - abs((size - 1) / 2 - i)
 76            buff += bytes([char + 48])
 77        buffers.append(buff)
 78        del buff
 79    return buffers
 80
 81
 82def master(count=1, size=32):  # count = 5 will transmit the list 5 times
 83    """Transmits multiple payloads using `RF24.send()` and `RF24.resend()`."""
 84    buffers = make_buffers(size)  # make a list of payloads
 85    nrf.listen = False  # ensures the nRF24L01 is in TX mode
 86    successful = 0  # keep track of success rate
 87    for _ in range(count):
 88        start_timer = time.monotonic_ns()  # start timer
 89        # NOTE force_retry=2 internally invokes `RF24.resend()` 2 times at
 90        # most for payloads that fail to transmit.
 91        result = nrf.send(buffers, force_retry=2)  # result is a list
 92        end_timer = time.monotonic_ns()  # end timer
 93        print("Transmission took", (end_timer - start_timer) / 1000, "us")
 94        for r in result:  # tally the resulting success rate
 95            successful += 1 if r else 0
 96    print(
 97        f"successfully sent {successful / (size * count) * 100}%",
 98        f"({successful}/{size * count})"
 99    )
100
101
102def master_fifo(count=1, size=32):
103    """Similar to the `master()` above except this function uses the full
104    TX FIFO and `RF24.write()` instead of `RF24.send()`"""
105    buf = make_buffers(size)  # make a list of payloads
106    nrf.listen = False  # ensures the nRF24L01 is in TX mode
107    for cnt in range(count):  # transmit the same payloads this many times
108        nrf.flush_tx()  # clear the TX FIFO so we can use all 3 levels
109        # NOTE the write_only parameter does not initiate sending
110        buf_iter = 0  # iterator of payloads for the while loop
111        failures = 0  # keep track of manual retries
112        start_timer = time.monotonic_ns()  # start timer
113        while buf_iter < size:  # cycle through all the payloads
114            nrf.ce_pin = False
115            while buf_iter < size and nrf.write(buf[buf_iter], write_only=1):
116                # NOTE write() returns False if TX FIFO is already full
117                buf_iter += 1  # increment iterator of payloads
118            nrf.ce_pin = True
119            while not nrf.fifo(True, True):  # updates irq_df flag
120                if nrf.irq_df:
121                    # reception failed; we need to reset the irq_rf flag
122                    nrf.ce_pin = False  # fall back to Standby-I mode
123                    failures += 1  # increment manual retries
124                    nrf.clear_status_flags()  # clear the irq_df flag
125                    if failures > 99 and buf_iter < 7 and cnt < 2:
126                        # we need to prevent an infinite loop
127                        print(
128                            "Make sure slave() node is listening."
129                            " Quiting master_fifo()"
130                        )
131                        buf_iter = size + 1  # be sure to exit the while loop
132                        nrf.flush_tx()  # discard all payloads in TX FIFO
133                    else:
134                        nrf.ce_pin = True  # start re-transmitting
135        nrf.ce_pin = False
136        end_timer = time.monotonic_ns()  # end timer
137        print(
138            f"Transmission took {(end_timer - start_timer) / 1000} us",
139            f"with {failures} failures detected."
140        )
141
142
143def slave(timeout=5):
144    """Stops listening after a `timeout` with no response"""
145    nrf.listen = True  # put radio into RX mode and power up
146    count = 0  # keep track of the number of received payloads
147    start_timer = time.monotonic()  # start timer
148    while time.monotonic() < start_timer + timeout:
149        if nrf.available():
150            count += 1
151            # retreive the received packet's payload
152            buffer = nrf.read()  # clears flags & empties RX FIFO
153            print(f"Received: {buffer} - {count}")
154            start_timer = time.monotonic()  # reset timer on every RX payload
155
156    # recommended behavior is to keep in TX mode while idle
157    nrf.listen = False  # put the nRF24L01 is in TX mode
158
159

Context Example

Changed in version 1.2.0: demonstrates switching between FakeBLE object & RF24 object with the same nRF24L01

This is a test to show how to use The with statement blocks to manage multiple different nRF24L01 configurations on 1 transceiver.

examples/nrf24l01_context_test.py
10from circuitpython_nrf24l01.rf24 import RF24
11from circuitpython_nrf24l01.fake_ble import FakeBLE
12
13# invalid default values for scoping
14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
15
16try:  # on Linux
17    import spidev
18
19    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
20    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
21    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
22
23except ImportError:  # on CircuitPython only
24    # using board.SPI() automatically selects the MCU's
25    # available SPI pins, board.SCK, board.MOSI, board.MISO
26    SPI_BUS = board.SPI()  # init spi bus object
27
28    # change these (digital output) pins accordingly
29    CE_PIN = DigitalInOut(board.D4)
30    CSN_PIN = DigitalInOut(board.D5)
31
32
33# initialize the nRF24L01 objects on the spi bus object
34# the first object will have all the features enabled
35nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
36# On Linux, csn value is a bit coded
37#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
38#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
39#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
40
41# enable the option to use custom ACK payloads
42nrf.ack = True
43# set the static payload length to 8 bytes
44nrf.payload_length = 8
45# RF power amplifier is set to -18 dbm
46nrf.pa_level = -18
47
48# the second object has most features disabled/altered
49ble = FakeBLE(SPI_BUS, CSN_PIN, CE_PIN)
50# the IRQ pin is configured to only go active on "data fail"
51# NOTE BLE operations prevent the IRQ pin going active on "data fail" events
52ble.interrupt_config(data_recv=False, data_sent=False)
53# using a channel 2
54ble.channel = 2
55# RF power amplifier is set to -12 dbm
56ble.pa_level = -12
57
58print("\nsettings configured by the nrf object")
59with nrf:
60    # only the first character gets written because it is on a pipe_number > 1
61    nrf.open_rx_pipe(5, b"1Node")  # NOTE we do this inside the "with" block
62
63    # display current settings of the nrf object
64    nrf.print_details(True)  # True dumps pipe info
65
66print("\nsettings configured by the ble object")
67with ble as nerf:  # the "as nerf" part is optional
68    nerf.print_details(1)
69
70# if you examine the outputs from print_details() you'll see:
71#   pipe 5 is opened using the nrf object, but closed using the ble object.
72#   pipe 0 is closed using the nrf object, but opened using the ble object.
73#   also notice the different addresses bound to the RX pipes
74# this is because the "with" statements load the existing settings
75# for the RF24 object specified after the word "with".
76
77# NOTE it is not advised to manipulate seperate RF24 objects outside of the
78# "with" block; you will encounter bugs about configurations when doing so.
79# Be sure to use 1 "with" block per RF24 object when instantiating multiple
80# RF24 objects in your program.
81# NOTE exiting a "with" block will always power down the nRF24L01
82# NOTE upon instantiation, this library closes all RX pipes &
83# extracts the TX/RX addresses from the nRF24L01 registers

Manual ACK Example

New in version 2.0.0: Previously, this example was strictly made for TMRh20’s RF24 library example titled “GettingStarted_HandlingData.ino”. With the latest addition of new examples to the TMRh20 RF24 library, this example was renamed from “nrf24l01_2arduino_handling_data.py” and adapted for both this library and TMRh20’s RF24 library.

This is a test to show how to use the library for acknowledgement (ACK) responses without using the automatic ACK packets (like the ACK Payloads Example does). Beware, that this technique is not faster and can be more prone to communication failure. However, This technique has the advantage of using more updated information in the responding payload as information in ACK payloads are always outdated by 1 transmission.

examples/nrf24l01_manual_ack_test.py
  5import time
  6import board
  7from digitalio import DigitalInOut
  8
  9# if running this on a ATSAMD21 M0 based board
 10# from circuitpython_nrf24l01.rf24_lite import RF24
 11from circuitpython_nrf24l01.rf24 import RF24
 12
 13# invalid default values for scoping
 14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 15
 16try:  # on Linux
 17    import spidev
 18
 19    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 20    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 21    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 22
 23except ImportError:  # on CircuitPython only
 24    # using board.SPI() automatically selects the MCU's
 25    # available SPI pins, board.SCK, board.MOSI, board.MISO
 26    SPI_BUS = board.SPI()  # init spi bus object
 27
 28    # change these (digital output) pins accordingly
 29    CE_PIN = DigitalInOut(board.D4)
 30    CSN_PIN = DigitalInOut(board.D5)
 31
 32# initialize the nRF24L01 on the spi bus object
 33nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
 34# On Linux, csn value is a bit coded
 35#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 36#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 37#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 38
 39# set the Power Amplifier level to -12 dBm since this test example is
 40# usually run with nRF24L01 transceivers in close proximity
 41nrf.pa_level = -12
 42
 43# addresses needs to be in a buffer protocol object (bytearray)
 44address = [b"1Node", b"2Node"]
 45
 46# to use different addresses on a pair of radios, we need a variable to
 47# uniquely identify which address this radio will use to transmit
 48# 0 uses address[0] to transmit, 1 uses address[1] to transmit
 49radio_number = bool(
 50    int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
 51)
 52
 53# set TX address of RX node into the TX pipe
 54nrf.open_tx_pipe(address[radio_number])  # always uses pipe 0
 55
 56# set RX address of TX node into an RX pipe
 57nrf.open_rx_pipe(1, address[not radio_number])  # using pipe 1
 58# nrf.open_rx_pipe(2, address[radio_number])  # for getting responses on pipe 2
 59
 60# using the python keyword global is bad practice. Instead we'll use a 1 item
 61# list to store our integer number for the payloads' counter
 62counter = [0]
 63
 64# uncomment the following 3 lines for compatibility with TMRh20 library
 65# nrf.allow_ask_no_ack = False
 66# nrf.dynamic_payloads = False
 67# nrf.payload_length = 8
 68
 69
 70def master(count=5):  # count = 5 will only transmit 5 packets
 71    """Transmits an arbitrary unsigned long value every second"""
 72    nrf.listen = False  # ensures the nRF24L01 is in TX mode
 73    while count:
 74        # construct a payload to send
 75        # add b"\0" as a c-string NULL terminating char
 76        buffer = b"Hello \0" + bytes([counter[0]])
 77        start_timer = time.monotonic_ns()  # start timer
 78        result = nrf.send(buffer)  # save the response (ACK payload)
 79        if not result:
 80            print("send() failed or timed out")
 81        else:  # sent successful; listen for a response
 82            nrf.listen = True  # get radio ready to receive a response
 83            timeout = time.monotonic_ns() + 200000000  # set sentinal for timeout
 84            while not nrf.available() and time.monotonic_ns() < timeout:
 85                # this loop hangs for 200 ms or until response is received
 86                pass
 87            nrf.listen = False  # put the radio back in TX mode
 88            end_timer = time.monotonic_ns()  # stop timer
 89            print(
 90                "Transmission successful! Time to transmit:",
 91                f"{int((end_timer - start_timer) / 1000)} us. Sent:",
 92                "{}{}".format(buffer[:6].decode("utf-8"), counter[0]),
 93                end=" ",
 94            )
 95            if nrf.pipe is None:  # is there a payload?
 96                # nrf.pipe is also updated using `nrf.listen = False`
 97                print("Received no response.")
 98            else:
 99                length = nrf.any()  # reset with read()
100                pipe_number = nrf.pipe  # reset with read()
101                received = nrf.read()  # grab the response
102                # save new counter from response
103                counter[0] = received[7:8][0]
104                print(
105                    f"Receieved {length} bytes with pipe {pipe_number}:",
106                    "{}{}".format(bytes(received[:6]).decode("utf-8"), counter[0]),
107                )
108        count -= 1
109        # make example readable in REPL by slowing down transmissions
110        time.sleep(1)
111
112
113def slave(timeout=6):
114    """Polls the radio and prints the received value. This method expires
115    after 6 seconds of no received transmission"""
116    nrf.listen = True  # put radio into RX mode and power up
117    start_timer = time.monotonic()  # used as a timeout
118    while (time.monotonic() - start_timer) < timeout:
119        if nrf.available():
120            length = nrf.any()  # grab payload length info
121            pipe = nrf.pipe  # grab pipe number info
122            received = nrf.read(length)  # clears info from any() and nrf.pipe
123            # increment counter before sending it back in responding payload
124            counter[0] = received[7:8][0] + 1
125            nrf.listen = False  # put the radio in TX mode
126            result = False
127            ack_timeout = time.monotonic_ns() + 200000000
128            while not result and time.monotonic_ns() < ack_timeout:
129                # try to send reply for 200 milliseconds (at most)
130                result = nrf.send(b"World \0" + bytes([counter[0]]))
131            nrf.listen = True  # put the radio back in RX mode
132            print(
133                f"Received {length} on pipe {pipe}:",
134                "{}{}".format(bytes(received[:6]).decode("utf-8"), received[7:8][0]),
135                end=" Sent: ",
136            )
137            if not result:
138                print("Response failed or timed out")
139            else:
140                print("World", counter[0])
141            start_timer = time.monotonic()  # reset timeout
142
143    # recommended behavior is to keep in TX mode when in idle
144    nrf.listen = False  # put the nRF24L01 in TX mode + Standby-I power state
145
146

Network Test

New in version 2.1.0.

The following network example is designed to be compatible with most of TMRh20’s C++ examples for the RF24Mesh and RF24Network libraries. However, due to some slight differences this example prompts for user input which can cover a broader spectrum of usage scenarios.

examples/nrf24l01_network_test.py
  4import time
  5import struct
  6import board
  7from digitalio import DigitalInOut
  8from circuitpython_nrf24l01.network.constants import MAX_FRAG_SIZE, NETWORK_DEFAULT_ADDR
  9
 10IS_MESH = (
 11    input(
 12        "    nrf24l01_network_test example\n"
 13        "Would you like to run as a mesh network node (y/n)? Defaults to 'Y' "
 14    ) or "Y"
 15).upper().startswith("Y")
 16
 17# to use different addresses on a set of radios, we need a variable to
 18# uniquely identify which address this radio will use
 19THIS_NODE = 0
 20print(
 21    "Remember, the master node always uses `0` as the node_address and node_id."
 22    "\nWhich node is this? Enter",
 23    end=" ",
 24)
 25if IS_MESH:
 26    # node_id must be less than 255
 27    THIS_NODE = int(input("a unique int. Defaults to '0' ") or "0") & 0xFF
 28else:
 29    # logical node_address is in octal
 30    THIS_NODE = int(input("an octal int. Defaults to '0' ") or "0", 8)
 31
 32if IS_MESH:
 33    if THIS_NODE:  # if this is not a mesh network master node
 34        from circuitpython_nrf24l01.rf24_mesh import RF24MeshNoMaster as Network
 35    else:
 36        from circuitpython_nrf24l01.rf24_mesh import RF24Mesh as Network
 37    print("Using RF24Mesh{} class".format("" if not THIS_NODE else "NoMaster"))
 38else:
 39    from circuitpython_nrf24l01.rf24_network import RF24Network as Network
 40
 41    # we need to construct frame headers for RF24Network.send()
 42    from circuitpython_nrf24l01.network.structs import RF24NetworkHeader
 43
 44    # we need to construct entire frames for RF24Network.write() (not for this example)
 45    # from circuitpython_nrf24l01.network.structs import RF24NetworkFrame
 46    print("Using RF24Network class")
 47
 48# invalid default values for scoping
 49SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 50
 51try:  # on Linux
 52    import spidev
 53
 54    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 55    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 56    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 57
 58except ImportError:  # on CircuitPython only
 59    # using board.SPI() automatically selects the MCU's
 60    # available SPI pins, board.SCK, board.MOSI, board.MISO
 61    SPI_BUS = board.SPI()  # init spi bus object
 62
 63    # change these (digital output) pins accordingly
 64    CE_PIN = DigitalInOut(board.D4)
 65    CSN_PIN = DigitalInOut(board.D5)
 66
 67
 68# initialize this node as the network
 69nrf = Network(SPI_BUS, CSN_PIN, CE_PIN, THIS_NODE)
 70
 71# TMRh20 examples use a channel 97 for RF24Mesh library
 72# TMRh20 examples use a channel 90 for RF24Network library
 73nrf.channel = 90 + IS_MESH * 7
 74
 75# set the Power Amplifier level to -12 dBm since this test example is
 76# usually run with nRF24L01 transceivers in close proximity
 77nrf.pa_level = -12
 78
 79# using the python keyword global is bad practice. Instead we'll use a 1 item
 80# list to store our number of the payloads sent
 81packets_sent = [0]
 82
 83if THIS_NODE:  # if this node is not the network master node
 84    if IS_MESH:  # mesh nodes need to bond with the master node
 85        print("Connecting to mesh network...", end=" ")
 86
 87        # get this node's assigned address and connect to network
 88        if nrf.renew_address() is None:
 89            print("failed. Please try again manually with `nrf.renew_address()`")
 90        else:
 91            print("assigned address:", oct(nrf.node_address))
 92else:
 93    print("Acting as network master node.")
 94
 95
 96def emit(node=not THIS_NODE, frag=False, count=5, interval=1):
 97    """Transmits 1 (or 2) integers or a large buffer
 98
 99    :param int node: The target node for network transmissions.
100        If using RF24Mesh, this is a unique node_id.
101        If using RF24Network, this is the node's logical address.
102    :param bool frag: Only use fragmented messages?
103    :param int count: The max number of messages to transmit.
104    :param int interval: time (in seconds) between transmitting messages.
105    """
106    failures = 0
107    start_timer = time.monotonic()
108    while failures < 6 and count:
109        nrf.update()  # keep the network layer current
110        now = time.monotonic()
111        if now >= start_timer + interval:  # its time to emmit
112            start_timer = now
113            count -= 1
114            packets_sent[0] += 1
115            #TMRh20's RF24Mesh examples use 1 long int containing a timestamp (in ms)
116            message = struct.pack("<L", int(now * 1000))
117            if frag:
118                message = bytes(
119                    range((packets_sent[0] + MAX_FRAG_SIZE) % nrf.max_message_length)
120                )
121            elif not IS_MESH:  # if using RF24Network
122                # TMRh20's RF24Network examples use 2 long ints, so add another
123                message += struct.pack("<L", packets_sent[0])
124            result = False
125            start = time.monotonic_ns()
126            # pylint: disable=no-value-for-parameter
127            if IS_MESH:  # send() is a little different for RF24Mesh vs RF24Network
128                result = nrf.send(node, "M", message)
129            else:
130                result = nrf.send(RF24NetworkHeader(node, "T"), message)
131            # pylint: enable=no-value-for-parameter
132            end = time.monotonic_ns()
133            failures += not result
134            print(
135                f"Sending {packets_sent[0]} (len {len(message)})...",
136                "ok." if result else "failed.",
137                f"Transmission took {int((end - start) / 1000000)} ms",
138            )
139
140
141def idle(timeout=30):
142    """Listen for any payloads and print the transaction
143
144    :param int timeout: The number of seconds to wait (with no transmission)
145        until exiting function.
146    """
147    print("idling for", timeout, "seconds")
148    start_timer = time.monotonic()
149    while (time.monotonic() - start_timer) < timeout:
150        nrf.update()  # keep the network layer current
151        while nrf.available():
152            start_timer = time.monotonic()  # reset timer
153            payload = nrf.read()
154            payload_len = len(payload.message)
155            print("Received payload", end=" ")
156            # TMRh20 examples only use 1 or 2 long ints as small messages
157            if payload_len < MAX_FRAG_SIZE and payload_len % 4 == 0:
158                # if not a large fragmented message and multiple of 4 bytes
159                fmt = "<" + "L" * int(payload_len / 4)
160                print(struct.unpack(fmt, bytes(payload.message)), end=" ")
161            print(payload.header.to_string(), "length", payload_len)
162
163

OTA compatibility

Fake BLE Example

New in version 1.2.0.

Changed in version 2.1.0: A new slave() function was added to demonstrate receiving BLE data.

This is a test to show how to use the nRF24L01 as a BLE advertising beacon using the FakeBLE class.

examples/nrf24l01_fake_ble_test.py
  7import time
  8import board
  9from digitalio import DigitalInOut
 10from circuitpython_nrf24l01.fake_ble import (
 11    chunk,
 12    FakeBLE,
 13    UrlServiceData,
 14    BatteryServiceData,
 15    TemperatureServiceData,
 16)
 17from circuitpython_nrf24l01.rf24 import address_repr
 18
 19# invalid default values for scoping
 20SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
 21
 22try:  # on Linux
 23    import spidev
 24
 25    SPI_BUS = spidev.SpiDev()  # for a faster interface on linux
 26    CSN_PIN = 0  # use CE0 on default bus (even faster than using any pin)
 27    CE_PIN = DigitalInOut(board.D22)  # using pin gpio22 (BCM numbering)
 28
 29except ImportError:  # on CircuitPython only
 30    # using board.SPI() automatically selects the MCU's
 31    # available SPI pins, board.SCK, board.MOSI, board.MISO
 32    SPI_BUS = board.SPI()  # init spi bus object
 33
 34    # change these (digital output) pins accordingly
 35    CE_PIN = DigitalInOut(board.D4)
 36    CSN_PIN = DigitalInOut(board.D5)
 37
 38
 39# initialize the nRF24L01 on the spi bus object as a BLE compliant radio
 40nrf = FakeBLE(SPI_BUS, CSN_PIN, CE_PIN)
 41# On Linux, csn value is a bit coded
 42#                 0 = bus 0, CE0  # SPI bus 0 is enabled by default
 43#                10 = bus 1, CE0  # enable SPI bus 2 prior to running this
 44#                21 = bus 2, CE1  # enable SPI bus 1 prior to running this
 45
 46# the name parameter is going to be its broadcasted BLE name
 47# this can be changed at any time using the `name` attribute
 48# nrf.name = b"foobar"
 49
 50# you can optionally set the arbitrary MAC address to be used as the
 51# BLE device's MAC address. Otherwise this is randomly generated upon
 52# instantiation of the FakeBLE object.
 53# nrf.mac = b"\x19\x12\x14\x26\x09\xE0"
 54
 55# set the Power Amplifier level to -12 dBm since this test example is
 56# usually run with nRF24L01 transceiver in close proximity to the
 57# BLE scanning application
 58nrf.pa_level = -12
 59
 60
 61def _prompt(remaining):
 62    if remaining % 5 == 0 or remaining < 5:
 63        if remaining - 1:
 64            print(remaining, "advertisments left to go!")
 65        else:
 66            print(remaining, "advertisment left to go!")
 67
 68
 69# create an object for manipulating the battery level data
 70battery_service = BatteryServiceData()
 71# battery level data is 1 unsigned byte representing a percentage
 72battery_service.data = 85
 73
 74
 75def master(count=50):
 76    """Sends out the device information."""
 77    # using the "with" statement is highly recommended if the nRF24L01 is
 78    # to be used for more than a BLE configuration
 79    with nrf as ble:
 80        ble.name = b"nRF24L01"
 81        # include the radio's pa_level attribute in the payload
 82        ble.show_pa_level = True
 83        print(
 84            "available bytes in next payload:",
 85            ble.len_available(chunk(battery_service.buffer)),
 86        )  # using chunk() gives an accurate estimate of available bytes
 87        for i in range(count):  # advertise data this many times
 88            if ble.len_available(chunk(battery_service.buffer)) >= 0:
 89                _prompt(count - i)  # something to show that it isn't frozen
 90                # broadcast the device name, MAC address, &
 91                # battery charge info; 0x16 means service data
 92                ble.advertise(battery_service.buffer, data_type=0x16)
 93                # channel hoping is recommended per BLE specs
 94                ble.hop_channel()
 95                time.sleep(0.5)  # wait till next broadcast
 96    # nrf.show_pa_level & nrf.name both are set to false when
 97    # exiting a with statement block
 98
 99
100# create an object for manipulating temperature measurements
101temperature_service = TemperatureServiceData()
102# temperature's float data has up to 2 decimal places of percision
103temperature_service.data = 42.0
104
105
106def send_temp(count=50):
107    """Sends out a fake temperature."""
108    with nrf as ble:
109        ble.name = b"nRF24L01"
110        print(
111            "available bytes in next payload:",
112            ble.len_available(chunk(temperature_service.buffer)),
113        )
114        for i in range(count):
115            if ble.len_available(chunk(temperature_service.buffer)) >= 0:
116                _prompt(count - i)
117                # broadcast a temperature measurement; 0x16 means service data
118                ble.advertise(temperature_service.buffer, data_type=0x16)
119                ble.hop_channel()
120                time.sleep(0.2)
121
122
123# use the Eddystone protocol from Google to broadcast a URL as
124# service data. We'll need an object to manipulate that also
125url_service = UrlServiceData()
126# the data attribute converts a URL string into a simplified
127# bytes object using byte codes defined by the Eddystone protocol.
128url_service.data = "http://www.google.com"
129# Eddystone protocol requires an estimated TX PA level at 1 meter
130# lower this estimate since we lowered the actual `ble.pa_level`
131url_service.pa_level_at_1_meter = -45  # defaults to -25 dBm
132
133
134def send_url(count=50):
135    """Sends out a URL."""
136    with nrf as ble:
137        print(
138            "available bytes in next payload:",
139            ble.len_available(chunk(url_service.buffer)),
140        )
141        # NOTE we did NOT set a device name in this with block
142        for i in range(count):
143            # URLs easily exceed the nRF24L01's max payload length
144            if ble.len_available(chunk(url_service.buffer)) >= 0:
145                _prompt(count - i)
146                ble.advertise(url_service.buffer, 0x16)
147                ble.hop_channel()
148                time.sleep(0.2)
149
150
151def slave(timeout=6):
152    """read and decipher BLE payloads for `timeout` seconds."""
153    nrf.listen = True
154    end_timer = time.monotonic() + timeout
155    while time.monotonic() <= end_timer:
156        if nrf.available():
157            result = nrf.read()
158            print(
159                "recevied payload from MAC address",
160                address_repr(result.mac, delimit=":")
161            )
162            if result.name is not None:
163                print("\tdevice name:", result.name)
164            if result.pa_level is not None:
165                print("\tdevice transmitting PA Level:", result.pa_level, "dbm")
166            for service_data in result.data:
167                if isinstance(service_data, (bytearray, bytes)):
168                    print("\traw buffer:", address_repr(service_data, False, " "))
169                else:
170                    print("\t" + repr(service_data))
171
172

TMRh20’s C++ libraries

All examples are designed to work with TMRh20’s RF24, RF24Network, and RF24Mesh libraries’ examples. This Circuitpython library uses dynamic payloads enabled by default. TMRh20’s RF24 library uses static payload lengths by default.

To make this circuitpython library compatible with TMRh20’s RF24 library:

  1. set dynamic_payloads to False.

  2. set allow_ask_no_ack to False.

  3. set payload_length to the value that is passed to TMRh20’s RF24::setPayloadSize(). 32 is the default (& maximum) payload length/size for both libraries.

    Warning

    Certain C++ datatypes allocate a different amount of memory depending on the board being used in the Arduino IDE. For example, uint8_t isn’t always allocated to 1 byte of memory for certain boards. Make sure you understand the amount of memory that different datatypes occupy in C++. This will help you comprehend how to configure payload_length.

For completness, TMRh20’s RF24 library uses a default value of 15 for the ard attribute, but this Circuitpython library uses a default value of 3.

Corresponding examples

circuitpython_nrf24l01

TMRh20’s C++ examples

nrf24l01_simple_test (1)

RF24 gettingStarted

nrf24l01_ack_payload_test

RF24 acknowledgementPayloads

nrf24l01_manual_ack_test (1)

RF24 manualAcknowledgements

nrf24l01_multiceiver_test (1)

RF24 multiceiverDemo

nrf24l01_stream_test (1)

RF24 streamingData

nrf24l01_interrupt_test

RF24 interruptConfigure

nrf24l01_context_test

feature is not available in C++

nrf24l01_fake_ble_test

feature is available via floe’s BTLE library

nrf24l01_network_test (2)

  • all RF24Network examples except Network_Ping & Network_Ping_Sleep

  • all RF24Mesh examples except RF24Mesh_Example_Node2NodeExtra (which may still work but the data is not interpretted as a string)

1(1,2,3,4)

Some of the Circuitpython examples (that are compatible with TMRh20’s examples) contain 2 or 3 lines of code that are commented out for easy modification. These lines look like this in the examples’ source code:

# uncomment the following 3 lines for compatibility with TMRh20 library
# nrf.allow_ask_no_ack = False
# nrf.dynamic_payloads = False
# nrf.payload_length = 4
2

When running the network examples, it is important to understand the typical network topology. Otherwise, entering incorrect answers to the example’s user prompts may result in seemingly bad connections.