缓冲要在 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 的最佳实践 [关闭]

向客户端发送数据的最佳实践是啥:返回实体还是 dto? [关闭]

如何定义通道缓冲区的最佳大小? [关闭]

Linux学习 : 裸板调试 之 配置UART

UART基础知识转载

Metal 最佳实践:三重缓冲 – 纹理?