lemariva / uPyLoRaWAN

ESP32 using MicroPython meets LoRa and LoRaWAN.
https://lemariva.com/blog/category/lora
Apache License 2.0
222 stars 54 forks source link

receiving with interrupt callback not working #16

Open dukeduck1984 opened 3 years ago

dukeduck1984 commented 3 years ago

Hi,

I have set the dio_0 pin number in config.py and changed the code in examples/LoRaReceiver.py as following:

def receive(lora):
    print('LoRa Receiver Callback')

    def cb(payload):
        lora.blink_led()
        print(payload)

    lora.on_receive(cb)
    lora.receive()

But it didn't work - couldn't receive any data. It only worked with while True loop as shown in the original example.

Did I miss something? Pls help!

It's an AI-Thinker Ra-02 LoRa module with ESP32. The MicroPython firmware is V1.13 stable.

I used the sx127x driver in the master branch, and the readme title is uPyLora.

dukeduck1984 commented 3 years ago

I think I have found the reason - it's in the method handle_on_receive.

When the IRQ gets triggered, it's not necessarily that the full message has been received, hence if (irq_flags == IRQ_RX_DONE_MASK) may be False which then automatically set the module to single RX mode. As a result, the callback funciton will never be called again.

It should be at least set a timeout loop to wait until the full message has come in and irq_flags == IRQ_RX_DONE_MASK for the callback to be executed.

I don't understand why in the original code it will in some cases set the module back to single RX mode - if the user has decided to use the module in countinuous mode, it should stick to that mode.

