STM32 优雅地解析 NMEA 句子,无需分配太多内存

huangapple go评论67阅读模式
英文:

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

问题

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,EGLM11
$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 unnecessarily 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 than receiving the whole block at a time.

Update

Thanks @0___________ for the hint.

I've 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) {
        sentence[sen_index] = c;
        sen_index++;

        if (c == '\n') {
    
            msgValid = 0;
            strncpy(prefix, sentence + 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.

英文:

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

得分: 2

  • 在中断处理程序中不要做太多的工作!中断处理程序只读取字符并将其添加到缓冲区。保持中断短而快。
  • 先进先出队列(FIFO)。中断处理程序从一侧将字符附加到FIFO,然后主循环从另一侧从FIFO中读取。请记住,中断和其他代码之间的通信需要使用volatile关键字和锁。
  • 最大的NMEA行包含82个字符。有一个静态的83字节缓冲区用于存储单行NMEA消息以供解析使用。请注意,这不是缓冲区的大小,只是用于一个消息的内部NMEA解析缓冲区。
  • 不要重新发明轮子 - NMEA很古老,有数百万库可用于解析它。Minmea是一个用于解析NMEA消息的良好库。
  • 如果您希望进行更快的传输,可以考虑使用DMA,但这确实很难实现,因为您不能依赖于每个字符的中断。我认为只有在波特率大于115200(或者您需要非常低的功耗)时才考虑使用DMA。
英文:
  • 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).

huangapple
  • 本文由 发表于 2023年2月26日 20:53:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75572120.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定