格式化打印到循环缓冲区

时间:2013-02-17 21:31:19

标签: c printf stm32 circular-buffer

我正在为STM32F3 mc(STM32F3-Discovery)编写嵌入式代码。我需要向UART输出一些数据,我正在使用DMA,因为这样我可以专注于传感器读取和数据处理,而不是等待字节传输完成。然而问题是我必须结合起来:

  1. 格式化输出(即来自printf的一些)
  2. 许多连续打印(在上一次打印完成之前发生)
  3. 所以我在想循环缓冲区。但我不认为我知道如何使sprintf尊重缓冲区的结束并继续写入缓冲区的开头。我当然可以创建另一个临时缓冲区,在那里打印,然后逐字节复制,但对我来说看起来并不优雅。

2 个答案:

答案 0 :(得分:2)

一种解决方案可能是实现您自己的sprintf,它可以使用环形缓冲区。不幸的是,这不会帮助你解决一个更基本的问题:如果你的铃声已经满了并且你调用了sprintf,你会怎么做?

如果你的记忆情况可以承受,我建议另一个解决这个问题的方法:

这个想法基于两个链接的缓冲区列表(一个列表用于空闲缓冲区,一个列表用作传输队列)。缓冲区的大小相同,因此它们可以存储最差情况下的长度字符串。缓冲区构建一个简单的堆,其中分配/释放仅从空闲列表或从传输列表中对元素进行出列/排队。

具有相同大小的缓冲区可确保您在动态分配内存时不会遇到像“checkerboarding”这样的外部碎片效应。为此作业构建自己的堆也可以让您完全控制可用于传输任务的总缓冲区大小。

我可以想象这运行如下:

  1. 您从空闲列表中分配缓冲区以将数据呈现到。
  2. 使用渲染函数(例如sprintf)在缓冲区中呈现数据
  3. 将要发送的数据附加到传输队列(并在必要时触发传输)
  4. 对于DMA传输,您处理传输结束IRQ。在那里,您将刚刚传输的缓冲区移动到“空闲列表”,并为队列中的下一个缓冲区设置传输。

    这个解决方案不是内存最有效的,但是运行时效率很好,因为你只需要写一次内存,而分配/解除分配只是在某处获取/存储指针。当然,您必须确保在应用程序和IRQ之间没有获得分配/解除分配的竞争条件。

    也许这个想法可以帮助您解决您的需求。

答案 1 :(得分:1)

您可以近似的一种方法是将printf缓冲区分配为环形缓冲区大小的2倍,然后使用前半部分作为环形缓冲区。检测到溢出,将后半部分的溢出复制回前面(环部分)。像这样:

void xprintf(const char *format, ...)
{
  int written;
  va_list args;
  va_start(args, format);
  written = vsnprintf(buffer, HALF_BUFFER_SIZE, format, args);
  va_end(args);
  if (buffer + written > bufferStart + HALF_BUFFER_SIZE)
  { // time to wrap
    int overflow = (buffer + written) - (bufferStart + HALF_BUFFER_SIZE); 
    memmove(bufferStart, bufferStart + HALF_BUFFER_SIZE, overflow;
    buffer = bufferStart + overflow;
  }
}