Esempio n. 1
0
class RADIO:
    def __init__(
            self,
            mode=LORA,  # 0 - LoRa, 1 - FSK, 2 - OOK
            pars={
                'freq_kHz': 434000,  # kHz
                'freq_Hz': 0,  # Hz
                'power': 10,  # 2...17 dBm
                'crc': True,  # CRC on/off
                # LoRa mode:
                'bw': 125,  # BW: 7.8...500 kHz
                'sf': 10,  # SF: 6..12
                'cr': 5,  # CR: 5...8
                'ldro': None,  # Low Data Rate Optimize (None - automatic)
                'sw': 0x12,  # Sync Word (allways 0x12)
                'preamble': 8,  # 6...65535
                'implicit_header': False,
                # FSK/OOK mode:
                'bitrate': 4800.,  # bit/s
                'fdev': 5000.,  # frequency deviation [Hz]
                'rx_bw': 10.4,  # 2.6...250 kHz
                'afc_bw': 2.6,  # 2.6...250 kHz
                'afc': False,  # AFC on/off
                'fixed': False,  # fixed packet size or variable
                'dcfree': 0
            },  # 0=None, 1=Manchester or 2=Whitening
            gpio={
                'led': 2,  # blue LED GPIO number on board
                'reset': 5,  # reset pin from GPIO5 (or may be None)
                'dio0': 4,  # DIO0 line to GPIO4
                'cs': 15,  # SPI CS
                'sck': 14,  # SPI SCK
                'mosi': 13,  # SPI MOSI
                'miso': 12
            },  # SPI MISO
            spi_hardware=True,
            spi_baudrate=None,
            onReceive=None):  # receive callback

        # init GPIO
        self.pin_led = Pin(gpio['led'], Pin.OUT)
        self.led(0)  # LED off
        if gpio['reset'] != None:
            self.pin_reset = Pin(gpio['reset'], Pin.OUT, Pin.PULL_UP)
            self.pin_reset.value(1)
        else:
            self.pin_reset = None
        self.pin_dio0 = Pin(gpio['dio0'], Pin.IN, Pin.PULL_UP)
        self.pin_cs = Pin(gpio['cs'], Pin.OUT, Pin.PULL_UP)
        self.pin_cs.value(1)

        # init SPI
        if spi_hardware:
            if spi_baudrate == None: spi_baudrate = 5000000  # 5MHz
            if ESP32:
                self.spi = SPI(1,
                               baudrate=spi_baudrate,
                               polarity=0,
                               phase=0,
                               sck=Pin(14),
                               mosi=Pin(13),
                               miso=Pin(12))
            else:
                self.spi = SPI(1, baudrate=spi_baudrate, polarity=0, phase=0)
        else:
            if spi_baudrate == None: spi_baudrate = 500000  # 500kHz
            self.spi = SPI(-1,
                           baudrate=spi_baudrate,
                           polarity=0,
                           phase=0,
                           sck=Pin(gpio['sck']),
                           mosi=Pin(gpio['mosi']),
                           miso=Pin(gpio['miso']))
            #bits=8, firstbit=SPI.MSB, # FIXME
            #sck=Pin(gpio['sck'], Pin.OUT, Pin.PULL_DOWN),
            #mosi=Pin(gpio['mosi'], Pin.OUT, Pin.PULL_UP),
            #miso=Pin(gpio['miso'], Pin.IN, Pin.PULL_UP))
        self.spi.init()
        self.onReceive(onReceive)
        #self._lock = False
        self.reset()
        self._mode = 0  # LoRa mode by default
        self.init(mode, pars)

    def __exit__(self):
        self.pin_dio0.irq(trigger=0, handler=None)
        self.spi.close()

    def spiTransfer(self, address, value=0x00):
        response = bytearray(1)
        self.pin_cs.value(0)
        self.spi.write(bytes([address]))
        self.spi.write_readinto(bytes([value]), response)
        self.pin_cs.value(1)
        return response

    def readReg(self, address, byteorder='big', signed=False):
        """read 8-bit register by SPI"""
        response = self.spiTransfer(address & 0x7F)
        return int.from_bytes(response, byteorder)

    def writeReg(self, address, value):
        """write 8-bit register by SPI"""
        self.spiTransfer(address | 0x80, value)

    def led(self, on=True):
        """on/off LED on GPIO pin"""
        self.pin_led.value(not LED_ON ^ on)

    def blink(self, times=1, on_ms=100, off_ms=20):
        """short blink LED on GPIO pin"""
        for i in range(times):
            self.led(1)
            sleep_ms(on_ms)
            self.led(0)
            sleep_ms(off_ms)

    def reset(self, low_ms=100, high_ms=100, times=1):
        """hard reset SX127x chip"""
        if self.pin_reset:
            for i in range(times):
                self.pin_reset.value(1)
                sleep_ms(high_ms)
                self.pin_reset.value(0)
                sleep_ms(low_ms)
                self.pin_reset.value(1)
                sleep_ms(high_ms)

    def version(self):
        """get SX127x crystal revision"""
        return self.readReg(REG_VERSION)

    def setMode(self, mode):
        """set mode"""
        self.writeReg(REG_OP_MODE,
                      (self.readReg(REG_OP_MODE) & ~MODES_MASK) | mode)

    def getMode(self):
        """get mode"""
        return self.readReg(REG_OP_MODE) & MODES_MASK

    def lora(self, lora=True):
        """switch to LoRa mode"""
        mode = self.readReg(REG_OP_MODE)  # read mode
        sleep = (mode & ~MODES_MASK) | MODE_SLEEP
        self.writeReg(REG_OP_MODE, sleep)  # go to sleep
        if lora:
            sleep |= MODE_LONG_RANGE
            mode |= MODE_LONG_RANGE
        else:
            sleep &= ~MODE_LONG_RANGE
            mode &= ~MODE_LONG_RANGE
        self.writeReg(REG_OP_MODE, sleep)  # write "long range" bit
        self.writeReg(REG_OP_MODE, mode)  # restore old mode

    def isLora(self):
        """check LoRa (or FSK/OOK) mode"""
        mode = self.readReg(REG_OP_MODE)  # read mode
        return True if (mode & MODE_LONG_RANGE) else False

    def fsk(self, fsk=True):
        """switch to FSK mode"""
        self.lora(not fsk)
        if fsk:
            self.writeReg(REG_OP_MODE,
                          (self.readReg(REG_OP_MODE) & ~MODES_MASK2)
                          | MODE_FSK)

    def ook(self, ook=True):
        """switch to OOK mode"""
        self.lora(not ook)
        if ook:
            self.writeReg(REG_OP_MODE,
                          (self.readReg(REG_OP_MODE) & ~MODES_MASK2)
                          | MODE_OOK)

    def sleep(self):
        """switch to Sleep Mode:"""
        self.setMode(MODE_SLEEP)

    def standby(self):
        """switch ro Standby mode"""
        self.setMode(MODE_STDBY)

    def tx(self, on=True):
        """on/off TX mode (off = standby)"""
        if on: self.setMode(MODE_TX)
        else: self.setMode(MODE_STDBY)

    def rx(self, on=True):
        """on/off RX (continuous) mode (off = standby)"""
        if on: self.setMode(MODE_RX_CONTINUOUS)
        else: self.setMode(MODE_STDBY)

    def cad(self, on=True):
        """on/off CAD (LoRa) mode (off = standby)"""
        if self._mode == 0:  # LoRa mode
            if on: self.setMode(MODE_CAD)
            else: self.setMode(MODE_STDBY)

    def init(self, mode=None, pars=None):
        """init chip"""
        if mode is not None: self._mode = mode
        if pars: self._pars = pars

        # check version
        version = self.version()
        print("SX127x selicon revision = 0x%02X" % version)
        if version != 0x12:
            raise Exception('Invalid SX127x selicon revision')

        # switch mode
        if self._mode == 1: self.fsk()  # FSK
        elif self._mode == 2: self.ook()  # OOK
        else: self.lora()  # LoRa

        # set RF frequency
        self.setFrequency(self._pars['freq_kHz'], self._pars['freq_Hz'])

        # set LNA boost: `LnaBoostHf`->3 (Boost on, 150% LNA current)
        self.setLnaBoost(True)

        # set output power level
        self.setPower(self._pars['power'])

        # enable/disable CRC
        self.enableCRC(self._pars["crc"])

        if self._mode == 0:
            # set LoRaTM options
            self.setBW(self._pars['bw'])
            self._implicitHeaderMode = None
            self.setImplicitHeaderMode(self._pars['implicit_header'])
            sf = self._pars['sf']
            self.setSF(sf)
            ldro = self._pars['ldro']
            if ldro == None:
                ldro = True if sf >= 10 else False  # FIXME
            self.setLDRO(ldro)
            self.setCR(self._pars['cr'])
            self.setPreamble(self._pars['preamble'])
            self.setSW(self._pars['sw'])

            # set AGC auto on (internal AGC loop)
            self.writeReg(REG_MODEM_CONFIG_3,
                          self.readReg(REG_MODEM_CONFIG_3)
                          | 0x04)  # `AgcAutoOn`

            # set base addresses
            self.writeReg(REG_FIFO_TX_BASE_ADDR, FIFO_TX_BASE_ADDR)
            self.writeReg(REG_FIFO_RX_BASE_ADDR, FIFO_RX_BASE_ADDR)

            # set DIO0 mapping (`RxDone`)
            self.writeReg(REG_DIO_MAPPING_1, 0x00)
        else:
            # set FSK/OOK options
            self.continuous(False)  # packet mode by default
            self.setBitrate(self._pars["bitrate"])
            self.setFdev(self._pars["fdev"])
            self.setRxBW(self._pars["rx_bw"])
            self.setAfcBW(self._pars["afc_bw"])
            self._fixedLen = None
            self.setFixedLen(self._pars["fixed"])
            self.enableAFC(self._pars["afc"])
            self.setDcFree(self._pars["dcfree"])

            self.writeReg(REG_RSSI_TRESH, 0xFF)  # default
            self.writeReg(REG_PREAMBLE_LSB, 8)  # 3 by default

            self.writeReg(REG_SYNC_VALUE_1, 0x69)  # 0x01 by default
            self.writeReg(REG_SYNC_VALUE_2, 0x81)  # 0x01 by default
            self.writeReg(REG_SYNC_VALUE_3, 0x7E)  # 0x01 by default
            self.writeReg(REG_SYNC_VALUE_4, 0x96)  # 0x01 by default

            # set `DataMode` to Packet (and reset PayloadLength(10:8) to 0)
            self.writeReg(REG_PACKET_CONFIG_2, 0x40)

            # set TX start FIFO condition
            self.writeReg(REG_FIFO_THRESH, TX_START_FIFO_NOEMPTY)

            # set DIO0 mapping (by default):
            #    in RxContin - `SyncAddres`
            #    in TxContin - `TxReady`
            #    in RxPacket - `PayloadReady` <- used signal
            #    in TxPacket - `PacketSent`
            self.writeReg(REG_DIO_MAPPING_1, 0x00)

            # RSSI and IQ callibrate
            self.rxCalibrate()

        self.standby()

    def setFrequency(self, freq_kHz, freq_Hz=0):
        """set RF frequency [kHz * 1000 + Hz]"""
        self._freq = int(freq_kHz) * 1000 + freq_Hz  # kHz + Hz -> Hz
        freq_code = int(round(self._freq / FSTEP))
        self.writeReg(REG_FRF_MSB, (freq_code >> 16) & 0xFF)
        self.writeReg(REG_FRF_MID, (freq_code >> 8) & 0xFF)
        self.writeReg(REG_FRF_LSB, freq_code & 0xFF)
        mode = self.readReg(REG_OP_MODE)
        if self._freq < 600000000:  # LF <= 525 < _600_ < 779 <= HF [MHz]
            mode |= MODE_LOW_FREQ_MODE_ON  # LF
        else:
            mode &= ~MODE_LOW_FREQ_MODE_ON  # HF
        self.writeReg(REG_OP_MODE, mode)

    def setPower(self, level, PA_BOOST=True, MaxPower=7):
        """set TX Power level 2...17 dBm, select PA_BOOST pin"""
        MaxPower = min(max(MaxPower, 0), 7)
        if PA_BOOST:
            # Select PA_BOOST pin: Pout is limited to ~17..20 dBm
            # Pout = 17 - (15 - OutputPower) dBm
            OutputPower = min(max(level - 2, 0), 15)
            self.writeReg(REG_PA_CONFIG, PA_SELECT | OutputPower)
        else:
            # Select RFO pin: Pout is limited to ~14..15 dBm
            # Pmax = 10.8 + 0.6 * MaxPower  [dBm]
            # Pout = Pmax - (15 - OutputPower)) = 0...15 dBm if MaxPower=7
            OutputPower = min(max(level, 0), 15)
            self.writeReg(REG_PA_CONFIG, (MaxPower << 4) | OutputPower)

    def setHighPower(self, on=True):
        """set high power on PA_BOOST up to +20 dBm"""
        if on:  # +3dB
            self.writeReg(REG_PA_DAC,
                          0x87)  # power on PA_BOOST pin up to +20 dBm
        else:
            self.writeReg(REG_PA_DAC, 0x84)  # default mode

    def setOCP(self, trim_mA=100., on=True):
        """set trimming of OCP current (45...240 mA)"""
        if trim_mA <= 120.:
            OcpTrim = round((trim_mA - 45.) / 5.)
        else:
            OcpTrim = round((trim_mA + 30.) / 10.)
        OcpTrim = min(max(OcpTrim, 0), 27)
        if on:
            OcpTrim |= 0x20  # `OcpOn`
        self.writeReg(REG_OCP, OcpTrim)

    def setLnaBoost(self, LnaBoost=True):
        """set LNA boost on/off (only for high frequency band)"""
        reg = self.readReg(REG_LNA)
        if LnaBoost:
            reg |= 0x03  # set `LnaBoostHf` to 3 (boost on, 150% LNA current)
        else:
            reg &= ~0x03  # set `LnaBoostHf` to 0 (default LNA current)
        self.writeReg(REG_LNA, reg)

    def setRamp(self, shaping=0, ramp=0x09):
        """set modulation shaping code 0..3 (FSK/OOK) and PA rise/fall time code 0..15 (FSK/Lora)"""
        shaping = min(max(shaping, 0), 3)
        ramp = min(max(ramp, 0), 15)
        reg = self.readReg(REG_PA_RAMP)
        reg = (reg & 0x90) | (shaping << 5) | ramp
        self.writeReg(REG_PA_RAMP, reg)

    def enableCRC(self, crc=True, crcAutoClearOff=True):
        """enable/disable CRC (and set CrcAutoClearOff in FSK/OOK mode)"""
        self._crc = crc
        if self._mode == 0:  # LoRa mode
            reg = self.readReg(REG_MODEM_CONFIG_2)
            reg = (reg | 0x04) if crc else (reg & ~0x04)  # `RxPayloadCrcOn`
            self.writeReg(REG_MODEM_CONFIG_2, reg)
        else:  # FSK/OOK mode
            reg = self.readReg(REG_PACKET_CONFIG_1) & ~0x18
            if crc: reg |= 0x10  # `CrcOn`
            if crcAutoClearOff: reg |= 0x08  # `CrcAutoClearOff`
            self.writeReg(REG_PACKET_CONFIG_1, reg)

    def getRxGain(self):
        """get current RX gain code [1..6] from `RegLna` (1 - maximum gain)"""
        return (self.readReg(REG_LNA) >> 5) & 0x07  # `LnaGain`

    def getPktRSSI(self):
        """get Packet RSSI [dB] (LoRa)"""
        if self._mode == 0:  # LoRa mode
            return self.readReg(REG_PKT_RSSI_VALUE) - \
                   (164. if self._freq < 600000000 else 157.)
        else:  # FSK/OOK mode
            return -0.5 * self.readReg(REG_RSSI_VALUE)

    def getRSSI(self):
        """get RSSI [dB]"""
        if self._mode == 0:  # LoRa mode
            return self.readReg(REG_LR_RSSI_VALUE) - \
                   (164. if self._freq < 600000000 else 157.)
        else:  # FSK/OOK mode
            return -0.5 * self.readReg(REG_RSSI_VALUE)

    def getSNR(self):
        """get SNR [dB] (LoRa)"""
        if self._mode == 0:  # LoRa mode
            snr = self.readReg(REG_PKT_SNR_VALUE)
            if snr & 0x80:  # sign bit is 1
                snr -= 256
            return snr * 0.25
        else:  # FSK/OOK mode
            return 0.

    def getIrqFlags(self):
        """get IRQ flags for debug"""
        if self._mode == 0:  # LoRa mode
            irqFlags = self.readReg(REG_IRQ_FLAGS)
            self.writeReg(REG_IRQ_FLAGS, irqFlags)
            return irqFlags
        else:  # FSK/OOK mode
            irqFlags1 = self.readReg(REG_IRQ_FLAGS_1)
            irqFlags2 = self.readReg(REG_IRQ_FLAGS_2)
            return (irqFlags2 << 8) | irqFlags1

    def enableRxIrq(self, enable=True):
        """enable/disable interrupt by RX done for debug (LoRa)"""
        if self._mode == 0:  # LoRa mode
            reg = self.readReg(REG_IRQ_FLAGS_MASK)
            if enable: reg &= ~IRQ_RX_DONE_MASK
            else: reg |= IRQ_RX_DONE_MASK
            self.writeReg(REG_IRQ_FLAGS_MASK, reg)

    def invertIQ(self, invert=True):
        """invert IQ channels (LoRa)"""
        if self._mode == 0:
            reg = self.readReg(REG_INVERT_IQ)
            if invert:
                reg |= 0x40  # `InvertIq` = 1
            else:
                reg &= ~0x40  # `InvertIq` = 0
            self.writeReg(REG_INVERT_IQ, reg)

    def setSF(self, sf=10):
        """set Spreading Factor 6...12 (LoRa)"""
        if self._mode == 0:
            sf = min(max(sf, 6), 12)
            self.writeReg(REG_DETECT_OPTIMIZE, 0xC5 if sf == 6 else 0xC3)
            self.writeReg(REG_DETECTION_THRESHOLD, 0x0C if sf == 6 else 0x0A)
            self.writeReg(REG_MODEM_CONFIG_2,
                          (self.readReg(REG_MODEM_CONFIG_2) & 0x0F) |
                          ((sf << 4) & 0xF0))

    def setLDRO(self, ldro):
        """set Low Data Rate Optimisation (LoRa)"""
        if self._mode == 0:
            self.writeReg(
                REG_MODEM_CONFIG_3,  # `LowDataRateOptimize`
                (self.readReg(REG_MODEM_CONFIG_3) & ~0x08)
                | 0x08 if ldro else 0)

    def setBW(self, sbw):
        """set signal Band Width 7.8-500 kHz (LoRa)"""
        if self._mode == 0:
            bw = len(BW_TABLE) - 1
            for i in range(bw + 1):
                if sbw <= BW_TABLE[i]:
                    bw = i
                    break
            self.writeReg(REG_MODEM_CONFIG_1, \
                               (self.readReg(REG_MODEM_CONFIG_1) & 0x0F) | (bw << 4))

    def setCR(self, denominator):
        """set Coding Rate [5..8] (LoRa)"""
        if self._mode == 0:
            denominator = min(max(denominator, 5), 8)
            cr = denominator - 4
            self.writeReg(REG_MODEM_CONFIG_1,
                          (self.readReg(REG_MODEM_CONFIG_1) & 0xF1) |
                          (cr << 1))

    def setPreamble(self, length):
        """set preamble length [6...65535] (LoRa)"""
        if self._mode == 0:
            self.writeReg(REG_PREAMBLE_MSB, (length >> 8) & 0xFF)
            self.writeReg(REG_PREAMBLE_LSB, (length) & 0xFF)

    def setSW(self, sw):  # LoRa mode only
        """set Sync Word (LoRa)"""
        if self._mode == 0:
            self.writeReg(REG_SYNC_WORD, sw)

    def setImplicitHeaderMode(self, implicitHeaderMode=True):
        """set ImplicitHeaderModeOn (LoRa)"""
        if self._mode == 0:
            if self._implicitHeaderMode != implicitHeaderMode:  # set value only if different
                self._implicitHeaderMode = implicitHeaderMode
                modem_config_1 = self.readReg(REG_MODEM_CONFIG_1)
                config = modem_config_1 | 0x01 if implicitHeaderMode else \
                         modem_config_1 & 0xFE
                self.writeReg(REG_MODEM_CONFIG_1, config)

    def setBitrate(self, bitrate=4800.):
        """set bitrate [bit/s] (FSK/OOK)"""
        if self._mode == 1:  # FSK
            code = int(round((FXOSC * 16.) / bitrate))  # bit/s -> code/frac
            self.writeReg(REG_BITRATE_MSB, (code >> 12) & 0xFF)
            self.writeReg(REG_BITRATE_LSB, (code >> 4) & 0xFF)
            self.writeReg(REG_BITRATE_FRAC, code & 0x0F)
        elif self._mode == 2:  # OOK
            code = int(round(FXOSC / bitrate))  # bit/s -> code
            self.writeReg(REG_BITRATE_MSB, (code >> 8) & 0xFF)
            self.writeReg(REG_BITRATE_LSB, code & 0xFF)
            self.writeReg(REG_BITRATE_FRAC, 0)

    def setFdev(self, fdev=5000.):
        """set frequency deviation (FSK)"""
        if self._mode:
            code = int(round(fdev / FSTEP))  # Hz -> code
            code = min(max(code, 0), 0x3FFF)
            self.writeReg(REG_FDEV_MSB, (code >> 8) & 0xFF)
            self.writeReg(REG_FDEV_LSB, code & 0xFF)

    def setRxBW(self, bw=10.4):
        """set RX BW [kHz] (FSK/OOK)"""
        if self._mode:
            m, e = getRxBw(bw)
            self.writeReg(REG_RX_BW, (m << 3) | e)

    def setAfcBW(self, bw=2.6):
        """set AFC BW [kHz] (FSK/OOK)"""
        if self._mode:
            m, e = getRxBw(bw)
            self.writeReg(REG_AFC_BW, (m << 3) | e)

    def enableAFC(self, afc=True):
        """enable/disable AFC (FSK/OOK)"""
        if self._mode:
            reg = self.readReg(REG_RX_CONFIG)
            if afc: reg |= 0x10  # bit 4: AfcAutoOn -> 1
            else: reg &= ~0x10  # bit 4: AfcAutoOn -> 0
            self.writeReg(REG_RX_CONFIG, reg)

    def setFixedLen(self, fixed=True):
        """set Fixed or Variable packet mode (FSK/OOK)"""
        if self._mode:
            if self._fixedLen != fixed:  # set value only if different
                self._fixedLen = fixed
                reg = self.readReg(REG_PACKET_CONFIG_1)
                if fixed: reg &= ~0x80  # bit 7: PacketFormat -> 0 (fixed size)
                else: reg |= 0x80  # bit 7: PacketFormat -> 1 (variable size)
                self.writeReg(REG_PACKET_CONFIG_1, reg)

    def setDcFree(self, mode=0):
        """set DcFree mode: 0=Off, 1=Manchester, 2=Whitening (FSK/OOK)"""
        if self._mode:
            reg = self.readReg(REG_PACKET_CONFIG_1)
            reg = (reg & 0x9F) | ((mode & 3) << 5)  # bit 6-5 `DcFree`
            self.writeReg(REG_PACKET_CONFIG_1, reg)

    def continuous(self, on=True):
        """select Continuous mode, must use DIO2->DATA, DIO1->DCLK (FSK/OOK)"""
        if self._mode:
            reg = self.readReg(REG_PACKET_CONFIG_2)
            if on: reg &= ~0x40  # bit 6: `DataMode` 0 -> Continuous mode
            else: reg |= 0x40  # bit 6: `DataMode` 1 -> Packet mode
            self.writeReg(REG_PACKET_CONFIG_2, reg)

    def rxCalibrate(self):
        """RSSI and IQ callibration (FSK/OOK)"""
        if self._mode:
            reg = self.readReg(REG_IMAGE_CAL)
            reg |= 0x40  # `ImageCalStart` bit
            self.writeReg(REG_IMAGE_CAL, reg)
            while (self.readReg(REG_IMAGE_CAL) & 0x20):  # `ImageCalRunning`
                pass  # FIXME: check timeout

    def setPllBW(self, bw=3):
        """set PLL bandwidth 0=75, 1=150, 2=225, 3=300 kHz (LoRa/FSK/OOK)"""
        bw = min(max(bw, 0), 3)
        reg = self.readReg(REG_PLL)
        reg = (reg & 0x3F) | (bw << 6)
        self.writeReg(REG_PLL, reg)

    def setFastHop(self, on=True):
        """on/off fast frequency PLL hopping (FSK/OOK)"""
        if self._mode:
            reg = self.readReg(REG_PLL_HOP)
            reg = reg | 0x80 if on else reg & 0x7F  # `FastHopOn`
            self.writeReg(REG_PLL_HOP, reg)

    #def aquire_lock(self, lock=False):
    #    if not MICROPYTHON: # MicroPython is single threaded, doesn't need lock.
    #        if lock:
    #            while self._lock: pass
    #            self._lock = True
    #        else:
    #            self._lock = False

    def send(self, string, fixed=False):
        """send packet (LoRa/FSK/OOK)"""
        #self.aquire_lock(True)  # wait until RX_Done, lock and begin writing.
        self.setMode(MODE_STDBY)
        buf = string.encode()
        size = len(buf)

        if self._mode == 0:  # LoRa mode
            self.setImplicitHeaderMode(fixed)

            # set FIFO TX base address
            self.writeReg(REG_FIFO_ADDR_PTR, FIFO_TX_BASE_ADDR)

            # check size
            size = min(size, MAX_PKT_LENGTH)

            # write data
            for i in range(size):
                self.writeReg(REG_FIFO, buf[i])

            # set length
            self.writeReg(REG_PAYLOAD_LENGTH, size)

            # start TX packet
            self.setMode(MODE_TX)  # put in TX mode

            # wait for TX done, standby automatically on TX_DONE
            while (self.readReg(REG_IRQ_FLAGS) & IRQ_TX_DONE) == 0:
                pass  # FIXME: check timeout

            # clear IRQ's
            self.writeReg(REG_IRQ_FLAGS, IRQ_TX_DONE)

        else:  # FSK/OOK mode
            self.setFixedLen(fixed)
            size = min(size, MAX_PKT_LENGTH)  # limit size

            # set TX start FIFO condition
            #self.writeReg(REG_FIFO_THRESH, TX_START_FIFO_NOEMPTY)

            # wait while FIFO is no empty
            while ((self.readReg(REG_IRQ_FLAGS_2) & IRQ2_FIFO_EMPTY) == 0):
                pass  # FIXME: check timeout

            if self._fixedLen:
                self.writeReg(REG_PAYLOAD_LEN, size)  # fixed length
                #add = 0
            else:
                self.writeReg(REG_FIFO, size)  # variable length
                #add = 1

            # set TX start FIFO condition
            #self.writeReg(REG_FIFO_THRESH, TX_START_FIFO_LEVEL | (size + add))

            # write data to FIFO
            for i in range(size):
                self.writeReg(REG_FIFO, buf[i])

            # start TX packet
            self.setMode(MODE_TX)

            # wait `TxRaedy` (bit 5 in `RegIrqFlags1`)
            #while ((self.readReg(REG_IRQ_FLAGS_1) & IRQ1_TX_READY) == 0):
            #    pass # FIXME: check timeout

            # wait `PacketSent` (bit 3 in `RegIrqFlags2`)
            while ((self.readReg(REG_IRQ_FLAGS_2) & IRQ2_PACKET_SENT) == 0):
                pass  # FIXME: check timeout

            # switch to standby mode
            self.setMode(MODE_STDBY)

        self.collect()
        #self.aquire_lock(False) # unlock when done writing

    def onReceive(self, callback):
        """set callback on receive packet (Lora/FSK/OOK)"""
        self._onReceive = callback
        if callback:
            self.pin_dio0.irq(trigger=Pin.IRQ_RISING,
                              handler=self._handleOnReceive)
        else:
            self.pin_dio0.irq(trigger=0, handler=None)

    def receive(self, size=0):
        """go to RX mode; wait callback by interrupt (LoRa/FSK/OOK)"""
        size = min(size, MAX_PKT_LENGTH)
        if self._mode == 0:  # LoRa mode
            self.setImplicitHeaderMode(size > 0)
            if size > 0:
                self.writeReg(REG_PAYLOAD_LENGTH, size)  # implicit header
        else:  # FSK/OOK mode
            self.setFixedLen(size > 0)
            if size > 0:
                self.writeReg(REG_PAYLOAD_LEN, size)  # fixed length
            else:
                self.writeReg(REG_PAYLOAD_LEN,
                              MAX_PKT_LENGTH)  # variable length
        self.setMode(MODE_RX_CONTINUOUS)

    def collect(self):
        """garbage collection"""
        gc.collect()
        #if MICROPYTHON:
        #    print('[Memory - free: {}   allocated: {}]'.format(gc.mem_free(), gc.mem_alloc()))

    def _handleOnReceive(self, event_source):
        #self.aquire_lock(True)
        if self._mode == 0:  # LoRa mode
            irqFlags = self.readReg(REG_IRQ_FLAGS)  # should be 0x50
            self.writeReg(REG_IRQ_FLAGS, irqFlags)

            if (irqFlags & IRQ_RX_DONE) == 0:  # check `RxDone`
                #self.aquire_lock(False)
                return  # `RxDone` is not set

            print(
                "DIO0 interrupt in LoRa mode by `RxDone` (RegIrqFlags=0x%02X)"
                % irqFlags)

            # check `PayloadCrcError` bit
            crcOk = not bool(irqFlags & IRQ_PAYLOAD_CRC_ERROR)

            # set FIFO address to current RX address
            self.writeReg(REG_FIFO_ADDR_PTR,
                          self.readReg(REG_FIFO_RX_CURRENT_ADDR))

            # read packet length
            packetLen = self.readReg(REG_PAYLOAD_LENGTH) if self._implicitHeaderMode else \
                        self.readReg(REG_RX_NB_BYTES)

        else:  # FSK/OOK mode
            irqFlags = self.readReg(REG_IRQ_FLAGS_2)  # should be 0x26/0x24
            if (irqFlags & IRQ2_PAYLOAD_READY) == 0:
                #self.aquire_lock(False)
                return  # `PayloadReady` is not set

            print(
                "DIO0 interrupt in FSK/OOK mode by `PayloadReady` (RegIrqFlags2=0x%02X)"
                % irqFlags)

            # check `CrcOk` bit
            crcOk = bool(irqFlags & IRQ2_CRC_OK)

            # read packet length
            if self.readReg(REG_PACKET_CONFIG_1) & 0x80:  # `PacketFormat`
                packetLen = self.readReg(REG_FIFO)  # variable length
            else:
                packetLen = self.readReg(REG_PAYLOAD_LEN)  # fixed length

        # read FIFO
        payload = bytearray(packetLen)
        for i in range(packetLen):
            payload[i] = self.readReg(REG_FIFO)
        payload = bytes(payload)
        self.collect()

        # run callback
        if self._onReceive:
            self._onReceive(self, payload, crcOk if self._crc else None)
        self.collect()

        #self.aquire_lock(False)

    def dump(self):
        for i in range(128):
            print("Reg[0x%02X] = 0x%02X" % (i, self.readReg(i)))
Esempio n. 2
0
class LORA:
    def __init__(
            self,
            mode=0,  # 0 - LoRa, 1 - FSK, 2 - OOK
            pars={
                'freq_kHz': 433000,  # kHz
                'freq_Hz': 0,  # Hz
                'tx_power_level': 10,  # dBm
                'enable_crc': False,
                # LoRa mode:
                'signal_bandwidth': 125e3,  # kHz
                'spreading_factor': 10,  # 6..12
                'ldr': None,  # Low Data Rate Optimize
                'coding_rate': 5,  # 5...8
                'preamble_length': 8,  # 6...65k
                'implicit_header': False,
                'sync_word': 0x12,
                # FSK/OOK mode:
                'bitrate': 4800.,  # bit/s
                'fdev': 5000.,  # frequency deviation [Hz]
                'rx_bw': 10.4,  # 2,6...250 kHz
                'afc_bw': 50.0,  # 2,6...250 kHz
                'enable_afc': True,
                'fix_len': False
            },
            gpio={
                'led': 2,  # blue led
                'led_on': 0,  # led on level (0 or 1)
                'reset': 0,  # reset pin
                'dio0': 4,  # DIO0 line
                'cs': 15,  # SPI CS
                'sck': 14,  # SPI SCK
                'mosi': 13,  # SPI MOSI
                'miso': 12
            },  # SPI MISO
            spi_hardware=True,
            spi_baudrate=None,
            onReceive=None):  # receive callback

        # init GPIO
        self.pin_led = Pin(gpio['led'], Pin.OUT)
        self.led_on = gpio['led_on']
        self.led(False)  # LED off
        if gpio['reset'] != None:
            self.pin_reset = Pin(gpio['reset'], Pin.OUT, Pin.PULL_UP)
            self.pin_reset.value(1)
        else:
            self.pin_reset = None
        self.pin_dio0 = Pin(gpio['dio0'], Pin.IN, Pin.PULL_UP)
        self.pin_cs = Pin(gpio['cs'], Pin.OUT, Pin.PULL_UP)
        self.pin_cs.value(1)

        # init SPI
        if spi_hardware:
            if spi_baudrate == None: spi_baudrate = 5000000  # 5MHz
            self.spi = SPI(1, baudrate=spi_baudrate, polarity=0, phase=0)
        else:
            if spi_baudrate == None: spi_baudrate = 500000  # 500kHz
            self.spi = SPI(-1,
                           baudrate=spi_baudrate,
                           polarity=0,
                           phase=0,
                           sck=Pin(gpio['sck']),
                           mosi=Pin(gpio['mosi']),
                           miso=Pin(gpio['miso']))
            #bits=8, firstbit=SPI.MSB, # FIXME
            #sck=Pin(gpio['sck'], Pin.OUT, Pin.PULL_DOWN),
            #mosi=Pin(gpio['mosi'], Pin.OUT, Pin.PULL_UP),
            #miso=Pin(gpio['miso'], Pin.IN, Pin.PULL_UP))
        self.spi.init()
        self.onReceive(onReceive)
        #self._lock = False
        self.reset()
        self.mode = 0  # LoRa mode by default (FIXME)
        self.init(mode, pars)

    def __exit__(self):
        self.pin_dio0.irq(trigger=0, handler=None)
        self.spi.close()

    def led(self, on=True):
        self.pin_led.value(not self.led_on ^ on)

    def blink(self, times=1, on_ms=100, off_ms=20):
        for i in range(times):
            self.led(1)
            sleep_ms(on_ms)
            self.led(0)
            sleep_ms(off_ms)

    def reset(self, low_ms=100, high_ms=100, times=1):
        if self.pin_reset:
            for i in range(times):
                self.pin_reset.value(1)
                sleep_ms(high_ms)
                self.pin_reset.value(0)
                sleep_ms(low_ms)
                self.pin_reset.value(1)
                sleep_ms(high_ms)

    def spi_transfer(self, address, value=0x00):
        response = bytearray(1)
        self.pin_cs.value(0)
        self.spi.write(bytes([address]))
        self.spi.write_readinto(bytes([value]), response)
        self.pin_cs.value(1)
        return response

    def readRegister(self, address, byteorder='big', signed=False):
        response = self.spi_transfer(address & 0x7F)
        return int.from_bytes(response, byteorder)

    def writeRegister(self, address, value):
        self.spi_transfer(address | 0x80, value)

    def version(self):
        return self.readRegister(REG_VERSION)

    def lora(self, lora=True):
        mode = self.readRegister(REG_OP_MODE)  # read mode
        sleep = (mode & ~MODES_MASK) | MODE_SLEEP
        self.writeRegister(REG_OP_MODE, sleep)  # go to sleep
        if lora:
            sleep |= MODE_LONG_RANGE
            mode |= MODE_LONG_RANGE
        else:
            sleep &= ~MODE_LONG_RANGE
            mode &= ~MODE_LONG_RANGE
        self.writeRegister(REG_OP_MODE, sleep)  # write "long range" bit
        self.writeRegister(REG_OP_MODE, mode)  # restore old mode

    def isLora(self):
        mode = self.readRegister(REG_OP_MODE)  # read mode
        return true if (mode & MODE_LONG_RANGE) else false

    def invertIQ(self, invert=True):
        reg = self.readRegister(REG_INVERT_IQ)
        if invert:
            reg |= 0b1000000
        else:
            reg &= ~0b1000000
        self.writeRegister(REG_INVERT_IQ, reg)

    def fsk(self, fsk=True):
        self.lora(not fsk)
        if fsk:
            self.writeRegister(REG_OP_MODE,
                               (self.readRegister(REG_OP_MODE) & ~MODES_MASK2)
                               | MODE_FSK)

    def ook(self, ook=True):
        self.lora(not ook)
        if ook:
            self.writeRegister(REG_OP_MODE,
                               (self.readRegister(REG_OP_MODE) & ~MODES_MASK2)
                               | MODE_OOK)

    def setMode(self, mode):
        self.writeRegister(
            REG_OP_MODE, (self.readRegister(REG_OP_MODE) & ~MODES_MASK) | mode)

    def getMode(self):
        return self.readRegister(REG_OP_MODE) & MODES_MASK

    def sleep(self):
        self.setMode(MODE_SLEEP)

    def standby(self):
        self.setMode(MODE_STDBY)

    def fstx(self, FSTX=True):
        if FSTX: self.setMode(MODE_FS_TX)
        else: self.setMode(MODE_SLEEP)

    def fsrx(self, FSRX=True):
        if FSRX: self.setMode(MODE_FS_RX)
        else: self.setMode(MODE_SLEEP)

    def init(self, mode=None, pars=None):
        if mode is not None: self.mode = mode
        if pars: self.pars = pars

        # check version
        version = self.version()
        print("SX127x selicon revision = 0x%02X" % version)
        if version != 0x12:
            raise Exception('Invalid SX127x selicon revision')

        # switch mode
        if self.mode == 1:
            self.fsk(True)
        elif self.mode == 2:
            self.ook(True)
        else:  # self.mode == 0
            self.lora(True)

        # config RF frequency
        self.setFrequency(self.pars['freq_kHz'], self.pars['freq_Hz'])

        # set LNA boost
        self.writeRegister(REG_LNA, self.readRegister(REG_LNA) | 0x03)

        if self.mode == 0:
            # set LoRaTM options
            self.setSignalBandwidth(self.pars['signal_bandwidth'])
            self.setTxPower(self.pars['tx_power_level'])
            self._implicitHeaderMode = None
            self.implicitHeaderMode(self.pars['implicit_header'])
            sf = self.pars['spreading_factor']
            self.setSpreadingFactor(sf)
            ldr = self.pars['ldr']
            if ldr == None:
                ldr = True if sf >= 10 else False
            self.setLDR(ldr)
            self.setCodingRate(self.pars['coding_rate'])
            self.setPreambleLength(self.pars['preamble_length'])
            self.setSyncWord(self.pars['sync_word'])
            self.enableCRC(self.pars['enable_crc'])

            # set base addresses
            self.writeRegister(REG_FIFO_TX_BASE_ADDR, FIFO_TX_BASE_ADDR)
            self.writeRegister(REG_FIFO_RX_BASE_ADDR, FIFO_RX_BASE_ADDR)
        else:
            # set FSK/OOK options
            self.bitrate(self.pars["bitrate"])
            self.fdev(self.pars["fdev"])
            self.rx_bw(self.pars["rx_bw"])
            self.afc_bw(self.pars["afc_bw"])
            self.enable_afc(self.pars["enable_afc"])
            self.fix_len(self.pars["fix_len"])
            pass  # FIXME

        self.standby()

    def write(self, buffer):  # LoRa mode only
        currentLength = self.readRegister(REG_PAYLOAD_LENGTH)
        size = len(buffer)

        # check size
        size = min(size, (MAX_PKT_LENGTH - FIFO_TX_BASE_ADDR - currentLength))

        # write data
        for i in range(size):
            self.writeRegister(REG_FIFO, buffer[i])

        # update length
        self.writeRegister(REG_PAYLOAD_LENGTH, currentLength + size)
        return size

    #def aquire_lock(self, lock=False):
    #    if not MICROPYTHON: # MicroPython is single threaded, doesn't need lock.
    #        if lock:
    #            while self._lock: pass
    #            self._lock = True
    #        else:
    #            self._lock = False

    def println(self, string, implicitHeader=False):  # LoRa/FSK/OOK
        #self.aquire_lock(True)  # wait until RX_Done, lock and begin writing.

        if self.mode == 0:  # LoRa mode
            # begin packet
            self.setMode(MODE_STDBY)
            self.implicitHeaderMode(implicitHeaderMode)

            # reset FIFO address and paload length
            self.writeRegister(REG_FIFO_ADDR_PTR, FIFO_TX_BASE_ADDR)
            self.writeRegister(REG_PAYLOAD_LENGTH, 0)
            self.write(string.encode())

            # end packet
            self.setMode(MODE_TX)  # put in TX mode

            # wait for TX done, standby automatically on TX_DONE
            while (self.readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0:
                pass

            # clear IRQ's
            self.writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK)

        else:  # FSK/OOK mode (not implemented yet)
            self.setMode(MODE_STDBY)

            buf = string.encode()
            size = len(buf)

            if self.readRegister(REG_PACKET_CONFIG_1) & 0x80:
                self.writeRegister(REG_FIFO, size)  # variable length
            else:
                self.writeRegister(REG_PAYLOAD_LEN, size)  # fixed length

            for i in range(size):
                self.writeRegister(REG_FIFO, buf[i])

            #self.writeRegister(REG_FIFO_THRESH, size) #!!!
            # REG_PACKET_CONFIG_1
            # REG_PACKET_CONFIG_2
            # REG_SEQ_CONFIG_1
            # REG_SEQ_CONFIG_2
            #self.setMode(MODE_FS_TX)

            self.setMode(MODE_TX)

            # wait for TX done... FIXME
            #while (self.readRegister(REG_...) & ...) == 0...:
            #    pass

            # clear IRQ's FIXME
            #...self.writeRegister()

            self.setMode(MODE_STDBY)  # FIXME

        self.collect()
        #self.aquire_lock(False) # unlock when done writing

    def getIrqFlags(self):
        if self.mode == 0:  # LoRa mode
            irqFlags = self.readRegister(REG_IRQ_FLAGS)
            self.writeRegister(REG_IRQ_FLAGS, irqFlags)
            return irqFlags
        else:  # FSK/OOK mode
            pass  # FIXME

    def rssi(self):  # dB
        if self.mode == 0:  # LoRa mode
            return self.readRegister(REG_PKT_RSSI_VALUE) - \
                   (164 if self.freq < 868000000 else 157)
        else:  # FSK/OOK mode
            return self.readRegister(REG_RSSI_VALUE) * 0.5

    def snr(self):  # dB
        if self.mode == 0:  # LoRa mode
            return (self.readRegister(REG_PKT_SNR_VALUE)) * 0.25
        else:  # FSK/OOK mode
            return self.readRegister(REG_RSSI_VALUE) * 0.5  # FIXME

    def setTxPower(self, level, PaSelect=True, MaxPower=7):
        MaxPower = min(max(MaxPower, 0), 7)
        if (not PaSelect):
            # Select RFO pin: Pout is limited to ~14..15 dBm
            # Pmax = 10.8 + 0.6 * MaxPower  [dBm]
            # Pout = Pmax - (15 - OutputPower)) = 0...15 dBm if MaxPower=7
            OutputPower = min(max(level, 0), 15)
            self.writeRegister(REG_PA_CONFIG, (MaxPower << 4) | OutputPower)
        else:
            # Select PA BOOST pin: Pout is limited to ~17..20 dBm
            # Pout = 17 - (15 - OutputPower) dBm
            OutputPower = min(max(level - 2, 0), 15)
            self.writeRegister(REG_PA_CONFIG, PA_SELECT | OutputPower)

    def setHighPower(self, hp=True):
        if hp:  # +3dB
            self.writeRegister(REG_PA_DAC,
                               0x87)  # power on PA_BOOST pin up to +20 dBm
        else:
            self.writeRegister(REG_PA_DAC, 0x84)  # default mode

    def setFrequency(self, freq_kHz, freq_Hz=0):
        self.freq = int(freq_kHz) * 1000 + freq_Hz  # kHz + Hz -> Hz
        freq_code = int(round(self.freq / FSTEP))
        self.writeRegister(REG_FRF_MSB, (freq_code >> 16) & 0xFF)
        self.writeRegister(REG_FRF_MID, (freq_code >> 8) & 0xFF)
        self.writeRegister(REG_FRF_LSB, freq_code & 0xFF)
        mode = self.readRegister(REG_OP_MODE)
        if self.freq < 600000000:  # FIXME ~ 600 MHz ?
            mode |= MODE_LOW_FREQ_MODE_ON  # LF
        else:
            mode &= ~MODE_LOW_FREQ_MODE_ON  # HF
        self.writeRegister(REG_OP_MODE, mode)

    def setSpreadingFactor(self, sf=10):  # LoRa mode only
        sf = min(max(sf, 6), 12)
        self.writeRegister(REG_DETECTION_OPTIMIZE, 0xC5 if sf == 6 else 0xC3)
        self.writeRegister(REG_DETECTION_THRESHOLD, 0x0C if sf == 6 else 0x0A)
        self.writeRegister(REG_MODEM_CONFIG_2,
                           (self.readRegister(REG_MODEM_CONFIG_2) & 0x0F) |
                           ((sf << 4) & 0xF0))

        # set AGC auto on (internal AGC loop)
        self.writeRegister(REG_MODEM_CONFIG_3,
                           self.readRegister(REG_MODEM_CONFIG_3) | 0x04)

    def setLDR(self, ldr):  # LoRa mode only
        self.writeRegister(REG_MODEM_CONFIG_3,
                           (self.readRegister(REG_MODEM_CONFIG_3) & ~0x08)
                           | 0x08 if ldr else 0)

    def setSignalBandwidth(self, sbw):  # LoRa mode only
        bins = (7.8e3, 10.4e3, 15.6e3, 20.8e3, 31.25e3, 41.7e3, 62.5e3, 125e3,
                250e3, 500e3)

        bw = 9  # max 500kHz
        for i in range(len(bins)):
            if sbw <= bins[i]:
                bw = i
                break

        self.writeRegister(REG_MODEM_CONFIG_1,
                           (self.readRegister(REG_MODEM_CONFIG_1) & 0x0F) |
                           (bw << 4))

    def setCodingRate(self, denominator):  # LoRa mode only
        denominator = min(max(denominator, 5), 8)
        cr = denominator - 4
        self.writeRegister(REG_MODEM_CONFIG_1,
                           (self.readRegister(REG_MODEM_CONFIG_1) & 0xF1) |
                           (cr << 1))

    def setPreambleLength(self, length):  # LoRa mode only
        self.writeRegister(REG_PREAMBLE_MSB, (length >> 8) & 0xFF)
        self.writeRegister(REG_PREAMBLE_LSB, (length >> 0) & 0xFF)

    def enableCRC(self, crc=True):
        if self.mode == 0:  # LoRa mode
            modem_config_2 = self.readRegister(REG_MODEM_CONFIG_2)
            config = (modem_config_2 | 0x04) if crc else (modem_config_2
                                                          & 0xFB)
            self.writeRegister(REG_MODEM_CONFIG_2, config)
        else:  # FSK/OOK mode
            pass  # FIXME

    def setSyncWord(self, sw):  # LoRa mode only
        self.writeRegister(REG_SYNC_WORD, sw)

    def enable_rx_irq(self, enable=True):
        if self.mode == 0:  # LoRa mode
            if enable:
                self.writeRegister(
                    REG_IRQ_FLAGS_MASK,
                    self.readRegister(REG_IRQ_FLAGS_MASK) & ~IRQ_RX_DONE_MASK)
            else:
                self.writeRegister(
                    REG_IRQ_FLAGS_MASK,
                    self.readRegister(REG_IRQ_FLAGS_MASK) | IRQ_RX_DONE_MASK)
        else:  # FSK/OOK mode
            pass  # FIXME

    #def dumpRegisters(self):
    #    for i in range(128):
    #        print("0x{0:02X}: {1:02X}".format(i, self.readRegister(i)))

    def implicitHeaderMode(self, implicitHeaderMode=False):  # LoRa only
        if self._implicitHeaderMode != implicitHeaderMode:  # set value only if different.
            self._implicitHeaderMode = implicitHeaderMode
            modem_config_1 = self.readRegister(REG_MODEM_CONFIG_1)
            config = modem_config_1 | 0x01 if implicitHeaderMode else modem_config_1 & 0xfe
            self.writeRegister(REG_MODEM_CONFIG_1, config)

    def bitrate(self, bitrate=4800., frac=None):  # FSK/OOK mode only
        if bitrate:
            code = int(round(FXOSC / bitrate))  # bit/s -> code
            self.writeRegister(REG_BITRATE_MSB, (code >> 8) & 0xFF)
            self.writeRegister(REG_BITRATE_LSB, code & 0xFF)
            if frac:
                self.writeRegister(REG_BITRATE_FRAC, frac)

    def fdev(self, fdev=5000.):  # FSK mode only
        if fdev:
            code = int(round(fdev / FSTEP))  # Hz -> code
            code = min(max(code, 0), 0x3FFF)
            self.writeRegister(REG_FDEV_MSB, (code >> 8) & 0xFF)
            self.writeRegister(REG_FDEV_LSB, code & 0xFF)

    def rx_bw(self, rx_bw=10.4):  # FSK/OOK mode only
        if rx_bw:
            m, e = get_rx_bw(rx_bw)
            self.writeRegister(REG_RX_BW, (m << 3) | e)

    def afc_bw(self, afc_bw=50.0):  # FSK/OOK mode only
        if afc_bw:
            m, e = get_rx_bw(afc_bw)
            self.writeRegister(REG_AFC_BW, (m << 3) | e)

    def enable_afc(self, enable_afc=True):  # FSK/OOK mode only
        rx_config = self.readRegister(REG_RX_CONFIG)
        if enable_afc:
            rx_config |= 0x08  # bit 3 on
        else:
            rx_config &= 0xF7  # bit 3 off
        self.writeRegister(REG_RX_CONFIG, rx_config)

    def fix_len(self, fixed_len=False):  # FSK/OOK mode only
        reg = self.readRegister(REG_PACKET_CONFIG_1)
        if fixed_len: reg &= 0x7F
        else: reg |= 0x80
        self.writeRegister(REG_PACKET_CONFIG_1, reg)

    def payload_len(self, length=0x40):  # FSK/OOK mode only
        self.writeRegister(REG_PAYLOAD_LEN, length)

    def onReceive(self, callback):
        self._onReceive = callback
        if callback:
            self.writeRegister(REG_DIO_MAPPING_1, 0x00)
            self.pin_dio0.irq(trigger=Pin.IRQ_RISING,
                              handler=self.handleOnReceive)
        else:
            self.pin_dio0.irq(trigger=0, handler=None)

    def receive(self, size=0):
        if self.mode == 0:  # LoRa mode
            self.implicitHeaderMode(size > 0)
            if size > 0: self.writeRegister(REG_PAYLOAD_LENGTH, size & 0xFF)

            # The last packet always starts at FIFO_RX_CURRENT_ADDR
            # no need to reset FIFO_ADDR_PTR
            self.setMode(MODE_RX_CONTINUOUS)
        else:  # FSK/OOK mode
            pass  # FIXME

    def handleOnReceive(self, event_source):
        #self.aquire_lock(True)              # lock until TX_Done
        if self.mode == 0:  # LoRa mode
            # irqFlags = self.getIrqFlags() should be 0x50
            if (self.getIrqFlags() & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0:
                if self._onReceive:
                    payload = self.read_payload()
                    #self.aquire_lock(False)     # unlock when done reading

                    self._onReceive(self, payload)
        else:  # FSK/OOK mode
            pass  # FIXME

        #self.aquire_lock(False)             # unlock in any case.

    def receivedPacket(self, size=0):
        if self.mode == 0:  # LoRa mode
            irqFlags = self.getIrqFlags()

            self.implicitHeaderMode(size > 0)
            if size > 0: self.writeRegister(REG_PAYLOAD_LENGTH, size & 0xff)

            # if (irqFlags & IRQ_RX_DONE_MASK) and \
            # (irqFlags & IRQ_RX_TIME_OUT_MASK == 0) and \
            # (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK == 0):

            if irqFlags == IRQ_RX_DONE_MASK:  # RX_DONE only, irqFlags should be 0x40
                # automatically standby when RX_DONE
                return True

            elif (self.readRegister(REG_OP_MODE)
                  & MODES_MASK) != MODE_RX_SINGLE:
                # no packet received.
                # reset FIFO address / # enter single RX mode
                self.writeRegister(REG_FIFO_ADDR_PTR, FIFO_RX_BASE_ADDR)
                self.setMode(MODE_RX_SINGLE)
        else:  # FSK/OOK mode
            return False  # FIXME

    def read_payload(self):
        if self.mode == 0:  # LoRa mode
            # set FIFO address to current RX address
            # fifo_rx_current_addr = self.readRegister(REG_FIFO_RX_CURRENT_ADDR)
            self.writeRegister(REG_FIFO_ADDR_PTR,
                               self.readRegister(REG_FIFO_RX_CURRENT_ADDR))

            # read packet length
            packetLength = self.readRegister(REG_PAYLOAD_LENGTH) if self._implicitHeaderMode else \
                           self.readRegister(REG_RX_NB_BYTES)

            payload = bytearray()
            for i in range(packetLength):
                payload.append(self.readRegister(REG_FIFO))

            self.collect()
            return bytes(payload)
        else:  # FSK/OOK mode
            return bytes((0, ))  # FIXME

    def collect(self):
        gc.collect()