My modified asyncio version as following (need to use Peter Hinch's IRQ_EVENT primitives to handle the IRQ):

    async def on_receive(self, callback):
        self._on_receive_cb = callback
        if self._pin_rx_done:
            if callback:
                # enable RxDone interrupt in DIO Mapping 1
                self.write_register(REG_DIO_MAPPING_1, 0x00)
                self._pin_rx_done.irq(handler=self._pin_rx_done_handler, trigger=Pin.IRQ_RISING)
                await self.handle_on_receive()
            else:
                self._pin_rx_done.irq(handler=None, trigger=0)

    def _pin_rx_done_handler(self, pin):
        self._event_rx.set()

    async def handle_on_receive(self):
        while True:
            print('waiting rx')
            await self._event_rx.wait()
            print('irq triggered')
            rx_income_ms = utime.ticks_ms()
            self.set_lock(True)  # lock until TX_Done
            while utime.ticks_diff(utime.ticks_ms(), rx_income_ms) < 3000:  # Set timeout=3000ms
                irq_flags = self.get_irq_flags()
                if irq_flags == IRQ_RX_DONE_MASK:  # RX_DONE only, irq_flags should be 0x40
                    # automatically standby when RX_DONE
                    if self._on_receive_cb:
                        print('msg coming in')
                        payload = self.read_payload()
                        self._on_receive_cb(payload)
                        break
                else:
                    await asyncio.sleep(0)

            # elif self.read_register(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE):
            #     print('no msg received')
            #     # no packet received.
            #     # reset FIFO address / # enter single RX mode
            #     self.write_register(REG_FIFO_ADDR_PTR, FifoRxBaseAddr)
            #     self.write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)
            self.set_lock(False)  # unlock in any case.
            self.collect_garbage()
jbfuzier commented 3 years ago

Hi,

[Edit] From what I found behavior will be dependent on your board and on the configuration of the register Regdiomapping. This register maps which information is made available on which pin of the Lora transceiver.

dukeduck1984 commented 3 years ago

Would you pls be more specific? Did you mean the timing of IRQ being triggered versus the RX status? Regarding the Regdiomapping configuration, I think it's set in the method on_receive() as following:

    def on_receive(self, callback):
        self._on_receive = callback

        if self._pin_rx_done:
            if callback:
                self.write_register(REG_DIO_MAPPING_1, 0x00)
                self._pin_rx_done.irq(
                    trigger=Pin.IRQ_RISING, handler = self.handle_on_receive
                )
            else:
                self._pin_rx_done.detach_irq()

According to the datasheet, writing 0x00 to REG_DIO_MAPPING enables the interrupt on DIO0 pin upon RX. Is there any other configurations need to be done?

jbfuzier commented 3 years ago

Sorry, you are totally right. I read the datasheet to quickly and mixed up the packet mode (which can involves more complex cases to handle due to the FIFO being only 64B) with the LORA mode.

image image image

What value do you have when you read the irq flags register ? Does it happen every time or only some times ? How long is the payload you are sending ?

dukeduck1984 commented 3 years ago

It happened every time - I never managed to receive messages until I modified the code as shown above. The payload was 15 bytes long.

I didn't print out the irq flags value, so I have no idea what it was. But according to the info you posted, the interrupt triggers when the payload is readily available (RxDone), no more waiting is needed.

dukeduck1984 commented 3 years ago

I read the datasheet again - the interrupt at DIO0 pin can occur in both Rx Single mode & Rx Continuous mode. In Rx Single mode, a Rx Timeout period should be set first, the LoRa module will wait for the preamble for that Rx Timeout period of time before triggers RxTimeout interruption and goes to standby mode. If the payload comes in before timeout, RxDone interruption will occur.

But in the original code, I didn't see anywhere that Rx Timeout period can be set (RegSymbTimeout).

dukeduck1984 commented 3 years ago

There's a bit difference in the code here, in the comment it says the IRQ Flag should be 0x50, rather than 0x40 in this repo.

dukeduck1984 commented 3 years ago

So I printed the value of IRQ flag, it's 80 which was 0x50, or 0b01010000, which meant RxDone & ValidHeader per datasheet on page 111. The working code as following:

    async def handle_on_receive(self):
        while True:
            print('waiting rx')
            await self._event_rx.wait()
            print('irq triggered')
            self.set_lock(True)  # lock until TX_Done
            irq_flags = self.get_irq_flags()
            # print(irq_flags)
            # irq_flags should be 0x50 (0b01010000): RxDone & ValidHeader
            if irq_flags & IRQ_RX_DONE_MASK == IRQ_RX_DONE_MASK:
                # automatically standby when RX_DONE
                if self._on_receive_cb:
                    print('msg coming in')
                    payload = self.read_payload()
                    self._on_receive_cb(payload)
            # elif self.read_register(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE):
            #     print('no msg received')
            #     # no packet received.
            #     # reset FIFO address / # enter single RX mode
            #     self.write_register(REG_FIFO_ADDR_PTR, FifoRxBaseAddr)
            #     self.write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)
            self.set_lock(False)  # unlock in any case.
            self.collect_garbage()
jbfuzier commented 3 years ago

It happened every time - I never managed to receive messages until I modified the code as shown above. The payload was 15 bytes long.

I didn't print out the irq flags value, so I have no idea what it was. But according to the info you posted, the interrupt triggers when the payload is readily available (RxDone), no more waiting is needed.

Strange, it worked for me on a TTGO Lora board V1.

I agree with you regarding the rx single part in the code, I do not understand why it is here...

0x50 seems better but 0x40 should works fine too, worst case you get rubish data.

I spent a lot of time strugling with the IQ config as the datasheed is not very clear regarding this part. (LORAWan is using reverse IQ so that gateway can not see packets from other gateways; same thing for nodes which can not see packet from each other).

Would you share your asyncio version ? (I am very new to asyncio and I would be interested to learn it)

dukeduck1984 commented 3 years ago

0x40 only worked for me as shown in my 2nd post from the top.

I only rewrote the interrupt part with asyncio in order to have it working with my other asyncio code base, as the interrupt may cause a racing condition. Below is the full driver for your reference (letter case also changed to comply with PEP)

import gc
import utime

from machine import Pin
from micropython import const

from primitives.irq_event import IRQ_EVENT

PA_OUTPUT_RFO_PIN = const(0)
PA_OUTPUT_PA_BOOST_PIN = const(1)

# registers
REG_FIFO = const(0x00)
REG_OP_MODE = const(0x01)
REG_FRF_MSB = const(0x06)
REG_FRF_MID = const(0x07)
REG_FRF_LSB = const(0x08)
REG_PA_CONFIG = const(0x09)
REG_LNA = const(0x0C)
REG_FIFO_ADDR_PTR = const(0x0D)

REG_FIFO_TX_BASE_ADDR = const(0x0E)
FifoTxBaseAddr = const(0x00)
# FifoTxBaseAddr = 0x80

REG_FIFO_RX_BASE_ADDR = const(0x0F)
FifoRxBaseAddr = const(0x00)
REG_FIFO_RX_CURRENT_ADDR = const(0x10)
REG_IRQ_FLAGS_MASK = const(0x11)
REG_IRQ_FLAGS = const(0x12)
REG_RX_NB_BYTES = const(0x13)
REG_PKT_RSSI_VALUE = const(0x1A)
REG_PKT_SNR_VALUE = const(0x1B)
REG_MODEM_CONFIG_1 = const(0x1D)
REG_MODEM_CONFIG_2 = const(0x1E)
REG_PREAMBLE_MSB = const(0x20)
REG_PREAMBLE_LSB = const(0x21)
REG_PAYLOAD_LENGTH = const(0x22)
REG_FIFO_RX_BYTE_ADDR = const(0x25)
REG_MODEM_CONFIG_3 = const(0x26)
REG_RSSI_WIDEBAND = const(0x2C)
REG_DETECTION_OPTIMIZE = const(0x31)
REG_DETECTION_THRESHOLD = const(0x37)
REG_SYNC_WORD = const(0x39)
REG_DIO_MAPPING_1 = const(0x40)
REG_VERSION = const(0x42)

# invert IQ
REG_INVERTIQ = const(0x33)
RFLR_INVERTIQ_RX_MASK = const(0xBF)
RFLR_INVERTIQ_RX_OFF = const(0x00)
RFLR_INVERTIQ_RX_ON = const(0x40)
RFLR_INVERTIQ_TX_MASK = const(0xFE)
RFLR_INVERTIQ_TX_OFF = const(0x01)
RFLR_INVERTIQ_TX_ON = const(0x00)

REG_INVERTIQ2 = const(0x3B)
RFLR_INVERTIQ2_ON = const(0x19)
RFLR_INVERTIQ2_OFF = const(0x1D)

# modes
MODE_LONG_RANGE_MODE = const(0x80)  # bit 7: 1 => LoRa mode
MODE_SLEEP = const(0x00)
MODE_STDBY = const(0x01)
MODE_TX = const(0x03)
MODE_RX_CONTINUOUS = const(0x05)
MODE_RX_SINGLE = const(0x06)

# PA config
PA_BOOST = const(0x80)

# IRQ masks
IRQ_TX_DONE_MASK = const(0x08)
IRQ_PAYLOAD_CRC_ERROR_MASK = const(0x20)
IRQ_RX_DONE_MASK = const(0x40)
IRQ_RX_TIME_OUT_MASK = const(0x80)

# Buffer size
MAX_PKT_LENGTH = const(255)

class AS_SX127x:
    """
    Slightly modified 'async' version in order to properly handle 'on_receive' interruption to work with asyncio.
    See 'on_receive' and 'handle_on_receive' methods.
    """
    default_params = {
            'frequency': 433E6,
            'tx_power_level': 14,  # Range 2-17, 2 being the low end
            'signal_bandwidth': 125E3,    
            'spreading_factor': 7,  # Range 7-10, 7 being short range but faster
            'coding_rate': 5,
            'preamble_length': 8,
            'implicit_header': False, 
            'sync_word': 0x12,  # ranges from 0-0xFF. 0x12 for private network; 0x34 for public network such as TTN
            'enable_crc': False,
            'invert_iq': False,
    }

    frfs = {
        169E6: (42, 64, 0),
        433E6: (108, 64, 0),
        434E6: (108, 128, 0),
        866E6: (216, 128, 0),
        868E6: (217, 0, 0),
        915E6: (228, 192, 0)
    }

    def __init__(
            self,
            spi_obj,
            nss_pin,
            reset_pin,
            dio_0_pin=None,
            # pins,
            params=None
    ):

        self._spi = spi_obj
        self._pin_ss = None
        self._pin_rst = None
        self._pin_rx_done = None
        self.set_pins(nss_pin, reset_pin, dio_0_pin)
        self.reset_module()
        # self._pins = pins
        if params:
            self._params = params
        else:
            self._params = self.default_params
        self._lock = False
        self._event_rx = IRQ_EVENT()  # IRQ_EVENT instance for receiving Pin

        # setting pins
        # if "dio_0" in self._pins:
        #     self._pin_rx_done = Pin(self._pins["dio_0"], Pin.IN)
        # if "ss" in self._pins:
        #     self._pin_ss = Pin(self._pins["ss"], Pin.OUT)
        # if "led" in self._pins:
        #     self._led_status = Pin(self._pins["led"], Pin.OUT)

        # check hardware version
        self.check_version()

        # put in LoRa and sleep mode
        self.sleep()
        # config
        self._frequency = None
        self.set_frequency(self._params['frequency'])
        self.set_signal_bandwidth(self._params['signal_bandwidth'])

        # set LNA boost
        self.write_register(REG_LNA, self.read_register(REG_LNA) | 0x03)

        # set auto AGC
        self.write_register(REG_MODEM_CONFIG_3, 0x04)

        self._tx_power_level = None
        self.set_tx_power(self._params['tx_power_level'])
        self._implicit_header_mode = None
        self.implicit_header_mode(self._params['implicit_header'])
        self.set_spreading_factor(self._params['spreading_factor'])
        self.set_coding_rate(self._params['coding_rate'])
        self.set_preamble_length(self._params['preamble_length'])
        self.set_sync_word(self._params['sync_word'])
        self.enable_crc(self._params['enable_crc'])
        self.invert_iq(self._params["invert_iq"])

        self._on_receive_cb = None  # Callback function used to handle the payload upon receiving new message

        # set LowDataRateOptimize flag if symbol time > 16ms (default disable on reset)
        # self.write_register(REG_MODEM_CONFIG_3, self.read_register(REG_MODEM_CONFIG_3) & 0xF7)  # default disable on reset
        bw = self._params["signal_bandwidth"]
        sf = self._params["spreading_factor"]
        if 1000 / (bw / 2**sf) > 16:
            self.write_register(REG_MODEM_CONFIG_3, self.read_register(REG_MODEM_CONFIG_3) | 0x08)
        # set base addresses
        self.write_register(REG_FIFO_TX_BASE_ADDR, FifoTxBaseAddr)
        self.write_register(REG_FIFO_RX_BASE_ADDR, FifoRxBaseAddr)
        self.standby()

    def check_version(self):
        self._pin_ss.value(0)
        version = 0
        for _ in range(5):
            version = self.read_register(REG_VERSION)
            if version:
                break
        if version != 0x12:
            print('Error: Invalid version.')
        else:
            print("SX version: {}".format(version))
        self._pin_ss.value(1)

    def begin_packet(self, implicit_header_mode=False):
        self.standby()
        self.implicit_header_mode(implicit_header_mode)
        # reset FIFO address and payload length
        self.write_register(REG_FIFO_ADDR_PTR, FifoTxBaseAddr)
        self.write_register(REG_PAYLOAD_LENGTH, 0)

    def end_packet(self):
        # put in TX mode
        self.write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX)
        # wait for TX done, standby automatically on TX_DONE
        tmout = 0
        while (self.read_register(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0:
            utime.sleep_ms(10)
            tmout += 1
            if tmout > 20:
                break
            pass
        # clear IRQ's
        self.write_register(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK)
        if tmout >= 20:
            print('Send timeout.')
        # self.collect_garbage()

    def write(self, buffer):
        current_length = self.read_register(REG_PAYLOAD_LENGTH)
        size = len(buffer)
        # check size
        size = min(size, (MAX_PKT_LENGTH - FifoTxBaseAddr - current_length))
        # write data
        for i in range(size):
            self.write_register(REG_FIFO, buffer[i])
        # update length
        self.write_register(REG_PAYLOAD_LENGTH, current_length + size)
        return size

    def set_lock(self, lock=False):
        self._lock = lock

    def println(self, msg, implicit_header=False):
        self.set_lock(True)  # wait until RX_Done, lock and begin writing.
        self.begin_packet(implicit_header)
        if isinstance(msg, str):
            msg = msg.encode()
        self.write(msg)
        self.end_packet()
        self.set_lock(False)  # unlock when done writing
        self.collect_garbage()

    def get_irq_flags(self):
        irq_flags = self.read_register(REG_IRQ_FLAGS)
        self.write_register(REG_IRQ_FLAGS, irq_flags)
        return irq_flags

    def packet_rssi(self):
        rssi = self.read_register(REG_PKT_RSSI_VALUE)
        return rssi - (164 if self._frequency < 868E6 else 157)

    def packet_snr(self):
        return self.read_register(REG_PKT_SNR_VALUE) * 0.25

    def standby(self):
        self.write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY)

    def sleep(self):
        self.write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP)

    def set_tx_power(self, level, output_pin=PA_OUTPUT_PA_BOOST_PIN):
        self._tx_power_level = level
        if output_pin == PA_OUTPUT_RFO_PIN:
            # RFO
            level = min(max(level, 0), 14)
            self.write_register(REG_PA_CONFIG, 0x70 | level)
        else:
            # PA BOOST
            level = min(max(level, 2), 17)
            self.write_register(REG_PA_CONFIG, PA_BOOST | (level - 2))

    # def set_frequency(self, frequency, freq_table=None):
    #     self._frequency = frequency
    #     if freq_table:
    #         table = freq_table
    #     else:
    #         table = self.frfs
    #     self.write_register(REG_FRF_MSB, table[frequency][0])
    #     self.write_register(REG_FRF_MID, table[frequency][1])
    #     self.write_register(REG_FRF_LSB, table[frequency][2])

    def set_frequency(self, frequency):
        self._frequency = frequency
        freq_reg = int(int(int(frequency) << 19) / 32000000) & 0xFFFFFF
        self.write_register(REG_FRF_MSB, (freq_reg & 0xFF0000) >> 16)
        self.write_register(REG_FRF_MID, (freq_reg & 0xFF00) >> 8)
        self.write_register(REG_FRF_LSB, (freq_reg & 0xFF))

    def set_spreading_factor(self, sf):
        sf = min(max(sf, 6), 12)
        self.write_register(REG_DETECTION_OPTIMIZE, 0xC5 if sf == 6 else 0xC3)
        self.write_register(REG_DETECTION_THRESHOLD, 0x0C if sf == 6 else 0x0A)
        self.write_register(
            REG_MODEM_CONFIG_2, 
            (self.read_register(REG_MODEM_CONFIG_2) & 0x0F) | ((sf << 4) & 0xF0)
        )

    def set_signal_bandwidth(self, sbw):
        bins = (7.8E3, 10.4E3, 15.6E3, 20.8E3, 31.25E3, 41.7E3, 62.5E3, 125E3, 250E3)
        bw = 9
        if sbw < 10:
            bw = sbw
        else:
            for i in range(len(bins)):
                if sbw <= bins[i]:
                    bw = i
                    break
        self.write_register(
            REG_MODEM_CONFIG_1, 
            (self.read_register(REG_MODEM_CONFIG_1) & 0x0F) | (bw << 4)
        )

    def set_coding_rate(self, denominator):
        denominator = min(max(denominator, 5), 8)
        cr = denominator - 4
        self.write_register(
            REG_MODEM_CONFIG_1, 
            (self.read_register(REG_MODEM_CONFIG_1) & 0xF1) | (cr << 1)
        )

    def set_preamble_length(self, length):
        self.write_register(REG_PREAMBLE_MSB,  (length >> 8) & 0xFF)
        self.write_register(REG_PREAMBLE_LSB,  (length >> 0) & 0xFF)

    def enable_crc(self, enable_crc=False):
        modem_config_2 = self.read_register(REG_MODEM_CONFIG_2)
        config = modem_config_2 | 0x04 if enable_crc else modem_config_2 & 0xFB
        self.write_register(REG_MODEM_CONFIG_2, config)

    def invert_iq(self, invert_iq):
        self._params["invert_iq"] = invert_iq
        if invert_iq:
            self.write_register(
                REG_INVERTIQ,
                (
                    (
                        self.read_register(REG_INVERTIQ)
                        & RFLR_INVERTIQ_TX_MASK
                        & RFLR_INVERTIQ_RX_MASK
                    )
                    | RFLR_INVERTIQ_RX_ON
                    | RFLR_INVERTIQ_TX_ON
                ),
            )
            self.write_register(REG_INVERTIQ2, RFLR_INVERTIQ2_ON)
        else:
            self.write_register(
                REG_INVERTIQ,
                (
                    (
                        self.read_register(REG_INVERTIQ)
                        & RFLR_INVERTIQ_TX_MASK
                        & RFLR_INVERTIQ_RX_MASK
                    )
                    | RFLR_INVERTIQ_RX_OFF
                    | RFLR_INVERTIQ_TX_OFF
                ),
            )
            self.write_register(REG_INVERTIQ2, RFLR_INVERTIQ2_OFF)

    def set_sync_word(self, sw):
        self.write_register(REG_SYNC_WORD, sw)

    def set_channel(self, parameters):
        self.standby()
        for key in parameters:
            if key == "frequency":
                self.set_frequency(parameters[key])
                continue
            if key == "invert_iq":
                self.invert_iq(parameters[key])
                continue
            if key == "tx_power_level":
                self.set_tx_power(parameters[key])
                continue

    def dump_registers(self):
        for i in range(128):
            print("0x{:02X}: {:02X}".format(i, self.read_register(i)), end="")
            if (i + 1) % 4 == 0:
                print()
            else:
                print(" | ", end="")

    def implicit_header_mode(self, implicit_header_mode=False):
        if self._implicit_header_mode != implicit_header_mode:  # set value only if different.
            self._implicit_header_mode = implicit_header_mode
            modem_config_1 = self.read_register(REG_MODEM_CONFIG_1)
            config = modem_config_1 | 0x01 if implicit_header_mode else modem_config_1 & 0xFE
            self.write_register(REG_MODEM_CONFIG_1, config)

    def receive(self, size=0):
        self.implicit_header_mode(size > 0)
        if size > 0: 
            self.write_register(REG_PAYLOAD_LENGTH, size & 0xFF)
        # The last packet always starts at FIFO_RX_CURRENT_ADDR
        # no need to reset FIFO_ADDR_PTR
        self.write_register(
            REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS
        )
        print('Into RxContinuous Mode')

    async def on_receive(self, callback):
        self._on_receive_cb = callback
        if self._pin_rx_done:
            if callback:
                # enable RxDone interrupt in DIO Mapping 1
                self.write_register(REG_DIO_MAPPING_1, 0x00)
                self._pin_rx_done.irq(handler=lambda e: self._event_rx.set(), trigger=Pin.IRQ_RISING)
                await self.handle_on_receive()
            else:
                self._pin_rx_done.irq(handler=None, trigger=0)

    async def handle_on_receive(self):
        while True:
            print('waiting rx')
            await self._event_rx.wait()
            print('irq triggered')
            self.set_lock(True)  # lock until TX_Done
            irq_flags = self.get_irq_flags()
            # print(irq_flags)
            # irq_flags should be 0x50 (0b01010000): RxDone & ValidHeader
            if irq_flags & IRQ_RX_DONE_MASK == IRQ_RX_DONE_MASK:
                # automatically standby when RX_DONE
                if self._on_receive_cb:
                    print('msg coming in')
                    payload = self.read_payload()
                    self._on_receive_cb(payload)
            # elif self.read_register(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE):
            #     print('no msg received')
            #     # no packet received.
            #     # reset FIFO address / # enter single RX mode
            #     self.write_register(REG_FIFO_ADDR_PTR, FifoRxBaseAddr)
            #     self.write_register(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)
            self.set_lock(False)  # unlock in any case.
            self.collect_garbage()

    def received_packet(self, size=0):
        # Don't check Rx until Tx is done
        if self._lock:
            return not self._lock
        irq_flags = self.get_irq_flags()
        self.implicit_header_mode(size > 0)
        if size > 0: 
            self.write_register(REG_PAYLOAD_LENGTH, size & 0xFF)

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

        if irq_flags == IRQ_RX_DONE_MASK:
            # RX_DONE only, irq_flags should be 0x40
            # automatically standby when RX_DONE
            return True
        elif self.read_register(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE):
            # no packet received.
            # reset FIFO address / # enter single RX mode
            self.write_register(REG_FIFO_ADDR_PTR, FifoRxBaseAddr)
            self.write_register(
                REG_OP_MODE, 
                MODE_LONG_RANGE_MODE | MODE_RX_SINGLE
            )

    def read_payload(self):
        # set FIFO address to current RX address
        # fifo_rx_current_addr = self.read_register(REG_FIFO_RX_CURRENT_ADDR)
        self.write_register(REG_FIFO_ADDR_PTR, self.read_register(REG_FIFO_RX_CURRENT_ADDR))

        # read packet length
        if self._implicit_header_mode:
            packet_length = self.read_register(REG_PAYLOAD_LENGTH)  
        else:
            packet_length = self.read_register(REG_RX_NB_BYTES)

        payload = bytearray()
        for i in range(packet_length):
            payload.append(self.read_register(REG_FIFO))

        self.collect_garbage()
        return bytes(payload)

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

    def write_register(self, address, value):
        self.transfer(address | 0x80, value)

    def transfer(self, address, value=0x00):
        response = bytearray(1)
        self._pin_ss.value(0)
        self._spi.write(bytes([address]))
        self._spi.write_readinto(bytes([value]), response)
        self._pin_ss.value(1)
        return response

    def reset_module(self, duration_low_ms=50, duration_high_ms=50):
        self._pin_rst.off()
        utime.sleep_ms(duration_low_ms)
        self._pin_rst.on()
        utime.sleep_ms(duration_high_ms)

    def set_pins(self, ss, rst, dio0=None):
        self._pin_ss = Pin(ss, Pin.OUT, value=1)
        self._pin_rst = Pin(rst, Pin.OUT, value=1)
        self._pin_rx_done = Pin(dio0, Pin.IN) if dio0 else None

    @staticmethod
    def collect_garbage():
        gc.collect()
        # print('[Memory - free: {}   allocated: {}]'.format(gc.mem_free(), gc.mem_alloc()))
