Zumo LSM303 Problem (SOLVED)

Zumo is working fine with Leonardo. I decided to investigate the LSM303 section of the shield. I first tried Calibrate. Viewing at Serial Terminal, I see this:

M min X; -389 Y; 82 Z; -4096 M max X; -337 Y; 150 Z; -2048

This seems wrong, for Z never changes, either Minimum or Maximum. I turn Zumo in another direction and do Caliberate again, same Z readings. And it seems the X and Y readings are not very responsive either. But, do see some change, but not very repeatable. Heading and Serial examples do compile and load, but answers would be wrong unless Calibrate first works. Do I have a bad chip? Or maybe Leonardo is not compatible with the 3 example sketches.

Hello.

I recently updated section 3.d of the Zumo shield user’s guide to include the following note:

Basically, you should not count on using the z axis of the Zumo’s magnetometer. You might try playing around with the sensitivity some to see if you can get more out of the x and y readings. I recommend you try just streaming the magnetometer readings to the serial monitor and look at them as you move the Zumo around.

I have some sample code around from when I was playing with the Zumo compass. We will be making a compass example sketch soon, but I’ll see if I can find my code and post it in the meantime.

- Ben

Ben, Thanks for the info about the LSM303. I really didn’t think chip was bad. Lots to know and investigate, just time to see what is important and how to use it. So after Calibration, I should not insert the Z info into the Heading Sketch, just X and Y, leaving that spot blank? I’ll also reduce the mag sensitvity.

I’m not sure off-hand how well the Heading sketch will work without z info. Maybe you should just try playing around with the program I wrote to test the compass. I just went through and commented it some, so I hope it will be easier to follow. When you push the user button, the Zumo will beep a few times, then spin in place for two seconds while calibrating the compass. After that, the main loop has it turn to face the four cardinal directions in order (pausing for a second after each one), turning the user LED on whenever it is trying to point north. It sets the compass to the highest sensitivity but ignores the z axis.

Some caveats:

  1. I wrote this before the Zumo Arduino libraries were released, so I made my own functions for setting the motor speeds and controlling the buzzer. Feel free to replace those with library functions. (I’m pretty sure my simple buzzer code will need to be modified if you are using a Leonardo.)
  2. I wrote this for a Zumo that was assembled with (I believe) 30:1 HP or 50:1 HP micro metal gearmotors. If your Zumo has different motors than mine, you might need to modify the speeds and/or PID parameters used in my code. For example, if the robot doesn’t rotate well (or at all) during the calibration phase, you should try changing the setMotors(150, -150) line of compassCalibrate() to something like setMotors(200, -200), and you will likely have to tweak the turnToHeading() function as well.
  3. I cleaned it up in a text editor and did not try to compile it after. If it doesn’t compile, that means I made some typos while cleaning it up!
  4. You need to have our LSM303 Arduino library installed to run this sketch.

Please let me know how it works for you, and don’t hesitate to ask if you have any questions about it. I’d like to use parts of it as a basis for a Zumo compass example down the road.

#include <Wire.h>
#include <LSM303.h>

#define CRA_REG_M          0x00    // magnetometer control register location
#define CRA_REG_M_75HZ     0x18    // reg value for 75 Hz update rate for magnetometer (default is 15 Hz)
#define CRA_REG_M_220HZ    0x1C    // reg value 220 Hz update rate for magnetometer


const unsigned char MAX_SPEED = 255;

const unsigned char M1PWM = 9;
const unsigned char M1DIR = 7;
const unsigned char M2PWM = 10;
const unsigned char M2DIR = 8;
const unsigned char BUTTON = 12;
const unsigned char BUZZER = 11;
const unsigned char LED = 13;

LSM303 compass;

// variables used for compass calibration
int xmax[10] = {-4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097};
int xmin[10] = {4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097};
int ymax[10] = {-4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097};
int ymin[10] = {4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097};
int xminavg = 0;
int xmaxavg = 0;
int yminavg = 0;
int ymaxavg = 0;


