2 years, 7 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.

Comment on this question
Be the first to answer this question.

You need to log in to post a question