UART1 hang issue with GNSS module

Hello,

I’m experiencing an issue with UART communication in a tracker application using the Rak3172 module and Quectel L76 GNSS module, developed with RUI in the Arduino environment.

Problem Description:

  • The UART1 hangs sporadically after 5-20 minutes of operation.
  • Only the first character from the serial input is read (‘$’), and all subsequent characters are lost.
  • This issue occurs more frequently when the GNSS module sends more messages.
  • The GNSS module is connected to UART1.
  • Changing the UART1 baud rate does not affect the behavior.

During the faulty operation, I used a logic analyzer to check the UART line and confirmed that the NMEA sentences were presented correctly. However, the Rak3172 only received the first character, suggesting a possible UART buffer overflow. This could be due to high interrupt load preventing the DMA buffer content from being copied to the UART RX buffer. I have not confirmed this with a debugger.

The issue is present in the following RUI versions: 3.5.6, 4.0.0, and 4.1.0 (tested only with these versions).

Workaround:
I modified the UART buffering in the BSP to use circular DMA mode. This change has resolved the issue, and I have not experienced the faulty behavior since. However, this workaround has a drawback: I can no longer reconfigure the UART baud rate after initialization.

The issue can be reproduced with the following application code

#include <TinyGPSPlus.h>

#define LogSerial Serial
#define GpsSerial Serial1

// LoRA parameters
#define CHANNEL_NUM         0

#define LORA_SF_RX           9
#define LORA_SF_TX           11
#define LORA_BW              1
#define LORA_CR              0
#define LORA_PREAMBLE        8
#define LORA_TX_POWER        14

#define SEND_PERIOD        45000
uint32_t channels[] = {868200000};

uint32_t myFreq;            // Hz
uint16_t sf;                // [SF7..SF12]
uint16_t bw;                // kHz 0 = 125, 1 = 250, 2 = 500, 3 = 7.8, 4 = 10.4, 5 = 15.63, 6 = 20.83, 7 = 31.25, 8 = 41.67, 9 = 62.5
uint16_t cr;                // 4/(cr+5)
uint16_t preamble;          // Same for Tx and Rx
uint16_t txPower;           // dBm

// The TinyGPSPlus object
TinyGPSPlus gps;


void recv_cb(rui_lora_p2p_recv_t data)
{
  LogSerial.printf("Incoming message, length: %d, RSSI: %d, SNR: %d", data.BufferSize, data.Rssi, data.Snr);
}

void send_cb(void)
{
    // After Tx set back to Rx mode   
    set_radio_sf_rx(); 
    bool is_in_rx{false};
    do{    
      is_in_rx = api.lorawan.precv(65534);
    }
    while(!is_in_rx);
}

void send_routine_periodic()
{
  uint8_t payload[25] = {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4}; //dummy message
  
  set_radio_sf_tx();
  bool send_result = false;
  while (!send_result) {
    send_result = api.lorawan.psend(sizeof(payload), payload);
    if (!send_result) {
      api.lorawan.precv(0);
    }
  }
}

void routine_100ms()
{
  listen_gps();
}

void listen_gps()
{
  uint16_t cntr = 0U;
  char c;

  while(GpsSerial.available())
  {
    cntr++;
    c = GpsSerial.read();
    gps.encode(c);
  }
  LogSerial.printf("%d\r\n",cntr); //Logging number of received characters
}


void set_radio_sf_rx()
{
  sf = LORA_SF_RX;
  api.lorawan.psf.set(sf);
}

void set_radio_sf_tx()
{
  sf = LORA_SF_TX;
  api.lorawan.psf.set(sf);
}

void set_radio_rx_init()
{
  myFreq = channels[CHANNEL_NUM];
  sf = LORA_SF_RX;
  bw = LORA_BW;
  cr = LORA_CR;
  preamble = LORA_PREAMBLE;
  txPower = LORA_TX_POWER;
  radio_init();
  LogSerial.println(F("RX mode"));
}

