3 years, 2 months ago.

inconsistent hanging on Serial.attach when placed inside class (on NUCLEO L073RZ)

I'm integrating a GPS that communicates over serial UART. I have a test case working perfectly, using RawSerial library and Tx/Rx IRQ interrupts. But when I integrate this code into it's own class within a larger program, the program will SOMETIMES hang when it gets to the serial.attach functions. I say "sometimes" because about 1/3rd of the time, the program actually executes properly and moves beyond the attach functions.

Actual program has many more modules and a few interrupts, but I have been able to isolate the issue to the following stripped down code (uses mbed OS 5):

main.cpp of malfunctioning code

#include "mbed.h"
#include "ORG4400.h"
#include <string.h>

int initGPS = 0;
ORG4400 gps(PB_3, PB_4, true); // tx:D3->PB_3, rx:D5->PB_4

/***** Inititalization Methods *****/
void initSensors() {
    printf("----- Initializing Sensors -----\n\n");
    
    printf("Initializing GPS...\n");
    gps.begin();
    printf("GPS initialized\n\n");
    initGPS = 1;
    
    printf("----- Finished Initializing Sensors -----\n\n"); 
}

/***** End IMU Wrapper Functions *****/
int main() {

    initSensors();         
       
    while(1) {       
        
        /*if (initGPS) {
            int attempts = 0;
            while (attempts < 10){
                //std::string *gpsJSON;
                std::string msg = gps.readMSG();
                if (msg != "") {
                    //printf("received valid NMEA message:\n%s\n\n", msg.c_str());  
                    attempts = 10;  
                    if (commWIFI.transmitJSON(msg) == 1) printf("message sent successfully\n\n");
                    else printf("message not sent successfully\n\n");
                }
                else {
                    attempts++;
                }
            }    
        }*/
        wait(2);
    }   
}

GPS class library file of malfunctioning code

#include "ORG4400.h"
#include "mbed.h"
#include <string.h>

    
ORG4400::ORG4400(PinName tx, PinName rx, bool debug) : _serial(tx,rx)
{
    _serial.baud(4800);
    _debug = debug;
    if (_debug) printf("in ORG4400 constructor\n");
    tx_in=0;
    tx_out=0;
    rx_in=0;
    rx_out=0;
    status = GPS_NOT_READY;
    if (_debug) printf("before attach\n");
    _serial.attach(callback(this, &ORG4400::Rx_interrupt), RawSerial::RxIrq);
    _serial.attach(callback(this, &ORG4400::Tx_interrupt), RawSerial::TxIrq);
    //_serial.attach(this, &ORG4400::Rx_interrupt, RawSerial::RxIrq); Deprecated in OS 5 -- have tried, still hangs
    //_serial.attach(this, &ORG4400::Tx_interrupt, RawSerial::RxIrq); Deprecated in OS 5 -- have tried, still hangs
    if (_debug) printf("after attach\n");
}


gps_mode_t ORG4400::begin()
{
    if (_debug) printf("in ORG4400 begin\n");
    int rx_i=0;
    while (1) {
        read_line();
        sscanf(rx_line,"%c",&rx_i);
        if (rx_line[0] == '$') {
        //if (_parser.parse(rx_line)) {
            if (_debug) printf("Valid NMEA line\n");
            status = GPS_FULL_PWR;
            return status; 
        }
        else {
            //add a timeout here
        }
    }   
}

