Debugging: Using Macros to Monitor Program Flow

Abstract

Outputting a debug value in a quasi-serial protocol, over a single output pin, can be an effective debugging method.

So now you've got that LED to blink, signifying that something is running in your MCU. But you'd like a little more information from your prototype system: perhaps some indication of which code paths are executing and which ones are not. One-way communication would suffice...

You can use C preprocessor macros to create a debug tag output. You can insert this output statement wherever you want to report an event, and then enable or disable it in your executable as necessary. When you need it, it only consumes a few bytes and cycles, and gives you a better window on your program than a blinking LED. When you don't need it, it disappears.

What's better, you don't need any special equipment to use it. At a minimum, an oscilloscope can give you a simple readout of the tag value, as a bit pattern. A trace analyser will give you lots more information, of course, but it isn't strictly necessary.

This debugging system requires you to use:

  • An spare output port pin on which to signal. Since pins are expensive on embedded systems, we know how much we're asking. The pin should be a spare pin.

    In a pinch the pin you choose can be used for other things, so long as the signal generated by the macro will not interfere with circuitry connected to the pin, or vice-versa. The first place to check for this conflict is in your code: the pin's output signal should be much slower than the output bit rate calculated below. Check your code to see that when your software drives the output pin from idle to active, it leaves it in that state longer than the start bit time (preferably twice as long or more, the longer the better).

  • An oscilloscope, to observe the output signal.