void radio_init()
{
  LogSerial.printf("Set P2P mode frequency %3.3f: %s\r\n", (myFreq / 1e6),
  api.lorawan.pfreq.set(myFreq) ? "Success" : "Fail");
  LogSerial.printf("Set P2P mode spreading factor %d: %s\r\n", sf,
  api.lorawan.psf.set(sf) ? "Success" : "Fail"); //P2P Spreading Factor (6, 7, 8, 9, 10, 11, 12)
  LogSerial.printf("Set P2P mode bandwidth %d: %s\r\n", bw,
  api.lorawan.pbw.set(bw) ? "Success" : "Fail"); //P2P bandwidth in kHz (0 = 125, 1 = 250, 2 = 500, 3 = 7.8, 4 = 10.4, 5 = 15.63, 6 = 20.83, 7 = 31.25, 8 = 41.67, 9 = 62.5)
  LogSerial.printf("Set P2P mode code rate 4/%d: %s\r\n", (cr + 5),
  api.lorawan.pcr.set(cr) ? "Success" : "Fail");
  LogSerial.printf("Set P2P mode preamble length %d: %s\r\n", preamble,
  api.lorawan.ppl.set(preamble) ? "Success" : "Fail"); //P2P Preamble Length (2-65535)
  LogSerial.printf("Set P2P mode tx power %d: %s\r\n", txPower,
  api.lorawan.ptp.set(txPower) ? "Success" : "Fail"); //P2P TX Power (5-22)
}

void setup() {
  LogSerial.begin(256000);
  
  if(api.lorawan.nwm.get() != 0)
    {
        LogSerial.printf("Set Node device work mode %s\r\n",
            api.lorawan.nwm.set(0) ? "Success" : "Fail"); // network working mode (0 = P2P, 1 = LoRaWAN, 2 = FSK)
        api.system.reboot();
    }
  
  set_radio_rx_init();
    api.lorawan.registerPRecvCallback(recv_cb);
    api.lorawan.registerPSendCallback(send_cb);

    bool is_in_rx = api.lorawan.precv(65534);
    LogSerial.printf("P2P set Rx mode %s\r\n",
      is_in_rx ? "Success" : "Fail");
    
  GpsSerial.begin(9600);
    
  if (api.system.timer.create(RAK_TIMER_0, (RAK_TIMER_HANDLER)send_routine_periodic, RAK_TIMER_PERIODIC) != true) {
    LogSerial.println(F("LoRa - Creating timer failed."));
    }
  
    if (api.system.timer.start(RAK_TIMER_0, SEND_PERIOD, NULL) != true) {
        LogSerial.println(F("LoRa - Starting timer failed."));
    }
    if (api.system.timer.create(RAK_TIMER_1, (RAK_TIMER_HANDLER)routine_100ms, RAK_TIMER_PERIODIC) != true) {
        LogSerial.println(F("GPS - Creating timer failed."));
    }
    if (api.system.timer.start(RAK_TIMER_1, 100, NULL) != true) {
        LogSerial.println(F("GPS - Starting timer failed."));
    }
}

void loop()
{
  api.system.sleep.cpu();
}

Application software operation:

  • The application operates in P2P mode with continuous RX, switching to TX only while transmitting data.
  • Different spreading factors are used for RX and TX, while other LoRa parameters remain the same.
  • A 100ms timer is used to read the UART buffer and parse NMEA sentences.
  • A 45-second timer is used to send a LoRa message.

Has anyone else experienced similar issues?

Thanks for reporting this.
I never tried to use GNSS modules over UART since we have the u-blox ZOE-M8Q modules with I2C communication.

Did you try a Serial2.end() and a new Serial2.begin(9600)` before reading from the UART?

Are you permanently reading from the GNSS module? Is this necessary? As the GNSS modules are quite power hungry, most apps I know keep the GNSS module off unless they really need to get a location.