dukeduck1984 commented 3 years ago

@jbfuzier Is the invert IQ documented somewhere? I'd like to learn about it in case someday I will be using LoRaWan as well. Thanks.

jbfuzier commented 3 years ago

Thanks.

Unfortunately I have not found much information regarding this invert IQ mecanism.

Ultimately, the goal is to reduce useless load on the node & gateway by making sure nodes don't see messages from each other and gateway does not see messages from each other (which would be useless).

To do that Gateways send messages with IQ inverted (which physically changes the phase of the signal), nodes must be expected IQ to be inverted in order to receive the messages.

The datasheet is very misleading (not to say wrong) regarding the INVERTIQ register

image

For example if you want to only have nodes that can all talk to each other (= do not invertIQ), you need to set the register to 0x01, otherwise nodes can not talk to each other.

If you want to have a gateway/nodes scenario, register value need to be 0x41 on gateway and 0x00 on the nodes (or the other way around).

To add lorawan I think the best course of action is to port a working arduino lib or a python lib to micropython. I am not using Lorawan (too complex for a private network IMHO), I just have a use case with several nodes and one receiver; I added on top a simple layer which do the encryption, integrity check and ack/retry. The issue is that encryption adds a lot of overhead to the payload 32bytes for AES128.

dukeduck1984 commented 3 years ago