To add this debugging capability to your program:

  1. Simply #define the following symbols...

    #define DEBUG_TRACE               /* Enable debugging output */ 
    #define D_IDLE    1     /* Idle, will change to BEGIN */
    #define D_DIRPORT DDRx  /* The data direction of the port and ... */ 
    #define D_DIRPIN  x     /* ... pin [0..7] on which to signal */ 
    #define D_SETOUT  1     /* The output data direction */ 
    #define D_PORT    PORTx /* The port and ... */ 
    #define D_PIN     x     /* ... pin [0..7] on which to signal */ 
    
  2. ...and then #include the following file.

    #ifndef __DEBUG_H 
    #define __DEBUG_H 
    /***************************************************************************** 
     *                                                                           * 
     * Byte Craft Limited Code Development Systems                               * 
     *                                                                           * 
     ***************************************************************************** 
     *                                                                           * 
     * Header file information:                                                  * 
     *                                                                           * 
     * $ DeviceName:       ALL $                                                 * 
     * $ Manufacturer:     ALL $                                                 * 
     * $ Filename:         DEBUG.H $                                             * 
     * $ HeaderVer:        1.00  $                                               * 
     * $ Copyright:        2002 $                                                *  
     * $ Compiler:         ALL $                                                 * 
     * $ CompilerVer:      ANY $                                                 * 
     *                                                                           * 
     * This code may be adapted for any purpose when used                        * 
     * with any Byte Craft Limited Code Development System.                      * 
     * No warranty is implied or given as to its usability                       * 
     * for any purpose.                                                          * 
     *                                                                           * 
     * (c) Copyright 2002 Byte Craft Limited                                     * 
     * 421 King St.N., Waterloo, ON, Canada, N2J 4E4                             * 
     * VOICE: 1 (519) 888 6911                                                   * 
     * FAX  : 1 (519) 746 6751                                                   *
     * email: support@bytecraft.com                                              * 
     *                                                                           * 
     *****************************************************************************/ 
    #pragma option -l;
    /***************************************************************************** 
     *                                                                           * 
     * Revision History:                                                         * 
     * ~~~~~~~~~~~~~~~~~                                                         * 
     * $ V:1.00  BH  11/12/02 Initial Version                                  $ * 
     *                                                                           * 
     ***************************************************************************** 
     *                                                                           * 
     * Notes:                                                                    * 
     * ~~~~~~                                                                    * 
     * These macros provide an easy way to troubleshoot programs with interrupts * 
     * by displaying different patterns on a chosen port pin.                    * 
     *                                                                           * 
     * Instructions:                                                             * 
     *                                                                           * 
     * 1. The following symbols must be defined prior to inclusion of this file: * 
     *       D_IDLE    (Line idle state)                                         * 
     *       D_DIRPORT (Data direction port for debug pin)                       * 
     *       D_DIRPIN  (Port pin direction bit in direction register)            * 
     *       D_SETOUT  (Value to set direction pin for output)                   * 
     *       D_PORT    (Data port to use for debugging)                          * 
     *       D_PIN     (Data port bit to use as debug output)                    * 
     *                                                                         * 
     * 2. In the initialization code, use the D_INIT() macro.                    * 
     *                                                                           * 
     * 3. D_TAG(n) may be used anywhere debugging output is required             * 
     *                                                                           * 
     *****************************************************************************/ 
    #pragma option +l;
     /* Debugging output is enabled */ 
     #ifdef DEBUG_TRACE     
     /* Check to ensure that the user has defined all necessary symbols */ 
     #if !defined(D_IDLE)    
     #error D_IDLE (debug pin idle state) must be defined! 
     #elif !defined(D_DIRPORT)    
     #error D_DIRPORT (debug direction port) must be defined! 
     #elif !defined(D_DIRPIN)    
     #error D_DIRPIN (debug direction pin) must be defined! 
     #elif !defined(D_SETOUT)    
     #error D_SETOUT (debug direction pin output state) must be defined! 
     #elif !defined(D_PORT)    
     #error D_PORT (debug port) must be defined! 
     #elif !defined(D_PIN)    
     #error D_PIN (debug pin) must be defined! 
     #endif 
    /* Initialization macro that sets direction and initial state of debug pin */ 
    #define D_INIT()  D_DIRPORT.D_DIRPIN=D_SETOUT; D_PORT.D_PIN=D_IDLE 
    /* Preamble and postamble for bit pattern */ 
    #define D_START() D_PORT.D_PIN=D_IDLE^1; D_PORT.D_PIN=D_IDLE 
    #define D_END()   D_PORT.D_PIN=D_IDLE 
    /* Shorten the defined names for a more compact macro */ 
    #define D_PT D_PORT 
    #define D_P  D_PIN 
    #define D_TAG(n) D_START();\
                      D_PT.D_P=(n>>7)&1;\
                      D_PT.D_P=(n>>6)&1;\
                      D_PT.D_P=(n>>5)&1;\
                      D_PT.D_P=(n>>4)&1;\
                      D_PT.D_P=(n>>3)&1;\
                      D_PT.D_P=(n>>2)&1;\
                      D_PT.D_P=(n>>1)&1;\
                      D_PT.D_P=(n>>0)&1;\
                      D_END() 
    /* Debugging output is disabled */                                
    #else 
    /* Define empty macros for D_INIT and D_TAG(n) so that no code is generated */ 
    #define D_INIT() 
    #define D_TAG(n) 
    #endif /* DEBUG_TRACE */                                                  
    #endif /* __DEBUG_H */ 
    
  3. Finally, add a call to D_TAG() wherever you want to cause a debugging output to occur on your oscilloscope or similar device. Call D_TAG() with a literal 8-bit integer value.

  4. Compile the software and program your embedded MCU.

    Here is an example of (C6808) generated code with the D_TAG() statement enabled.

    8001 3F FE        CLR                < 3 >  for(i = 0; i < 256; i++)
                                                         { 
    8003 3F FD        CLR                < 3 >      for(j = 0; j < 256; j++)
                                                             { 
    8005 11 00        BCLR   0,$00       < 4 >          D_TAG(6);
    8007 10 00        BSET   0,$00       < 4 >
    8009 11 00        BCLR   0,$00       < 4 >
    800B 11 00        BCLR   0,$00       < 4 >
    800D 11 00        BCLR   0,$00       < 4 >
    800F 11 00        BCLR   0,$00       < 4 >
    8011 11 00        BCLR   0,$00       < 4 >
    8013 10 00        BSET   0,$00       < 4 >
    8015 10 00        BSET   0,$00       < 4 >
    8017 11 00        BCLR   0,$00       < 4 >
    8019 10 00        BSET   0,$00       < 4 >
    801B 3F FC        CLR                < 3 >         for(k = 0; k < 256; k++)
                                                               { 
    801D AD E1        BSR    $8000       < 4 >             do_something();
                                                               } 
    801F 3C FC        INC                < 4 >
    8021 20 FA        BRA    $801D       < 3 >
                                                           }
    8023 3C FD        INC                < 4 >
    8025 20 DE        BRA    $8005       < 3 >
                                                        }
    8027 3C FE        INC                < 4 >
    8029 20 D8        BRA    $8003       < 3 >
    
  5. Run the device, and 'scope the port pin you specified above. You should see a bit pattern with start, data and stop bits.

  • Set your 'scope to sync on a transition (either direction, depending on your settings) to lock the waveform on your display.

  • Write D_TAG() macros with different bit patterns to trigger different bits in the display. This gives you up to eight tags that you can observe simultaneously.

    Note that this may require a very forgiving oscilloscope.

  • To determine how long the debugging code takes to run, set the compiler to report the instruction cycle counts with the instruction

    #pragma option INSTRUCTIONTIMING;
    

    The example above demonstrates what this output looks like.

  • If the debugging code takes too long to run, you can certainly truncate it. Remove or comment out several of the data bit assignment lines:

    D_PT.D_P=(n>>x)&1;\
    

    Leave only as many lines as you need data bits of debugging tag information.

  • Surround calls to D_TAG() with preprocessor conditionals to isolate different code paths.

    This isn't required, as D_TAG() is only generated when DEBUG_TRACE is defined. But to watch only certain debugging tags, you will need to determine which D_TAG()s are generated by other symbols.

When enabled, D_TAG() calls generate 11 bit-change instructions; the exact instruction will vary from platform to platform.

In the example above, the tag code takes 44 cycles. At the MC68HC08 clock speed of 12.8MHz, the bus clock period (1 instruction cycle as reported by INSTRUCTIONTIMING) is 312.5ns. Each bit set/bit clear takes 1.25ms, and the entire debug tag output takes 55ms.

The start bit sequence (active, idle) takes 8 cycles or 2.5ms. Any active level generated by your software on the same pin as DPORT.DPIN should be longer than twice that: 5ms or longer.

D_TAG() is not a magic solution. This testing strategy will not help with one kind of bug: a Heisenbug. The computer industry makes up the most entertaining terms, and Heisenbug is one of our favourites.

In 1927, Werner Heisenberg formulated the Uncertainty Principle, an advance in Quantum Mechanics. One gross generalization of its implications is to say that 'you can never measure a process without influencing the results': taking a measurement involves adding to or removing from the system under test, which in turn influences the reading.

The Uncertainty Principle is therefore very descriptive of any strategy of testing that unavoidably alters the target. The technique described above does just that: the program running in a testing mode is different than the program running in production.

Infuriatingly, Heisenbugs are problems only when you are not debugging. For instance, the debugging code above causes delays when outputting a debugging value to a port pin. This delay may allow another part of the system to recover from an unstable state. Remove the debugging code, and the preventative delay is no longer effective.

(For more information on Heisenberg, see the exhibit at the American Institute of Physics.)

The waveform generated by the D_TAG() output is simple enough that a circuit could capture the D_TAG() data values and make them available to some external device. A serial port link or other solution may be possible. Stay tuned...

For now, this testing method can provide a window into your system. It can do so in production hardware, so long as the output signal doesn't interfere with external components or critical timing. We hope you find it useful in your next project.