嵌入式格式化输出(LOG、自己实现printf、可变参数、LOG颜色)

本文介绍了如何在C语言中实现可变参数的LOG输出,讲解了stdarg库的使用,包括va_list、va_start、va_arg和va_end。同时探讨了printf函数的实现,特别是vsnprintf函数在格式化输出中的作用。还提到了预定义宏__VA_ARGS__在处理可变参数中的应用,并简单说明了如何结合预先整理的格式化字符串和外部调用的格式化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天在项目上遇到一个问题,需要将目前代码中的全部LOG增加此条LOG所在文件及行数等定位标识,方便定位代码。在网上折腾了一下,整理一下“printf”中用到的一些接口及一个相对使用较方便的LOG输出格式。

//不想弄清楚实现原理的直接拿走代码用,想了解一下的可以往下看一看。
#include "stdio.h" 

#define __FILE__ 		"main.c"	//LOG所在文件名,因为是上位机测试因为在此处直接使用define模拟
#define __FUNCTION__	"main" 		//LOG所在函数名,因为是上位机测试因为在此处直接使用define模拟
#define __LINE__		147			//LOG所在行号,因为是上位机测试因为在此处直接使用define模拟
#define LOG_LEV			7			//LOG等级,可以分为ERR、WARNING、INFO、NOTICE·····

#define MY_LOG(APP_LEVEL, APP_MODU,CONTENT,...)	 printf("<%d>[%s][%s][%s][%d]:"CONTENT,APP_LEVEL, APP_MODU,__FILE__,__FUNCTION__,__LINE__,##__VA_ARGS__);

int main()
{
	MY_LOG(LOG_LEV,"TEST","test:[char*]%s,[int]%d\r\n\r\n\r\n\r\n","hello",1);

	return 0;
}

在这里插入图片描述
直接看上面的代码可能是有点看不懂(看懂的大神可以出门了[奸笑]),尤其是"##__VA_ARGS__“与”…"不知道是什么东西。接下来便来介绍这些东西

一、可变参数和printf的实现