Thanks for the explaination. My use case is similar to yours - multiple nodes & one receiver, TTN not involved. What hardware (LoRa module & microcontroller) do you use for the receiver? Same as the nodes?

saramonteiro commented 3 years ago

Hi @dukeduck1984 @jbfuzier I am using a customized board and I also faced the same issue. The RX handler is not being triggered at all. Only the original polling mode is working for me. I even used a print inside the handle_on_receive to see if the interruption was being triggered, but it seems it isn't. Did it also happen to you @dukeduck1984 ? I mean, was your handler triggered and you couldn't retrieve the data or have your callback called or your handler was not triggered at all, just like me? I still didn't have the chance to try your async mode, I have to understand how it works.

dukeduck1984 commented 3 years ago

You need to change the if statement inside the handle_on_receive to: if irq_flags & IRQ_RX_DONE_MASK == IRQ_RX_DONE_MASK:, then it will work.

So it should look like:

            if irq_flags & IRQ_RX_DONE_MASK == IRQ_RX_DONE_MASK:
                if self._on_receive_cb:
                    print('msg coming in')
                    payload = self.read_payload()
                    self._on_receive_cb(payload)
saramonteiro commented 3 years ago

Hum.. ok, thank you @dukeduck1984 But in my case, handle_on_receive isn't being triggered.

