嵌入式C中使用printf进行多级调试

时间:2016-06-15 14:10:36

标签: c debugging macros embedded printf

我正在使用嵌入式系统,我使用printf在UART上创建日志。

我想创建一个调试源文件,我可以在其中设置我需要的调试类型。

我定义了这个常数:

  • 系统日志的DEBUG_LOG_0
  • DEBUG_LOG_1进行系统调试
  • DEBUG_LOG_2用于系统高级调试

从这个常量开始,我定义了这个宏来包装标准的printf:

    /* Define for debugging level 0 - System Logs */
#ifdef DEBUG_LEVEL_0
#define edi_Print_L0(...) printf(__VA_ARGS__)
#endif

#ifndef DEBUG_LEVEL_0
#define edi_Print_L0(...) printf(...)
#endif

/* Define for debugging level 1 - Debug */
#ifdef DEBUG_LEVEL_1
#define edi_Print_L0(...) printf(__VA_ARGS__)
#define edi_Print_L1(...) printf(__VA_ARGS__)
#endif

#ifndef DEBUG_LEVEL_1
#define edi_Print_L0(...) printf(...)
#define edi_Print_L1(...) printf(...)
#endif

/* Define for debugging level 2 - Advanced Debug */   
#ifdef DEBUG_LEVEL_2
#define edi_Print_L0(...) printf(__VA_ARGS__)
#define edi_Print_L1(...) printf(__VA_ARGS__)
#define edi_Print_L2(...) printf(__VA_ARGS__)
#endif

#ifndef DEBUG_LEVEL_2
#define edi_Print_L0(...) printf(...)
#define edi_Print_L1(...) printf(...)
#define edi_Print_L2(...) printf(...)
#endif

接下来,我将从Header文件导入调试常量,以启用所选的调试级别。

有关宏定义的任何建议吗?有没有一种聪明的方法来实现我的范围?

谢谢!

4 个答案:

答案 0 :(得分:2)

无论定义了什么级别,您都需要定义所有宏,否则任何调用未定义宏的地方都会产生错误。您想要处于非活动状态的宏可以简单地定义为空语句。

您还有重新定义不起作用,例如,如果未定义DEBUG_LEVEL_0DEBUG_LEVEL_1您有两个不同的edi_Print_L0()定义。同样,如果定义DEBUG_LEVEL_0DEBUG_LEVEL_1,您仍然有多个定义。您需要使定义互斥,或者如果存在多个级别的宏定义,请确保只有最高级别处于活动状态:

#if defined DEBUG_LEVEL_2
    #define edi_Print_L0(...) printf(__VA_ARGS__)
    #define edi_Print_L1(...) printf(__VA_ARGS__)
    #define edi_Print_L2(...) printf(__VA_ARGS__)
#elif defined DEBUG_LEVEL_1
    #define edi_Print_L0(...) printf(__VA_ARGS__)
    #define edi_Print_L1(...) printf(__VA_ARGS__)
    #define edi_Print_L2(...)
#elif defined DEBUG_LEVEL_0
    #define edi_Print_L0(...) printf(__VA_ARGS__)
    #define edi_Print_L1(...)
    #define edi_Print_L2(...)
#else
    #define edi_Print_L0(...)
    #define edi_Print_L1(...)
    #define edi_Print_L2(...)
#endif

我还建议对调试宏使用更有用的定义,例如:

#define edi_Print_L0( format, ... )   printf( "\nL0:%s::%s(%d) " format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )
#define edi_Print_L1( format, ... )   printf( "\nL1:%s::%s(%d) " format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )
#define edi_Print_L2( format, ... )   printf( "\nL2:%s::%s(%d) " format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )

以这种方式,例如,行:

edi_Print_L2( "counter=%d", counter ) ;

在函数main()中的main.c文件的第24行,例如当counter等于25时,在0或1级别什么都不做,但在2级会输出:

L2:main.c::main(24) counter=25

因此,您需要获得所需的调试输出,其中包含代码中的确切位置以及在此处发布的调试级别。

更好(更容易维护)的解决方案是使用一个带有数值的宏DEBUG_LEVEL

#if !defined DEBUG_LEVEL
    #define DEBUG_LEVEL = -1
#endif

#if DEBUG_LEVEL >= 2
    #define edi_Print_L2( format, ... )   printf( "\nL2:%s::%s(%d) " format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )
#else
    #define edi_Print_L2(...)
#endif

#if DEBUG_LEVEL >= 1
    #define edi_Print_L1( format, ... )   printf( "\nL1:%s::%s(%d) " format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )
#else
    #define edi_Print_L1(...)
#endif

