我需要通过 UART 接收一个六字节的数据包(使用 STM32L010F4)。这六个字节中包含一个 SOF 和一个 EOF 字节。当没有数据发送时,可能会有很长的一段,然后是这些六字节数据包的流,间隔为 5 毫秒。收到有效数据包后,我需要对四个非 SOF/EOF 字节执行一些基本数学运算(一些加法和乘法),将数据重新打包到另一个数据包中,然后将其发送到不同 UART 上的主机.
使用 DMA 似乎导致我无法与这些传入消息同步,正如我所指出的那样。最好的架构是什么?使用更大的缓冲区?
我建议使用 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
条目。替代方案(回收“浪费的”条目)涉及诸如在某些地方禁用中断等,并且增加更多的工作却收效甚微。
您可以创建一个基本的缓冲区处理数据结构:一个指向缓冲区(或缓冲区数组)的指针和一个索引。
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。