【问题标题】:Best practice for buffering data to be sent on UART [closed]缓冲要在 UART 上发送的数据的最佳实践 [关闭]
【发布时间】: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。

标签: embedded stm32 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();
    }
    

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

    【讨论】:

      猜你喜欢
      • 2016-08-28
      • 2013-06-30
      • 2010-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-16
      • 2018-11-07
      相关资源
      最近更新 更多