From dda51b61586e0c8866bd3a7af24e9a37b097b85d Mon Sep 17 00:00:00 2001 From: thetazero Date: Fri, 25 Nov 2022 16:26:25 -0500 Subject: [PATCH 1/3] draft for looking at diff --- .../lib/adafruit_bus_device/__init__.py | 0 .../lib/adafruit_bus_device/i2c_device.mpy | Bin 0 -> 1943 bytes .../lib/adafruit_bus_device/spi_device.mpy | Bin 0 -> 1373 bytes fsk_radio_range_test/main.py | 3 +- radio-utils/lib/adafruit_rfm9x.py | 627 ++++++++++-------- 5 files changed, 335 insertions(+), 295 deletions(-) create mode 100644 fsk_radio_range_test/lib/adafruit_bus_device/__init__.py create mode 100644 fsk_radio_range_test/lib/adafruit_bus_device/i2c_device.mpy create mode 100644 fsk_radio_range_test/lib/adafruit_bus_device/spi_device.mpy diff --git a/fsk_radio_range_test/lib/adafruit_bus_device/__init__.py b/fsk_radio_range_test/lib/adafruit_bus_device/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fsk_radio_range_test/lib/adafruit_bus_device/i2c_device.mpy b/fsk_radio_range_test/lib/adafruit_bus_device/i2c_device.mpy new file mode 100644 index 0000000000000000000000000000000000000000..10507a18fe85a4c0668a8562af3e60fcd84cf54f GIT binary patch literal 1943 zcmb7FO>Y}j6uoc!ov+4ea4O@3Njo@#;8=SLladjfJ(L41nGI~c|%4x9`j}< zjZqO}sf)7cf`7nIaFWtJ0`Uu2mp_5n()(t{I0RQVUPRZ=_uYH$IrqGL5|ZBA3W4}6 z)?kyUa}O&V^hDx&vE zLk%h0q#0J0p&3zDpcz$)NYi+%={zATUG$n7i+wMZXfZ)!OuMt${cR10= zFT?c$*|(|H$>VAr{pXtve0d_>h3}8E~D=;jID>^q1Q5)O50Dr6s^5vm!Ir{i; z1hpif?!6Uic@k7#=Gk$^Jh}fb!p17ZLt$9n76_YeDqqMy6nwrs$LFuFMqf@f@|#C) z%}0Eix<$^=FfY+BKcQV->CZ#R@}$k1agc!YcYlay;)&a(}{eFx%+=@G30(7AwR)d%VZF$rG)<%~a0S y{#1XDuFQR|b{yv2zOaO%M8Lb`KLIVUHa<0F{2wU$Z=I~#a`4>Uk++X?$8P|$XkO9) literal 0 HcmV?d00001 diff --git a/fsk_radio_range_test/lib/adafruit_bus_device/spi_device.mpy b/fsk_radio_range_test/lib/adafruit_bus_device/spi_device.mpy new file mode 100644 index 0000000000000000000000000000000000000000..0ff863c31e6db3dd93bd69e8ee009f53987aa52a GIT binary patch literal 1373 zcma)*&rcIU6vyYaR9mQNORy$rfx@y2{RuUti7^W5#Y7VlkP9Z$?RH8xmhQScTj+_c zQS`$9vMT65ApQd$^x&0~SK?J?b|EY^5$3S_c4p_j`F`Gao(2Qsxgg@>S_P#+u2vUC zg3zDj6bO@W0|^35R3SMH^?)!9iS_W^O|5RV$i1{&mm7wqQe3l4Tqhk>Aw^SHPr`!U z4V4S?h4~?jJH#+mt&MRQV}s}##*vlf$B*1CXAyw8*rZf9?-Yw0Ds5V|f}(8}Ps1Oa->-*?=o0J`HKui`(M(+v{%znVOrENJt~5LJ(168CFm{ zISZ>JyL%JK46NG8EPv$qBM+-G5=K!KggB}Ogkh9eKRe^aqM5|Dyh$(~#MmR{R~yCn zr6sq#KVpiBWreC8f;)1{BH@~B)eV`Fu&%XaL#5r2-jq!eCEL`HvC`6%7beTEwi$FB z!I-ouF!ptMJc1A)6pM3wPo{F2H7U#Bl@X&N!B`O*sB*mH z#&f`&9}) z&n5rZrO!8>d;SN-zvj{8wN8lk0#OzrG&0O-tVy|9NtCjJRL(FYuEwaV@f(j_k$JFx zRPNiiUZY+ezKryC_HD^Y<>B4&q5Jx6|Evty2fd@i1G^lv=f1im7ht~urQXr}?e7b} zc1jE%LtSC0OL6W!&)OFw^K0pEzJJZrdG1a{YF$cX!;rh@rq6wFFrh7WDG86RSZ&^! W6H;Natzg>K$yFXOS6*=^YJUNrHM_z9 literal 0 HcmV?d00001 diff --git a/fsk_radio_range_test/main.py b/fsk_radio_range_test/main.py index aea581a..3989a3c 100644 --- a/fsk_radio_range_test/main.py +++ b/fsk_radio_range_test/main.py @@ -125,7 +125,6 @@ def set_param_from_input_range(param, prompt_str, choice_range, allow_default=Fa CS.switch_to_output(value=True) RESET.switch_to_output(value=True) - raise ValueError("Feather: untested") print(f"{bold}{green}Feather{normal} selected") else: # board_str == "r" # raspberry pi @@ -155,7 +154,7 @@ def set_param_from_input_range(param, prompt_str, choice_range, allow_default=Fa rfm9x.rx_bandwidth = 50.0 timeout = 30 rfm9x.preamble_length = 16 -rfm9x.ack_wait = 10 +rfm9x.ack_wait = 1.0 if param_str == "y": rfm9x.frequency_mhz = set_param_from_input_range(rfm9x.frequency_mhz, f"Frequency (currently {rfm9x.frequency_mhz} MHz)", diff --git a/radio-utils/lib/adafruit_rfm9x.py b/radio-utils/lib/adafruit_rfm9x.py index fa65888..c3431ad 100644 --- a/radio-utils/lib/adafruit_rfm9x.py +++ b/radio-utils/lib/adafruit_rfm9x.py @@ -30,58 +30,97 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM9x.git" # Internal constants: -# Register names (FSK Mode even though we use LoRa instead, from table 85) +# Register names + +# FIFO _RH_RF95_REG_00_FIFO = const(0x00) +# Registers for common settings _RH_RF95_REG_01_OP_MODE = const(0x01) +_RH_RF95_REG_02_BITRATE_MSB = const(0x02) +_RH_RF95_REG_03_BITRATE_LSB = const(0x03) +_RH_RF95_REG_04_FREQ_DEVIATION_MSB = const(0x04) +_RH_RF95_REG_05_FREQ_DEVIATION_LSB = const(0x05) _RH_RF95_REG_06_FRF_MSB = const(0x06) _RH_RF95_REG_07_FRF_MID = const(0x07) _RH_RF95_REG_08_FRF_LSB = const(0x08) +# Registers for the transmitter _RH_RF95_REG_09_PA_CONFIG = const(0x09) _RH_RF95_REG_0A_PA_RAMP = const(0x0A) _RH_RF95_REG_0B_OCP = const(0x0B) +# Registers for the receiver _RH_RF95_REG_0C_LNA = const(0x0C) -_RH_RF95_REG_0D_FIFO_ADDR_PTR = const(0x0D) -_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR = const(0x0E) -_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR = const(0x0F) -_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR = const(0x10) -_RH_RF95_REG_11_IRQ_FLAGS_MASK = const(0x11) -_RH_RF95_REG_12_IRQ_FLAGS = const(0x12) -_RH_RF95_REG_13_RX_NB_BYTES = const(0x13) -_RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB = const(0x14) -_RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB = const(0x15) -_RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB = const(0x16) -_RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB = const(0x17) -_RH_RF95_REG_18_MODEM_STAT = const(0x18) -_RH_RF95_REG_19_PKT_SNR_VALUE = const(0x19) -_RH_RF95_REG_1A_PKT_RSSI_VALUE = const(0x1A) -_RH_RF95_REG_1B_RSSI_VALUE = const(0x1B) -_RH_RF95_REG_1C_HOP_CHANNEL = const(0x1C) -_RH_RF95_REG_1D_MODEM_CONFIG1 = const(0x1D) -_RH_RF95_REG_1E_MODEM_CONFIG2 = const(0x1E) -_RH_RF95_REG_1F_SYMB_TIMEOUT_LSB = const(0x1F) -_RH_RF95_REG_20_PREAMBLE_MSB = const(0x20) -_RH_RF95_REG_21_PREAMBLE_LSB = const(0x21) -_RH_RF95_REG_22_PAYLOAD_LENGTH = const(0x22) -_RH_RF95_REG_23_MAX_PAYLOAD_LENGTH = const(0x23) -_RH_RF95_REG_24_HOP_PERIOD = const(0x24) -_RH_RF95_REG_25_FIFO_RX_BYTE_ADDR = const(0x25) -_RH_RF95_REG_26_MODEM_CONFIG3 = const(0x26) - +_RH_RF95_REG_0D_RX_CONFIG = const(0x0D) +_RH_RF95_REG_0E_RSSI_CONFIG = const(0x0E) +_RH_RF95_REG_0F_RSSI_COLLISION = const(0x0F) +_RH_RF95_REG_10_RSSI_THRESH = const(0x10) +_RH_RF95_REG_11_RSSI_VALUE = const(0x11) +_RH_RF95_REG_12_RX_BW = const(0x12) +_RH_RF95_REG_13_AFC_BW = const(0x13) +_RH_RF95_REG_14_OOK_PEAK = const(0x14) +_RH_RF95_REG_15_OOK_FIX = const(0x15) +_RH_RF95_REG_16_OOK_AVG = const(0x16) +# 0x17 to 0x19 - Reserved +_RH_RF95_REG_1A_AFC_FEI = const(0x1A) +_RH_RF95_REG_1B_AFC_MSB = const(0x1B) +_RH_RF95_REG_1C_AFC_LSB = const(0x1C) +_RH_RF95_REG_1D_FEI_MSB = const(0x1D) +_RH_RF95_REG_1E_FEI_LSB = const(0x1E) +_RH_RF95_REG_1F_PREAMBLE_DETECT = const(0x1F) +_RH_RF95_REG_20_RX_TIMEOUT1 = const(0x20) +_RH_RF95_REG_21_RX_TIMEOUT2 = const(0x21) +_RH_RF95_REG_22_RX_TIMEOUT3 = const(0x22) +_RH_RF95_REG_23_RX_DELAY = const(0x23) +# Registers for RC oscillator +_RH_RF95_REG_24_OSC = const(0x24) +# Registers for packet handling +_RH_RF95_REG_25_PREAMBLE_MSB = const(0x25) +_RH_RF95_REG_26_PREAMBLE_LSB = const(0x26) +_RH_RF95_REG_27_SYNC_CONFIG = const(0x27) +_RH_RF95_REG_28_SYNC_VALUE_1 = const(0x28) # Most significant byte +_RH_RF95_REG_29_SYNC_VALUE_2 = const(0x29) +_RH_RF95_REG_2A_SYNC_VALUE_3 = const(0x2A) +_RH_RF95_REG_2B_SYNC_VALUE_4 = const(0x2B) +_RH_RF95_REG_2C_SYNC_VALUE_5 = const(0x2C) +_RH_RF95_REG_2D_SYNC_VALUE_6 = const(0x2D) +_RH_RF95_REG_2E_SYNC_VALUE_7 = const(0x2E) +_RH_RF95_REG_2F_SYNC_VALUE_8 = const(0x2F) +_RH_RF95_REG_30_PKT_CONFIG_1 = const(0x30) +_RH_RF95_REG_31_PKT_CONFIG_2 = const(0x31) +_RH_RF95_REG_32_PAYLOAD_LEN = const(0x32) +_RH_RF95_REG_33_NODE_ADDRESS = const(0x33) +_RH_RF95_REG_34_BROADCAST_ADDRESS = const(0x34) +_RH_RF95_REG_35_FIFO_THRESH = const(0x35) +# Sequencer registers +_RH_RF95_REG_36_SEQ_CONFIG_1 = const(0x36) +_RH_RF95_REG_37_SEQ_CONFIG_2 = const(0x37) +_RH_RF95_REG_38_TIMER_RESOLUTION = const(0x38) +_RH_RF95_REG_39_TIMER1_COEF = const(0x39) +_RH_RF95_REG_3A_TIMER2_COEF = const(0x3A) +# Service registers +_RH_RF95_REG_3B_IMAGE_CAL = const(0x3B) +_RH_RF95_REG_3C_TEMP = const(0x3C) +_RH_RF95_REG_3D_LOW_BATT = const(0x3C) +# Status registers +_RH_RF95_REG_3E_IRQ_FLAGS_1 = const(0x3D) +_RH_RF95_REG_3F_IRQ_FLAGS_2 = const(0x3F) +# IO control registers _RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) _RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) _RH_RF95_REG_42_VERSION = const(0x42) - +# Additional registers +_RH_RF95_REG_44_PLL_HOP = const(0x44) _RH_RF95_REG_4B_TCXO = const(0x4B) _RH_RF95_REG_4D_PA_DAC = const(0x4D) _RH_RF95_REG_5B_FORMER_TEMP = const(0x5B) +_RH_RF95_REG_5D_BITRATE_FRAC = const(0x5D) +# Band-specific additional registers _RH_RF95_REG_61_AGC_REF = const(0x61) _RH_RF95_REG_62_AGC_THRESH1 = const(0x62) _RH_RF95_REG_63_AGC_THRESH2 = const(0x63) _RH_RF95_REG_64_AGC_THRESH3 = const(0x64) +_RH_RF95_REG_70_PLL = const(0x70) -_RH_RF95_DETECTION_OPTIMIZE = const(0x31) -_RH_RF95_DETECTION_THRESHOLD = const(0x37) - +# PA DAC register options _RH_RF95_PA_DAC_DISABLE = const(0x04) _RH_RF95_PA_DAC_ENABLE = const(0x07) @@ -112,6 +151,8 @@ _TICKS_MAX = const(_TICKS_PERIOD - 1) _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) +_MAX_FIFO_LENGTH = 66 + # Disable the too many instance members warning. Pylint has no knowledge # of the context and is merely guessing at the proper amount of members. This # is a complex chip which requires exposing many attributes and state. Disable @@ -129,9 +170,16 @@ def ticks_diff(ticks1, ticks2): return diff +def twos_comp(val, bits): + """compute the 2's complement of int value val""" + if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 + val = val - (1 << bits) # compute negative value + return val # return positive value as is + + class RFM9x: - """Interface to a RFM95/6/7/8 LoRa radio module. Allows sending and - receivng bytes of data in long range LoRa mode at a support board frequency + """Interface to a RFM95/6/7/8 radio module. Allows sending and + receiving bytes of data in FSK mode at a supported board frequency (433/915mhz). You must specify the following parameters: @@ -208,38 +256,48 @@ def __set__(self, obj, val): reg_value |= (val & 0xFF) << self._offset obj._write_u8(self._address, reg_value) - operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, bits=3) + operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=0, bits=3) - low_frequency_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=3, bits=1) + low_frequency_mode = _RegisterBits( + _RH_RF95_REG_01_OP_MODE, offset=3, bits=1) modulation_type = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=5, bits=2) - # Long range/LoRa mode can only be set in sleep mode! + # Long range mode (LoRa or FSK) can only be set in sleep mode! long_range_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=7, bits=1) - output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, bits=4) + output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=0, bits=4) max_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3) pa_select = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1) - pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, bits=3) + pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, offset=0, bits=3) dio0_mapping = _RegisterBits(_RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2) - auto_agc = _RegisterBits(_RH_RF95_REG_26_MODEM_CONFIG3, offset=2, bits=1) - - low_datarate_optimize = _RegisterBits( - _RH_RF95_REG_26_MODEM_CONFIG3, offset=3, bits=1 - ) - lna_boost_hf = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=0, bits=2) - auto_ifon = _RegisterBits(_RH_RF95_DETECTION_OPTIMIZE, offset=7, bits=1) + lna_gain = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=5, bits=3) + + modulation_shaping = _RegisterBits( + _RH_RF95_REG_0A_PA_RAMP, offset=6, bits=2) - detection_optimize = _RegisterBits(_RH_RF95_DETECTION_OPTIMIZE, offset=0, bits=3) + packet_format = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=7, bits=1) + dc_free = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=5, bits=2) + crc_on = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=4, bits=1) + crc_auto_clear = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=3, bits=1) + address_filtering = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=1, bits=2) + data_mode = _RegisterBits(_RH_RF95_REG_31_PKT_CONFIG_2, offset=6, bits=1) - bw_bins = (7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000) + _bw_mantissa = _RegisterBits(_RH_RF95_REG_12_RX_BW, offset=3, bits=2) + _bw_exponent = _RegisterBits(_RH_RF95_REG_12_RX_BW, offset=0, bits=3) + _bw_bins_kHz = (2.5, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, + 25.0, 31.3, 41.7, 50.0, 62.5, 83.3, 100.0, 125.0, 166.7, 200.0, 250.0) + _bw_mant_bins = (2, 1, 0, 2, 1, 0, 2, 1, 0, 2, + 1, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0) + _bw_exp_bins = (7, 7, 7, 6, 6, 6, 5, 5, 5, 4, + 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1) def __init__( self, @@ -247,18 +305,18 @@ def __init__( cs, reset, frequency, - *, preamble_length=8, high_power=True, - code_rate=5, - baudrate=5000000, - agc=False, + bitrate=1200, + frequency_deviation=5000, + spi_baudrate=5000000, crc=True ): self.high_power = high_power # Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz. # Set Default Baudrate to 5MHz to avoid problems - self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0) + self._device = spidev.SPIDevice( + spi, cs, baudrate=spi_baudrate, polarity=0, phase=0) # Setup reset as a digital output - initially High # This line is pulled low as an output quickly to trigger a reset. self._reset = reset @@ -274,48 +332,47 @@ def __init__( ) # Set sleep mode, wait 10s and confirm in sleep mode (basic device check). - # Also set long range mode (LoRa mode) as it can only be done in sleep. + # Also set long range mode to false (FSK mode) as it can only be done in sleep. self.sleep() time.sleep(0.01) - self.long_range_mode = True - if self.operation_mode != SLEEP_MODE or not self.long_range_mode: - raise RuntimeError("Failed to configure radio for LoRa mode, check wiring!") + self.long_range_mode = False # choose FSK instead of LoRA + if self.operation_mode != SLEEP_MODE or self.long_range_mode: + raise RuntimeError( + "Failed to configure radio for FSK mode, check wiring!") # clear default setting for access to LF registers if frequency > 525MHz if frequency > 525: self.low_frequency_mode = 0 - # Setup entire 256 byte FIFO - self._write_u8(_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0x00) - self._write_u8(_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00) - # Set mode idle + else: + self.low_frequency_mode = 1 + self.idle() - # Set frequency + + # Set modulation type to FSK + self.modulation_type = 0x00 + # Gaussian filter, BT = 0.5 + self.modulation_shaping = 0b10 + self.frequency_deviation = frequency_deviation + self.bitrate = bitrate self.frequency_mhz = frequency - # Set preamble length (default 8 bytes to match radiohead). self.preamble_length = preamble_length - # Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode. - self.signal_bandwidth = 125000 - self.coding_rate = code_rate - self.spreading_factor = 7 - # Default to enable CRC checking on incoming packets. + + self.packet_format = 0b1 # variable length packets + self.dc_free = 0b01 # Manchester coding + self.crc_on = crc self.enable_crc = crc - """CRC Enable state""" - # set AGC - Default = False - self.auto_agc = agc - """Automatic Gain Control state""" - # Set transmit power to 13 dBm, a safe value any module supports. - self.tx_power = 13 - # initialize last RSSI reading + self.crc_auto_clear = 0b1 # FIFO not cleared for packets that fail CRC + self.address_filtering = 0b00 # no address filtering - handled in software + self.data_mode = 0b1 # packet mode + + self.tx_power = 13 # 13 dBm is a safe value any module support + self.last_rssi = 0.0 """The RSSI of the last received packet. Stored when the packet was received. The instantaneous RSSI value may not be accurate once the operating mode has been changed. """ - self.last_snr = 0.0 - """The SNR of the last received packet. Stored when the packet was received. - The instantaneous SNR value may not be accurate once the - operating mode has been changed. - """ - # initialize timeouts and delays delays + + # initialize timeouts and delays self.ack_wait = 0.5 """The delay time before attempting a retry after not receiving an ACK""" self.receive_timeout = 0.5 @@ -376,6 +433,16 @@ def _read_into(self, address, buf, length=None): device.write(self._BUFFER, end=1) device.readinto(buf, end=length) + def _read_until_flag(self, address, buf, flag): + # read bytes from the given address until flag is true + idx = 0 + while not flag(): + buf[idx] = self._read_u8(address) + idx += 1 + if idx > len(buf): + raise RuntimeError(f"Overflow reading into buffer of length {len(buf)}") + return idx + def _read_u8(self, address): # Read a single byte from the provided address and return it. self._read_into(address, self._BUFFER, length=1) @@ -419,7 +486,7 @@ def sleep(self): self.operation_mode = SLEEP_MODE def listen(self): - """Listen for packets to be received by the chip. Use :py:func:`receive` + """Listen for packets to be received by the chip. Use: py: func: `receive` to listen, wait and retrieve packets as they're available. """ self.operation_mode = RX_MODE @@ -428,31 +495,31 @@ def listen(self): def transmit(self): """Transmit a packet which is queued in the FIFO. This is a low level function for entering transmit mode and more. For generating and - transmitting a packet of data use :py:func:`send` instead. + transmitting a packet of data use: py: func: `send` instead. """ self.operation_mode = TX_MODE - self.dio0_mapping = 0b01 # Interrupt on tx done. + self.dio0_mapping = 0b00 # Interrupt on tx done. @property def preamble_length(self): - """The length of the preamble for sent and received packets, an unsigned - 16-bit value. Received packets must match this length or they are - ignored! Set to 8 to match the RadioHead RFM95 library. + """The length of the preamble for sent packets, an unsigned + 16-bit value. Default is 0x0003. """ - msb = self._read_u8(_RH_RF95_REG_20_PREAMBLE_MSB) - lsb = self._read_u8(_RH_RF95_REG_21_PREAMBLE_LSB) + msb = self._read_u8(_RH_RF95_REG_25_PREAMBLE_MSB) + lsb = self._read_u8(_RH_RF95_REG_26_PREAMBLE_LSB) return ((msb << 8) | lsb) & 0xFFFF @preamble_length.setter def preamble_length(self, val): + val = int(val) assert 0 <= val <= 65535 - self._write_u8(_RH_RF95_REG_20_PREAMBLE_MSB, (val >> 8) & 0xFF) - self._write_u8(_RH_RF95_REG_21_PREAMBLE_LSB, val & 0xFF) + self._write_u8(_RH_RF95_REG_25_PREAMBLE_MSB, (val >> 8) & 0xFF) + self._write_u8(_RH_RF95_REG_26_PREAMBLE_LSB, val & 0xFF) @property def frequency_mhz(self): """The frequency of the radio in Megahertz. Only the allowed values for - your radio must be specified (i.e. 433 vs. 915 mhz)! + your radio must be specified(i.e. 433 vs. 915 mhz)! """ msb = self._read_u8(_RH_RF95_REG_06_FRF_MSB) mid = self._read_u8(_RH_RF95_REG_07_FRF_MID) @@ -475,19 +542,78 @@ def frequency_mhz(self, val): self._write_u8(_RH_RF95_REG_07_FRF_MID, mid) self._write_u8(_RH_RF95_REG_08_FRF_LSB, lsb) + @property + def bitrate(self): + msb = self._read_u8(_RH_RF95_REG_02_BITRATE_MSB) + lsb = self._read_u8(_RH_RF95_REG_03_BITRATE_LSB) + frac = self._read_u8(_RH_RF95_REG_5D_BITRATE_FRAC) & 0x0F + + int_part = ((msb << 8) | lsb) & 0xFFFF + + br = _RH_RF95_FXOSC / (int_part + (frac / 16)) + + return br + + @bitrate.setter + def bitrate(self, val): + br = _RH_RF95_FXOSC / val + int_part = int(br) + frac_part = int(16 * (br % 1)) & 0x0F + + msb = (int_part >> 8) & 0xFF + lsb = int_part & 0xFF + + self._write_u8(_RH_RF95_REG_02_BITRATE_MSB, msb) + self._write_u8(_RH_RF95_REG_03_BITRATE_LSB, lsb) + self._write_u8(_RH_RF95_REG_5D_BITRATE_FRAC, frac_part) + + @property + def frequency_deviation(self): + msb = self._read_u8(_RH_RF95_REG_04_FREQ_DEVIATION_MSB) & 0x3F + lsb = self._read_u8(_RH_RF95_REG_05_FREQ_DEVIATION_LSB) + + fd = (((msb << 8) | lsb) & 0xFFFF) * _RH_RF95_FSTEP + + return fd + + @frequency_deviation.setter + def frequency_deviation(self, val): + val = int(val / _RH_RF95_FSTEP) + msb = (val >> 8) & 0x3F + lsb = val & 0xFF + + self._write_u8(_RH_RF95_REG_04_FREQ_DEVIATION_MSB, msb) + self._write_u8(_RH_RF95_REG_05_FREQ_DEVIATION_LSB, lsb) + + @property + def frequency_error(self): + """ + The frequency error + """ + msb = self._read_u8(_RH_RF95_REG_1D_FEI_MSB) + lsb = self._read_u8(_RH_RF95_REG_1E_FEI_LSB) + + fei_value = twos_comp( + ((msb << 8) | lsb) & 0xFFFF, 16) + f_error = fei_value * _RH_RF95_FSTEP + return f_error + @property def tx_power(self): """The transmit power in dBm. Can be set to a value from 5 to 23 for - high power devices (RFM95/96/97/98, high_power=True) or -1 to 14 for low - power devices. Only integer power levels are actually set (i.e. 12.5 + high power devices(RFM95/96/97/98, high_power=True) or -1 to 14 for low + power devices. Only integer power levels are actually set(i.e. 12.5 will result in a value of 12 dBm). - The actual maximum setting for high_power=True is 20dBm but for values > 20 + The actual maximum setting for high_power = True is 20dBm but for values > 20 the PA_BOOST will be enabled resulting in an additional gain of 3dBm. The actual setting is reduced by 3dBm. - The reported value will reflect the reduced setting. """ if self.high_power: - return self.output_power + 5 + if self.pa_dac & 0x07 == _RH_RF95_PA_DAC_ENABLE: + return self.output_power + 5 + 3 + else: + return self.output_power + 5 + return self.output_power - 1 @tx_power.setter @@ -516,151 +642,56 @@ def rssi(self): """The received strength indicator (in dBm) of the last received message.""" # Read RSSI register and convert to value using formula in datasheet. # Remember in LoRa mode the payload register changes function to RSSI! - raw_rssi = self._read_u8(_RH_RF95_REG_1A_PKT_RSSI_VALUE) - if self.low_frequency_mode: - raw_rssi -= 157 - else: - raw_rssi -= 164 - return raw_rssi - - @property - def snr(self): - """The SNR (in dB) of the last received message.""" - # Read SNR 0x19 register and convert to value using formula in datasheet. - # SNR(dB) = PacketSnr [twos complement] / 4 - snr_byte = self._read_u8(_RH_RF95_REG_19_PKT_SNR_VALUE) - if snr_byte > 127: - snr_byte = (256 - snr_byte) * -1 - return snr_byte / 4 + raw_rssi = self._read_u8(_RH_RF95_REG_11_RSSI_VALUE) + return -raw_rssi / 2 @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), - ) - if val >= 500000: - # see Semtech SX1276 errata note 2.3 - self.auto_ifon = True - # see Semtech SX1276 errata note 2.1 - if self.low_frequency_mode: - self._write_u8(0x36, 0x02) - self._write_u8(0x3A, 0x7F) - else: - self._write_u8(0x36, 0x02) - self._write_u8(0x3A, 0x64) + def rx_bandwidth(self): + """ + The receiver filter bandwidth in kHz. + """ + # Defined using a mantissa and exponent(see table 40, pg 88 in Semtech Docs) + mant_binary = self._bw_mantissa + exp = self._bw_exponent + + if mant_binary == 0b10: + mant = 24 + elif mant_binary == 0b01: + mant = 20 + elif mant_binary == 0b00: + mant = 16 else: - # see Semtech SX1276 errata note 2.3 - self.auto_ifon = False - self._write_u8(0x36, 0x03) - if val == 7800: - self._write_u8(0x2F, 0x48) - elif val >= 62500: - # see Semtech SX1276 errata note 2.3 - self._write_u8(0x2F, 0x40) - else: - self._write_u8(0x2F, 0x44) - self._write_u8(0x30, 0) + raise ValueError(f"RX bandwidth mantissa {mant_binary} invalid") - @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), - ) + rxbw = _RH_RF95_FXOSC / (mant * (2**(exp+2))) + return rxbw/1000 - @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) - - if val == 6: - self.detection_optimize = 0x5 - else: - self.detection_optimize = 0x3 + @rx_bandwidth.setter + def rx_bandwidth(self, val): + try: + idx = self._bw_bins_kHz.index(val) + except ValueError: + raise ValueError( + f"Invalid recieve bandwidth {val}, must be one of {self._bw_bins_kHz}") - 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, - ) + self._bw_mantissa = self._bw_mant_bins[idx] + self._bw_exponent = self._bw_exp_bins[idx] def tx_done(self): """Transmit status""" - return (self._read_u8(_RH_RF95_REG_12_IRQ_FLAGS) & 0x8) >> 3 + return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b1000) >> 3 def rx_done(self): """Receive status""" - return (self._read_u8(_RH_RF95_REG_12_IRQ_FLAGS) & 0x40) >> 6 + return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0100) >> 2 - def crc_error(self): + def crc_ok(self): """crc status""" - return (self._read_u8(_RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 + return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0010) >> 1 + + def fifo_empty(self): + """True when FIFO is empty""" + return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & (0b1 << 6)) >> 6 # pylint: disable=too-many-branches def send( @@ -678,8 +709,8 @@ def send( (limited by chip's FIFO size and appended headers). This appends a 4 byte header to be compatible with the RadioHead library. The header defaults to using the initialized attributes: - (destination,node,identifier,flags) - It may be temporarily overidden via the kwargs - destination,node,identifier,flags. + (destination, node, identifier, flags) + It may be temporarily overidden via the kwargs - destination, node, identifier, flags. Values passed via kwargs do not alter the attribute settings. The keep_listening argument should be set to True if you want to start listening automatically after the packet is sent. The default setting is False. @@ -691,34 +722,34 @@ def send( # efficient and proper way to ensure a precondition that the provided # buffer be within an expected range of bounds. Disable this check. # pylint: disable=len-as-condition - assert 0 < len(data) <= 252 + assert 0 < len(data) <= 59 # TODO: Allow longer packets, see pg 76 # pylint: enable=len-as-condition self.idle() # Stop receiving to clear FIFO and keep it clear. - # Fill the FIFO with a packet to send. - self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. + # Combine header and data to form payload - payload = bytearray(4) + payload = bytearray(5) + payload[0] = len(payload) + len(data) - 1 # first byte is length to meet semtech FSK requirements (pg 74) if destination is None: # use attribute - payload[0] = self.destination + payload[1] = self.destination else: # use kwarg - payload[0] = destination + payload[1] = destination if node is None: # use attribute - payload[1] = self.node + payload[2] = self.node else: # use kwarg - payload[1] = node + payload[2] = node if identifier is None: # use attribute - payload[2] = self.identifier + payload[3] = self.identifier else: # use kwarg - payload[2] = identifier + payload[3] = identifier if flags is None: # use attribute - payload[3] = self.flags + payload[4] = self.flags else: # use kwarg - payload[3] = flags + payload[4] = flags + payload = payload + data # Write payload. self._write_from(_RH_RF95_REG_00_FIFO, payload) - # Write payload and header length. - self._write_u8(_RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + # Turn on transmit mode to send out the packet. self.transmit() # Wait for tx done interrupt with explicit polling (not ideal but @@ -734,17 +765,16 @@ def send( while not timed_out and not self.tx_done(): if time.monotonic() - start >= self.xmit_timeout: timed_out = True - # Listen again if necessary and return the result packet. + + # Done transmitting - change modes (interrupt automatically cleared on mode change) if keep_listening: self.listen() else: # Enter idle mode to stop receiving other packets. self.idle() - # Clear interrupt. - self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return not timed_out - def send_with_ack(self, data): + def send_with_ack(self, data, debug=False): """Reliable Datagram mode: Send a packet with data and wait for an ACK response. The packet header is automatically generated. @@ -764,17 +794,22 @@ def send_with_ack(self, data): got_ack = True else: # wait for a packet from our destination - ack_packet = self.receive(timeout=self.ack_wait, with_header=True) + ack_packet = self.receive( + timeout=self.ack_wait, with_header=True, debug=debug) if ack_packet is not None: - if ack_packet[3] & _RH_FLAGS_ACK: + if ack_packet[4] & _RH_FLAGS_ACK: # check the ID - if ack_packet[2] == self.identifier: + if ack_packet[3] == self.identifier: got_ack = True break + if debug: + print(f"Invalid ACK packet {str(ack_packet)}") # pause before next retry -- random delay if not got_ack: # delay by random amount before next try time.sleep(self.ack_wait + self.ack_wait * random.random()) + if debug: + print(f"No ACK, retrying send - retries remaining: {retries_remaining}") retries_remaining = retries_remaining - 1 # set retry flag in packet header self.flags |= _RH_FLAGS_RETRY @@ -782,21 +817,21 @@ def send_with_ack(self, data): return got_ack def receive( - self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None + self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None, debug=False ): """Wait to receive a packet from the receiver. If a packet is found the payload bytes - are returned, otherwise None is returned (which indicates the timeout elapsed with no + are returned, otherwise None is returned(which indicates the timeout elapsed with no reception). If keep_listening is True (the default) the chip will immediately enter listening mode after reception of a packet, otherwise it will fall back to idle mode and ignore any future reception. All packets must have a 4-byte header for compatibilty with the RadioHead library. - The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip + The header consists of 4 bytes(To, From, ID, Flags). The default setting will strip the header before returning the packet to the caller. If with_header is True then the 4 byte header will be returned with the packet. The payload then begins at packet[4]. - If with_ack is True, send an ACK after receipt (Reliable Datagram mode) + If with_ack is True, send an ACK after receipt(Reliable Datagram mode) """ timed_out = False if timeout is None: @@ -824,43 +859,47 @@ def receive( # save last RSSI reading self.last_rssi = self.rssi - # save the last SNR reading - self.last_snr = self.snr - - # Enter idle mode to stop receiving other packets. - self.idle() if not timed_out: - print("Received packet") - if self.enable_crc and self.crc_error(): + # Enter idle mode to stop receiving other packets. + self.idle() + if self.enable_crc and not self.crc_ok(): + if debug: + print("RFM9X: CRC Error") self.crc_error_count += 1 else: # Read the data from the FIFO. - # Read the length of the FIFO. - fifo_length = self._read_u8(_RH_RF95_REG_13_RX_NB_BYTES) - # Handle if the received packet is too small to include the 4 byte + packet = bytearray(_MAX_FIFO_LENGTH) + # Read the packet. + packet_length = self._read_until_flag(_RH_RF95_REG_00_FIFO, packet, self.fifo_empty) + + # Handle if the received packet is too small to include the 1 byte length, 4 byte # RadioHead header and at least one byte of data --reject this packet and ignore it. - if fifo_length > 0: # read and clear the FIFO if anything in it - 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(fifo_length) - # Read the packet. - self._read_into(_RH_RF95_REG_00_FIFO, packet) - # Clear interrupt. - self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - if fifo_length < 5: + if packet_length < 6: + if debug: + print( + f"RFM9X: Incomplete message (packet_length = {packet_length} < 6, packet = {packet.decode('utf-8', 'backslashreplace')}") packet = None else: - if ( + internal_packet_length = packet[0] + if internal_packet_length != packet_length - 1: + if debug: + print( + f"RFM9X: Received packet length ({packet_length}) does not match transmitted packet length ({internal_packet_length})") + packet = None + elif ( self.node != _RH_BROADCAST_ADDRESS - and packet[0] != _RH_BROADCAST_ADDRESS - and packet[0] != self.node + and packet[1] != _RH_BROADCAST_ADDRESS + and packet[1] != self.node ): + if debug: + print( + f"RFM9X: Incorrect Address (packet address = {packet[1]} != my address = {self.node}") packet = None # send ACK unless this was an ACK or a broadcast elif ( with_ack - and ((packet[3] & _RH_FLAGS_ACK) == 0) - and (packet[0] != _RH_BROADCAST_ADDRESS) + and ((packet[4] & _RH_FLAGS_ACK) == 0) + and (packet[1] != _RH_BROADCAST_ADDRESS) ): # delay before sending Ack to give receiver a chance to get ready if self.ack_delay is not None: @@ -868,28 +907,30 @@ def receive( # send ACK packet to sender (data is b'!') self.send( b"!", - destination=packet[1], - node=packet[0], - identifier=packet[2], - flags=(packet[3] | _RH_FLAGS_ACK), + destination=packet[2], + node=packet[1], + identifier=packet[3], + flags=(packet[4] | _RH_FLAGS_ACK), ) # reject Retries if we have seen this idetifier from this source before - if (self.seen_ids[packet[1]] == packet[2]) and ( - packet[3] & _RH_FLAGS_RETRY + # TODO: Does seen_ids ever get cleared? + if (self.seen_ids[packet[2]] == packet[3]) and ( + packet[4] & _RH_FLAGS_RETRY ): + if debug: + print(f"RFM9X: dropping retried packet") packet = None else: # save the packet identifier for this source - self.seen_ids[packet[1]] = packet[2] + self.seen_ids[packet[2]] = packet[3] if ( not with_header and packet is not None ): # skip the header if not wanted - packet = packet[4:] - # Listen again if necessary and return the result packet. - if keep_listening: - self.listen() - else: + packet = packet[5:] + # Listen again if necessary and return the result packet. + if keep_listening: + self.listen() + + if not keep_listening: # Enter idle mode to stop receiving other packets. self.idle() - # Clear interrupt. - self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return packet From 19b422222eaa6be0ce310b7937c911524edda02e Mon Sep 17 00:00:00 2001 From: thetazero Date: Fri, 25 Nov 2022 16:54:13 -0500 Subject: [PATCH 2/3] undo mistake --- radio-utils/lib/adafruit_rfm9x.py | 626 ++++++++++++++---------------- 1 file changed, 292 insertions(+), 334 deletions(-) diff --git a/radio-utils/lib/adafruit_rfm9x.py b/radio-utils/lib/adafruit_rfm9x.py index c3431ad..e215ce4 100644 --- a/radio-utils/lib/adafruit_rfm9x.py +++ b/radio-utils/lib/adafruit_rfm9x.py @@ -30,97 +30,58 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM9x.git" # Internal constants: -# Register names - -# FIFO +# Register names (FSK Mode even though we use LoRa instead, from table 85) _RH_RF95_REG_00_FIFO = const(0x00) -# Registers for common settings _RH_RF95_REG_01_OP_MODE = const(0x01) -_RH_RF95_REG_02_BITRATE_MSB = const(0x02) -_RH_RF95_REG_03_BITRATE_LSB = const(0x03) -_RH_RF95_REG_04_FREQ_DEVIATION_MSB = const(0x04) -_RH_RF95_REG_05_FREQ_DEVIATION_LSB = const(0x05) _RH_RF95_REG_06_FRF_MSB = const(0x06) _RH_RF95_REG_07_FRF_MID = const(0x07) _RH_RF95_REG_08_FRF_LSB = const(0x08) -# Registers for the transmitter _RH_RF95_REG_09_PA_CONFIG = const(0x09) _RH_RF95_REG_0A_PA_RAMP = const(0x0A) _RH_RF95_REG_0B_OCP = const(0x0B) -# Registers for the receiver _RH_RF95_REG_0C_LNA = const(0x0C) -_RH_RF95_REG_0D_RX_CONFIG = const(0x0D) -_RH_RF95_REG_0E_RSSI_CONFIG = const(0x0E) -_RH_RF95_REG_0F_RSSI_COLLISION = const(0x0F) -_RH_RF95_REG_10_RSSI_THRESH = const(0x10) -_RH_RF95_REG_11_RSSI_VALUE = const(0x11) -_RH_RF95_REG_12_RX_BW = const(0x12) -_RH_RF95_REG_13_AFC_BW = const(0x13) -_RH_RF95_REG_14_OOK_PEAK = const(0x14) -_RH_RF95_REG_15_OOK_FIX = const(0x15) -_RH_RF95_REG_16_OOK_AVG = const(0x16) -# 0x17 to 0x19 - Reserved -_RH_RF95_REG_1A_AFC_FEI = const(0x1A) -_RH_RF95_REG_1B_AFC_MSB = const(0x1B) -_RH_RF95_REG_1C_AFC_LSB = const(0x1C) -_RH_RF95_REG_1D_FEI_MSB = const(0x1D) -_RH_RF95_REG_1E_FEI_LSB = const(0x1E) -_RH_RF95_REG_1F_PREAMBLE_DETECT = const(0x1F) -_RH_RF95_REG_20_RX_TIMEOUT1 = const(0x20) -_RH_RF95_REG_21_RX_TIMEOUT2 = const(0x21) -_RH_RF95_REG_22_RX_TIMEOUT3 = const(0x22) -_RH_RF95_REG_23_RX_DELAY = const(0x23) -# Registers for RC oscillator -_RH_RF95_REG_24_OSC = const(0x24) -# Registers for packet handling -_RH_RF95_REG_25_PREAMBLE_MSB = const(0x25) -_RH_RF95_REG_26_PREAMBLE_LSB = const(0x26) -_RH_RF95_REG_27_SYNC_CONFIG = const(0x27) -_RH_RF95_REG_28_SYNC_VALUE_1 = const(0x28) # Most significant byte -_RH_RF95_REG_29_SYNC_VALUE_2 = const(0x29) -_RH_RF95_REG_2A_SYNC_VALUE_3 = const(0x2A) -_RH_RF95_REG_2B_SYNC_VALUE_4 = const(0x2B) -_RH_RF95_REG_2C_SYNC_VALUE_5 = const(0x2C) -_RH_RF95_REG_2D_SYNC_VALUE_6 = const(0x2D) -_RH_RF95_REG_2E_SYNC_VALUE_7 = const(0x2E) -_RH_RF95_REG_2F_SYNC_VALUE_8 = const(0x2F) -_RH_RF95_REG_30_PKT_CONFIG_1 = const(0x30) -_RH_RF95_REG_31_PKT_CONFIG_2 = const(0x31) -_RH_RF95_REG_32_PAYLOAD_LEN = const(0x32) -_RH_RF95_REG_33_NODE_ADDRESS = const(0x33) -_RH_RF95_REG_34_BROADCAST_ADDRESS = const(0x34) -_RH_RF95_REG_35_FIFO_THRESH = const(0x35) -# Sequencer registers -_RH_RF95_REG_36_SEQ_CONFIG_1 = const(0x36) -_RH_RF95_REG_37_SEQ_CONFIG_2 = const(0x37) -_RH_RF95_REG_38_TIMER_RESOLUTION = const(0x38) -_RH_RF95_REG_39_TIMER1_COEF = const(0x39) -_RH_RF95_REG_3A_TIMER2_COEF = const(0x3A) -# Service registers -_RH_RF95_REG_3B_IMAGE_CAL = const(0x3B) -_RH_RF95_REG_3C_TEMP = const(0x3C) -_RH_RF95_REG_3D_LOW_BATT = const(0x3C) -# Status registers -_RH_RF95_REG_3E_IRQ_FLAGS_1 = const(0x3D) -_RH_RF95_REG_3F_IRQ_FLAGS_2 = const(0x3F) -# IO control registers +_RH_RF95_REG_0D_FIFO_ADDR_PTR = const(0x0D) +_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR = const(0x0E) +_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR = const(0x0F) +_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR = const(0x10) +_RH_RF95_REG_11_IRQ_FLAGS_MASK = const(0x11) +_RH_RF95_REG_12_IRQ_FLAGS = const(0x12) +_RH_RF95_REG_13_RX_NB_BYTES = const(0x13) +_RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB = const(0x14) +_RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB = const(0x15) +_RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB = const(0x16) +_RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB = const(0x17) +_RH_RF95_REG_18_MODEM_STAT = const(0x18) +_RH_RF95_REG_19_PKT_SNR_VALUE = const(0x19) +_RH_RF95_REG_1A_PKT_RSSI_VALUE = const(0x1A) +_RH_RF95_REG_1B_RSSI_VALUE = const(0x1B) +_RH_RF95_REG_1C_HOP_CHANNEL = const(0x1C) +_RH_RF95_REG_1D_MODEM_CONFIG1 = const(0x1D) +_RH_RF95_REG_1E_MODEM_CONFIG2 = const(0x1E) +_RH_RF95_REG_1F_SYMB_TIMEOUT_LSB = const(0x1F) +_RH_RF95_REG_20_PREAMBLE_MSB = const(0x20) +_RH_RF95_REG_21_PREAMBLE_LSB = const(0x21) +_RH_RF95_REG_22_PAYLOAD_LENGTH = const(0x22) +_RH_RF95_REG_23_MAX_PAYLOAD_LENGTH = const(0x23) +_RH_RF95_REG_24_HOP_PERIOD = const(0x24) +_RH_RF95_REG_25_FIFO_RX_BYTE_ADDR = const(0x25) +_RH_RF95_REG_26_MODEM_CONFIG3 = const(0x26) + _RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) _RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) _RH_RF95_REG_42_VERSION = const(0x42) -# Additional registers -_RH_RF95_REG_44_PLL_HOP = const(0x44) + _RH_RF95_REG_4B_TCXO = const(0x4B) _RH_RF95_REG_4D_PA_DAC = const(0x4D) _RH_RF95_REG_5B_FORMER_TEMP = const(0x5B) -_RH_RF95_REG_5D_BITRATE_FRAC = const(0x5D) -# Band-specific additional registers _RH_RF95_REG_61_AGC_REF = const(0x61) _RH_RF95_REG_62_AGC_THRESH1 = const(0x62) _RH_RF95_REG_63_AGC_THRESH2 = const(0x63) _RH_RF95_REG_64_AGC_THRESH3 = const(0x64) -_RH_RF95_REG_70_PLL = const(0x70) -# PA DAC register options +_RH_RF95_DETECTION_OPTIMIZE = const(0x31) +_RH_RF95_DETECTION_THRESHOLD = const(0x37) + _RH_RF95_PA_DAC_DISABLE = const(0x04) _RH_RF95_PA_DAC_ENABLE = const(0x07) @@ -151,8 +112,6 @@ _TICKS_MAX = const(_TICKS_PERIOD - 1) _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) -_MAX_FIFO_LENGTH = 66 - # Disable the too many instance members warning. Pylint has no knowledge # of the context and is merely guessing at the proper amount of members. This # is a complex chip which requires exposing many attributes and state. Disable @@ -170,16 +129,9 @@ def ticks_diff(ticks1, ticks2): return diff -def twos_comp(val, bits): - """compute the 2's complement of int value val""" - if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 - val = val - (1 << bits) # compute negative value - return val # return positive value as is - - class RFM9x: - """Interface to a RFM95/6/7/8 radio module. Allows sending and - receiving bytes of data in FSK mode at a supported board frequency + """Interface to a RFM95/6/7/8 LoRa radio module. Allows sending and + receivng bytes of data in long range LoRa mode at a support board frequency (433/915mhz). You must specify the following parameters: @@ -256,48 +208,38 @@ def __set__(self, obj, val): reg_value |= (val & 0xFF) << self._offset obj._write_u8(self._address, reg_value) - operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=0, bits=3) + operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, bits=3) - low_frequency_mode = _RegisterBits( - _RH_RF95_REG_01_OP_MODE, offset=3, bits=1) + low_frequency_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=3, bits=1) modulation_type = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=5, bits=2) - # Long range mode (LoRa or FSK) can only be set in sleep mode! + # Long range/LoRa mode can only be set in sleep mode! long_range_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=7, bits=1) - output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=0, bits=4) + output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, bits=4) max_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3) pa_select = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1) - pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, offset=0, bits=3) + pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, bits=3) dio0_mapping = _RegisterBits(_RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2) - lna_boost_hf = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=0, bits=2) + auto_agc = _RegisterBits(_RH_RF95_REG_26_MODEM_CONFIG3, offset=2, bits=1) - lna_gain = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=5, bits=3) + low_datarate_optimize = _RegisterBits( + _RH_RF95_REG_26_MODEM_CONFIG3, offset=3, bits=1 + ) + + lna_boost_hf = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=0, bits=2) - modulation_shaping = _RegisterBits( - _RH_RF95_REG_0A_PA_RAMP, offset=6, bits=2) + auto_ifon = _RegisterBits(_RH_RF95_DETECTION_OPTIMIZE, offset=7, bits=1) - packet_format = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=7, bits=1) - dc_free = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=5, bits=2) - crc_on = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=4, bits=1) - crc_auto_clear = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=3, bits=1) - address_filtering = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=1, bits=2) - data_mode = _RegisterBits(_RH_RF95_REG_31_PKT_CONFIG_2, offset=6, bits=1) + detection_optimize = _RegisterBits(_RH_RF95_DETECTION_OPTIMIZE, offset=0, bits=3) - _bw_mantissa = _RegisterBits(_RH_RF95_REG_12_RX_BW, offset=3, bits=2) - _bw_exponent = _RegisterBits(_RH_RF95_REG_12_RX_BW, offset=0, bits=3) - _bw_bins_kHz = (2.5, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, - 25.0, 31.3, 41.7, 50.0, 62.5, 83.3, 100.0, 125.0, 166.7, 200.0, 250.0) - _bw_mant_bins = (2, 1, 0, 2, 1, 0, 2, 1, 0, 2, - 1, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0) - _bw_exp_bins = (7, 7, 7, 6, 6, 6, 5, 5, 5, 4, - 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1) + bw_bins = (7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000) def __init__( self, @@ -305,18 +247,18 @@ def __init__( cs, reset, frequency, + *, preamble_length=8, high_power=True, - bitrate=1200, - frequency_deviation=5000, - spi_baudrate=5000000, + code_rate=5, + baudrate=5000000, + agc=False, crc=True ): self.high_power = high_power # Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz. # Set Default Baudrate to 5MHz to avoid problems - self._device = spidev.SPIDevice( - spi, cs, baudrate=spi_baudrate, polarity=0, phase=0) + self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0) # Setup reset as a digital output - initially High # This line is pulled low as an output quickly to trigger a reset. self._reset = reset @@ -332,47 +274,48 @@ def __init__( ) # Set sleep mode, wait 10s and confirm in sleep mode (basic device check). - # Also set long range mode to false (FSK mode) as it can only be done in sleep. + # Also set long range mode (LoRa mode) as it can only be done in sleep. self.sleep() time.sleep(0.01) - self.long_range_mode = False # choose FSK instead of LoRA - if self.operation_mode != SLEEP_MODE or self.long_range_mode: - raise RuntimeError( - "Failed to configure radio for FSK mode, check wiring!") + self.long_range_mode = True + if self.operation_mode != SLEEP_MODE or not self.long_range_mode: + raise RuntimeError("Failed to configure radio for LoRa mode, check wiring!") # clear default setting for access to LF registers if frequency > 525MHz if frequency > 525: self.low_frequency_mode = 0 - else: - self.low_frequency_mode = 1 - + # Setup entire 256 byte FIFO + self._write_u8(_RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0x00) + self._write_u8(_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00) + # Set mode idle self.idle() - - # Set modulation type to FSK - self.modulation_type = 0x00 - # Gaussian filter, BT = 0.5 - self.modulation_shaping = 0b10 - self.frequency_deviation = frequency_deviation - self.bitrate = bitrate + # Set frequency self.frequency_mhz = frequency + # Set preamble length (default 8 bytes to match radiohead). self.preamble_length = preamble_length - - self.packet_format = 0b1 # variable length packets - self.dc_free = 0b01 # Manchester coding - self.crc_on = crc + # Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode. + self.signal_bandwidth = 125000 + self.coding_rate = code_rate + self.spreading_factor = 7 + # Default to enable CRC checking on incoming packets. self.enable_crc = crc - self.crc_auto_clear = 0b1 # FIFO not cleared for packets that fail CRC - self.address_filtering = 0b00 # no address filtering - handled in software - self.data_mode = 0b1 # packet mode - - self.tx_power = 13 # 13 dBm is a safe value any module support - + """CRC Enable state""" + # set AGC - Default = False + self.auto_agc = agc + """Automatic Gain Control state""" + # Set transmit power to 13 dBm, a safe value any module supports. + self.tx_power = 13 + # initialize last RSSI reading self.last_rssi = 0.0 """The RSSI of the last received packet. Stored when the packet was received. The instantaneous RSSI value may not be accurate once the operating mode has been changed. """ - - # initialize timeouts and delays + self.last_snr = 0.0 + """The SNR of the last received packet. Stored when the packet was received. + The instantaneous SNR value may not be accurate once the + operating mode has been changed. + """ + # initialize timeouts and delays delays self.ack_wait = 0.5 """The delay time before attempting a retry after not receiving an ACK""" self.receive_timeout = 0.5 @@ -433,16 +376,6 @@ def _read_into(self, address, buf, length=None): device.write(self._BUFFER, end=1) device.readinto(buf, end=length) - def _read_until_flag(self, address, buf, flag): - # read bytes from the given address until flag is true - idx = 0 - while not flag(): - buf[idx] = self._read_u8(address) - idx += 1 - if idx > len(buf): - raise RuntimeError(f"Overflow reading into buffer of length {len(buf)}") - return idx - def _read_u8(self, address): # Read a single byte from the provided address and return it. self._read_into(address, self._BUFFER, length=1) @@ -486,7 +419,7 @@ def sleep(self): self.operation_mode = SLEEP_MODE def listen(self): - """Listen for packets to be received by the chip. Use: py: func: `receive` + """Listen for packets to be received by the chip. Use :py:func:`receive` to listen, wait and retrieve packets as they're available. """ self.operation_mode = RX_MODE @@ -495,31 +428,31 @@ def listen(self): def transmit(self): """Transmit a packet which is queued in the FIFO. This is a low level function for entering transmit mode and more. For generating and - transmitting a packet of data use: py: func: `send` instead. + transmitting a packet of data use :py:func:`send` instead. """ self.operation_mode = TX_MODE - self.dio0_mapping = 0b00 # Interrupt on tx done. + self.dio0_mapping = 0b01 # Interrupt on tx done. @property def preamble_length(self): - """The length of the preamble for sent packets, an unsigned - 16-bit value. Default is 0x0003. + """The length of the preamble for sent and received packets, an unsigned + 16-bit value. Received packets must match this length or they are + ignored! Set to 8 to match the RadioHead RFM95 library. """ - msb = self._read_u8(_RH_RF95_REG_25_PREAMBLE_MSB) - lsb = self._read_u8(_RH_RF95_REG_26_PREAMBLE_LSB) + msb = self._read_u8(_RH_RF95_REG_20_PREAMBLE_MSB) + lsb = self._read_u8(_RH_RF95_REG_21_PREAMBLE_LSB) return ((msb << 8) | lsb) & 0xFFFF @preamble_length.setter def preamble_length(self, val): - val = int(val) assert 0 <= val <= 65535 - self._write_u8(_RH_RF95_REG_25_PREAMBLE_MSB, (val >> 8) & 0xFF) - self._write_u8(_RH_RF95_REG_26_PREAMBLE_LSB, val & 0xFF) + self._write_u8(_RH_RF95_REG_20_PREAMBLE_MSB, (val >> 8) & 0xFF) + self._write_u8(_RH_RF95_REG_21_PREAMBLE_LSB, val & 0xFF) @property def frequency_mhz(self): """The frequency of the radio in Megahertz. Only the allowed values for - your radio must be specified(i.e. 433 vs. 915 mhz)! + your radio must be specified (i.e. 433 vs. 915 mhz)! """ msb = self._read_u8(_RH_RF95_REG_06_FRF_MSB) mid = self._read_u8(_RH_RF95_REG_07_FRF_MID) @@ -542,78 +475,19 @@ def frequency_mhz(self, val): self._write_u8(_RH_RF95_REG_07_FRF_MID, mid) self._write_u8(_RH_RF95_REG_08_FRF_LSB, lsb) - @property - def bitrate(self): - msb = self._read_u8(_RH_RF95_REG_02_BITRATE_MSB) - lsb = self._read_u8(_RH_RF95_REG_03_BITRATE_LSB) - frac = self._read_u8(_RH_RF95_REG_5D_BITRATE_FRAC) & 0x0F - - int_part = ((msb << 8) | lsb) & 0xFFFF - - br = _RH_RF95_FXOSC / (int_part + (frac / 16)) - - return br - - @bitrate.setter - def bitrate(self, val): - br = _RH_RF95_FXOSC / val - int_part = int(br) - frac_part = int(16 * (br % 1)) & 0x0F - - msb = (int_part >> 8) & 0xFF - lsb = int_part & 0xFF - - self._write_u8(_RH_RF95_REG_02_BITRATE_MSB, msb) - self._write_u8(_RH_RF95_REG_03_BITRATE_LSB, lsb) - self._write_u8(_RH_RF95_REG_5D_BITRATE_FRAC, frac_part) - - @property - def frequency_deviation(self): - msb = self._read_u8(_RH_RF95_REG_04_FREQ_DEVIATION_MSB) & 0x3F - lsb = self._read_u8(_RH_RF95_REG_05_FREQ_DEVIATION_LSB) - - fd = (((msb << 8) | lsb) & 0xFFFF) * _RH_RF95_FSTEP - - return fd - - @frequency_deviation.setter - def frequency_deviation(self, val): - val = int(val / _RH_RF95_FSTEP) - msb = (val >> 8) & 0x3F - lsb = val & 0xFF - - self._write_u8(_RH_RF95_REG_04_FREQ_DEVIATION_MSB, msb) - self._write_u8(_RH_RF95_REG_05_FREQ_DEVIATION_LSB, lsb) - - @property - def frequency_error(self): - """ - The frequency error - """ - msb = self._read_u8(_RH_RF95_REG_1D_FEI_MSB) - lsb = self._read_u8(_RH_RF95_REG_1E_FEI_LSB) - - fei_value = twos_comp( - ((msb << 8) | lsb) & 0xFFFF, 16) - f_error = fei_value * _RH_RF95_FSTEP - return f_error - @property def tx_power(self): """The transmit power in dBm. Can be set to a value from 5 to 23 for - high power devices(RFM95/96/97/98, high_power=True) or -1 to 14 for low - power devices. Only integer power levels are actually set(i.e. 12.5 + high power devices (RFM95/96/97/98, high_power=True) or -1 to 14 for low + power devices. Only integer power levels are actually set (i.e. 12.5 will result in a value of 12 dBm). - The actual maximum setting for high_power = True is 20dBm but for values > 20 + The actual maximum setting for high_power=True is 20dBm but for values > 20 the PA_BOOST will be enabled resulting in an additional gain of 3dBm. The actual setting is reduced by 3dBm. + The reported value will reflect the reduced setting. """ if self.high_power: - if self.pa_dac & 0x07 == _RH_RF95_PA_DAC_ENABLE: - return self.output_power + 5 + 3 - else: - return self.output_power + 5 - + return self.output_power + 5 return self.output_power - 1 @tx_power.setter @@ -642,56 +516,151 @@ def rssi(self): """The received strength indicator (in dBm) of the last received message.""" # Read RSSI register and convert to value using formula in datasheet. # Remember in LoRa mode the payload register changes function to RSSI! - raw_rssi = self._read_u8(_RH_RF95_REG_11_RSSI_VALUE) - return -raw_rssi / 2 + raw_rssi = self._read_u8(_RH_RF95_REG_1A_PKT_RSSI_VALUE) + if self.low_frequency_mode: + raw_rssi -= 157 + else: + raw_rssi -= 164 + return raw_rssi @property - def rx_bandwidth(self): - """ - The receiver filter bandwidth in kHz. - """ - # Defined using a mantissa and exponent(see table 40, pg 88 in Semtech Docs) - mant_binary = self._bw_mantissa - exp = self._bw_exponent - - if mant_binary == 0b10: - mant = 24 - elif mant_binary == 0b01: - mant = 20 - elif mant_binary == 0b00: - mant = 16 + def snr(self): + """The SNR (in dB) of the last received message.""" + # Read SNR 0x19 register and convert to value using formula in datasheet. + # SNR(dB) = PacketSnr [twos complement] / 4 + snr_byte = self._read_u8(_RH_RF95_REG_19_PKT_SNR_VALUE) + if snr_byte > 127: + snr_byte = (256 - snr_byte) * -1 + return snr_byte / 4 + + @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: - raise ValueError(f"RX bandwidth mantissa {mant_binary} invalid") + 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), + ) + if val >= 500000: + # see Semtech SX1276 errata note 2.3 + self.auto_ifon = True + # see Semtech SX1276 errata note 2.1 + if self.low_frequency_mode: + self._write_u8(0x36, 0x02) + self._write_u8(0x3A, 0x7F) + else: + self._write_u8(0x36, 0x02) + self._write_u8(0x3A, 0x64) + else: + # see Semtech SX1276 errata note 2.3 + self.auto_ifon = False + self._write_u8(0x36, 0x03) + if val == 7800: + self._write_u8(0x2F, 0x48) + elif val >= 62500: + # see Semtech SX1276 errata note 2.3 + self._write_u8(0x2F, 0x40) + else: + self._write_u8(0x2F, 0x44) + self._write_u8(0x30, 0) - rxbw = _RH_RF95_FXOSC / (mant * (2**(exp+2))) - return rxbw/1000 + @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), + ) - @rx_bandwidth.setter - def rx_bandwidth(self, val): - try: - idx = self._bw_bins_kHz.index(val) - except ValueError: - raise ValueError( - f"Invalid recieve bandwidth {val}, must be one of {self._bw_bins_kHz}") + @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) + + if val == 6: + self.detection_optimize = 0x5 + else: + self.detection_optimize = 0x3 - self._bw_mantissa = self._bw_mant_bins[idx] - self._bw_exponent = self._bw_exp_bins[idx] + 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 tx_done(self): """Transmit status""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b1000) >> 3 + return (self._read_u8(_RH_RF95_REG_12_IRQ_FLAGS) & 0x8) >> 3 def rx_done(self): """Receive status""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0100) >> 2 + return (self._read_u8(_RH_RF95_REG_12_IRQ_FLAGS) & 0x40) >> 6 - def crc_ok(self): + def crc_error(self): """crc status""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0010) >> 1 - - def fifo_empty(self): - """True when FIFO is empty""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & (0b1 << 6)) >> 6 + return (self._read_u8(_RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 # pylint: disable=too-many-branches def send( @@ -709,8 +678,8 @@ def send( (limited by chip's FIFO size and appended headers). This appends a 4 byte header to be compatible with the RadioHead library. The header defaults to using the initialized attributes: - (destination, node, identifier, flags) - It may be temporarily overidden via the kwargs - destination, node, identifier, flags. + (destination,node,identifier,flags) + It may be temporarily overidden via the kwargs - destination,node,identifier,flags. Values passed via kwargs do not alter the attribute settings. The keep_listening argument should be set to True if you want to start listening automatically after the packet is sent. The default setting is False. @@ -722,34 +691,34 @@ def send( # efficient and proper way to ensure a precondition that the provided # buffer be within an expected range of bounds. Disable this check. # pylint: disable=len-as-condition - assert 0 < len(data) <= 59 # TODO: Allow longer packets, see pg 76 + assert 0 < len(data) <= 252 # pylint: enable=len-as-condition self.idle() # Stop receiving to clear FIFO and keep it clear. - + # Fill the FIFO with a packet to send. + self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. # Combine header and data to form payload - payload = bytearray(5) - payload[0] = len(payload) + len(data) - 1 # first byte is length to meet semtech FSK requirements (pg 74) + payload = bytearray(4) if destination is None: # use attribute - payload[1] = self.destination + payload[0] = self.destination else: # use kwarg - payload[1] = destination + payload[0] = destination if node is None: # use attribute - payload[2] = self.node + payload[1] = self.node else: # use kwarg - payload[2] = node + payload[1] = node if identifier is None: # use attribute - payload[3] = self.identifier + payload[2] = self.identifier else: # use kwarg - payload[3] = identifier + payload[2] = identifier if flags is None: # use attribute - payload[4] = self.flags + payload[3] = self.flags else: # use kwarg - payload[4] = flags - + payload[3] = flags payload = payload + data # Write payload. self._write_from(_RH_RF95_REG_00_FIFO, payload) - + # Write payload and header length. + self._write_u8(_RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) # Turn on transmit mode to send out the packet. self.transmit() # Wait for tx done interrupt with explicit polling (not ideal but @@ -765,16 +734,17 @@ def send( while not timed_out and not self.tx_done(): if time.monotonic() - start >= self.xmit_timeout: timed_out = True - - # Done transmitting - change modes (interrupt automatically cleared on mode change) + # Listen again if necessary and return the result packet. if keep_listening: self.listen() else: # Enter idle mode to stop receiving other packets. self.idle() + # Clear interrupt. + self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return not timed_out - def send_with_ack(self, data, debug=False): + def send_with_ack(self, data): """Reliable Datagram mode: Send a packet with data and wait for an ACK response. The packet header is automatically generated. @@ -794,22 +764,17 @@ def send_with_ack(self, data, debug=False): got_ack = True else: # wait for a packet from our destination - ack_packet = self.receive( - timeout=self.ack_wait, with_header=True, debug=debug) + ack_packet = self.receive(timeout=self.ack_wait, with_header=True) if ack_packet is not None: - if ack_packet[4] & _RH_FLAGS_ACK: + if ack_packet[3] & _RH_FLAGS_ACK: # check the ID - if ack_packet[3] == self.identifier: + if ack_packet[2] == self.identifier: got_ack = True break - if debug: - print(f"Invalid ACK packet {str(ack_packet)}") # pause before next retry -- random delay if not got_ack: # delay by random amount before next try time.sleep(self.ack_wait + self.ack_wait * random.random()) - if debug: - print(f"No ACK, retrying send - retries remaining: {retries_remaining}") retries_remaining = retries_remaining - 1 # set retry flag in packet header self.flags |= _RH_FLAGS_RETRY @@ -817,21 +782,21 @@ def send_with_ack(self, data, debug=False): return got_ack def receive( - self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None, debug=False + self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None ): """Wait to receive a packet from the receiver. If a packet is found the payload bytes - are returned, otherwise None is returned(which indicates the timeout elapsed with no + are returned, otherwise None is returned (which indicates the timeout elapsed with no reception). If keep_listening is True (the default) the chip will immediately enter listening mode after reception of a packet, otherwise it will fall back to idle mode and ignore any future reception. All packets must have a 4-byte header for compatibilty with the RadioHead library. - The header consists of 4 bytes(To, From, ID, Flags). The default setting will strip + The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip the header before returning the packet to the caller. If with_header is True then the 4 byte header will be returned with the packet. The payload then begins at packet[4]. - If with_ack is True, send an ACK after receipt(Reliable Datagram mode) + If with_ack is True, send an ACK after receipt (Reliable Datagram mode) """ timed_out = False if timeout is None: @@ -859,47 +824,42 @@ def receive( # save last RSSI reading self.last_rssi = self.rssi + # save the last SNR reading + self.last_snr = self.snr + + # Enter idle mode to stop receiving other packets. + self.idle() if not timed_out: - # Enter idle mode to stop receiving other packets. - self.idle() - if self.enable_crc and not self.crc_ok(): - if debug: - print("RFM9X: CRC Error") + if self.enable_crc and self.crc_error(): self.crc_error_count += 1 else: # Read the data from the FIFO. - packet = bytearray(_MAX_FIFO_LENGTH) - # Read the packet. - packet_length = self._read_until_flag(_RH_RF95_REG_00_FIFO, packet, self.fifo_empty) - - # Handle if the received packet is too small to include the 1 byte length, 4 byte + # Read the length of the FIFO. + fifo_length = self._read_u8(_RH_RF95_REG_13_RX_NB_BYTES) + # Handle if the received packet is too small to include the 4 byte # RadioHead header and at least one byte of data --reject this packet and ignore it. - if packet_length < 6: - if debug: - print( - f"RFM9X: Incomplete message (packet_length = {packet_length} < 6, packet = {packet.decode('utf-8', 'backslashreplace')}") + if fifo_length > 0: # read and clear the FIFO if anything in it + 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(fifo_length) + # Read the packet. + self._read_into(_RH_RF95_REG_00_FIFO, packet) + # Clear interrupt. + self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + if fifo_length < 5: packet = None else: - internal_packet_length = packet[0] - if internal_packet_length != packet_length - 1: - if debug: - print( - f"RFM9X: Received packet length ({packet_length}) does not match transmitted packet length ({internal_packet_length})") - packet = None - elif ( + if ( self.node != _RH_BROADCAST_ADDRESS - and packet[1] != _RH_BROADCAST_ADDRESS - and packet[1] != self.node + and packet[0] != _RH_BROADCAST_ADDRESS + and packet[0] != self.node ): - if debug: - print( - f"RFM9X: Incorrect Address (packet address = {packet[1]} != my address = {self.node}") packet = None # send ACK unless this was an ACK or a broadcast elif ( with_ack - and ((packet[4] & _RH_FLAGS_ACK) == 0) - and (packet[1] != _RH_BROADCAST_ADDRESS) + and ((packet[3] & _RH_FLAGS_ACK) == 0) + and (packet[0] != _RH_BROADCAST_ADDRESS) ): # delay before sending Ack to give receiver a chance to get ready if self.ack_delay is not None: @@ -907,30 +867,28 @@ def receive( # send ACK packet to sender (data is b'!') self.send( b"!", - destination=packet[2], - node=packet[1], - identifier=packet[3], - flags=(packet[4] | _RH_FLAGS_ACK), + destination=packet[1], + node=packet[0], + identifier=packet[2], + flags=(packet[3] | _RH_FLAGS_ACK), ) # reject Retries if we have seen this idetifier from this source before - # TODO: Does seen_ids ever get cleared? - if (self.seen_ids[packet[2]] == packet[3]) and ( - packet[4] & _RH_FLAGS_RETRY + if (self.seen_ids[packet[1]] == packet[2]) and ( + packet[3] & _RH_FLAGS_RETRY ): - if debug: - print(f"RFM9X: dropping retried packet") packet = None else: # save the packet identifier for this source - self.seen_ids[packet[2]] = packet[3] + self.seen_ids[packet[1]] = packet[2] if ( not with_header and packet is not None ): # skip the header if not wanted - packet = packet[5:] - # Listen again if necessary and return the result packet. - if keep_listening: - self.listen() - - if not keep_listening: + packet = packet[4:] + # Listen again if necessary and return the result packet. + if keep_listening: + self.listen() + else: # Enter idle mode to stop receiving other packets. self.idle() + # Clear interrupt. + self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return packet From 3c88f31252831f587e0d78cca759f183624727d8 Mon Sep 17 00:00:00 2001 From: thetazero Date: Thu, 1 Dec 2022 10:05:55 -0500 Subject: [PATCH 3/3] FSK/Lora dual driver initial work --- radio-utils/lib/adafruit_rfm9x.py | 74 ++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/radio-utils/lib/adafruit_rfm9x.py b/radio-utils/lib/adafruit_rfm9x.py index e215ce4..e9c2121 100644 --- a/radio-utils/lib/adafruit_rfm9x.py +++ b/radio-utils/lib/adafruit_rfm9x.py @@ -30,7 +30,9 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM9x.git" # Internal constants: -# Register names (FSK Mode even though we use LoRa instead, from table 85) +# Register names from table 85 of the HOPERF RF96/97/98 datasheet + +# Shared registers _RH_RF95_REG_00_FIFO = const(0x00) _RH_RF95_REG_01_OP_MODE = const(0x01) _RH_RF95_REG_06_FRF_MSB = const(0x06) @@ -40,6 +42,66 @@ _RH_RF95_REG_0A_PA_RAMP = const(0x0A) _RH_RF95_REG_0B_OCP = const(0x0B) _RH_RF95_REG_0C_LNA = const(0x0C) + +_RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) +_RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) +_RH_RF95_REG_42_VERSION = const(0x42) + +# FSK / OOK registers +_BITRATE_MSB = const(0x02) +_BITRATE_LSB = const(0x03) +_FDEV_MSB = const(0x04) +_FDEB_LSB = const(0x05) +_RX_CONFIG = const(0x0D) +_RSSI_CONFIG = const(0x0E) +_RSSI_COLLISION = const(0x0F) +_RSSI_THRESH = const(0x10) +_RSSI_VALUE = const(0x11) +_RX_BW = const(0x12) +_AFC_BW = const(0x13) +_OOK_PEAK = const(0x14) +_OOK_FIX = const(0x15) +_OOK_AVG = const(0x16) +_AFC_FEI = const(0x1A) +_AFC_MSB = const(0x1B) +_AFC_LSB = const(0x1C) +_FEIL_MSB = const(0x1D) +_FEIL_LSB = const(0x1E) +_PREAMBLE_DETECT = const(0x1F) +_RX_TIMEOUT1 = const(0x20) +_RX_TIMEOUT2 = const(0x21) +_RX_TIMEOUT3 = const(0x22) +_RX_DELAY = const(0x23) +_OSC = const(0x24) +_PREAMBLE_MSB = const(0x25) +_PREAMBLE_LSB = const(0x26) +_SYNC_CONFIG = const(0x27) +_SYNC_VALUE1 = const(0x28) +_SYNC_VALUE2 = const(0x29) +_SYNC_VALUE3 = const(0x2a) +_SYNC_VALUE4 = const(0x2b) +_SYNC_VALUE5 = const(0x2c) +_SYNC_VALUE6 = const(0x2d) +_SYNC_VALUE7 = const(0x2e) +_SYNC_VALUE8 = const(0x2f) +_PACKET_CONFIG1 = const(0x30) +_PACKET_CONFIG2 = const(0x31) +_PAYLOAD_LENGTH = const(0x32) +_NODE_ADRS = const(0x33) +_BROADCAST_ADRS = const(0x34) +_FIFO_THRESH = const(0x35) +_SEQ_CONFIG1 = const(0x36) +_SEQ_CONFIG2 = const(0x37) +_TIMER_RESOL = const(0x38) +_TIMER_1_COEF = const(0x39) +_TIMER_2_COEF = const(0x3a) +_IMAGE_CAL = const(0x3b) +_TEMP = const(0x3c) +_LOW_BAT = const(0x3d) +_IRQ_FLAGS1 = const(0x3e) +_IRQ_FLAGS2 = const(0x3f) + +# LoRa TM Mode (From table in section 6.4 of the datasheet) _RH_RF95_REG_0D_FIFO_ADDR_PTR = const(0x0D) _RH_RF95_REG_0E_FIFO_TX_BASE_ADDR = const(0x0E) _RH_RF95_REG_0F_FIFO_RX_BASE_ADDR = const(0x0F) @@ -66,11 +128,9 @@ _RH_RF95_REG_24_HOP_PERIOD = const(0x24) _RH_RF95_REG_25_FIFO_RX_BYTE_ADDR = const(0x25) _RH_RF95_REG_26_MODEM_CONFIG3 = const(0x26) +_RH_RF95_REG_44_PLL_HOP = const(0x44) -_RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) -_RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) -_RH_RF95_REG_42_VERSION = const(0x42) - +# TODO: _RH_RF95_REG_4B_TCXO = const(0x4B) _RH_RF95_REG_4D_PA_DAC = const(0x4D) _RH_RF95_REG_5B_FORMER_TEMP = const(0x5B) @@ -716,7 +776,7 @@ def send( payload[3] = flags payload = payload + data # Write payload. - self._write_from(_RH_RF95_REG_00_FIFO, payload) + self._write_from(_FIFO, payload) # Write payload and header length. self._write_u8(_RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) # Turn on transmit mode to send out the packet. @@ -843,7 +903,7 @@ def receive( self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) packet = bytearray(fifo_length) # Read the packet. - self._read_into(_RH_RF95_REG_00_FIFO, packet) + self._read_into(_FIFO, packet) # Clear interrupt. self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) if fifo_length < 5: