格式化打印到环形缓冲区

6
我正在为STM32F3 mc (STM32F3-Discovery)编写嵌入式代码。我需要将一些数据输出到UART,为此我使用DMA,因为这使我能够集中精力处理传感器读取和数据处理,而不是等待字节传输完成。然而,问题在于我必须结合以下两个方面:
  1. 格式化输出(即某种形式的printf)
  2. 一系列连续的打印(发生在上一个打印完成之前)
所以我考虑使用循环缓冲区。但我不知道如何让sprintf遵守缓冲区的末尾并继续写入缓冲区的开头。当然,我可以创建另一个临时缓冲区,在那里打印,然后逐字节复制,但我觉得这不太优雅。

1
有趣的需求,但我认为你不会比“格式化到临时缓冲区,然后通过适当的复制函数将其复制到循环缓冲区”做得更好。 - Jonathan Leffler
2个回答

2
一种解决方法是实现自己的 sprintf,以便能够使用环形缓冲区。不幸的是,这并不能解决一个更基本的问题:如果您的环形缓冲区已满并调用了 sprintf,该怎么办?
如果您的内存情况允许,我建议另一种解决方案:
这个想法基于两个缓冲区的链接列表(一个用于空闲缓冲区,一个作为传输队列)。缓冲区大小相等,因此它们可以存储最坏情况下的长度字符串。缓冲区构成一个简单的堆,其中分配/释放仅是从空闲或传输列表中出列/入列元素。
拥有大小相等的缓冲区可以确保您不会遭受动态分配内存时的外部碎片效应,例如“棋盘状”。为此工作构建自己的堆还将使您完全控制可用于传输任务的总缓冲区大小。
我可以想象它运行如下:
1. 从空闲列表中分配一个缓冲区以渲染数据。 2. 使用您的渲染函数(例如sprintf)在缓冲区中呈现数据。 3. 将要发送的数据附加到传输队列中(并在必要时触发传输)。
对于DMA传输,您需要处理传输结束IRQ。在那里,您将刚刚传输的缓冲区移动到“空闲列表”并设置下一个队列中的缓冲区的传输。
这个解决方案可能不是最节省内存的,但运行时效率很高,因为您只写入内存一次,并且分配/释放仅在某个地方获取/存储指针。当然,您必须确保应用程序和分配/释放IRQ之间没有竞争条件。
也许这个想法可以给您启示来解决您的要求。

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;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接