// Read a line from the large rx buffer from rx interrupt routine
void ORG4400::read_line() {
    int i;
    i = 0;
// Start Critical Section - don't interrupt while changing global buffer variables
    NVIC_DisableIRQ(USART4_5_IRQn);
// Loop reading rx buffer characters until end of line character
    while ((i==0) || (rx_line[i-1] != '\r')) {
// Wait if buffer empty
        if (rx_in == rx_out) {
// End Critical Section - need to allow rx interrupt to get new characters for buffer
            NVIC_EnableIRQ(USART4_5_IRQn);
            while (rx_in == rx_out) {
            }
// Start Critical Section - don't interrupt while changing global buffer variables
            NVIC_DisableIRQ(USART4_5_IRQn);
        }
        rx_line[i] = rx_buffer[rx_out];
        i++;
        rx_out = (rx_out + 1) % buffer_size;
    }
// End Critical Section
    NVIC_EnableIRQ(USART4_5_IRQn);
    rx_line[i-1] = 0;
    return;
}
 
 
// Interupt Routine to read in data from serial port
void ORG4400::Rx_interrupt() {
// Loop just in case more than one character is in UART's receive FIFO buffer
// Stop if buffer full
    while ((_serial.readable()) && (((rx_in + 1) % buffer_size) != rx_out)) {
        rx_buffer[rx_in] = _serial.getc();
        rx_in = (rx_in + 1) % buffer_size;
    }
    return;
}
 
// Interupt Routine to write out data to serial port
void ORG4400::Tx_interrupt() {
// Loop to fill more than one character in UART's transmit FIFO buffer
// Stop if buffer empty
    while ((_serial.writeable()) && (tx_in != tx_out)) {
        _serial.putc(tx_buffer[tx_out]);
        tx_out = (tx_out + 1) % buffer_size;
    }
    return;
}

GPS module library header file of malfunctioning code

#ifndef __SP_ORG4400_H__
#define __SP_ORG4400_H__

#include "stdint.h"
#include "mbed.h"
#include <string.h>

typedef enum
{
    GPS_FULL_PWR,
    GPS_STANDBY,
    GPS_HIBERNATE,
    GPS_ATP,
    GPS_PTF,
    GPS_APM,
    GPS_MPM,
    GPS_NOT_READY,
    //...
} gps_mode_t;

class ORG4400
{
public:

    gps_mode_t status;   
    
    ORG4400(PinName tx, PinName rx, bool debug);
    
    gps_mode_t begin();
   
private:
    RawSerial _serial;
    bool _debug;
    
    static const int buffer_size = 255; // Circular buffers for serial TX and RX data - used by interrupt routines

    char volatile tx_buffer[buffer_size+1];
    char volatile rx_buffer[buffer_size+1];   
    int volatile tx_in;
    int volatile tx_out;
    int volatile rx_in;
    int volatile rx_out;
    // Line buffers for sprintf and sscanf
    char tx_line[80];
    char rx_line[80];
      
    void Tx_interrupt();
    void Rx_interrupt();
    void send_line();
    void read_line();

};

#endif // __SP_ORG4400_H__ //

The test case that is working perfectly, with practically the same code, is:

#include "mbed.h"
#include <string.h>

RawSerial device(PB_3, PB_4);  // tx, rx
 
void Tx_interrupt();
void Rx_interrupt();
void send_line();
void read_line();
 
const int buffer_size = 255;
char tx_buffer[buffer_size+1];
char rx_buffer[buffer_size+1];
int volatile tx_in=0;
int volatile tx_out=0;
int volatile rx_in=0;
int volatile rx_out=0;
char tx_line[80];
char rx_line[80];
 
int main() {
    int i=0;
    int rx_i=0;
    
    device.baud(4800);
    device.attach(&Rx_interrupt, RawSerial::RxIrq);
    device.attach(&Tx_interrupt, Serial::TxIrq);
    
    while (1) {
        read_line();
        sscanf(rx_line,"%c",&rx_i);
        printf("received '%s'\n",rx_line);
    }
}
 
