Let's Make Robots!

Struggling with creating libraries for Arduino

UPDATE

Documentation of my efforts will be continued in my blog: Building a new controller - fun and games

Thanks to the help of my fellow LMR members I now have a working library. I have attached the initial library created as a result of this forum. A big thanks to cr0c0, Maus and Mogule who put a lot of effort into helping me with my code. The attached library is not the final product, I need to add more functions and examples yet (I may also need more help).

Thanks also goes to RobotFreak for his tutorial Arduino 101: Timers and Interrupts.

 

 


 

Currently I am trying to create a Library for use with Arduino. Unfortunately programming is not my specialty. I have been studying the library tutorials but unfortunately their attempts to "keep it simple" means they are often too simple to answer my questions. I've tried looking at the code of other libraries but it is not helping much.

My main question for now is a general C++ question.

I am creating a function in my library that generates 4 intergers, how do I pass these 4 integers back to the main routine without using global variables. The tutorials I have found indicate that a function can only return 1 variable which seems utterly pointless and mostly useless.

Do I need to create a data structure and if so, how? Links to tutorials much appreciated. I am trying to learn more about C++ programming structure as well as using interrupts, timers etc.

RobotFreak has written a good tutorial on Timers here: http://letsmakerobots.com/node/28278
However I am still struggling with using timer 2 to call an interrupt to my Library every 1mS to update sensor readings.

All help appreciated.

 


 

Update: 24th January 2012

After looking at my options, Rogue's suggestion as demonstrated by Mogul seemed the easiest to implement. My "Impact" funtion now looks like this:

 

volatile byte interruptflag;

void setup()
{
  // initialize timer2
  noInterrupts();                                      // disable all interrupts
  TCCR2A=0;
  TCCR2B=0;
  TCNT2 =0;

  OCR2A=31;                                            // compare match register 8MHz/256/1000Hz
  TCCR2B |= (1 << WGM22);                              // CTC mode
  TCCR2B |= (1 << CS22);                               // 256 prescaler
  TIMSK2 |= (1 << OCIE2A);                             // enable timer compare interrupt
  interrupts();                                        // enable all interrupts
  Serial.begin(57600);
}

void loop()
{
  int deltx,delty,deltz,magnitude;
  if (interruptflag==1)
  {
    interruptflag=0;
    magnitude=Impact(deltx,delty,deltz);
    if(magnitude>0)
    {
      Serial.print("\tMagnitude:");
      Serial.print(magnitude);
      Serial.print("\tDelta X:");
      Serial.print(deltx);
      Serial.print("\tDelta Y:");
      Serial.print(delty);
      Serial.print("\tDelta Z:");
      Serial.println(deltz);
      Serial.println("");     
    }
  }
}


ISR(TIMER2_COMPA_vect)                                 // timer compare interrupt service routine
{
  interruptflag=1;
}


int c;                                                  // counts program loops since last impact (1 loop / mS)
int x,y,z;                                              // x,y and z axis readings from accelerometer
#define sens 30                                         // sensitivity of impact detection
byte Impact(int &deltx, int &delty, int &deltz)         // Impact function header
{
  int oldx=x;                                           // local variables store previous axis readings
  int oldy=y;
  int oldz=z;

  x=analogRead(0);                                      // new axis readings are taken
  y=analogRead(1);
  z=analogRead(2);

  deltx=x-oldx;                                         // delta between old and new axis readings
  delty=y-oldy;
  deltz=z-oldz;
  int magnitude=sqrt(sq(deltx)+sq(delty)+sq(deltz));    // magnitude of delta x,y,z using pythagorus

  if (magnitude>sens && c<1)                            // has a new impact occured
  {
    c=500;                                              // reset loop counter
    return magnitude;                                   // return impact magnitude
  }
  else
  {
    c--;                                                // loop counter prevents false triggering 
    if(c<0) c=0;                                        // as robot vibrates due to impact
    return 0;
  }
}


I have now included a timer interrupt based on information posted by RobotFreak. Unfortunately the tutorial did not explain things well enought for me to fully understand the process but it was good enough that I could modify an example to use timer 2 to call my function roughly once every millisecond. I tested this originally by having it print micros() everytime the interrupt occured.

My only issue (not a big one) is that I had to use a global variable for the interrupt flag. I am trying to avoid global variables in my library. Is there a way around this?

 


 

Update: 25th of January

I have now tidied up my code a bit more and eliminated the interruptflag by calling the impact function directly from the ISR. This has given me a new problem as now magnitude, deltx, delty and deltz need to be global for my main routine to read them. My new code now looks like this:

 

