UART communication over Lora

In order to become familiar with RIOT-OS and because I need anyway some long range communication of data collected from a UART, I am trying to merge tests/driver_sx127x, tests/periph_uart and tests/periph_timer_periodic with the following objective:

  • collect from UART an array of bytes to be transmitted (typically about 400)
  • once the transmission has stopped (as detected with the timer) send the array with the Lora interface
  • repeat UART data collection (these are GPS sentences so the communication repeats for a short burst of data every second). A rough calculation says the SX1272 with 500 kHz bandwidth and a spreading factor of 7 should provide the necessary bandwidth.

Each test application works fine by itself, but every combination fails:

  • sx127x with uart: activating the UART reception with a separate thread as
void *_rs_thread(void *arg)
{
    (void)arg;
    while (1)
       {stdio_read ((char*)&c[compteurin], 1);
        compteurin++;
        if (compteurin==BUFSIZE) {compteurin=0;printf("*");}
       }
}

leads to gnrc_netdev: possibly lost interrupt. and even attempting to reset the SX1272 will not allow for sending another byte. This sentence has been properly transmitted though and was received by another Lora transceiver so it is really when the driver returns from transmitting that the ISR state is corrupted. Since UART reception and Lora transmission never occur at the same time I wanted to put the UART reception thread to sleep or take it out of the scheduler queue but I cannot figure out how to do that from the main loop.

  • sx127x with timer just hangs when attempting to send a packet through the Lora interface … I see quite a few timers initialized in drivers/sx127x so possibly some conflict with my
timer_init(TIMER_CYCL, timer_hz, timercb, NULL);
timer_set_periodic(TIMER_CYCL, 1, steps, TIM_FLAG_RESET_ON_MATCH);

but here too I am unsure how to proceed.

Any help welcome, thanks, Jean-Michel

actually just adding a single additional thread to tests/driver_sx127x triggered periodically, even at low rate

void *_tim_thread(void *arg)
{
    (void)arg;
    uint32_t interval = 256000;

    while (1)
       {
        xtimer_ticks32_t last_wakeup = xtimer_now();
        xtimer_periodic_wakeup(&last_wakeup, interval);
        printf("%ld\n",xtimer_now_usec());
       }
}

int main()
{...
      _tim_pid =   thread_create(stack, sizeof(stack), THREAD_PRIORITY_MAIN - 1,
                              THREAD_CREATE_STACKTEST, _tim_thread, NULL, "rs_thread");
}

is enough to induce the missing interrupt error when sending a Lora packet over the SX1282 of the im880b.

hi @jmfriedt

This error is triggered when the “offloader” thread (the one that should process interrupts) does not have a msg queue or the queue is full. From your code, I don’t see any queue initialization such as:

#define MAIN_QUEUE_SIZE     (8)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];

int main(void)
{
    /* we need a message queue for the thread running the shell in order to
     * receive potentially fast incoming networking packets */
    msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
...

Could you try if that fixes your issue?

BTW the tests/driver_sx127x implementation uses messages (msg_t) to offload ISR. However, a better approach is to use the event module to post events to a running thread (you can use the RIOT module event_thread if you don’t want to create the threads on your own). Check for instance tests/ieee802154_hal, you will see some event_post calls instead of msg_send.

Some general observations:

  1. The internal FIFO of the radio cannot store more than 256 bytes, therefore you cannot send 400 bytes
  2. Note that some regions (e.g EU868) impose a duty cycle limitation, which means you cannot exceed 1% of time on air during an observation period of 1 hour. According to http://loratools.nl, the time on air of a 256 bytes packet with SF7 and BW500 is ~100 ms. Therefore, make sure to send every 10 seconds or more. Other regions have other limitations (maximum dwell time, multi-channel requirements, etc). You can get an idea of that with https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf

That was very helpful indeed as I had not understood this message queue initialization. So at least now the ISR are acknowledged, but I am still stuck.

I have created https://github.com/jmfriedt/RIOT_tests with two subdirectories, one named lora_GPS that adds a RS232 reading thread and should send through the SX1272 interface the messages as they come (at the moment typed from keyboard so slow rate) and another option (lora_GPS_just_timer) where I was considering the alternate architecture of a timer detecting when the stream of messages on the RS232 port stops to send on the LoRa interface. In both architectures sending through the SX1272 is sequential to acquiring on RS232, both operations never happening at the same time. Whatever I do (both examples of my repository), I can send the first sentence (using the same program set to listen mode with a jumper) but then any subsequent communication fails with “Cannot send: radio is still transmitting” which means that the completion of the transmission was not acknowledged. However sending is only done with send_command(message…) and testing the returned flag from calling the driver so I fail to understand where switching from message to event would help.

Any hint in debugging the issue is welcome.

Thanks

This is most probably not the right way, but looking into this message passing IPC issues I just realized that moving the ISR handling from the separate _lora_thread() to the _event_cb() itself, handling dev->driver->isr(dev) right when event==NETDEV_EVENT_ISR seems to solve my problem. Not sure what the overhead is or loss of performance, but at least now the radio interface no longer hangs when sending packets.

Following the last correction, I have slowly managed to get a functional application able to transfer flawlessly 500 bytes/s over the SX1272 LoRa link on the im880b. The github repository https://github.com/jmfriedt/RIOT_tests cited above has been re-created with the functional code if it can be of any use.

1 Like

Great! that should definitely work, but note that dev->driver->isr() will be called from ISR (which is not desirable, as these are IRQ offloaders). You can try the using the event module to properly offload the ISR (as mentioned above)

Could you please confirm that lora_GPS/main.c#L175-L191 (sorry the forum will not allow me to include the full url !) of my repository is the intended way of offloading tasks from the ISR? I am positively surprised that indeed as opposed to messages, events seem to handle the load, but I have added the WDT just in case the stack crashes again :slight_smile: Most worrying to me is the use of the global variable lora_GPS/main.c#L176 which I am not sure of: I usually dislike just global variables but an not aware of any other technique for sharing anything between ISR and a procedure/function, and see that tests/ieee802154_hal/main.c#L64 also seems to use such a global variable for sharing requests.

Thank you, Jean-Michel

Correct, the ISR offload logic is right.

If you don’t want to use global state variables you can do something like this:

typedef struct sx127x_bhp {
    event_t event;
    sx127x_t dev;
} sx127x_bhp_t; 

static sx127x_bhp_t sx127x_ctx = {.event.handler = _handler_high }; /**< use this one for the device descriptor and the event handler */

/* e.g access the device with*/
  netdev_t *netdev = &sx127x_ctx.dev.netdev;
...

/* Re-implement _handler_high to get driver from the event. */
static void _handler_high(event_t *event) {
    /* Cast back `event` into `sx127x_bhp_t` */   
     sx127x_bhp_t ctx = (sx127x_bhp_t*) event;
    ctx->dev.netdev.driver->isr(&ctx->dev.netdev);
}

...
static void _event_cb(netdev_t *dev, netdev_event_t event)
{
    if (event == NETDEV_EVENT_ISR) {
#ifdef with_event
        event_post(EVENT_PRIO_HIGHEST, &sx127x_ctx.event);
#else
        dev->driver->isr(dev);
#endif
    }