STM32 elegant way to parse NMEA sentences without allocating to much memory

762 Views Asked by At

I'm writing a parser for NMEA sentences. They can look like this:

$GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*11
$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
$GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43

This block of code repeats around each second. I would like to analyze line by line. I use the STM32 Hal UART command to read this. First I use HAL_UART_Receive_IT to generate an interrupt when an incoming sentence begins with $. Then I parse the first line. By reading char by char in string until the break line command \n is reached. So in this example I parse the first command $GPBWC. When I'm done I want to parse the next line $GPRMS. However during the parsing of $GPBWC UART doesn't stop and I'm missing the sentences $GPRMC and $GPVTG.

I can receive the whole block at a time but this requires a lot of allocated memory which is constantly blocked and makes my code unnecessary heavy. Also I need to extend the memory even more if I will have more types of sentences.

I want to get all sentences line by line without missing any of them. There has to be a more elegant way then receiving the whole block at a time.

Update

Thx @0___________ for the hint.

I'm implemented the following code:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    update(rx_byte[0]);
    HAL_UART_Receive_IT(&hlpuart1, rx_byte, 1);
}

void update(char c) {

    if (c == '$') {
        sen_index = 0;
        msgValid = 1;
    }
    
    if (msgValid == 1) {
        sentense[sen_index] = c;
        sen_index++;

        if (c == '\n') {
    
            msgValid = 0;
            strncpy(prefix, sentense + 1, 5);
    
            if (strcmp(prefix, "GPRMC") == 0) {
                ...
            }
    
            if (strcmp(prefix, "GPBWC") == 0) {
                ...
            }
    
            if (strcmp(prefix, "GPVTG") == 0) {
                ...
            }
        }
        ...

    while (1) {
        HAL_UART_Receive_IT(&hlpuart1, rx_byte, 1);
    }

This is not the best code but it works.

1

There are 1 best solutions below

1
KamilCuk On
  • Do not do much work in interrupt handler! Interrupt handler only reads characters and adds to a buffer. Keep interrupts short & fast.
  • FIFO. Interrupt handler appends characters to a FIFO from one side. Then the main loop reads from the fifo from the other side. Remember about volatile and locking needed for communication between interrupt and other code.
  • Max NMEA line has 82 characters. Have a static 83 buffer to store a single line for parser to parse. Note this is not the size of the buffer, only internal NMEA parser buffer for one message.
  • Do not reinvent the wheel - NMEA is old, there are millions libraries to parse it. Minmea is a fine library for parsing NMEA messages.
  • You may want for faster transfer use DMA, however it is really hard to implement, because you can't depend on interrupt every character. I think only consider DMA if your baudrate is greater than 115200 (or you need really low power consumption).