Pages: [1]   Go Down
Author Topic: [Update] dbg.h : debug library  (Read 4688 times)
0 Members and 1 Guest are viewing this topic.
Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

History:
Version 1.1
- fix: reduced the debug array from 1000 to 10. Highly recommend you download and use this version instead... or nothing works. Oops.
- fix: renamed a couple of global variables to hopefully remove any sketch clashes

Version 1.0
- example sketch, library, keywords and empty library released
- version, addvar, addpin, dump and debug commands implemented


Example:
The current example program:

Code:
#include "Arduino.h"
#include "dbg.h"
//#include "nodbg.h"

#define pin13 13
int charCount = 0;
String inputBuffer;
byte dPin13;                    // digital pin 13 (the onboard lED)

void setup() {
    Serial.begin(9600);
    Serial.println("Ready");
    
    pinMode(pin13, INPUT);
    
    DBG_VERSION;     // output the version
    DBG_ADDVAR("number of characters received", charCount)   // add a variable
    DBG_ADDVAR(F("serial input buffer"), inputBuffer)              // add another variable
    DBG_ADDPIN(F("onboard LED"), dPin13)                             // add a pin
    DBG_DUMP;    // debug all currently added variables            // output all debug variables
}

void loop() {
    dPin13 = digitalRead(pin13);
    
    DBG_DEBUG(dPin13);                                                     // debug just this variable
}    

void serialEvent() {
    while (Serial.available()) {
        charCount++;
        char inChar = Serial.read();
        inputBuffer += inChar;
        switch (inChar) {
            case 'z' : // let's clear the buffer
                       Serial.println("\nClearing input buffer");
                       inputBuffer = "";
                       break;
            default   : Serial.print(inChar);
        }
    }
    DBG_DEBUG(charCount)
    DBG_DEBUG(inputBuffer)
}

The output from the example program, as displayed in Tellurium:

Raw output, showing debug library version:


Parsed variable output, showing variables and pins grouped, and the effect of sending "hello":


Files:
There are 4 files associated with this library:


Installation:
The dbg and nodbg header files should be placed into corresponding dbg and nodbg directories in your Arduino libraries folder. Place the keywords.txt file in the dbg and nodbg directories also. I have colour coded dbg commands as KEYWORD1 so they stand out a bit.

Commands:
All the commands have been created as #define macros in dbg.h. These same macros have been defined as "empty" macros in nodbg.h, allowing you to switch between the 2 include files and remove all debugging actions quickly and easily, whilst leaving the debugging commands in your code.

Commands that have been (fairly) reliably implemented are:
  • DBG_VERSION: print the version of the dbg library to the serial port
  • DBG_ADDVAR(descriptive name, variable): associate a descriptive name with a variable and add it to the debug array
  • DBG_ADDPIN(descriptive name, variable): associate a descriptive name with a variable, flag it as a pin and add it to the debug array
  • DBG_DUMP: send all debug variables to the serial port
  • DBG_DEBUG(variable): send the variable to the serial port

NB: If you go poking around, you will notice there are a couple of other commands present in dbg.h, but their correct operation is not guaranteed.

Roadmap: (in random order)
- fix: range checking on debug array
- fix: store debug variables in a linked list instead of an array
- fix: handle [local to function] variable debugging gracefully
- add: sketch flow - sketch actions, function entry & exit, function return values

Background
In my development, I have found debugging incredibly useful in working out what is going on. This is especially true when something goes wrong.

With the Arduino, the serial port is an obvious communication conduit, allowing you to send data back to a serial monitor for viewing / debugging purposes.

It wasn't long before I got sick of doing things like

Serial.print("some variable name: ");
Serial.println(someVariable);

and sought to put together a library that would make the debugging process a little easier.

I had also found the Arduino serial monitor slightly less than optimal for debugging purposes, what with it clearing my sent text, resetting the board only at start up and reluctance to appear when I pressed Ctrl+Shift+M...