volatile int magnitude,deltx,delty,deltz;

void setup()
{
  // initialize timer2
  noInterrupts();                                      // disable all interrupts
  TCCR2A=0;
  TCCR2B=0;
  TCNT2 =0;

  OCR2A=31;                                            // compare match register 8MHz/256/1000Hz
  TCCR2B |= (1 << WGM22);                              // CTC mode
  TCCR2B |= (1 << CS22);                               // 256 prescaler
  TIMSK2 |= (1 << OCIE2A);                             // enable timer compare interrupt
  interrupts();                                        // enable all interrupts
  Serial.begin(57600);
}

void loop()
{
  
  if(magnitude>0)
  {
    Serial.print("\tMagnitude:");
    Serial.print(magnitude);
    Serial.print("\tDelta X:");
    Serial.print(deltx);
    Serial.print("\tDelta Y:");
    Serial.print(delty);
    Serial.print("\tDelta Z:");
    Serial.println(deltz);
    Serial.println("");     
  }
  
}


ISR(TIMER2_COMPA_vect)                                 // timer compare interrupt service routine
{
  Impact(magnitude,deltx,delty,deltz);
}



void Impact(volatile int &magnitude, volatile int &deltx, volatile int &delty, volatile int &deltz)          // Impact function header
{
  volatile static int vibration;                      // counts program loops since last impact (1 loop / mS)
  volatile static int xaxis,yaxis,zaxis;              // x,y and z axis readings from accelerometer
#define SENSITIVITY 30                                // sensitivity of impact detection

  volatile int oldx=xaxis;                            // local variables store previous axis readings
  volatile int oldy=yaxis;
  volatile int oldz=zaxis;

  xaxis=analogRead(0);                                // new axis readings are taken
  yaxis=analogRead(1);
  zaxis=analogRead(2);
  
  if(vibration<500) magnitude=0;
  vibration--;                                        // loop counter prevents false triggering 
  if(vibration<0) vibration=0;                        // as robot vibrates due to impact
  if(vibration>0) return;                             // until vibration has subsided no further calculations required

  deltx=xaxis-oldx;                                   // delta between old and new axis readings
  delty=yaxis-oldy;
  deltz=zaxis-oldz;
  magnitude=sqrt(sq(deltx)+sq(delty)+sq(deltz));      // magnitude of delta x,y,z using pythagorus

  if (magnitude>SENSITIVITY)                           // has a new impact occured
  {
    vibration=500;                                      // reset loop counter for 500mS
    return;
  }
  else
  {
    magnitude=0;
  }
}

Once this is converted to a library, the "Impact" function will be called once every mS by the timer interrupt. This is all good except that now I seem to need to make my magnitude and axis deltas into global variables so that the main program can access them.

After thinking more about how I want this Library to work, I decided that as the impact function is being called by the timer then it is best to simply have it's results as global variables that can be accessed anytime by any other function.

I've now attempted to create my MicroM.h and MicroM.cpp files based on two tutorials I found on the Arduino website.

/******************************************
 *               MicroM.h                 *
 *                                        *
 *  a library to simplify the use of the  *
 *     DAGU Micro Magician Controller     *
 *                                        *
 *              written by                *
 *            Russell Cameron             *
 ******************************************/

#ifndef MicroM_h
#define MicroM_h

#include "Wprogram.h"

class MicroM
{
public:
  volatile int sensitivity,magnitude,deltx,delty,deltz;

private:
  volatile static int _vibration,_xaxis,_yaxis,_zaxis;
  volatile int _oldx,_oldy,_oldz;

  void _Setup();
  void _Impact(int sensitivity, volatile int &magnitude, volatile int &deltx, volatile int &delty, volatile int) ;
};

#endif /* MicroM_h */





/******************************************
 *              MicroM.cpp                *
 *                                        *
 *  a library to simplify the use of the  *
 *     DAGU Micro Magician Controller     *
 *                                        *
 *              written by                *
 *            Russell Cameron             *
 ******************************************/


#include "Wprogram.h"
#include "MicroM.h"



MicroM::_Setup()
{ 
  noInterrupts();                                      // disable all interrupts
  TCCR2A=0;                                            // initialize timer2
  TCCR2B=0;
  TCNT2 =0;

  OCR2A=31;                                            // compare match register 8MHz/256/1000Hz
  TCCR2B |= (1 << WGM22);                              // CTC mode
  TCCR2B |= (1 << CS22);                               // 256 prescaler
  TIMSK2 |= (1 << OCIE2A);                             // enable timer compare interrupt
  interrupts();                                        // enable all interrupts
  sensitivity=35;				       // sets a default sensitivity
}



