缓冲要在 UART 上发送的数据的最佳实践

Best practice for buffering data to be sent on UART

我正在使用 STM32F7 设备开发一个嵌入式项目,编写裸机 C。

我希望能够在程序中的任何时候将数据发送到 UART 以进行调试,而不会在发送数据时阻塞。我正在使用 DMA 来尽量减少用于此的 cpu 时间。

目前我正在将数据填充到一个FIFO队列中,然后发起DMA请求将数据直接从FIFO队列发送到UART

这个问题是我无法将 DMA 设置为同时从 FIFO 缓冲区的开头和结尾读取,如果 FIFO 的中间未使用并且消息从结尾处回绕开始的缓冲区。

对此的两种解决方案是设置第一个 DMA 请求以从 FIFO 的头部读取到缓冲区的末尾,然后一旦完成,从缓冲区的开头读取到缓冲区的末尾。 FIFO 的尾部。

另一种方法是通过 memcpy() 将要发送到另一个缓冲区的字节发送到另一个缓冲区,其中它们都是顺序的,然后发起单个 DMA 请求以一次发送所有数据。

这两种方法都可能有效,但我正在寻找最佳方法的见解。

我通常选择的实施方式与您提出的类似:

  • 日志记录函数创建文本并将其添加到循环缓冲区。
  • 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();
}

限制传输块的长度通常是有意义的。越短,循环缓冲区中的 space 释放得越快。

到目前为止提出的例子都是火了就不管了。在您的代码需要知道数据是否已发送的情况下。我们使用了以下结构,其中 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 结构添加响应并检查响应并对其采取行动。