Skip to content

GH-20: Expose bw, cr, sf controls and CRC checks #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,27 @@ This is easily achieved by downloading
Usage Example
=============

See examples/rfm9x_simpletest.py for a demo of the usage.
Initialization of the RFM radio requires specifying a frequency appropriate to
your radio hardware (i.e. 868-915 or 433 MHz) and specifying the pins used in your
wiring from the controller board to the radio module.

This example code matches the wiring used in the
`LoRa and LoRaWAN Radio for Raspberry Pi <https://learn.adafruit.com/lora-and-lorawan-radio-for-raspberry-pi/>`_
project:

.. code-block:: python

import digitalio
import board
import busio
import adafruit_rfm9x

RADIO_FREQ_MHZ = 915.0
CS = digitalio.DigitalInOut(board.CE1)
RESET = digitalio.DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

Note: the default baudrate for the SPI is 50000000 (5MHz). The maximum setting is 10Mhz but
transmission errors have been observed expecially when using breakout boards.
For breakout boards or other configurations where the boards are separated, it may be necessary to reduce
Expand All @@ -39,16 +59,30 @@ To set it to 1000000 use :

.. code-block:: python

# Initialze RFM radio
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ,baudrate=1000000)
# Initialze RFM radio with a more conservative baudrate
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ, baudrate=1000000)

Optional controls exist to alter the signal bandwidth, coding rate, and spreading factor
settings used by the radio to achieve better performance in different environments.
By default, settings compatible with RadioHead Bw125Cr45Sf128 mode are used, which can
be altered in the following manner (continued from the above example):

.. code-block:: python

# Apply new modem config settings to the radio to improve its effective range
rfm9x.signal_bandwidth = 62500
rfm9x.coding_rate = 6
rfm9x.spreading_factor = 8
rfm9x.enable_crc = True

See examples/rfm9x_simpletest.py for an expanded demo of the usage.


Contributing
============

Contributions are welcome! Please read our `Code of Conduct
<https://github.com/adafruit/adafruit_CircuitPython_rfm95/blob/master/CODE_OF_CONDUCT.md>`_
<https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/blob/master/CODE_OF_CONDUCT.md>`_
before contributing to help this project stay welcoming.

Building locally
Expand Down
153 changes: 133 additions & 20 deletions adafruit_rfm9x.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
import digitalio
from micropython import const

try:
from warnings import warn
except ImportError:
def warn(msg, **kwargs):
"Issue a warning to stdout."
print("%s: %s" % ("Warning" if kwargs.get("cat") is None else kwargs["cat"].__name__, msg))

import adafruit_bus_device.spi_device as spidev


Expand Down Expand Up @@ -192,6 +199,8 @@
_RH_RF95_LOW_DATA_RATE_OPTIMIZE = const(0x01)

# RH_RF95_REG_1E_MODEM_CONFIG2 0x1e
_RH_RF95_DETECTION_OPTIMIZE = const(0x31)
_RH_RF95_DETECTION_THRESHOLD = const(0x37)
_RH_RF95_SPREADING_FACTOR = const(0xf0)
_RH_RF95_SPREADING_FACTOR_64CPS = const(0x60)
_RH_RF95_SPREADING_FACTOR_128CPS = const(0x70)
Expand Down Expand Up @@ -333,6 +342,10 @@ def __set__(self, obj, val):

rx_done = _RegisterBits(_RH_RF95_REG_12_IRQ_FLAGS, offset=6, bits=1)

crc_error = _RegisterBits(_RH_RF95_REG_12_IRQ_FLAGS, offset=5, bits=1)

bw_bins = (7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000)

def __init__(self, spi, cs, reset, frequency, *, preamble_length=8,
high_power=True, baudrate=5000000):
self.high_power = high_power
Expand Down Expand Up @@ -367,10 +380,13 @@ def __init__(self, spi, cs, reset, frequency, *, preamble_length=8,
self._write_u8(_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00)
# Set mode idle
self.idle()
# Set modem config to RadioHead compatible Bw125Cr45Sf128 mode.
# Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode.
self.signal_bandwidth = 125000
self.coding_rate = 5
self.spreading_factor = 7
# Default to disable CRC checking on incoming packets.
self.enable_crc = False
# Note no sync word is set for LoRa mode either!
self._write_u8(_RH_RF95_REG_1D_MODEM_CONFIG1, 0x72) # Fei msb?
self._write_u8(_RH_RF95_REG_1E_MODEM_CONFIG2, 0x74) # Fei lsb?
self._write_u8(_RH_RF95_REG_26_MODEM_CONFIG3, 0x00) # Preamble lsb?
# Set preamble length (default 8 bytes to match radiohead).
self.preamble_length = preamble_length
Expand Down Expand Up @@ -533,6 +549,100 @@ def rssi(self):
# Remember in LoRa mode the payload register changes function to RSSI!
return self._read_u8(_RH_RF95_REG_1A_PKT_RSSI_VALUE) - 137

@property
def signal_bandwidth(self):
"""The signal bandwidth used by the radio (try setting to a higher
value to increase throughput or to a lower value to increase the
likelihood of successfully received payloads). Valid values are
listed in RFM9x.bw_bins."""
bw_id = (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0xf0) >> 4
if bw_id >= len(self.bw_bins):
current_bandwidth = 500000
else:
current_bandwidth = self.bw_bins[bw_id]
return current_bandwidth

@signal_bandwidth.setter
def signal_bandwidth(self, val):
# Set signal bandwidth (set to 125000 to match RadioHead Bw125).
for bw_id, cutoff in enumerate(self.bw_bins):
if val <= cutoff:
break
else:
bw_id = 9
self._write_u8(
_RH_RF95_REG_1D_MODEM_CONFIG1,
(self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0f) | (bw_id << 4)
)

@property
def coding_rate(self):
"""The coding rate used by the radio to control forward error
correction (try setting to a higher value to increase tolerance of
short bursts of interference or to a lower value to increase bit
rate). Valid values are limited to 5, 6, 7, or 8."""
cr_id = (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0e) >> 1
denominator = cr_id + 4
return denominator

@coding_rate.setter
def coding_rate(self, val):
# Set coding rate (set to 5 to match RadioHead Cr45).
denominator = min(max(val, 5), 8)
cr_id = denominator - 4
self._write_u8(
_RH_RF95_REG_1D_MODEM_CONFIG1,
(self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0xf1) | (cr_id << 1)
)

@property
def spreading_factor(self):
"""The spreading factor used by the radio (try setting to a higher
value to increase the receiver's ability to distinguish signal from
noise or to a lower value to increase the data transmission rate).
Valid values are limited to 6, 7, 8, 9, 10, 11, or 12."""
sf_id = (self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xf0) >> 4
return sf_id

@spreading_factor.setter
def spreading_factor(self, val):
# Set spreading factor (set to 7 to match RadioHead Sf128).
val = min(max(val, 6), 12)
self._write_u8(
_RH_RF95_DETECTION_OPTIMIZE, 0xc5 if val == 6 else 0xc3
)
self._write_u8(
_RH_RF95_DETECTION_THRESHOLD, 0x0c if val == 6 else 0x0a
)
self._write_u8(
_RH_RF95_REG_1E_MODEM_CONFIG2,
(
(self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0x0f) |
((val << 4) & 0xf0)
)
)

@property
def enable_crc(self):
"""Set to True to enable hardware CRC checking of incoming packets.
Incoming packets that fail the CRC check are not processed. Set to
False to disable CRC checking and process all incoming packets."""
return (self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0x04) == 0x04

@enable_crc.setter
def enable_crc(self, val):
# Optionally enable CRC checking on incoming packets.
if val:
self._write_u8(
_RH_RF95_REG_1E_MODEM_CONFIG2,
self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) | 0x04
)
else:
self._write_u8(
_RH_RF95_REG_1E_MODEM_CONFIG2,
self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xfb
)

def send(self, data, timeout=2.,
tx_header=(_RH_BROADCAST_ADDRESS, _RH_BROADCAST_ADDRESS, 0, 0)):
"""Send a string of data using the transmitter.
Expand Down Expand Up @@ -617,25 +727,28 @@ def receive(self, timeout=0.5, keep_listening=True, with_header=False,
# Payload ready is set, a packet is in the FIFO.
packet = None
if not timed_out:
# Grab the length of the received packet and check it has at least 5
# bytes to indicate the 4 byte header and at least 1 byte of user data.
length = self._read_u8(_RH_RF95_REG_13_RX_NB_BYTES)
if length < 5:
packet = None
if self.enable_crc and self.crc_error:
warn("CRC error, packet ignored")
else:
# Have a good packet, grab it from the FIFO.
# Reset the fifo read ptr to the beginning of the packet.
current_addr = self._read_u8(_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR)
self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr)
packet = bytearray(length)
# Read the packet.
self._read_into(_RH_RF95_REG_00_FIFO, packet)
if (rx_filter != _RH_BROADCAST_ADDRESS and packet[0] != _RH_BROADCAST_ADDRESS
and packet[0] != rx_filter):
# Grab the length of the received packet and check it has at least 5
# bytes to indicate the 4 byte header and at least 1 byte of user data.
length = self._read_u8(_RH_RF95_REG_13_RX_NB_BYTES)
if length < 5:
packet = None
elif not with_header: # skip the header if not wanted
packet = packet[4:]
# Listen again if necessary and return the result packet.
else:
# Have a good packet, grab it from the FIFO.
# Reset the fifo read ptr to the beginning of the packet.
current_addr = self._read_u8(_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR)
self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr)
packet = bytearray(length)
# Read the packet.
self._read_into(_RH_RF95_REG_00_FIFO, packet)
if (rx_filter != _RH_BROADCAST_ADDRESS and packet[0] != _RH_BROADCAST_ADDRESS
and packet[0] != rx_filter):
packet = None
elif not with_header: # skip the header if not wanted
packet = packet[4:]
# Listen again if necessary and return the result packet.
if keep_listening:
self.listen()
else:
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.7',
],

# What does your project relate to?
Expand Down