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.
Thanks for the help. I will test it today and let you know how it works. Once I have studied how you wrote the library then I can try and change a few things to improve it. This impact routine is my code equivalent of a crash test dummy.

I have taken your code (hope did not miss something) and made it into a library.

----------------------- MicroM.h file:

#ifndef MicroM_h
#define MicroM_h

#include <inttypes.h>

class MicroM
{
private:
    int vibration, xaxis, yaxis, zaxis;
    int oldx, oldy, oldz;

public:
    MicroM(void);
   
    volatile int sensitivity, magnitude, deltx, delty, deltz;
    void Setup();
    void Impact() ;
};

extern MicroM microM;

#endif

----------------------- MicroM.cpp file:

#include "MicroM.h"

#include "WProgram.h"

// Preinstantiate the object

MicroM microM = MicroM();

// class definitions follow

MicroM::MicroM()
{
    magnitude = 0;
    xaxis = yaxis = zaxis = 0;
}

void MicroM::Setup()
{
    cli();                      // 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
    sei();                      // enable all interrupts
    sensitivity=35;                // sets a default sensitivity
}

void MicroM::Impact()
{
    oldx = xaxis;
    oldy = yaxis;
    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){
        // a new impact occured
        vibration = 500;                                      // reset loop counter for 500mS
    }
    else{
        magnitude = 0;
    }
}

// timer compare interrupt service routine
ISR(TIMER2_COMPA_vect)
{
    microM.Impact();
}

----------------------- keywords.txt file:

# Syntax Coloring Map For MicroM

# Datatypes (KEYWORD1)

# Methods and Functions (KEYWORD2)

# Instances (KEYWORD2)

microM    KEYWORD2

# Constants (LITERAL1)

----------------------- and a test file:

#include <MicroM.h>

void setup()
{
  Serial.begin(57600);
  microM.Setup();
}

void loop()
{
  int mag, dx, dy, dz;
 
  if(microM.magnitude > 0){
    // disable interrupt and get a copy of data
    cli();
    mag = microM.magnitude;
    dx = microM.deltx;
    dy = microM.delty;
    dz = microM.deltz;
    sei();
    // print the data
    Serial.print("\tMagnitude:");
    Serial.print(mag);
    Serial.print("\tDelta X:");
    Serial.print(dx);
    Serial.print("\tDelta Y:");
    Serial.print(dy);
    Serial.print("\tDelta Z:");
    Serial.println(dz);
    Serial.println("");
  }
}

All these compile correctly but I cannot test them having no accelerometer.

However, I would have choosed another route to avoid calling analogRead in the interrupt routine.

Hope these helps.

Heya OddBot!

Good job on learning more about programming, it's been cool to read about the progress!

This was probably already mentioned and you probably already know about this, but check out github.com! You can commit your code there, and each time you push a commit, you can see a diff, which is a comparison between versions so that you can see what lines have changed.

HTH! :D

Thanks for the support. I did teach myself Borland C++ from a book when I was your age. DOS 6.2 was the newest operating system, the school computers only had floppy drives and I had designed an equivalent of an Arduino using a Z80 although it was a lot bigger and heavier. Now I'm trying to catch up a bit. I have had a look at github, so far I'm just more comfortable with LMR.

I recommend you set a flag in your isr to keep the isr as small as possible. You can make a struct called flags if you want to tidy up those global variables (makes sense if you have multiple global flag variables)

That was my first revision. I have decided I want the magnitude and axises as global. My problem now is getting the library to work. Unfortunately the tutorials tend to assume you are an expert C language programmer when it comes to creating the header and cpp files.

As Rouge, I'm afraid that you put too much work in your ISR. But looking at your initial program, why do you at all need to make it interrupt driven?

You could simply let main call your microm.impact with or without parameters, and let it do the math only when needed.

If you go with the parameter less call, your accessor functions could easily return the three deltas and magnitude.

And you could use millis() inside impact() to tell if you are on cool down. Something like this perhaps:

class Mogul
{
  public:
    bool impact();
    int getDeltaX();
  
  private:
    int old_x, delta_x;
    unsigned long cooldown_until;
};

bool Mogul::impact()
{
  if (cooldown_until > millis())
    return 0;
 
  int x = random(100) - 50;
  delta_x = x - old_x;
  old_x = x;
 
  Serial.print("DEBUG: delta x: ");
  Serial.println(delta_x);

  if (delta_x < 40)
    return 0;
   
  cooldown_until = millis() + 5000;
  return 1;
}

int Mogul::getDeltaX()
{
  return delta_x;
}

Mogul m;
void setup()
{
  Serial.begin(57600);
  Serial.println("READY");
}

void loop()
{
  if (m.impact())
    Serial.println(m.getDeltaX());
}

Because we are measuring acceleration and there is also a gravity component. It is critical that samples are taken and compared at regular intervals. Otherwise climbing a hill would register as a continuous impact.

An impact is not a large reading from the sensor, it is a large change in the value over a short space in time. Two other reasons for the interrupt timer are:

  1. I need to monitor the IR receiver at regular intervals.
  2. I need to learn how to use timer interrupts in a library. 

Although there are other IR receiver libraries, some do not work with an 8MHz clock and only timer2 is available. Timer1 is used by the servo library and Timer0 is used by the system for timing functions. By writing a library that combines impact detection and IR receiver monitoring using timer2 I can make sure that all features of the Micro Magician are available to the user simultaneously.

I have modified the code in my second example so that some calulations are not done after initial impact. This will help to reduce the computational overhead and I will attempt to streamline it more in the future.

I still need to create some other functions but once I've had help with this one I hope I can work the rest out myself.

Later tonight I'll try to tidy up the code and post it here,  I'm not on my home computer at the moment.

The quick things I do see are:

  • _Setup() should really be the constructor MicroM() and should be public.
  • _Impact() has access to the variables in the class and would not need to have those passed in.

I'll work with what you posted.

Maus

 


 

Edit:

Using Mogul's code, you could set the timer up like the following:

class Maus
{
  public:
    Maus();
    bool impact();
    int getDeltaX() { return delta_x; };
    void flagSensor() { readSensor = TRUE; };
 
  private:
    bool readSensor;
    int old_x, delta_x;
    unsigned long cooldown_until;
};

Maus::Maus() { // constructor to set up timer
  readSensor = FALSE;

  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
}

bool Maus::impact()
{
  if (cooldown_until > millis())
    return 0;

  if (!readSensor)

    return 0;


  readSensor = FALSE;
 
  int x = random(100) - 50;
  delta_x = x - old_x;
  old_x = x;
 
  Serial.print("DEBUG: delta x: ");
  Serial.println(delta_x);
 
  if (delta_x < 40)
    return 0;
  
  cooldown_until = millis() + 5000;
  return 1;
}


Maus m;
void setup()
{
  Serial.begin(57600);
  Serial.println("READY");
}

void loop()
{
  if (m.impact())
    Serial.println(m.getDeltaX());
}

// timer get defined here (don't think you can do it as class method)

ISR(TIMER2_COMPA_vect)                         // timer compare interrupt service routine
{
  m.flagSensor();
}

I like the time based cooldown and how the impact method is constructed.

Since we are using C++, another possibility would be the creation of a class that would provide all the functionality for the sensor in a complete package.  Below is an untested sample of what I mean:

class AccelSensor
{
    int  pinX;
    int  pinY;
    int  pinZ;
    int  prevX;
    int  prevY;
    int  prevZ;
    int  x;
    int  y;
    int  z;
    int  deltaX;
    int  deltaY;
    int  deltaZ;
    int  sensitivity;
    int  coolDown;

public:
    AccelSensor();
    AccelSensor(int px, int py, int pz);

    void readSensor();
    bool hadImpact();
    int  getMagnitude();

