CircuitPython for RFM9x LoRa

It's easy to use the RFM9x LoRa radio with CircuitPython and the Adafruit CircuitPython RFM9x module.  This module allows you to easily write Python code that sends and receives packets of data with the radio.  Be careful to note this library is for the RFM95/96/97/98 LoRa radio only and will not work with the simper RFM69 packet radio.

Design Considerations

One thing to be aware of before you use the RFM9x series of radios with CircuitPython are some of the limitations and design considerations for its module.  Keep these in mind as you think about projects using the RFM9x and CircuitPython:

  • You can only send and receive packets up to 252 bytes in length at a time.  The size of the radio's internal buffer dictates this limit so if you want to send longer messages you'll need to break them into a series of smaller send calls in your application code.
  • Receiving packets is a 'best effort' in pure Python code.  Unlike the Arduino versions of the RFM9x library there is no interrupt support which means when a packet is received it must be immediately processed by the Python code or it could be lost.  For your application it will work best to only receive small, single packet messages at a time.  Don't try to receive kilobytes of data or else you'll lose packets.  This module is really intended for simple single packet messages like 'ON', 'OFF', etc.
  • Sending and receiving packets will 'block' your Python code until the packet is fully processed.  This means you can't do a lot of other things while sending and waiting for packets to be received.  Design your application so the radio usage is the primary scenario and very little other tasks need to happen in the background.
  • The module is written to be compatible with the RadioHead RFM95 Arduino library.  This means by default the module will setup the radio with the same modulation and configuration for transmitting and receiving at the maximum distance with LoRa support.  In addition the CircuitPython module uses the same packet preamble (8 bytes) and header (4 bytes) as RadioHead.  If you want to use different modulations or settings you'll need to configure the radio yourself after carefully consulting the datasheet.
  • The CircuitPython module does not support advanced RadioHead features like guaranteed delivery or its internal node addressing scheme.  Only the simple 'raw' RadioHead examples are supported (i.e. broadcasting a packet to all listening radios).
  • Encryption and sync words are also not supported by the LoRa radio module.  You must perform these operations yourself in your application code if they're desired.

Wiring With Breakout

First wire up a RFM9x breakout to your board as shown on the previous pages for Arduino.  Note that the G0/interrupt line is not used by the CircuitPython module and can remain unconnected.   Here's an example of wiring a Feather M0 to the radio with a SPI connection:

  • Board 3V to radio VIN
  • Board GND to radio GND
  • Board SCK to radio SCK
  • Board MOSI to radio MOSI
  • Board MISO to radio MISO
  • Board D5 to radio CS (or any other digital I/O pin)
  • Board D6 to radio RST (or any other digital I/O pin)
The Feather M0 LoRa does NOT come with UF2 bootloader or CircuitPython pre-installed, you must do 2 things before you can use it with CircuitPython - update the bootloader manually, and install CircuitPython

Usage with All-In-One Feather M0

Alternatively you can use the Feather M0 RFM9x board but be sure you've loaded the adafruit-circuitpython-feather_m0_rfm9x-*.bin version of CircuitPython on your board!  This is very important as the RFM9x build has special pins added to the board module which are used to access the radio's control lines!

For details on how to load a binary circuitpython build, check out our Non-UF2-Install guide

Adafruit Feather M0 with RFM95 LoRa Radio - 900MHz

PRODUCT ID: 3178
This is the Adafruit Feather M0 RFM95 LoRa Radio (900MHz). We call these RadioFruits, our take on an microcontroller with a...
OUT OF STOCK

Adafruit Feather M0 RFM96 LoRa Radio - 433MHz

PRODUCT ID: 3179
This is the Adafruit Feather M0 RFM96 LoRa Radio (433 MHz). We call these RadioFruits, our take on an microcontroller with a "
OUT OF STOCK

Module Install

Next you'll need to install the Adafruit CircuitPython RFM9x module on your CircuitPython board.  Before you do that make sure you are running the latest version of Adafruit CircuitPython for your board too (again be sure to the load the Feather M0 RFM9x version if you're using that board and want to use its built-in radio module).

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle.  Our introduction guide has a great page on how to install the library bundle for both express and non-express boards.

Remember for non-express boards like the, you'll need to manually install the necessary libraries from the bundle:

  • adafruit_rfm9x.mpy
  • adafruit_bus_device

You can also download the adafruit_rfm9x.mpy from its releases page on Github.

Before continuing make sure your board's lib folder or root filesystem has the adafruit_rfm9x.mpy, and adafruit_bus_device files and folders copied over.

Next connect to the board's serial REPL so you are at the CircuitPython >>> prompt.

Usage

To demonstrate the usage of the radio we'll initialize it and send and receive data from the board's Python REPL.

Run the following code to import the necessary modules and initialize the SPI connection with the radio:

Download: file
import board
import busio
import digitalio
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

Now define a few of the pins connected to the RFM9x, specifically the CS and RST pins:

Download: file
cs = digitalio.DigitalInOut(board.D5)
reset = digitalio.DigitalInOut(board.D6)

However if you're using the Feather M0 RFM69 board with a built-in RFM9x radio (and you've loaded the special version of CircuitPython just for this board as mentioned above), you instead want to use these pins for the CS and RST lines:

Download: file
cs = digitalio.DigitalInOut(board.RFM9X_CS)
reset = digitalio.DigitalInOut(board.RFM9X_RST)

You're ready to import the RFM9x module and create an instance of the RFM9x class inside it.  Before you create the radio module instance you'll need to check if you're using a 433mhz or 915mhz radio module as the initializer requires the frequency to be specified--confirm which frequency your module uses and run one of the following lines.  

For a 915mhz radio use:

Download: file
import adafruit_rfm9x
rfm9x = adafruit_rfm9x.RFM9x(spi, cs, reset, 915.0)

Or for a 433mhz radio use:

Download: file
import adafruit_rfm9x
rfm9x = adafruit_rfm9x.RFM9x(spi, cs, reset, 433.0)

Notice the initializer takes the following required parameters:

  • spi - The SPI bus connected to the board.
  • cs - The DigitalInOut instance connected to the CS line of the radio.
  • reset - The DigitalInOut instance connected to the RST or reset line of the radio.
  • frequency - The frequency in megahertz of the radio module.  Remember this frequency depends on which type of radio you're using and the frequency you desire to use!

In addition there are some optional parameters you might specify:

  • baudrate - The baud rate to use for the SPI connection to the radio.  By default this is 10mhz which is as fast as the radio can handle, but in some cases it might be too fast if you're wiring up a breakout to a breadboard (breadboards can be notorious for not working well with high speed signals).  If you run into odd errors like being unable to find the RFM9x radio try lowering the baudrate by specifying a baudrate=1000000 keyword (which sets the speed to a lower 1mhz value).

Once the RFM9x class is created and initialized you're ready to start sending and receiving data.

Remember by default the module will be configured to interface with the "RadioHead" RFM9x setup so you can also send and receive packets with an Arduino running the RFM95 TX/RX examples!

To send a message simply call the send function and provide a string or byte string of data:

Download: file
rfm9x.send('Hello world!')

Remember you can only send a message up to 252 bytes in length at a time!  Attempting to send a message longer than 252 bytes will fail with an exception error.  If you need to send a longer message it will have to be broken up into multiple send calls and reconstructed on the receiving side.

If you have another RFM9x on the same frequency waiting to receive messages (like another CircuitPython module running receive code below) you should see it receive the message.

You can even have an Arduino running the RadioHead library's RFM95 client example see the message that was sent:

To receive a message simply call the receive function.  This function will wait for half a second for any packet to be received.  If a packet is found it will be returned as a byte string (remember packets are at most 252 bytes long), or if no packet was found a result of None is returned.

Download: file
rfm9x.receive()

You can increase the amount of time the module waits for a packet to be received by specifying the time in seconds as a parameter to the receive call:

Download: file
rfm9x.receive(timeout_s=5.0)  # Wait 5 seconds instead of 0.5 seconds.

Notice this waits longer at the REPL for a packet to be received before returning.  If you have another RFM9x setup try having it send a message while the other is waiting to receive it.  You should see a byte string returned.  You can also have an Arduino running the RadioHead library's RFM95 client example send messages that are received by your code:

One thing to note in Python byte strings aren't exactly like text strings and you might not be able to do all the text processing (like find, replace, etc.) as you expect.  However you can convert a byte string into text by assuming a specific text encoding like ASCII.  For example to receive a packet and convert the contents to an ASCII text string you can run code like:

Download: file
packet = rfm9x.receive()  # Wait for a packet to be received (up to 0.5 seconds)
if packet is not None:
    packet_text = str(packet, 'ascii')
    print('Received: {0}'.format(packet_text))

Notice this code first receives a packet, then checks if one was actually found (the packet is not None check--if no packet is received a value of None is returned), and then converts the packet data to a string assuming an ASCII text encoding.

Beyond RX & TX

Beyond basic sending and receiving there are a few properties of the RFM69 class you might want to interact with:

  • tx_power - This is a power level (in dB) to use when transmitting with the radio.  By default this is set to a moderate 13 dB value, however you can increase this depending on the type of radio you're using.  For high power radios (the modules sold by Adafruit) they support a range of TX power from 5 to 23 dB.  Try increasing this to the maximum 23 dB level (however check your local laws for permission to transmit with such power!) to get the most distance and range.
  • rssi - The received signal strength indicator is a property you can read to see the strength of the radio signal being received.  This is updated when packets are received and returns a value in decibels (typically negative, so the smaller the number and closer to 0, the higher the strength / better the signal).

That's all there is to the basic RFM9x radio usage!  Remember the CircuitPython module is designed for sending and receiving small up to 252 byte control messages and not large or high bandwidth amounts of data.

Here's a complete example of sending a message and waiting to receive and print any received messages.  Save this as main.py on your board and open the serial REPL to see it print data and any received messages.  If you have two boards and radios setup to run this code at the same time they'll send each other a message on start up!

Temporarily unable to load content:
This guide was first published on Apr 15, 2016. It was last updated on Apr 15, 2016. This page (CircuitPython for RFM9x LoRa) was last updated on Aug 19, 2019.