Accumulating gross amounts of text in a serial monitor was not as helpful as expected either.

So the goal was to develop a serial monitor that would cooperate with some debugging sent from an Arduino debugging library. The serial monitor has been released. More information here: http://forum.arduino.cc/index.php?topic=169518.0

The other thing that is a challenge is the way Arduino sketches work, you are always looping around, so careful placement of debug statements is required to prevent a torrent form the serial port flooding the serial monitor. To this end, the debug library tracks variable values, and only sends them to the serial port when they change. You still need to be mindful of debug placement, but some assistance is provided via this mechanism.

* dbg.zip (3.43 KB - downloaded 29 times.)
« Last Edit: August 25, 2013, 06:03:41 pm by aarondc » Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here's the library, so you don't have to download it to see it:

Code:
#include "Arduino.h"
#ifndef DBG_H
#define DBG_H

#define _DBG_VERSION_ 1.1
#define DBG_ADDARRAY(S1, S2) DBGARRAY[DBGCOUNTER] = newArrayChild(S1, *S2, sizeof(S2)/sizeof(S2[0])); DBGCOUNTER++;
#define DBG_ADDPIN(S1, S2) DBGARRAY[DBGCOUNTER] = newChild(S1, S2, true); DBGCOUNTER++;
#define DBG_ADDPINARRAY(S1, S2) DBGARRAY[DBGCOUNTER] = newArrayChild(S1, *S2, sizeof(S2)/sizeof(S2[0]), true);  DBGCOUNTER++;
#define DBG_ADDVAR(S1, S2) DBGARRAY[DBGCOUNTER] = newChild(S1, S2); DBGCOUNTER++;
#define DBG_DEBUG(S1) DBG_debug(S1);
#define DBG_DUMP  for (int i = 0; i < DBGCOUNTER; i++) DBGARRAY[i] -> debug();
#define DBG_VERSION Serial.print("\nDBG Version: "); Serial.println(_DBG_VERSION_);


class DBG_baseClass {
    protected:
        bool isDone;
        bool isPin;
    public:
        DBG_baseClass() {isDone = false;};
        virtual void debug() {};
        virtual bool isEqual(void *thisVarPtr) {};
};


template <typename T1, typename T2>
class DBG_childClass :  public DBG_baseClass {
    private:
        T1 variableName;
        T2 lastValue;
        T2 *variablePtr;
        int arraySize;
       
        bool valueChanged() {
            if (arraySize ==1) return (*variablePtr != lastValue);
            else return true; // (memcmp(variablePtr, &lastValue, arraySize) == 0);
        };
       
        void saveValue() {
            if (arraySize ==1) lastValue = *variablePtr;
            else {
                // memcpy(*variablePtr, lastValue, arraySize);
                lastValue = *variablePtr;
            }
        }

        /*
            Control codes used in sending info back out the serial port:
            sSTX = #02;         // Start text - start of set of data
            sETX = #03;         // End of text - plain variable
            sEOT = #04;         // end of transmission - complete set of data
            sENQ = #05;         // enquiry - pins
            sACK = #06;         // acknowledge - actions
            sSO  = #14;         // shift out - return value from function
        */
       
        void divider() {
            if (!isPin) Serial.print((char)0x03);
            else        Serial.print((char)0x05);   
        };

        void debugStart() {
            Serial.print((char)0x02);
            Serial.print(variableName);
            divider();
        };
       
        void debugEnd() { Serial.print((char)0x04); };
       
        void debugValue() {
            debugStart();
            Serial.print(*variablePtr);
            debugEnd();
        };
    public:
        DBG_childClass(T1 thisVariableName, T2 &thisVariable, int thisSize = 1, bool thisIsPin = false) {
            arraySize = thisSize;
            isPin = thisIsPin;
            variableName = thisVariableName;
            variablePtr = &thisVariable;
            lastValue = thisVariable;
        };
       
       
        virtual void debug() {
            if ((!isDone) || (valueChanged())) {
                isDone = true;
                saveValue();
               
                if (arraySize == 1) debugValue();
                else {
                    debugStart();
                    for (int i = 0; i < arraySize; i++) {
                        if (variablePtr[i] == NULL) break;
                        Serial.print(variablePtr[i]);   // output the next value from the array
                        Serial.print(" ");              // separate array values with spaces
                    }
                    debugEnd();
                }
            }
        };
       
        // are the addresses the same
        virtual bool isEqual(void *thisVarPtr) { return (thisVarPtr == variablePtr); };
};

template <typename T1, typename T2>
DBG_baseClass *newChild(T1 thisVariableName, T2 &thisVariable, bool thisIsPin = false) {
    return new DBG_childClass<T1, T2>(thisVariableName, thisVariable, 1, thisIsPin);
};

template <typename T1, typename T2>
DBG_baseClass *newArrayChild(T1 thisVariableName, T2 &thisVariable, int thisSize, bool thisIsPin = false) {
    return new DBG_childClass<T1, T2>(thisVariableName, thisVariable, thisSize, thisIsPin);
};


int DBGCOUNTER = 0;
DBG_baseClass *DBGARRAY[10];

template <typename T>
void DBG_debug(T &thisVariable) {
    for (int i = 0; i < DBGCOUNTER; i++) {
        if (DBGARRAY[i] -> isEqual(&thisVariable)) {
            DBGARRAY[i] -> debug();
            break;
        }
    }
};

#endif
« Last Edit: June 21, 2013, 11:03:13 am by aarondc » Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 65
Posts: 2107
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I like the way you constructed your class, are you intending it to run on both AVR and SAM Arduinos?

I saw your thread in the programming questions forum, It seems like you are very interested in templates and meta-programming, so I'm doing up an example for you to see the power of templates, I also have some nice links I can dig up. There are many things you can do to make this a nice tool for Arduino like removing polymorphic behavior and new.
Logged


Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks man.

At the moment I am AVRing as nothing I am doing requires grunt, but the Sam chips I have seen make me feel funny with their 100+ MHz clock speed, so I imagine down the track I will want to do something that needs more processing and then will want to do debugging as well.

I read a blog entry by someone (the LUFA author wunderkind) describing the debugging they can do - I think it was via JTAG (??) - and it sounded like it had some limitations.

I was pretty pleased about using the function call to generate the class template, as I couldn't work out how to do it directly, and to be honest, pointers still mess with my head. I program intuitively and then try a few combinations of & and * until it works. ::embarrassed look::

I was mainly interested in templates / meta as the combinations of functions I would have to define was something like 6 data types x 3 name types x 2 variable types (pin or normal) = 36 x 2 function types = 72+ functions. Bugger that. For day to day programming in my projects I'd rather keep it as lean as possible, but would welcome any links or even better an example of doing it.

Any suggestions for upgrading the usefulness or functionality of this class would be more than welcome, and much appreciated. Looking forward to it.




« Last Edit: June 24, 2013, 06:50:56 am by aarondc » Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm particularly interested in leaning the memory usage out.

I have a program flow idea brewing, partly implemented in both the library and the serial monitor, and it all involves string storage and then a psuedo-reference counting methodology. Makes sense in my head but not necessarily here until I get it up and running.

Anyway yes. Any hints on reducing memory footprint would be great.
Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 65
Posts: 2107
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I originally recreated your original design, however there is one major difference with what I have now.

I'm using a technique I first discovered when writing my AtomicBlock library ( link in my sig, template based lib ), which exploits the lifetime of automatic variables to ensure that something always executes last ( can give more info ).

This allowed me to move away from the original design of reporting each value in the loop function to a design that simply reports every time the variable changes state.

At the moment it uses a duplicate, however Atmel provides a crc16 lib which may be beneficial for variables above 2 bytes. But even further, there is a temporary that can store the previous value/crc that will only use memory while the variable is in use. This would reduce memory significantly or at least its lifetime, and only use the stack for its buffers ( heap use is permanent unless dynamic allocation is introduced ).

There is still a lot of work to do, the manual reporting method can be implemented and support for non-aggregate types is partially implemented. I have not fully implemented it as there is better ways to do most things my version provides, I just have to discover them (  abstract syntax can be blurry after too many beers ).

Below is a working AVR example, it is completely unfinished,... for now ( arrays may need specialising ) .

Code:
template< typename T >
  class Var {
    public:
    
      Var( const char *c_Name, const T &t_Value ) : Name( c_Name ), Data( t_Value ), Old() { const DebugThisVar d( *this ); }
      template< typename InType > T &operator =( const InType& i )  { return DebugThisVar( *this ).RefOut() = ( const T& ) i;  }
      operator T&() { return DebugThisVar( *this ).RefOut(); }
      
    protected:
    
      template< typename TA, typename UA > friend Var< TA > &operator +=( Var< TA > &LHS, const UA &RHS );
    
      struct DebugThisVar{

        DebugThisVar( Var &v_Var ) : v( v_Var ) {}
        
        T& RefOut( void ){ return v.Data; }
      
        ~DebugThisVar( void )
          {
            if( v.Data != v.Old ){
              Serial.print( ( const __FlashStringHelper* ) v.Name );
              Serial.print( " == " );
              Serial.print( v.Data );
              Serial.print( " ( Last: " );
              Serial.print( v.Old );
              Serial.print( " )\r\n" );
              v.Old = v.Data;
            }
          }
          
        Var &v;
      };    
    private:
      const char *Name;
      T Data;
      T Old;
};

template< typename T, typename U > Var< T > &operator +=( Var< T > &LHS, const U &RHS )
  {
    typename Var< T >::DebugThisVar( LHS ).RefOut() += RHS;
    return LHS;
  }

void setup() {
  
  Serial.begin( 115200 );
  
  Var< int > v_IntA( PSTR( "INT_A" ), 0x00 );
  Var< int > v_IntB( PSTR( "INT_B" ), 0x00 );

  for(  ; v_IntA < 10 ; ++v_IntA ){
    
   if( v_IntA % 2 )
      ++v_IntB;
  }
  
  for( v_IntA = 0x00 ; v_IntA < 10 ; ++v_IntA ){
  
    if( v_IntA % 2 )
      --v_IntB;    
  }
  
  v_IntB += 55;
  v_IntB += 55;
  v_IntB = 22;
  
  Var< String > v_Str( PSTR( "STRING_A" ), "This is an initial value" );

  v_Str = "This is a new string";
  v_Str += ", this is appended on the end";
  v_Str = "OI";
}

void loop(){}

You can add in more specific operators ( +=, -=,... ), but I believe this can be omitted once I can work out the problem with mixing POD & non-aggregates ( string vs int ).

Also a simple improvement is the variable names are stored in PROGMEM, not ram ( slower, but increases available RAM ).

EDIT: the class takes a template type, the constructor takes a PROGMEM address and initial value for the variable.
« Last Edit: June 24, 2013, 07:40:06 am by pYro_65 » Logged


Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Might take me a year or two to wrap my head around it - it's certainly interesting and instructional. I'm also looking at your atomic blocks library, as it could help with my next debug function idea for program flow tracking...

This allowed me to move away from the original design of reporting each value in the loop function to a design that simply reports every time the variable changes state.

Because you overloaded the = operator, yeah? And would require overaloading the other operators that could potentially change the variable value, as well as handling calls to functions like memcpy. ( I see you expanded further below ).

Just to clarify - my library allows you to report changes anywhere in the sketch - not just loop() - but it's developer chosen, which is decidedly my preference, rather than an automatic process.