// Copy tx line buffer to large tx buffer for tx interrupt routine
void send_line() {
    int i;
    char temp_char;
    bool empty;
    i = 0;
    // Start Critical Section - don't interrupt while changing global buffer variables
    NVIC_DisableIRQ(USART4_5_IRQn);
    empty = (tx_in == tx_out);
    while ((i==0) || (tx_line[i-1] != '\n')) {
        // Wait if buffer full
        if (((tx_in + 1) % buffer_size) == tx_out) {
            // End Critical Section - need to let interrupt routine empty buffer by sending
            NVIC_EnableIRQ(USART4_5_IRQn);
            while (((tx_in + 1) % buffer_size) == tx_out) {
            }
            // Start Critical Section - don't interrupt while changing global buffer variables
            NVIC_DisableIRQ(USART4_5_IRQn);
        }
        tx_buffer[tx_in] = tx_line[i];
        i++;
        tx_in = (tx_in + 1) % buffer_size;
    }
    if (device.writeable() && (empty)) {
        temp_char = tx_buffer[tx_out];
        tx_out = (tx_out + 1) % buffer_size;
        // Send first character to start tx interrupts, if stopped
        device.putc(temp_char);
    }
    // End Critical Section
    NVIC_EnableIRQ(USART4_5_IRQn);
    
    return;
}
 
// Read a line from the large rx buffer from rx interrupt routine
void read_line() {
    int i;
    i = 0;
    // Start Critical Section - don't interrupt while changing global buffer variables
    NVIC_DisableIRQ(USART4_5_IRQn);
    // Loop reading rx buffer characters until end of line character
    while ((i==0) || (rx_line[i-1] != '\r')) {
        // Wait if buffer empty
        if (rx_in == rx_out) {
            // End Critical Section - need to allow rx interrupt to get new characters for buffer
            NVIC_EnableIRQ(USART4_5_IRQn);
            while (rx_in == rx_out) {
            }
            // Start Critical Section - don't interrupt while changing global buffer variables
            NVIC_DisableIRQ(USART4_5_IRQn);
        }
        rx_line[i] = rx_buffer[rx_out];
        i++;
        rx_out = (rx_out + 1) % buffer_size;
    }
    // End Critical Section
    NVIC_EnableIRQ(USART4_5_IRQn);
    rx_line[i-1] = 0;
    return;
}
 
// Interupt Routine to read in data from serial port
void Rx_interrupt() {
    // Loop just in case more than one character is in UART's receive FIFO buffer
    // Stop if buffer full
    while ((device.readable()) && (((rx_in + 1) % buffer_size) != rx_out)) {
        rx_buffer[rx_in] = device.getc();
        rx_in = (rx_in + 1) % buffer_size;
    }
    return;
}
 
// Interupt Routine to write out data to serial port
void Tx_interrupt() {
    // Loop to fill more than one character in UART's transmit FIFO buffer
    // Stop if buffer empty
    while ((device.writeable()) && (tx_in != tx_out)) {
        device.putc(tx_buffer[tx_out]);
        tx_out = (tx_out + 1) % buffer_size;
    }
    return;
}

The main difference is that the code is packaged into its own class and member variables/functions. I have played with declaring the important member variables as globals (to mimic the test case), have tried changing

I've also tried calling _serial.getc after the while loop in interrupts, before return is called, in case the serial device is not writeable/readable upon first entering the interrupt. I did this because I've read that serial interrupts might loop infinitely until at least one getc/putc is called (see this thread for one reference: https://developer.mbed.org/questions/167/Controller-hangs-once-a-serial-interrupt/)

I have read of a similar issue on some boards, running mbed OS 2, and someone suggested disabling the UART before attach and re-enabling after attach (see this thread for reference: https://developer.mbed.org/questions/77509/Embed-hanged-when-use-Serial-attachfun-m/). I have not had luck successfully disabling and re-enabling in the test case, as I believe it's performed differently in OS 5. For instance tried:

__USART5_FORCE_RESET();
__USART5_RELEASE_RESET();
__USART5_CLK_DISABLE();
.... attach functions ....
__USART5_CLK_ENABLE();

but this caused even the test case to run improperly (obviously doing something wrong there). Any suggestions extremely appreciated.

Be the first to answer this question.