如何最好地缓冲传入的 UART 数据

问题描述 投票:0回答:2

我需要通过 UART 接收一个六字节的数据包(使用 STM32L010F4)。这六个字节中包含一个 SOF 和一个 EOF 字节。当没有数据发送时,可能会有很长的一段,然后是这些六字节数据包的流,间隔为 5 毫秒。收到有效数据包后,我需要对四个非 SOF/EOF 字节执行一些基本数学运算(一些加法和乘法),将数据重新打包到另一个数据包中,然后将其发送到不同 UART 上的主机.

使用 DMA 似乎导致我无法与这些传入消息同步,正如我所指出的那样。最好的架构是什么?使用更大的缓冲区?

stm32 uart dma
2个回答
2
投票

我建议使用 ISR 来处理 UART 接收到的每个字符,并维护一个指示当前接收状态的状态机(例如等待 SOF、读取数据、等待 EOF)。这样做的好处是,如果数据流被中断,它会自然地与数据流重新同步,如果您正在 DMAing 固定大小的数据块并希望它们对齐,这是很难做到的。

我还会维护一个接收消息的循环缓冲区,ISR 正在写入该缓冲区。然后我会让我的主“线程”循环从此队列中读取消息、处理它们并发送响应。这意味着如果一堆消息真的很快进入,并且处理跟不上,你不会丢失任何消息。

一些评论(未经测试)的代码,希望能展示这个想法:

#define DATA_SIZE    4u   // How many bytes in each message
#define NUM_MESSAGES 32u  // How many messages can be in the queue

enum state_e {
    STATE_WAIT_FOR_SOF,
    STATE_DATA,
    STATE_WAIT_FOR_EOF
};

struct message {
    uint8_t data[DATA_SIZE];
};

// A circular buffer of messages
// Volatile since the ISR modifies them. (Probably not necessary, but harmless)
static volatile struct message msgs[NUM_MESSAGES];

// The index for the next entry to be written
// Volatile since the ISR can modify it
static volatile uint8_t head_ix = 0u;

// The index for the next entry to read
static uint8_t tail_ix = 0u;

// Start off waiting for SOF
static enum state_e state = STATE_WAIT_FOR_SOF;

// An index into the current message (being written)'s data[] array
static uint8_t data_ix;

static void process_msg(struct message *msg)
{
    // Process msg and send response
}

int main(void)
{
    // Any other initialisation happens...
  
    while (1)
    {
        // Any messages in the queue? Queue is empty if head_ix == tail_ix.
        if (tail_ix != head_ix)
        {
            // Consume a message
            process_message(&msgs[tail_ix]);
            tail_ix = (tail_ix + 1u) % NUM_MESSAGES;
        }
    }
}

// A ficticious interrupt handler that deals with a single character.
// Your real implementation will have to deal with reading the character
// from the UART, clearing the interrupt, etc, then call something like this.
void handle_rx_interrupt(uint8_t datum)
{
    switch (state)
    {
    case STATE_WAIT_FOR_SOF:
        if (datum == SOF)
        {
            // Got SOF, move to data state
            state = STATE_DATA;

            // Waiting for the first byte of data
            data_ix = 0u;
        }
        break;

    case STATE_DATA:
        // Store this datum in the message
        msgs[head_ix].data[data_ix] = datum;
        ++data_ix;

        // If we have a full message, move to waiting for EOF state
        if (data_ix == DATA_SIZE)
        {
            state = STATE_WAIT_FOR_EOF;
        }
        break;

    case STATE_WAIT_FOR_EOF:
        if (datum == EOF)
        {
            // See if we have space in the queue for this message
            uint8_t new_head = (head_ix + 1u) % NUM_MESSAGES;

            if (new_head != tail_ix)
            {
                // Yes, add this message
                head_ix = new_head;
            }
            else
            {
                // No space for this message. It will just be overwritten
                // in place by the next message
            }
        }

        // No matter what happened, we're now waiting for SOF
        state = STATE_WAIT_FOR_SOF;
        break;
    }
}

请注意,由于满/空条件的工作方式,实际上队列中只能有

NUM_MESSAGES - 1
条目。替代方案(回收“浪费的”条目)涉及诸如在某些地方禁用中断等,并且增加更多的工作却收效甚微。


0
投票

您可以创建一个基本的缓冲区处理数据结构:一个指向缓冲区(或缓冲区数组)的指针和一个索引。

typedef struct{
  int8_t* RxBuffer;
  volatile uint8_t RxFirstEmptyIndex;
  uint8_t RxBufferMaxLength;
}UART_RxBufferHandle;

为 RxBuffer 分配一些内存(malloc 或其他任何东西),将最大长度放入适当的变量中。

数据接收中断时,将UART数据寄存器读入RxBuffer[RxFirstEmptyIndex],然后将RxFirstEmptyIndex加1,如果达到MaxLength(缓冲区末尾),需要处理(否则USART外设会溢出错误,拒绝接收)工作直到你解决它 - 读取传入数据并清除错误标志)。在收到 N 个字符后你可以处理输入(例如,在收到 6 个字符后 - 当 RxFirstEmptyIndex 变为 6 时),并将 RxFirstEmptyIndex 重置回 0;易失性,因为它的值在中断中发生变化。

您可以从结构中创建一个全局变量,使其对中断处理程序可见。或者为它寻找其他解决方案。

您可以通过在此类结构中添加/更改变量来为其他协议和通信使用或多或少类似的机制。但缓冲区处理通常与此类似。显然,如果你觉得 256 个字符太少,欢迎你将索引计数器和长度更改为 uint16_t 或 uint32_t。

© www.soinside.com 2019 - 2024. All rights reserved.