At the moment it uses a duplicate, however Atmel provides a crc16 lib which may be beneficial for variables above 2 bytes. But even further, there is a temporary that can store the previous value/crc that will only use memory while the variable is in use. This would reduce memory significantly or at least its lifetime, and only use the stack for its buffers ( heap use is permanent unless dynamic allocation is introduced ).

I had not considered generating a CRC to track changes in types that are bigger than 2 bytes or so. That could certainly save on space requirements for string and struct or array type variables. Brilliant idea.

You can add in more specific operators ( +=, -=,... ), but I believe this can be omitted once I can work out the problem with mixing POD & non-aggregates ( string vs int ).

I am not even sure what POD is. Google says "plain old data"?

Also a simple improvement is the variable names are stored in PROGMEM, not ram ( slower, but increases available RAM ).

Do you mean a simple improvement over my class? I'm confused because my class implementation allows you to specify the variable name as F() macro call, PSTR() cast, String var, char array, etc. It's entirely up to the user. This is something I implemented as soon as coding badly suggested it, and was 50% of the driver for exploring a template implementation in the first place. I'm not 100% sure if it works as I expected / intended it to, but the intent was there! smiley-wink


I want to look at your example app briefly:


Code:
...
Var< int > v_IntA( PSTR( "INT_A" ), 0x00 );
Var< int > v_IntB( PSTR( "INT_B" ), 0x00 );
...
void setup() {
 
  Serial.begin( 115200 );
 
  v_IntaA = 10;
  v_IntaB = 12;


Would output the changes in both variables.

My library would require the following:
Code:
...
int v_IntA = 0;
int v_IntB = 0;
...
void setup() {
 
  Serial.begin( 115200 );
 
  v_IntaA = 10;
  v_IntaB = 12;

  DBG_ADD(F("INT_A"), v_IntA);
  DBG_ADD(F("INT_B"), v_IntB);
  DBG_DEBUG(v_IntA);
  DBG_DEBUG(v_IntB); // or a DBG_DUMP;


The primary intent of this class was to allow someone to add debugging code to their sketch, to see what was going on. Once they have finished debugging, they then have to remove code (using typical Serial.print debugging strategies they would have to go through and remove / comment out the lines).

Hence the macro calls in this library, meaning you swap out the debug library for the faux debug library and all that debug code disappears, without having to go through and delete all the debug lines from your sketch.

Once a user is satisfied their sketch is working, how would they remove the debug behaviour using your class?
And a silly question - but the debugging is not added, is it? But integrated from the start, via variable creation using your template class?
« Last Edit: June 25, 2013, 05:19:14 am by aarondc » Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Coming soon to a debug library near you...

* overloaded macros
* breakpoints
* dynamic gotos
Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The files are missing from dropbox.  Do you have another download link or can you please re uploda them?

Thanks.
Logged

Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The files are missing from dropbox.  Do you have another download link or can you please re uploda them?

Thanks.

I have attached a zip with keywords.txt and dbg.h to the first post. Have been busy with a few other things, feel free to post here if something breaks / is broken.

NB: the library is a work in progress. Only the functions outlined above currently have any sort of chance of working as expected.
Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

Melbourne, Australia
Offline Offline
God Member
*****
Karma: 8
Posts: 567
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It may be even worse than originally stated. I need to update docs, etc. Most significant change is as follows:

You need to specify the debug level before including the header file thusly:

Code:
#define DBG_LEVEL 0;   // turns debugging off
#define DBG_LEVEL 1;   // basic debugging
#define DBG_LEVEL 2;   // more advanced debugging functions
#include "dbg.h"

This was a design decision providing more flexibility in the library vs having multiple #include files.
Logged

Windows serial port monitor: Tellurium | Arduino serial port debugging library: DBG | Cusom LCD char generator | Technical questions will only be answered in forum threads

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It's all good.  Thanks mate.  Sure beats the debug I was doing. 

Now if I can just get the windows debugger to work in Linux.  I may have to rebuild an old windows netbook.

Logged

Pages: [1]   Go Up
Jump to: