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)))
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()