平时写代码时,总是需要用到打印调试输出,之前每次都是直接使用 printf 函数直接打印的,但是越来越发现很不方便。比如有时我想屏蔽打印调试信息时,总不能回去一点点的把原来的打印代码删除了吧。有时我需要分级别打印时,直接使用 printf 函数打印也不好操作。
在网上我发现了一个打印调试的日志库 EasyLogger
,这个 log 库的功能非常强大,可以满足各种需求了。到时我写文章记录下使用心得。
但是,我其实又不用这么强大的功能,虽然 EasyLogger
移植使用起来也不难,但自己所需要的功能也不用那么多。于是就自己捣鼓一下 log 的调试输出代码。
我大概需要的功能就是:
我自己参考了 RT-Thread 写了这部分代码。
其中核心代码就是这部分分级打印代码:
dbg_log_line
宏定义如下:
这个宏的第一行代码就是首先打印输出 log 信息的前缀以及设置打印信息的显示颜色,打印的前缀我们可以根据需要自己设置,一般都是设置打印出文件名、行数等等。接着就是打印我们真正的 log 信息,最后一行接着打印换行。
完整的代码如下:
#ifndef __LOG_H__
#define __LOG_H__
#ifdef __cplusplus
extern "C" {
#endif
#include
/* 日志输出总开关 */
#define DBG_ENABLE
/* 颜色输出开关 */
#define DBG_COLOR
/* 日志打印级别 */
#define DBG_ERROR 0
#define DBG_WARNING 1
#define DBG_INFO 2
#define DBG_LOG 3
/* 设置日志打印级别,级别越高输出的日志信息越多 */
#define DBG_LEVEL DBG_LOG
#ifdef DBG_ENABLE
/*
* The color for terminal (foreground)
* BLACK 30
* RED 31
* GREEN 32
* YELLOW 33
* BLUE 34
* PURPLE 35
* CYAN 36
* WHITE 37
*/
#ifdef DBG_COLOR
#define _DBG_COLOR(n) printf("\033["#n"m")
#define _DBG_LOG_HDR(lvl_name, color_n) \
printf("\033["#color_n"m[" lvl_name "/" "<%s:%d>" "] ", __FILE__, __LINE__)
#define _DBG_LOG_X_END \
printf("\033[0m\n")
#else
#define _DBG_COLOR(n)
#define _DBG_LOG_HDR(lvl_name, color_n) \
printf("[" lvl_name "/" "%s:%d" "] ", __FILE__, __LINE__)
#define _DBG_LOG_X_END \
printf("\n")
#endif /* DBG_COLOR */
#define dbg_log_line(lvl, color_n, fmt, ...) \
do \
{ \
_DBG_LOG_HDR(lvl, color_n); \
printf(fmt, ##__VA_ARGS__); \
_DBG_LOG_X_END; \
} \
while (0)
#else
#define dbg_log_line(lvl, color_n, fmt, ...)
#endif /* DBG_ENABLE */
/* debug */
#if (DBG_LEVEL >= DBG_LOG)
#define LOG_D(fmt, ...) dbg_log_line("D", 0, fmt, ##__VA_ARGS__)
#else
#define LOG_D(...)
#endif
/* info */
#if (DBG_LEVEL >= DBG_INFO)
#define LOG_I(fmt, ...) dbg_log_line("I", 32, fmt, ##__VA_ARGS__)
#else
#define LOG_I(...)
#endif
/* warn */
#if (DBG_LEVEL >= DBG_WARNING)
#define LOG_W(fmt, ...) dbg_log_line("W", 33, fmt, ##__VA_ARGS__)
#else
#define LOG_W(...)
#endif
/* error */
#if (DBG_LEVEL >= DBG_ERROR)
#define LOG_E(fmt, ...) dbg_log_line("E", 31, fmt, ##__VA_ARGS__)
#else
#define LOG_E(...)
#endif
#ifdef __cplusplus
}
#endif
#endif /* __LOG_H__ */
由于输出的信息颜色,有些终端可能不支持,所以在 ubuntu 下测试。
测试代码其实很简单,就是直接打印信息就行。
#include
#include "log.h"
int main(void)
{
LOG_D("***********************log test start***********************");
LOG_D("hello world...");
LOG_I("hello world...");
LOG_W("hello world...");
LOG_E("hello world...");
LOG_D("***********************log test end***********************");
return 0;
}
打印输出效果如下:
可以看到有不同的颜色效果。而且,如果是不支持颜色显示的话,可以关闭颜色显示功能。
在 MCU 上测试的话,需要有串口的支持。我使用的测试开发板是 STM32F407ZGT6 芯片,关于串口的初始化相关的代码,我直接使用 STM32CubeMX 生成了,这里不多讲。如果你使用的是其他芯片,自己添加初始化串口相关的代码。
初始化完串口之后,我们要想使用 printf 打印函数,需要重定向这个函数是向串口打印输出信息的。
我们只要在工程代码中,重新实现 fputc
这个函数即可。我使用 HAL 库编写的代码如下:
int fputc(int ch, FILE* f)
{
char a = '\r';
__HAL_UNLOCK(&huart1);
if (ch == '\n')
{
HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 1);
}
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1);
return ch;
}
重新实现了 fputc
函数之后,我们还需要配合使用 MDK 提供的 MicroLIB
,这个库其实就是标准的 C 库高度优化而来的,占用资源更少。
在配置串口配置如下:
在配置框中勾上 Use MicroLib
。一定要选择这个,不选择的话,会导致输出不成功的。
如果我们不勾选 Use MicroLib
也想正常打印输出调试信息的话,那就要我们多添加一些代码了。
因为 printf 函数使用了半主机模式,所以直接使用这个函数会导致程序无法运行的,我们需要提前高速编译器不要使用半主机模式。
不勾选 Use MicroLib
库需要添加的完整代码如下:
#if 1
/* 告知连接器不从C库链接使用半主机的函数 */
#pragma import(__use_no_semihosting)
/* 标准库需要的支持数据类型 */
struct __FILE
{
int handle;
};
FILE __stdout;
/**
* @brief 定义_sys_exit()以避免使用半主机模式
* @param void
* @return void
*/
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE* f)
{
char a = '\r';
__HAL_UNLOCK(&huart1);
if (ch == '\n')
{
HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 1);
}
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1);
return ch;
}
#endif
这样添加了上述代码之后,我们就算不够选 Use MicroLib
库也可以正常打印输出日志信息了。
在 main 函数中,我们添加在 ubuntu 下测试的那几行打印测试代码,然后打开 MobaXterm
终端软件,可以看到如下打印效果:
可以看到有不同颜色的打印效果,如果不想打印某些信息的级别,修改打印日志输出级别的宏定义就行。
注意:如果是使用常用的串口助手测试工具的话,可能不支持输出信息带颜色的,而且反而会看到一些不需要的颜色编码,这个时候我们可以把颜色输出的宏定义关闭就可以不用输出颜色了。