Federico
Federico

Reputation: 1157

Multilevel debug with printf in embedded C

I'm working on embedded system, where I use printf to create log on UART.

I would like to create a debug source file where I can set the type of debug I need.

I defined this constant:

Starting from this constant I defined this macro to wrap the standard 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

Next I would import the debug constant from a Header file in order to enable the selected level of debug.

Any suggestion about the macro definition? Is there a smart way to achieve my scope?

Thanks!

Upvotes: 2

Views: 3352

Answers (4)

user50619
user50619

Reputation: 353

The real trick with re-directing printf on embedded systems is that printf calls putch(unsigned char) for every byte it outputs. See: https://www.quora.com/What-is-the-use-of-getch-putch-getchar-putchar-Cant-we-write-a-program-without-using-these-in-C-language

If you make your own putch() function, it will override the system one (because the linker will have seen it first).

You can then bung it down a UART or what ever. Here is a simple example in microchip xc8 C for a PIC18F2480 micro-controller. Obviously the port pins and RS232 BAUD rates were set up earlier.

/* 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
}

Upvotes: 0

vgru
vgru

Reputation: 51224

It would make more sense to pass the log level/source throughout the code, and then simply disable/enable individual levels or sources at one place.

I.e. in your code, you would use:

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");

And then you simply have:

// 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)

(edit)

As pointed out by @Clifford in comments, there might also be a need to globally disable a certain level, without having to go through all source defines. This can be done by specifying an additinal mask:

// 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)

One additional concern might be a number of "unreachable code" warnings this will produce around your code. I am not sure how to fix this in other compilers, but, for example in Visual Studio, it can be resolved by adding a pragma around the if statement:

// 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)

Not it looks a bit uglier, but IMHO it allows easy usage and much more control.

Of course, nothing stops you from having a separate macro for each log level (perhaps simpler because you have one parameter less) i.e.:

#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");

Upvotes: 3

LPs
LPs

Reputation: 16223

  1. I don't know your compiler but printf(...) generates a compiler error.
  2. Your header cannot compile due to the multiple definition of functions with same names.

To gives you an hint you can simply:

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

But is better to rethink your header. I used to:

#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__)

where color can b, e.g.:

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

Upvotes: 0

Clifford
Clifford

Reputation: 93476

You will need to define all the macros regardless of what level is defined, otherwise any place you invoke a macros that is undefined will generate an error. The macros you want to be inactive can simply be defined as empty statements.

You also have redefinitions that won't work, for example if DEBUG_LEVEL_0 is not defined but DEBUG_LEVEL_1 is you have two different definitions of edi_Print_L0(). Similarly if both DEBUG_LEVEL_0 and DEBUG_LEVEL_1 are defined you still have multiple definitions. You need to make the definitions mutually exclusive, or if multiple level macro definitions exist ensure only the highest level is active:

#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

I would also suggest a more useful definition for the debug macros such as:

#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__ )

In that way for example, the line:

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

in say line 24 of file main.c in function main() when counter equals 25 for example will do nothing at level 0 or 1, but at level 2 will output:

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

So you get the debug output you require with the exact location in the code and the debug level it was issued at.

A better (more easily maintained) solution is to have a single macro DEBUG_LEVEL with a numeric value:

#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

This solution allows just one definition of each macro, so maintenance is a lot easier and less error prone.

Upvotes: 2

Related Questions