    void setSensitivity(int sens) { sensitivity = sens; }
    int  getSensitivity() { return sensitivity; }

    int getDeltaX() { return deltaX; }
    int getDeltaY() { return deltaY; }
    int getDeltaZ() { return deltaZ; }
};

AccelSensor::AccelSensor()
{
    pinX = 0; // Default pin configuraion
    pinY = 1;
    pinZ = 2;

    sensitivity = 30;

    coolDown = 0;
}

AccelSensor::AccelSensor(int px, int py, int pz)
{
    AccelSensor();
    pinX = px;
    pinY = py;
    pinZ = pz;
}

void AccelSensor::readSensor()
{

    if (coolDown < 1) {
        prevX = x;              // Store previous sensor readings
        prevY = y;
        prevZ = z;

        x = analogRead(pinX);   // Get the new values
        y = analogRead(pinY);
        z = analogRead(pinZ);

        deltaX = x - prevX;     // Compute our axis deltas
        deltaY = y - prevY;
        deltaZ = z - prevZ;

    }
}

int AccelSensor::getMagnitude()
{
    return sqrt(sq(deltaX) + sq(deltaY) + sq(deltaZ));    // magnitude of delta x,y,z using pythagorus
}

bool AccelSensor::hadImpact()
{
    if (getMagnitude() > sensitivity && coolDown < 1)                            // has a new impact occured
    {
        coolDown = 500;                                              // reset loop counter
        return TRUE;                                   // return impact magnitude
    }

    coolDown--;                                                // loop counter prevents false triggering

    if(coolDown < 0)
        coolDown = 0;                                        // as robot vibrates due to impact

    return FALSE;
}

//------------------------------------------------------------------------------

volatile byte interruptflag;

AccelSensor sensor(0, 1, 2);

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 (interruptflag == 1)
    {
        interruptflag=0;

        sensor.readSensor();

        if (sensor.hadImpact()) {
            if (sensor.getMagnitude() > 0) {
                Serial.print("\tMagnitude:");
                Serial.print(sensor.getMagnitude());
                Serial.print("\tDelta X:");
                Serial.print(sensor.getDeltaX());
                Serial.print("\tDelta Y:");
                Serial.print(sensor.getDeltaY());
                Serial.print("\tDelta Z:");
                Serial.println(sensor.getDeltaZ());
                Serial.println("");
            }
        }
    }
}


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

So here we see the following:

  1. In the main loop, all we have to do is reference the sensor and call methods on that to find our values or determine if we've had an impact or not.  The code would then be a little easier for new programmers to read and use.  Another added benefit is that we can have multiple sensors as well by changing the pins.
  2. With a class, you have "constructors" which set up values for your class.  In our case we have a default one that does not take any parameters and uses the pins you defined.  We also have a constructor that accepts pin assignments which I used for an example when I created the object.
  3. Quick points on the methods:
  • readSensor() - This contains the code to keep the previous readings, read the current values of the sensor pins and calculate your deltas.  We only want to do this after our cooldown period.
  • getMagnitude() - contains the calculation for our impact magnitude.  This is just one of the "get" methods to grab the needed values out of the class.  We also have accessors set up for the deltas as well since we display those to the serial port.
  • hadImpact() - we use this method in conditionals to interrogate if we had an impact greater than our sensitivity and perform our cooldown.  I was going back and forth on whether to put the cooldown code here or in the readSensor() method.  Discussion can open up on where.

I'm not saying this is the best method, but just another way to skin the cat.  The advantage of using C++ classes is that you can bundle the data and logic together and not have to worry about "global" variables as such.  The sensor would be a self contained unit in the global context.

You'll still need one for the interrupt since that can be set outside the loop();  We could create a method to "set" an interrupt flag in the class and then change our logic appropriately to read the sensor when it is set.

As far as creating a library, the class code (the part above the dashed comment line) would be broken out into a header file and a cpp code file for the user to include into their project.

Maus