// the setup routine runs once when you press reset:
void setup() {                
  pinMode(LED, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(M1PWM, OUTPUT);
  pinMode(M1DIR, OUTPUT);
  pinMode(M2PWM, OUTPUT);
  pinMode(M2DIR, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  
  Serial.begin(9600);
  Wire.begin();
  compass.init();
  compass.enableDefault();
  compass.setMagGain(compass.magGain_13);    // highest sensitivity: +/- 1.3 gauss range (this is the default)
  compass.writeMagReg(CRA_REG_M, CRA_REG_M_220HZ);  // 220 Hz compass update rate (default is 15 Hz)
  
  delay(5);
  // wait for a debounced button press
  while (digitalRead(BUTTON))  // loop while button is not pressed
  {
    while (digitalRead(BUTTON));  // loop while button is not pressed
    delay(10);  // debounce
  }

  // make the buzzer beep a few times
  delay(500);
  analogWrite(BUZZER, 128);
  delay(100);
  analogWrite(BUZZER, 0);
  delay(250);
  analogWrite(BUZZER, 128);
  delay(100);
  analogWrite(BUZZER, 0);
  delay(250);
  analogWrite(BUZZER, 128);
  delay(250);
  analogWrite(BUZZER, 0);
  delay(250);
  
  compassCalibrate();  // makes Zumo spin in place while calibrating the compass
  delay(100);
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(LED, HIGH);  // turn LED on to signal north
  turnToHeading(0);  // turn to face north
  delay(1000);
  digitalWrite(LED, LOW);  // turn LED off
  turnToHeading(90);  // turn to face east
  delay(1000);
  turnToHeading(180);  // turn to face south
  delay(1000);
  turnToHeading(270);  // turn to face west
  delay(1000);
}


// turns the Zumo to face the requested magnetic heading
void turnToHeading(int angle)
{
  // make sure angle is a number from 0 to 360
  while (angle < 0)
    angle += 360;
  while (angle >= 360)
    angle -= 360;
    
  int currentAngle;
  long diffSum = 0;
  int lastDiff = 1000;  // dummy value outside allowed -180 to 180 range indicates uninitialized
  unsigned char count = 0;
  
  while (1)
  {
    currentAngle = getHeading();
    int diff = currentAngle - angle;
    if (diff < 0)
      diff += 360;
    if (diff > 180)
      diff -= 360;
    
    if (lastDiff == 1000);
      lastDiff = diff;
      
    if (diff < 10 && diff > -10)
    {
      // if we are within 10 degrees of our target heading
      count++;
      if (count > 6)
      {
        // if we have been within 10 degrees of our target for 6 consecutive readings
        //  we have reached our target; stop motors and return
        setMotors(0, 0);
        return;
      }
    }
    else
      count = 0;

    // use PID on the heading error (diff) to determine what the motor speed should be
    // proportional constant is 5/2, derivative constant is 30, integral constant is 1/7   
    int motor_speed = diff*5/2 + (diff - lastDiff)*30 + diffSum/7;
    setMotors(-motor_speed, motor_speed);  // update speed of motors

    lastDiff = diff;
    
    // only include small errors in error sum (integral term only grows when Zumo is close to its target)
    if (diff > -40  && diff < 40)
      diffSum += diff;
    
    // cap the magnitude of the error sum at 100,000
    if (diffSum > 100000)
      diffSum == 100000;
    if (diffSum < -100000)
      diffSum = -100000;
    
    /*
    // the following code is an alternate approach that doesn't use PID
    //  it doesn't work as well, but it's easier to understand
    if (diff < -40)
      setMotors(150, -150);
    else if (diff < -30)
      setMotors(100, -100);
    else if (diff < -20)
      setMotors(80, -80);
    if (diff > 40)
      setMotors(-150, 150);
    else if (diff > 30)
      setMotors(-100, 100);
    else if (diff > 20)
      setMotors(-80, 80);
    */
    
    delay(5);
  }
}


// inserts value into array at the appropriate location and reorders the array
// elements so that "array" always contains the largest values and is ordered
// from smallest to largest.  Examples:
//  if array is {3, 5, 7} before checkMax and value is 2, array is {3, 5, 7} after
//  if array is {3, 5, 7} before checkMax and value is 4, array is {4, 5, 7} after
//  if array is {3, 5, 7} before checkMax and value is 6, array is {5, 6, 7} after
//  if array is {3, 5, 7} before checkMax and value is 9, array is {5, 7, 9} after
// This function makes it easy to fill an array with the largest n numbers from a set
// of data.
// Note: array must already be sorted from smallest to largest or else this function
// will not work.  It is fine if all elements have the same value.
void checkMax(int value, int *array, unsigned int arraySize)
{
  if (value <= array[0])
    return;
  unsigned int i = 0;
  while (value > array[i])
  {
    if (++i >= arraySize)
      break; 
  }
  unsigned int c;
  for (c = 1; c < i; c++)
    array[c-1] = array`;
  array[i-1] = value;
}


// inserts value into array at the appropriate location and reorders the array
// elements so that "array" always contains the smallest values and is ordered
// from largest to smallest.  Examples:
//  if array is {7, 5, 3} before checkMax and value is 2, array is {5, 3, 2} after
//  if array is {7, 5, 3} before checkMax and value is 4, array is {5, 4, 3} after
//  if array is {7, 5, 3} before checkMax and value is 6, array is {6, 5, 3} after
//  if array is {7, 5, 3} before checkMax and value is 9, array is {7, 5, 3} after
// This function makes it easy to fill an array with the largest n numbers from a set
// of data.
// Note: array must already be sorted from largest to smallest or else this function
// will not work.  It is fine if all elements have the same value.
void checkMin(int value, int *array, unsigned int arraySize)
{
  if (value >= array[0])
    return;
  unsigned int i = 0;
  while (value < array[i])
  {
    if (++i >= arraySize)
      break; 
  }
  unsigned int c;
  for (c = 1; c < i; c++)
    array[c-1] = array`;
  array[i-1] = value;
}


// Spins the Zumo around in place for two seconds while reading the magnetometer x and y values
// every ten milliseconds.  The ten highest and ten lowest values for the x and y axes are 
// stored in respective max and min arrays.  Once the Zumo stops, the middle six values from
// each array are averaged to produce final calibration coefficients.
// (This level of calibration is probably overkill.)
void compassCalibrate()
{
  setMotors(150, -150);  // make the Zumo spin (might need to adjust speeds for your motors)
  
  delay(200);  // give it time to start moving before we begin reading the magnetometer
  
  unsigned int i;
  for (i = 0; i < 180; i++)  // takes 180 readings over 1.8 seconds
  {
    compass.read();
    checkMax((int)compass.m.x, xmax, 10);
    checkMin((int)compass.m.x, xmin, 10);
    checkMax((int)compass.m.y, ymax, 10);
    checkMin((int)compass.m.y, ymin, 10);
    delay(10);
  }

  setMotors(0, 0);
  
  // simple approach to reject potential outliers: only average the middle array values
  for (i = 2; i < 8; i++)
  {
     xminavg += xmin[i];
     xmaxavg += xmax[i];
     yminavg += ymin[i];
     ymaxavg += ymax[i];
  }
  
  xminavg /= 6;  // typical minimum reading we can expect from the x axis of the magnetometer
  xmaxavg /= 6;  // typical maximum reading we can expect from the x axis of the magnetometer
  yminavg /= 6;  // typical minimum reading we can expect from the y axis of the magnetometer
  ymaxavg /= 6;  // typical maximum reading we can expect from the y axis of the magnetometer
}


// Gets x and y readings from the magnetometer, scales them based on calibration coefficients,
// and computes a heading angle in degrees from them.  z magnetometer axis is ignored.
// The returned angle is always a number from 0 to 360.
int getHeading()
{
  compass.read();
  int x = (int)compass.m.x;
  int y = (int)compass.m.y;
  float xScaled =  2.0*(float)(x - xminavg) / (xmaxavg - xminavg) - 1.0;
  float yScaled =  2.0*(float)(y - yminavg) / (ymaxavg - yminavg) - 1.0;
  
  int angle = round(atan2(yScaled, xScaled)*180 / M_PI);
  if (angle < 0)
    angle += 360;
  return angle;
}


// Sets the Zumo motor speeds.  (I wrote this before the Zumo Arduino libraries were released,
// so I made my own basic function for interfacing with the motor drivers.)
void setMotors(int m2Speed, int m1Speed)
{
  if (m1Speed > 255)
    m1Speed = 255;
  if (m1Speed < -255)
    m1Speed = -255;
    
  if (m1Speed <= 0)
  {
    digitalWrite(M1DIR, HIGH);
    analogWrite(M1PWM, -m1Speed);
  }
  else
  {
    digitalWrite(M1DIR, LOW);
    analogWrite(M1PWM, m1Speed);
  }
  
  if (m2Speed > 255)
    m2Speed = 255;
  if (m2Speed < -255)
    m2Speed = -255;
    
  if (m2Speed <= 0)
  {
    digitalWrite(M2DIR, HIGH);
    analogWrite(M2PWM, -m2Speed);
  }
  else
  {
    digitalWrite(M2DIR, LOW);
    analogWrite(M2PWM, m2Speed);
  }
}

- Ben

Hi Ben,
Got on to other things, then caught the flu. Not fair ar all!

OK here is where I am.
Using Windows 7 Starter on Netbook. Arduin0 1.0.1 IDE is there.
Using Leonardo with Zumo. all works fine with example code. Added Sharp GP distance and 2 QTR-1RC.
Now, on to your code about compass, etc, etc. contained in the1st reply.
Had to change a few details.

#include <Zumomotors.h>
#include <ZumoBuzzer.h>
#include <LSM303.h>

Changed BUZZER pin from 11 to 6
in setup()

Used: Compass.setMagGain(Compass.MagGain_13)
Then: Went to 25
No visable change with either value., later.
Compile and Load went OK.
Left USB connected to Zumo. Turned it on. Went to Serial Monitor. Hit User button. Heard Buzzer beep twice and then one longer beep. Zumo began to spin CCW, about twice. But, then it got caught in a very stacato left and right movement, about 5 degrees each direction. I never saw any data on Serial Monitor. Was set at 9600. Had to shut down or Zumo would shake it’s little heart out :smiley:

I would like to leap over the ocean breaker! Not to try this or try that, and wouldn’t do anything. I say this for I don’t want the flu to come back. If so, I might quit. Still, you Pololu guys are the best. The 3Pi was my start, and I’m sure you helped me then.

Imagine, a COMPASS inside of a tiny speck of Silicon!!!

Don

Sorry for my delayed reply. My sample program doesn’t print anything to the serial monitor, which is why you didn’t see anything; you could add some print statements in there for debugging.

Basically, it is trying to point north, and it keeps trying until it gets close enough for long enough. If you are using different motors than I did, you probably need to adjust the algorithm constants some so that it actually settles down enough. Alternatively, you could try commenting out the PID algorithm and uncommenting the simpler code below it.

- Ben

Ben, I guess I’m lost on what to try next. I commented out the PID routine and used the the simpler one. No real change. Spins a few times and then goes into the “shake” mode. Tried changing motor speed from 150, -150 to 100, -100, etc for 50. Just spins slower. I’m using the HD 75:1 motors. I show include# ZumoMotors.h, ZumoBuzzer.h, Wire.h and LSM303.h
I commented out ZumoMotors.h using your motor library. No change. By the way, buzzer pin change works fine, for using Leonardo.
Maybe Leonardo has some other differences keeping the LSM303 from working fully?

I think I’ll just wait till more Compass code comes out from Pololu. Don’t think I’m smart enough to troubleshoot the LSM device with Zumo. Looked at the data sheet, a lot to understand. Anyway, was wondering why Pololu incorporated it in Zumo, for you mentioned buzzer noise and motor interference and magnetic fields can really reduce performance. I was thinking if the chip was mounted on a break out and then on a 2-4" standoff above the the Arduino board, this would help? Sure hope you guys will continue to help with LSM side of Zumo. But, got a feeling it’s up to the users.
Thanks

We definitely will release some sample stuff for the LSM303. That sensor isn’t intended as an integral part of the Zumo, just as a way to add to its versatility and give advanced users more tools for making their Zumo robots different or more capable.

The main benefit of that sensor is probably the accelerometer part, since you could conceivably use that in sumo competitions to tell when (and maybe from which side) you have been hit by the opponent. The compass is less effective due to magnetic interference, but it can still be useful, especially since it’s difficult to make tracked chassis like the Zumo turn in place reliably without some sort of feedback. The compass would probably work better if mounted on a mast well above the PCB; we sell a carrier board for the compass if that’s something you’d want to try.

I suggest you try changing this part of the code:

if (diff < 10 && diff > -10)
{
  // if we are within 10 degrees of our target heading
  count++;
  if (count > 6)
  {
    // if we have been within 10 degrees of our target for 6 consecutive readings
    //  we have reached our target; stop motors and return
    setMotors(0, 0);
    return;
  }
}

That is the code that decides when the Zumo is close enough to the desired heading. It sounds like this is just too strict for your particular setup, and instead of settling down, the Zumo is just bouncing back and forth around the target heading. What happens if you weaken the restrictions to make it “if (count > 3)”? If that doesn’t work, what happens if you take out the count stuff altogether and make it:

if (diff < 10 && diff > -10)
{
  // if we are within 10 degrees of our target heading
  //  we have reached our target; stop motors and return
  setMotors(0, 0);
  return;
}

- Ben

Ben,
Did all your last suggestions, right to removing count stuff. No joy.

Spins a couple times CCW, then goes into the left-right shake, on and on, till I turn it off.
Don

Does it make a difference if you put it in a different spot (e.g. outside on the driveway)? Is it at least approximately facing north while shaking?

- Ben

NO! Never thought about it, but Zumo always starts shaking pointing South, inside or outside on driveway.
First, it spins 2 couple of times CCW.

Here is code as of now.

// #include <ZumoMotors.h> (using your motor code)
#include <ZumoBuzzer.h>
#include <Wire.h>
#include <LSM303.h>

Buzzer = 6 instead of 11

compass.setMagGain(compass.magGain_25); was 13

All commented out between

if (diff <10 && diff > -10)

but not

setMotors(0, 0);
return;

and commented out

else
count = 0;

Ready to try something else!

Can you post the full program as you have it now?

The initial spinning around is just the robot calibrating the compass. After that, it should turn on the yellow LED and try to face north. Once it is satisfied that it is pointing north, it should try to point east, then south, then west, etc. Every time it is trying to point north, it will turn on the yellow LED. Do you see the yellow LED on while it is shaking? Also, can you try leaving the mag gain at 1.3 (the way I have it in my code)?

- Ben

Will do, but first, should this statement read as below?

compass.setMagGain(compass.magGain_13);

Or this way?

compass.setMagGain(compass.magGain_1.3);

After the CCW spin, Zumo points South (shovel forward), orange LED comes on solid, and starts the +/- degree shake, always, in about the South direction.

OK, here it is;

//#include <ZumoMotors.h>
#include <ZumoBuzzer.h>
#include <Wire.h>
#include <LSM303.h>

#define CRA_REG_M          0x00    // magnetometer control register location
#define CRA_REG_M_75HZ     0x18    // reg value for 75 Hz update rate for magnetometer (default is 15 Hz)
#define CRA_REG_M_220HZ    0x1C    // reg value 220 Hz update rate for magnetometer


const unsigned char MAX_SPEED = 255;

const unsigned char M1PWM = 9;
const unsigned char M1DIR = 7;
const unsigned char M2PWM = 10;
const unsigned char M2DIR = 8;
const unsigned char BUTTON = 12;
const unsigned char BUZZER = 6;  // 11
const unsigned char LED = 13;

LSM303 compass;

// variables used for compass calibration
int xmax[10] = {-4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097};
int xmin[10] = {4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097};
int ymax[10] = {-4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097, -4097};
int ymin[10] = {4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097, 4097};
int xminavg = 0;
int xmaxavg = 0;
int yminavg = 0;
int ymaxavg = 0;


// the setup routine runs once when you press reset:
void setup() {                
  pinMode(LED, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(M1PWM, OUTPUT);
  pinMode(M1DIR, OUTPUT);
  pinMode(M2PWM, OUTPUT);
  pinMode(M2DIR, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  
  //Serial.begin(9600);
  Wire.begin();
  compass.init();
  compass.enableDefault();
  compass.setMagGain(compass.magGain_13);    // highest sensitivity: +/- 1.3 gauss range (this is the default)
  compass.writeMagReg(CRA_REG_M, CRA_REG_M_220HZ);  // 220 Hz compass update rate (default is 15 Hz)
  
  delay(5);
  // wait for a debounced button press
  while (digitalRead(BUTTON))  // loop while button is not pressed
  {
    while (digitalRead(BUTTON));  // loop while button is not pressed
    delay(10);  // debounce
  }

  // make the buzzer beep a few times
  delay(500);
  analogWrite(BUZZER, 128);
  delay(100);
  analogWrite(BUZZER, 0);
  delay(250);
  analogWrite(BUZZER, 128);
  delay(100);
  analogWrite(BUZZER, 0);
  delay(250);
  analogWrite(BUZZER, 128);
  delay(250);
  analogWrite(BUZZER, 0);
  delay(250);
  
  compassCalibrate();  // makes Zumo spin in place while calibrating the compass
  delay(100);
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(LED, HIGH);  // turn LED on to signal north
  turnToHeading(0);  // turn to face north
  delay(1000);
  digitalWrite(LED, LOW);  // turn LED off
  turnToHeading(90);  // turn to face east
  delay(1000);
  turnToHeading(180);  // turn to face south
  delay(1000);
  turnToHeading(270);  // turn to face west
  delay(1000);
}


// turns the Zumo to face the requested magnetic heading
void turnToHeading(int angle)
{
  // make sure angle is a number from 0 to 360
  while (angle < 0)
    angle += 360;
  while (angle >= 360)
    angle -= 360;
    
  int currentAngle;
  long diffSum = 0;
  int lastDiff = 1000;  // dummy value outside allowed -180 to 180 range indicates uninitialized
  unsigned char count = 0;
  
  while (1)
  {
    currentAngle = getHeading();
    int diff = currentAngle - angle;
    if (diff < 0)
      diff += 360;
    if (diff > 180)
      diff -= 360;
    
    if (lastDiff == 1000);
      lastDiff = diff;
      
    if (diff < 10 && diff > -10)
    {
      // if we are within 10 degrees of our target heading
    //  count++;
    //  if (count > 6)
      {
        // if we have been within 10 degrees of our target for 6 consecutive readings
        //  we have reached our target; stop motors and return
        setMotors(0, 0);
        return;
      }
   }
 //   else
  //    count = 0;

    // use PID on the heading error (diff) to determine what the motor speed should be
    // proportional constant is 5/2, derivative constant is 30, integral constant is 1/7   
    int motor_speed = diff*5/2 + (diff - lastDiff)*30 + diffSum/7;
    setMotors(-motor_speed, motor_speed);  // update speed of motors

    lastDiff = diff;
    
    // only include large errors in error sum
    //  (I'm not sure anymore why I did this, and it seems like a potentially bad idea)
    if (diff < 40 && diff > -40)
      diffSum += diff;
    
    // cap the magnitude of the error sum at 100,000
    if (diffSum > 100000)
      diffSum == 100000;
    if (diffSum < -100000)
      diffSum = -100000;
    
    /*
    // the following code is an alternate approach that doesn't use PID
    //  it doesn't work as well, but it's easier to understand
    if (diff < -40)
      setMotors(150, -150);
    else if (diff < -30)
      setMotors(100, -100);
    else if (diff < -20)
      setMotors(80, -80);
    if (diff > 40)
      setMotors(-150, 150);
    else if (diff > 30)
      setMotors(-100, 100);
    else if (diff > 20)
      setMotors(-80, 80);
    */
    
    delay(5);
  }
}


// inserts value into array at the appropriate location and reorders the array
// elements so that "array" always contains the largest values and is ordered
// from smallest to largest.  Examples:
//  if array is {3, 5, 7} before checkMax and value is 2, array is {3, 5, 7} after
//  if array is {3, 5, 7} before checkMax and value is 4, array is {4, 5, 7} after
//  if array is {3, 5, 7} before checkMax and value is 6, array is {5, 6, 7} after
//  if array is {3, 5, 7} before checkMax and value is 9, array is {5, 7, 9} after
// This function makes it easy to fill an array with the largest n numbers from a set
// of data.
// Note: array must already be sorted from smallest to largest or else this function
// will not work.  It is fine if all elements have the same value.
void checkMax(int value, int *array, unsigned int arraySize)
{
  if (value <= array[0])
    return;
  unsigned int i = 0;
  while (value > array[i])
  {
    if (++i >= arraySize)
      break; 
  }
  unsigned int c;
  for (c = 1; c < i; c++)
    array[c-1] = array`;
  array[i-1] = value;
}


// inserts value into array at the appropriate location and reorders the array
// elements so that "array" always contains the smallest values and is ordered
// from largest to smallest.  Examples:
//  if array is {7, 5, 3} before checkMax and value is 2, array is {5, 3, 2} after
//  if array is {7, 5, 3} before checkMax and value is 4, array is {5, 4, 3} after
//  if array is {7, 5, 3} before checkMax and value is 6, array is {6, 5, 3} after
//  if array is {7, 5, 3} before checkMax and value is 9, array is {7, 5, 3} after
// This function makes it easy to fill an array with the largest n numbers from a set
// of data.
// Note: array must already be sorted from largest to smallest or else this function
// will not work.  It is fine if all elements have the same value.
void checkMin(int value, int *array, unsigned int arraySize)
{
  if (value >= array[0])
    return;
  unsigned int i = 0;
  while (value < array[i])
  {
    if (++i >= arraySize)
      break; 
  }
  unsigned int c;
  for (c = 1; c < i; c++)
    array[c-1] = array`;
  array[i-1] = value;
}


// Spins the Zumo around in place for two seconds while reading the magnetometer x and y values
// every ten milliseconds.  The ten highest and ten lowest values for the x and y axes are 
// stored in respective max and min arrays.  Once the Zumo stops, the middle six values from
// each array are averaged to produce final calibration coefficients.
// (This level of calibration is probably overkill.)
void compassCalibrate()
{
  setMotors(150, -150);  // make the Zumo spin (might need to adjust speeds for your motors)
  
  delay(200);  // give it time to start moving before we begin reading the magnetometer
  
  unsigned int i;
  for (i = 0; i < 180; i++)  // takes 180 readings over 1.8 seconds
  {
    compass.read();
    checkMax((int)compass.m.x, xmax, 10);
    checkMin((int)compass.m.x, xmin, 10);
    checkMax((int)compass.m.y, ymax, 10);
    checkMin((int)compass.m.y, ymin, 10);
    delay(10);
  }

  setMotors(0, 0);
  
  // simple approach to reject potential outliers: only average the middle array values
  for (i = 2; i < 8; i++)
  {
     xminavg += xmin[i];
     xmaxavg += xmax[i];
     yminavg += ymin[i];
     ymaxavg += ymax[i];
  }
  
  xminavg /= 6;  // typical minimum reading we can expect from the x axis of the magnetometer
  xmaxavg /= 6;  // typical maximum reading we can expect from the x axis of the magnetometer
  yminavg /= 6;  // typical minimum reading we can expect from the y axis of the magnetometer
  ymaxavg /= 6;  // typical maximum reading we can expect from the y axis of the magnetometer
}


// Gets x and y readings from the magnetometer, scales them based on calibration coefficients,
// and computes a heading angle in degrees from them.  z magnetometer axis is ignored.
// The returned angle is always a number from 0 to 360.
int getHeading()
{
  compass.read();
  int x = (int)compass.m.x;
  int y = (int)compass.m.y;
  float xScaled =  2.0*(float)(x - xminavg) / (xmaxavg - xminavg) - 1.0;
  float yScaled =  2.0*(float)(y - yminavg) / (ymaxavg - yminavg) - 1.0;
  
  int angle = round(atan2(yScaled, xScaled)*180 / M_PI);
  if (angle < 0)
    angle += 360;
  return angle;
}


// Sets the Zumo motor speeds.  (I wrote this before the Zumo Arduino libraries were released,
// so I made my own basic function for interfacing with the motor drivers.)
void setMotors(int m2Speed, int m1Speed)
{
  if (m1Speed > 255)
    m1Speed = 255;
  if (m1Speed < -255)
    m1Speed = -255;
    
  if (m1Speed <= 0)
  {
    digitalWrite(M1DIR, LOW);
    analogWrite(M1PWM, -m1Speed);
  }
  else
  {
    digitalWrite(M1DIR, HIGH);
    analogWrite(M1PWM, m1Speed);
  }
  
  if (m2Speed > 255)
    m2Speed = 255;
  if (m2Speed < -255)
    m2Speed = -255;
    
  if (m2Speed <= 0)
  {
    digitalWrite(M2DIR, LOW);
    analogWrite(M2PWM, -m2Speed);
  }
  else
  {
    digitalWrite(M2DIR, HIGH);
    analogWrite(M2PWM, m2Speed);
  }
}

I was having trouble figuring out why my code was making your robot point south when it is supposed to be pointing north, and it suddenly occurred to me that maybe you have your motors soldered in differently from how mine were soldered in, which would make my code not work.

Can you run the following code and tell me exactly what your Zumo does?

#include <Wire.h>
#include <LSM303.h>

const unsigned char M1PWM = 9;
const unsigned char M1DIR = 7;
const unsigned char M2PWM = 10;
const unsigned char M2DIR = 8;
const unsigned char BUTTON = 12;


// the setup routine runs once when you press reset:
void setup() {                
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(M1PWM, OUTPUT);
  pinMode(M1DIR, OUTPUT);
  pinMode(M2PWM, OUTPUT);
  pinMode(M2DIR, OUTPUT);
  
  delay(5);
  // wait for a debounced button press
  while (digitalRead(BUTTON))  // loop while button is not pressed
  {
    while (digitalRead(BUTTON));  // loop while button is not pressed
    delay(10);  // debounce
  }

  delay(1000);
  
  setMotors(150, 150);  // drive forward
  delay(500);
  
  setMotors(0, 0);  // stop
  delay(1000);

  setMotors(-150, -150);  //drive backwards
  delay(500);
  
  setMotors(0, 0);  // stop
}

// the loop routine runs over and over again forever:
void loop() {
  
}


// Sets the Zumo motor speeds.  (I wrote this before the Zumo Arduino libraries were released,
// so I made my own basic function for interfacing with the motor drivers.)
void setMotors(int m2Speed, int m1Speed)
{
  if (m1Speed > 255)
    m1Speed = 255;
  if (m1Speed < -255)
    m1Speed = -255;
    
  if (m1Speed <= 0)
  {
    digitalWrite(M1DIR, LOW);
    analogWrite(M1PWM, -m1Speed);
  }
  else
  {
    digitalWrite(M1DIR, HIGH);
    analogWrite(M1PWM, m1Speed);
  }
  
  if (m2Speed > 255)
    m2Speed = 255;
  if (m2Speed < -255)
    m2Speed = -255;
    
  if (m2Speed <= 0)
  {
    digitalWrite(M2DIR, LOW);
    analogWrite(M2PWM, -m2Speed);
  }
  else
  {
    digitalWrite(M2DIR, HIGH);
    analogWrite(M2PWM, m2Speed);
  }
}

When you push the user button, the Zumo should drive forward for half a second, stop for one second, and they drive backwards for half a second. Is that what you see?

- Ben

Well Ben!

Doing what you say,
Zumo goes backward for half second, stops for 1 second, goes forward for half second and stops. Just the opposite. Did test 3 times.

The Zumo sample code just to get started has always worked fine. The sensors I added, no problem. Zumo always starts moving forward. I never applied the flip code te reverse the motor directions. Again, I’m always using Leonardo.

Don

I probably put my Zumo together before we wrote the assembly instructions and came up with a convention for the motor orientation. To correct for this, you can either replace my setMotors() function with the ZumoMotors library equivalent, which you say works properly for your Zumo, or you can replace it your code with the following corrected version:

// Sets the Zumo motor speeds.  (I wrote this before the Zumo Arduino libraries were released,
// so I made my own basic function for interfacing with the motor drivers.)
void setMotors(int m2Speed, int m1Speed)
{
  if (m1Speed > 255)
    m1Speed = 255;
  if (m1Speed < -255)
    m1Speed = -255;
    
  if (m1Speed <= 0)
  {
    digitalWrite(M1DIR, HIGH);
    analogWrite(M1PWM, -m1Speed);
  }
  else
  {
    digitalWrite(M1DIR, LOW);
    analogWrite(M1PWM, m1Speed);
  }
  
  if (m2Speed > 255)
    m2Speed = 255;
  if (m2Speed < -255)
    m2Speed = -255;
    
  if (m2Speed <= 0)
  {
    digitalWrite(M2DIR, HIGH);
    analogWrite(M2PWM, -m2Speed);
  }
  else
  {
    digitalWrite(M2DIR, LOW);
    analogWrite(M2PWM, m2Speed);
  }
}

Please first try it with that simple test program that makes the Zumo first drive forward and then backward. If that works right, then try it in my compass example (you can probably restore it to its original form by uncommenting those lines we commented out earlier when trying to debug this). Hopefully it will just work after this change!

- Ben

Hi Ben,

Test program now works! Goes forward half second, stops, goes backward half second, stops.

Onward ro Mr. Compass!

Don

Hi Ben,

It works! I see Zumo spinning CW to calibrate, then pointing North with orange LED on, then LED off and turning East, then South, then back to North and LED on. I see a bit of correction at NESW, but not all the time. Mostly accurate at each Cardinal point.
No more shaking! Beautiful :smiley:

Any concern here:

// only include large errors in error sum
    //  (I'm not sure anymore why I did this, and it seems like a potentially bad idea)
    if (diff < 40 && diff > -40)
      diffSum += diff;

Ben, Thank You for your help!
Don

That’s great! I edited the code in my original post so that it should work for any properly-assembled Zumo. I’m sorry it took so long for me to figure out what was going wrong.

Regarding that code you quoted, I just remember why I added it. I think it helps, but you can try commenting it out and see if the behavior changes significantly.

- Ben