saramonteiro commented 3 years ago

@dukeduck1984 I used a LA to monitor the DI0 pin and I figured out it is not changing level and I have another node continuously sending data. My irq_flags are 0x00.

saramonteiro commented 3 years ago

@dukeduck1984 my issue was solved. I forgot to call lora.receive() to change the LoRa mode. ¬¬

Bujtar commented 2 years ago

@dukeduck1984 The IRQ_EVENT is deprecated in the newer version of uasyncio. (https://forum.micropython.org/viewtopic.php?t=9867) Do you have maybe an updated your lorawan program with this new version? If not, could please guide me, how to do that?

Thanks is advance.

dukeduck1984 commented 2 years ago

Hi @Bujtar ,

As Peter pointed out in his post, you can use ThreadSafeFlag, as per the example shown here.

kjm1102 commented 1 year ago

According to note [1] at the bottom of https://lora-developers.semtech.com/documentation/tech-papers-and-guides/channel-activity-detection-ensuring-your-lora-packets-are-sent/how-to-ensure-your-lora-packets-are-sent-properly/ the sx1276 should be able to receive lora packets without host mcu involvement.

I'm trying use dio0 to wake an esp32 from deepsleep when the sx1276 receives a packet. It's working but when the esp32 wakes up I can't recover the packet with lora.read_payload()

davefes commented 1 year ago

Would you tell me how you configured DIO0 to wake-up the ESP32? What happens after the ESP32 wakes-up? Can you tell where things stall?

kjm1102 commented 1 year ago

Let me put what I did in a proper program that you can easily run instead of the interactive nightmare I was developing with. That way I can also send you a copy of the while loop code benchmark code I was using previously with the much reduced sx1276 current. Might take me a day or so to put some lipstick on the pig. I'll also put it on https://github.com/orgs/micropython/discussions/11515 so our lora chats are all in one place.

davefes commented 1 year ago

Good. I will take me some time to swap-over to his code on my test link.