使用循环缓冲区自定义printf的7个步骤

通常不建议将printf与基于微控制器的嵌入式系统一起使用,这是行业最佳实践。避免使用printf的几个原因是,它通常效率低下,具有阻塞功能并且会中断嵌入式系统的实时行为。但事实是,printf是嵌入式系统的关键调试工具。可以改进printf的一种方法是使用利用中断的循环缓冲区重写它。本文将说明执行此操作的过程,示例代码可从下面的链接下载。

下载链接

步骤1 –定义printf函数

定义自定义printf函数的第一步,是确定如何命名新函数,以使其与库函数不冲突,并了解如何处理printf参数列表。因为对于嵌入式系统,我们感兴趣的是将printf用于调试数据,因此定义一个名为Debug_printf的新函数是一个很好的第一步。标准printf函数要求将常量char *传递给它,这是要输出的字符串以及变量的可变列表。参数的变量列表由库stdarg.h处理。通过使用三个句点在函数参数列表中定义变量参数的使用。新的printf函数的定义如图1所示。

Debug_printf图1 – Debug_printf定义

步骤2 –定义局部变量

为了格式化printf字符串,然后将其压入传输循环缓冲区,将需要许多变量。第一个变量是本地缓冲区数组,用于存储格式化的printf字符串。接下来,有一个名为Args的变量参数列表变量。 Args存储传递到函数中的参数。长度一旦格式化,将存储最终字符串的大小。 TxChar只是一个变量,用于获取要在串行设备上传输的第一个字符,而我只是一个循环变量。 Debug_printf所需的所有变量都可以在图2中找到。

printf_vars 图2 – printf函数变量

 步骤3 –格式化printf字符串

生成最终格式化的printf字符串可能需要一些工作,但是值得庆幸的是,有C库函数可用于执行大多数工作。第一个帮助程序va_start是一个宏,它接受变量参数列表和字符串指针,并准备用于格式化的Args列表。接下来,vsprintf用于获取格式和参数列表,并将它们转换为存储在Buffer中的最终字符串。最后,调用va_end宏以结束变量列表并允许其返回。结果代码相当简单,如图3所示。

printf_formatting

图3 –格式化printf字符串以存储在Buffer中

 步骤4 –将Buffer推入循环缓冲区

一旦最后一个字符串被格式化,一个标准的printf函数就会开始通过串行设备顺序发送缓冲区。通常以阻塞的方式进行传输,以便在传输完整个字符串之前,不执行其他任何代码。以这种方式进行传输显然会并且将影响系统的实时性能。字符串越长,响应时间越差。为了提高实时性能,可以将缓冲区推入循环缓冲区,该缓冲区以中断驱动的方式处理串行传输。要了解有关循环缓冲区的更多信息,请参见“开源循环缓冲区”。

由于Buffer是格式化的字符串,因此strlen可用于确定字符串中存在多少个字符。有了这些信息,可以使用一个简单的循环,调用CBUF_Push将每个字符压入缓冲区。实施转移的过程如图4所示。

printf_buffer

图4 –复制缓冲区到循环Tx缓冲区

 步骤5 –启动传输

在成功传输第一个字符之前,不会触发传输中断。为了做到这一点,Debug_printf函数需要将第一个字符发送到UART驱动程序。为此,需要从循环缓冲区弹出第一个字符并将其发送到UART发送功能。 UART驱动程序调用不在本文讨论范围之内,但从示例代码和图5可以看出,对于printf函数需要使用的内容的一般概念。

printf_tx

图5 –开始传送

 步骤6 –填写发送中断

一旦发送了第一个字符,发送中断将完成繁重的工作。此时,软件将继续执行代码,同时一次传输一个缓冲区中的每个字符。在图6中可以看到一个简单的示例。该中断基本上清除了中断标志,如果缓冲区还没有为空,则发送下一个字符。

printf_tx_isr

图6 –传送ISR

 步骤7 –进行实时测量

一旦新的Debug_printf函数和相关函数完成,就可以测试代码了。在您的应用程序代码上添加gpio切换开关,并测量代码执行和响应各种事件所花费的时间。一旦完成基线测量,便开始使用新的调试功能并监视时序测量。对于非常基本的输出,实时影响应该可以忽略不计。不要忘记对系统进行压力测试,并了解在影响实时性能之前可以走多远。

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *

该网站使用Akismet减少垃圾邮件。 了解如何处理您的评论数据.