MicroM::ISR(TIMER2_COMPA_vect)                         // timer compare interrupt service routine
{
  Impact(sensitivity,magnitude,deltx,delty,deltz);
}



MicroM::_Impact(int sensitivity, volatile int &magnitude, volatile int &deltx, volatile int &delty, volatile int &deltz)
{
  volatile static int _vibration;                     // counts program loops since last impact (1 loop / mS)
  volatile static int _xaxis,_yaxis,_zaxis;           // x,y and z axis readings from accelerometer

  volatile int _oldx=_xaxis;                          // local variables store previous axis readings
  volatile int _oldy=_yaxis;
  volatile int _oldz=_zaxis;

  _xaxis=analogRead(0);                               // new axis readings are taken
  _yaxis=analogRead(1);
  _zaxis=analogRead(2);

  _vibration--;                                       // loop counter prevents false triggering 
  if(_vibration<0) _vibration=0;                      // as robot vibrates due to impact
  if(_vibration>0) return;                            // until vibration has subsided no further calculations required

  deltx=_xaxis-_oldx;                                 // delta between old and new axis readings
  delty=_yaxis-_oldy;
  deltz=_zaxis-_oldz;

  magnitude=sqrt(sq(deltx)+sq(delty)+sq(deltz));        // magnitude of delta x,y,z using pythagorus

  if (magnitude>sensitivity)                          // has a new impact occured
  {
    _vibration=500;                                   // reset loop counter for 500mS
    return;
  }
  else
  {
    magnitude=0;
  }
}


keywords.txt
MicroM  KEYWORD1


As I was having trouble following the tutorials I would apreciate it if you can point out my mistakes. The tutorials I refer to are:

http://arduino.cc/it/Hacking/LibraryTutorial and http://arduino.cc/playground/Code/Library#API

 

AttachmentSize
MicroM.zip2.89 KB

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Interesting project, OddBot.

I agree with mogul about to keep the ISR as short as possible. Have done some measuring with your code and here is the result.

The LED pin has been used for time measurement.

1. The impact function has been modified to set the LED pin to HIGH at the beginning and LOW at the end of the function. The result is 420µs (cursor A-C) for the whole impact function.

2. A LED toggle LOW/HIGH has been placed after the analog readings and before the math functions in the impact function. The analog reading needs 350µs (Cursor A-B) and the math stuff only 70µs (Cursor B-C).

Surprisingly the analog readings eats up most of the time.

 

 

Ah yes, obvious. Analog readings is done by firing a trigger, waiting, and reading the results. Perhaps your ISR function should work like this:

  1. read analog registers
  2. if you have all 3 readings: do math and store in "globals"
  3. fire trigger on next ADC

 

I went back to my earlier method of simply using the interrupt timer to set a flag. This has solved a lot of timing problems according to my digital oscilloscope. For one thing my servos has stopped dancing on the table and is now doing a simple sweep pattern without jitters.

The main reason I was initially trying to avoid global variables is because the library tutorials cited this as good practise. From what I understand of the library creation process I can have a global variable within my library but have it set to private or public as required. This solves a lot of problems for me.

Now that I am using the flag method everything is running smoothly. The actual start/finish times of my impact funtion jitter a bit due to other timer interrupts but this is fine as the impact detection function does not require super precise timing. Read my blog if you want the latest details.

Yes. I understand the problem. The big time hog is the analog read function. With an 8Mz clock this is taking about 800uS just to read the 3 axis's. I have grabbed the digital oscilloscope from work so I can refine the process. The problem is that you must compare reading before and after to determine if an impact occurs. According to the datasheet the ADC should be able to read much faster but it depends on how the Arduino IDE has configured everything.

Ok, I tried cr0c0's code this morning, it all compiles fine but impacts are not detected. I am trying to work out why. I will also test the code posted by Maus and see how it goes.

cr0c0's code worked fine once I realised I had accidentally missed out on the last 5 lines of the cpp file that called the ISR.

 

It is easy for you to test these without an accelerometer. just turn on the pullup resistors for analog inputs 0,1 and 2. By shorting any one of the inputs to ground you will create a sudden change which the code would register as an impact.

I need to study what you guys have done so I can try to understand it better. Quite often you put in lines I don't understand such as:

// Preinstantiate the object
MicroM microM = MicroM();

In cr0c0's code.