#include <stdio.h>
#include 
<think>我们正在讨论C语言宏定义在调试打印中的应用。用户之前遇到了DAP-Link连接问题,现在转向代码层面的调试工具使用。这个宏定义是嵌入式调试的常用技巧。宏定义#definelog_d(...)printf(__VA_ARGS__)的功能解析:1.这是一个带可变参数的宏,使用C99标准中的可变参数宏语法2....表示宏可以接受任意数量的参数3.__VA_ARGS__是预处理器标识,会被替换为宏调用时传入的实际参数列表4.整个宏的作用是将log_d(...)替换为printf(...)实现调试日志输出在MCUUSB打印调试中的意义:1.抽象层作用:在底层硬件变化时(如更换串口或USBCDC),只需修改宏定义即可保持上层代码不变2.条件编译:通常配合#ifdefDEBUG开关,在发布版本中禁用日志以减少代码体积3.添加额外信息:可扩展宏定义自动添加文件名、行号等调试信息,例如:#definelog_d(...)printf("[%s:%d]",__FILE__,__LINE__);printf(__VA_ARGS__)4.性能优化:通过预处理器控制,在非调试版本中完全消除日志代码典型应用场景:在STM32HAL库项目中,通过重定向printf到USBCDC接口,结合此宏实现调试输出:1.在usbd_cdc_if.c中实现CDC_Transmit_FS()函数2.重写_write函数(对于newlib)或使用__io_putchar()3.定义调试宏:#ifdefDEBUG#definelog_d(...)printf(__VA_ARGS__)#else#definelog_d(...)#endif数学表达示例(虽然不直接相关,但可展示格式要求):当考虑日志系统性能时,输出延迟$t_{delay}$由USB传输时间$t_{usb}$和格式化时间$t_{format}$组成:$$t_{delay}=t_{usb}+t_{format}$$注意事项:1.在资源有限的MCU中,避免在高频中断使用printf,可能导致阻塞2.使用snprintf防止缓冲区溢出3.对于实时性要求高的场景,考虑异步日志机制代码示例(STM32CubeIDE):```c//重定向printf到USBCDC#include"usbd_cdc_if.h"int_write(intfile,char*ptr,intlen){CDC_Transmit_FS((uint8_t*)ptr,len);returnlen;}//调试宏定义#defineDEBUG1#ifDEBUG#definelog_d(...)printf("[DEBUG]"__VA_ARGS__)#else#definelog_d(...)#endif//使用示例voidmain(){log_d("Systemstart,clock=%luHz\n",SystemCoreClock);}```相关问题:1.如何将printf重定向到串口?2.在中断服务程序中可以使用printf吗?3.如何实现带时间戳的日志系统?4.条件编译宏定义的高级用法有哪些?</think>### C语言宏定义 `#define log_d(...) printf(__VA_ARGS__)` 的功能与用途解析 #### 1. **宏定义的基本功能** ```c #define log_d(...) printf(__VA_ARGS__) ``` - **可变参数处理**: `...` 表示接受任意数量的参数,`__VA_ARGS__` 将这些参数原样传递给 `printf`。 例如 `log_d("Value: %d", x)` 会被预处理为 `printf("Value: %d", x)`。 - **语法简化**: 将复杂的调试输出简化为统一的 `log_d` 接口,提高代码可读性。 对比: ```c // 未使用宏 printf("[DEBUG] Sensor value: %f\n", sensor_data); // 使用宏 log_d("[DEBUG] Sensor value: %f\n", sensor_data); // 语义更清晰 ``` #### 2. **在MCU USB打印调试中的核心意义** - **调试信息分级管理**(关键作用): 通常配合条件编译实现调试级别控制: ```c #define DEBUG_LEVEL 2 // 调试级别开关 #if DEBUG_LEVEL >= 1 #define log_e(...) printf("[ERROR] " __VA_ARGS__) // 错误级 #endif #if DEBUG_LEVEL >= 2 #define log_d(...) printf("[DEBUG] " __VA_ARGS__) // 调试级(您的宏) #endif ``` **优势**: - 发布版本中通过 `DEBUG_LEVEL 0` 自动移除所有调试代码,减少固件体积 - 动态控制日志详细程度(如仅输出错误/全调试信息) - **添加调试元数据**: 扩展宏定义自动嵌入关键信息: ```c #define log_d(...) \ printf("[%s:%d] ", __FILE__, __LINE__); \ // 自动添加文件名和行号 printf(__VA_ARGS__) ``` 输出示例: `[sensor.c:25] ADC value: 3.14V` - **统一输出接口**: 当更换输出方式(如串口→USB-CDC→SWO)时,只需修改宏定义: ```c // 切换为SWO输出(ARM Cortex-M) #define log_d(...) ITM_SendString(__VA_ARGS__) ``` #### 3. **MCU调试中的特殊优势** 1. **资源优化**: - 通过条件编译彻底移除调试代码,节省Flash/RAM - 避免在低功耗模式因调试输出唤醒MCU 2. **实时性问题规避**: ```c // 在中断中安全使用 void ADC_IRQHandler() { log_d("ISR triggered"); // 比直接调用printf更可控 } ``` - 可扩展为**异步日志**:缓存日志到队列,在非中断环境输出 3. **波特率容错设计**: 当USB-CDC虚拟串口波特率不匹配时,宏可扩展为缓冲机制: ```c #define log_d(...) do { \ char buf[128]; \ snprintf(buf, sizeof(buf), __VA_ARGS__); \ CDC_Transmit_FS((uint8_t*)buf, strlen(buf)); \ } while(0) ``` #### 4. **典型应用场景** ```c // 在STM32CubeIDE中的完整示例 #include "usbd_cdc.h" // 调试宏定义 #define DEBUG 1 #if DEBUG #define log_d(...) \ printf("[%lu][%s] ", HAL_GetTick(), __FUNCTION__); \ printf(__VA_ARGS__) #else #define log_d(...) #endif void read_sensor() { float voltage = read_adc(); log_d("Voltage=%.2fV\n", voltage); // 输出:[12345][read_sensor] Voltage=3.30V } ``` #### 5. **注意事项** - **内存安全**: 避免在栈空间有限的线程中使用长字符串,优先使用 `snprintf` 封装。 - **实时性影响**: `printf` 在资源受限MCU上可能耗时较长,建议: $$ t_{\text{exec}} \leq \frac{1}{10}T_{\text{ISR}} $$ 其中 $t_{\text{exec}}$ 是日志输出耗时,$T_{\text{ISR}}$ 是中断周期。 - **依赖项管理**: 需确保: 1. `printf` 已重定向到USB-CDC(参考 `_write()` 重定向) 2. 链接器包含 `--specs=rdimon.specs`(ARM GCC)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值