缓冲要在 UART 上发送的数据的最佳实践 [关闭]
Posted
技术标签:
【中文标题】缓冲要在 UART 上发送的数据的最佳实践 [关闭]【英文标题】:Best practice for buffering data to be sent on UART [closed] 【发布时间】:2021-10-20 19:33:44 【问题描述】:我正在使用 STM32F7 设备开发一个嵌入式项目,编写裸机 C。
我希望能够在程序中的任何时候将数据发送到 UART 以进行调试,而不会在发送数据时阻塞。我正在使用 DMA 来尽量减少用于此的 CPU 时间。
目前我正在将数据填充到 FIFO 队列中,然后发起 DMA 请求,将数据直接从 FIFO 队列发送到 UART。
这个问题是我无法设置 DMA 从 FIFO 缓冲区的开始和结束读取,如果 FIFO 的中间未使用并且消息从缓冲区的末尾换行开始吧。
解决这个问题的两个方法是设置第一个 DMA 请求从 FIFO 的头部读取到缓冲区的末尾,然后一旦完成,从缓冲区的开头读取到缓冲区的尾部先进先出。
另一种方法是 memcpy() 取出要发送到另一个缓冲区的字节,它们都是顺序的,然后发起一个 DMA 请求以一次发送所有数据。
这两种方法都可能有效,但我正在寻找有关最佳方法的见解。
【问题讨论】:
虽然这需要意见,这可能会导致这个问题的结束,这就是我的想法。将字节复制到单独的缓冲区中似乎比不使用 DMA 并使用中断更昂贵。这样您就可以使用循环缓冲区。 -- 但是,如果任何缓冲区已满,您将需要阻塞,并且您不想丢失任何数据。 你需要一个足够大的循环 FIFO,不会溢出 @thebusybee,并非总是如此,有时通过memcpy()
进行复制比 DMA 快很多。实际上,似乎人们完全误解了 DMA 的用途(剧透:在许多情况下不是为了速度)。附注:bounce buffers (memcpy()
+ DMA) 技术用于特殊情况。
我并没有说使用 DMA 更快,相反。 OP 的第二个想法包括memcpy()
和 DMA,这显然是过度设计的。所以我建议不要使用 DMA。
【参考方案1】:
目前提出的例子是“一劳永逸”。在您的代码需要知道数据是否已发送的情况下。我们使用了以下结构,其中 fifo 保存指向数据的结构。
这样,您的数据由发送它的代码保存。它能够监视传输,但它也负责在传输完成之前不使用数据。
另一个优点是您不必提前分配缓冲区。只需要两个指针来指向链表结构的开始和结束。
一些元代码:
enum transmission_state
Unused,
WaitToBeSend,
Sending,
Done,
Error // Optional but handy
struct data_to_send
// Point to your data.
data* data_pointer;
// Set the length of your data.
int length;
// What is the current state of this transmission.
transmission_state state;
// Pointer to the next data to be send creating a linked list.
// Only have the send and dma functions use this.
data_to_send* next;
;
// Definition of the fifo.
data_to_send* fifo_first = null;
data_to_send* fifo_end = null;
// Use this function in your code to add data to be send.
void send(data_to_send* dts)
if(null == fifo_first)
fifo_first = dts;
fifo_end = dts;
dts.next = null;
start_dma_transfer(fifo_first);
else
fifo_end.next = dts;
fifo_end = dts;
dts.state = WaitToBeSend;
dts.next = null;
;
// Start a transfer.
void start_dma_transfer(data_to_send* dts)
dts->state = Sending;
// Do some DMA stuff to start the transmission.
dma_transfer(dts->data, dts->length)
// The interrupt handler called when the dma is done.
void dma_interrupt_handler()
fifo_first->state = Done;
if(null != fifo_first->next)
// Send the next data.
fifo_first = fifo_first->next;
start_dma_transfer(fifo_first);
else
// No new data to be send.
fifo_first = null;
fifo_end = null;
int main()
// Setup a transmission
byte data[3] = x,y,z;
data_to_send transmission = default_dts; // Set to some default.
transmission.data = data;
transmission.length = 3;
send(&transmission);
// Do other important things.
// Later periodically check the transmission.
if(Done == transmission.status)
// You could use the data for something else or send new data.
此结构也可用于 I2C 和 SPI,在这种情况下,您可以将响应添加到 data_to_send
结构并检查响应并对其采取行动。
【讨论】:
【参考方案2】:我通常选择的实现与您提出的类似:
日志记录函数创建一个文本并将其添加到循环缓冲区。 DMA 用于 UART 传输。 DMA 设置为发送连续的数据块。 只要 DMA 完成,就会触发中断。它首先释放循环缓冲区中传输的数据。然后它检查是否需要传输更多数据。如果是这样,它会立即使用新数据重新启动。伪代码:
tx_len = 0;
void log_message(const char* msg)
circ_buf_add(msg);
start_tx();
void start_tx()
if (tx_len > 0)
return; // already transmitting
const char* start;
int len;
circ_buf_get_chunk(&start, &tx_len);
if (tx_len == 0)
return;
uart_tx_dma(start, tx_len);
void dma_interrupt_handler()
circ_buf_remove(tx_len);
tx_len = 0;
start_tx();
限制传输块的长度通常是有意义的。越短,循环缓冲区中的空间就越快释放。
【讨论】:
以上是关于缓冲要在 UART 上发送的数据的最佳实践 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
创建要在 C# 中编组的 C++ Dll 的最佳实践 [关闭]