#if DEBUG_LEVEL >= 0
    #define edi_Print_L0( format, ... )   printf( "\nL0:%s::%s(%d) " format, __FILE__, __FUNCTION__,  __LINE__, __VA_ARGS__ )
#else
    #define edi_Print_L0(...)
#endif

此解决方案只允许每个宏的一个定义,因此维护更容易,更不容易出错。

答案 1 :(得分:2)

在整个代码中传递日志级别/源更有意义,然后只需在一个地方禁用/启用单个级别或源。

即。在您的代码中,您将使用:

Log(Log_Comm, LevelDebug, "Some minor stuff");
Log(Log_Comm, LevelWarn, "Something strange");
Log(Log_Comm, LevelError, "Something seriously wrong");
Log(Log_System, LevelDebug, "Some minor stuff");
Log(Log_System, LevelWarn, "Something strange");
Log(Log_System, LevelError, "Something seriously wrong");

然后你只需要:

// log levels
#define LevelDebug 0x01
#define LevelInfo  0x02
#define LevelWarn  0x04
#define LevelError 0x08
#define LevelAll   0x0F

// enabled levels for individual log sources
#define Log_Comm    (LevelWarn | LevelError)
#define Log_System  (LevelAll)

#define Log(source, level, message) do { \
   if (source & level) { \
        sendToPort(message); \
   } \
} while (0)

(适用编辑)

正如@Clifford在评论中指出的那样,可能还需要全局禁用某个级别,而不必遍历所有源定义。这可以通过指定附加掩码来完成:

// if LevelDebug is omitted from this mask,
// debug message will not be logged regardless
// of individual source settings
#define Global_Level_Mask (LevelWarn | LevelError)

#define Log(source, level, message) do { \
    if (source & level & Global_Level_Mask) { \
       sendToPort(message); \
    } \
} while (0)

另外一个问题可能是许多无法访问的代码"警告这会产生你的代码。我不知道如何在其他编译器中解决这个问题,但是,例如在Visual Studio中,可以通过在if语句周围添加一个pragma来解决它:

// visual studio will show a warning 
// C4127: "conditional expression is constant"
// when compiling with all warnings enabled (-w4)

// these pragmas will disable the warning around if's
#define Log(source, level, message) do { \
__pragma(warning(push)) \
__pragma(warning(disable:4127)) \
    if (source & level & Global_Level_Mask) { \
__pragma(warning(pop)) \
        sendToPort(message); \
    } \
} while (0)

不是看起来有点丑陋,但恕我直言,它允许简单的使用和更多的控制。

当然,没有什么可以阻止你为每个日志级别设置一个单独的宏(可能更简单,因为你有一个参数更少),即:

#define LogDebug(source, message) Log(source, LevelDebug, message)
#define LogInfo(source, message) Log(source, LevelInfo, message)
#define LogWarn(source, message) Log(source, LevelWarn, message)

// usage example:
LogDebug(Log_Comm, "Some minor stuff");
LogWarn(Log_System, "Something strange");

答案 2 :(得分:0)

  1. 我不知道你的编译器,但printf(...)生成了一个编译器 错误。
  2. 由于具有相同名称的函数的多重定义,您的标头无法编译。
  3. 为了给你提示,你可以简单地说:

    #ifdef DEBUG_LEVEL_0
    #define edi_Print_L0(format, ...) printf(format, ##__VA_ARGS__)
    #else
    #define edi_Print_L0(...) do{}while(0)
    #endif
    

    但最好重新考虑一下你的标题。我曾经:

    #define STRCAT(a, b, c, d, e) a b c d e
    #define DEBUG(level, message, color, ...) if (level < DEBUG_LEVEL) fprintf(stdout, STRCAT(color, "%s: %s - ", message, MH_END, "\n"), __FILE__, __func__, ##__VA_ARGS__)
    

    其中颜色可以是b,例如:

    #define MH_HGREEN   "\033[1m\033[32m"
    #define MH_END      "\033[m"
    

答案 3 :(得分:0)

在嵌入式系统上重定向printf的真正技巧是 printf为输出的每个字节调用putch(unsigned char)。 看到: https://www.quora.com/What-is-the-use-of-getch-putch-getchar-putchar-Cant-we-write-a-program-without-using-these-in-C-language

如果您创建自己的putch()函数,它将覆盖 系统一(因为链接器将首先看到它)。

然后可以将其塞入UART或其他任何器件。 这是PIC18F2480的xc8 C单片机中的简单示例 微控制器。显然,端口引脚和RS232 BAUD速率是较早设置的。

/* printf will now write to the UART */

void putch(char c)
{
    int count = 0;

    while (!TRMT) // still transmitting, prob should not be 
        count++;
    TXREG = c;
    wait_us(1500); // wait 1.5mS if 9600 baud so as not to over run the dual buffer
}