Well, the class definition is useless if there is not at least an object of the type defined by the class. In this case two objects of type MicroM would be useless because of the fixed resources A0, A1, A2. So, there should be only one object of type MicroM so I have declared one with the line:

MicroM microM = MicroM();

and I have wrote the timer interrupt to use that object. If you need more objects of type MicroM the interrupt routine must be changed to call them all, or, better, to call a user defined function.

I am glad the code works and is usefull to you.

Thanks cr0c0, I have now tested your library and posted the final product here as an attachment.

Please be patient, originally I learned to program in basic and although I'm fairly good with the Arduino C my code still tends to read like a basic program. I've had no formal training and am entirely self taught.

It is the advanced features of C that I need to learn, classes, structures, pointers, references etc. are all new to me.

Here is a sketch that works on my Arduino Duemilanove but I don't have a sensor to test that part.  Sorry.

You have the idea down with the header (.h) and code (.cpp) files. You just need to translate this code into that format.

 


#define DEBUG // remove this line if you have a sensor.

class MicroM
{
  public:
    MicroM();
    bool impact();
   
    /* Getters/Setters for values in the object */
    int getDeltaX() { return delta_x; };
    int getDeltaY() { return delta_y; };
    int getDeltaZ() { return delta_z; };
    int getCooldownTime() { return cooldown_time; }
    void setCooldownTime(int c) { cooldown_time = c; }
    int getSensitivity() { return sensitivity; }
    void setSensitivity(int s) { sensitivity = s; }
    int getMagnitude() { return magnitude; }
    void flagSensor() { readSensor = 1; };
 
  private:
    bool readSensor;
    int old_x, curr_x, delta_x;
    int old_y, curr_y, delta_y;
    int old_z, curr_z, delta_z;
    int magnitude;
    unsigned long cooldown_until;
    unsigned long cooldown_time;
    int sensitivity;
};

/*
 * Constructor to set up initial values and get our timer going
 */
MicroM::MicroM() { // constructor to set up timer
  readSensor    = 0;
  cooldown_time = 5000;
  sensitivity   = 30;

  noInterrupts();                                      // disable all interrupts
  TCCR2A =0;                                           // initialize timer2
  TCCR2B =0;
  TCNT2  =0;

  OCR2A = 31;                                          // compare match register 8MHz/256/1000Hz
  TCCR2B |= (1 << WGM22);                              // CTC mode
  TCCR2B |= (1 << CS22);                               // 256 prescaler
  TIMSK2 |= (1 << OCIE2A);                             // enable timer compare interrupt
  interrupts();                                        // enable all interrupts
}

/*
 * Impact function that tells us we crashed.
 */
bool MicroM::impact()
{
  if (cooldown_until > millis())
    return 0;

  if (!readSensor)
    return 0;

  readSensor = 0;
 
  old_x = curr_x;
  old_y = curr_y;
  old_z = curr_z;

#ifdef DEBUG 
  curr_x = random(100) - 50; // debug code
  curr_y = random(100) - 50;
  curr_z = random(100) - 50;
#else
  curr_x = analogRead(0); // sensor code
  curr_y = analogRead(1);
  curr_z = analogRead(2);
#endif
 
  delta_x = curr_x - old_x;
  delta_y = curr_y - old_y;
  delta_z = curr_z - old_z;

  magnitude = sqrt(sq(delta_x) + sq(delta_y) + sq(delta_z));
 
#ifdef DEBUG
  Serial.print("DEBUG: magnitude: ");
  Serial.println(magnitude);
#endif

  if (magnitude < sensitivity)
    return 0;
 
  cooldown_until = millis() + cooldown_time;
  return 1;
}


/*
 * MicroM m;
 *
 * This line creates the instance of the object and calls the default constructor (MicroM::MicroM())
 * You do not assign it a value like a regular variable, i.e. MicroM m = MicroM();
 */
MicroM m;
void setup()
{
  Serial.begin(57600);
  Serial.println("READY");
}

void loop()
{
  if (m.impact()) {
    Serial.print("Impact! ");
    Serial.println(m.getDeltaX()); // this is a getter function to grab values from object
  }
}

/*
 * timer get defined here (don't think you can do it as class method)
 * You also have the chance to add different things to the timer as well in case
 * there is additional flags, etc you want to put in there.
 */
ISR(TIMER2_COMPA_vect)                         // timer compare interrupt service routine
{
  m.flagSensor();
}

 

Your efforts have not gone in vain. I will study your implementation when I have time as I need to understand classes better. Hopefully there will be others on LMR who will also benifit from this post.

Thats why I've collected this thread. ; - )