Controlling stepper motor with ITG3200 gyroscope

Search didn’t turn anything useful up for me. Sparkfun’s forum didn’t either. Any help is greatly appreciated!

We’re using an ITG3200 gyroscope to analyze rotation around the Z axis, and use that data to control a stepper motor.

In my code, I have the gyro’s raw data converted to °/s, then converted to RPM, then it goes through a couple more conversions before being turned into a delay in microseconds between motor pulses.

Our project is to send a spin plate into space on a spin-stabilized rocket. Our gyro will analyze the spin of the rocket and spin the stepper motor in the opposite direction so that the spin plate will be as close to stationary relative to the rocket as possible.

Here’s the problem: it doesn’t work. We haven’t been able to get the rotor to turn according to the Z data, in either direction.

  • Do I need to add any sort of sample rate? The gyro is super sensitive, and the data is always going to be spitting out a different, odd, integer at a ridiculous rate.

  • Should I add a rounding function so that the delay is always a nice, even number?

  • Do I need to only call for (dps) once every second or something?

  • How well can the motor keep up? Do I need to protect it from getting too much information?

It should be noted that I’m only just beginning to understand the code. I just started working on this a few weeks ago. I’ve been reading the Reference section pretty heavily, but I only have a very basic understanding of what I’m reading in there.

I hope this isn’t too convoluted. I appreciate any advice in advance!

Here’s the first part of the code. We called our delay “derps per second” because we were frustrated:

//The Wire library is used for I2C communication
#include <Wire.h>

//This is a list of registers in the ITG-3200. Registers are parameters that determine how the sensor will behave, or they can hold data that represent the
//sensors current status.
//To learn more about the registers on the ITG-3200, download and read the datasheet.
char WHO_AM_I = 0x00;
char SMPLRT_DIV= 0x15;
char DLPF_FS = 0x16;
char GYRO_XOUT_H = 0x1D;
char GYRO_XOUT_L = 0x1E;
char GYRO_YOUT_H = 0x1F;
char GYRO_YOUT_L = 0x20;
char GYRO_ZOUT_H = 0x21;
char GYRO_ZOUT_L = 0x22;

//This is a list of settings that can be loaded into the registers.
//DLPF, Full Scale Register Bits
//FS_SEL must be set to 3 for proper operation
//Set DLPF_CFG to 3 for 1kHz Fint and 42 Hz Low Pass Filter
char DLPF_CFG_0 = 1<<0;
char DLPF_CFG_1 = 1<<1;
char DLPF_CFG_2 = 1<<2;
char DLPF_FS_SEL_0 = 1<<3;
char DLPF_FS_SEL_1 = 1<<4;

//I2C devices each have an address. The address is defined in the datasheet for the device. The ITG-3200 breakout board can have different address depending on how
//the jumper on top of the board is configured. By default, the jumper is connected to the VDD pin. When the jumper is connected to the VDD pin the I2C address
//is 0x69.
char itgAddress = 0x69;

//In the setup section of the sketch the serial port will be configured, the i2c communication will be initialized, and the itg-3200 will be configured.
void setup()
{
  //Create a serial connection using a 9600bps baud rate.
  Serial.begin(9600);
  
  pinMode(2,OUTPUT); // pulse control, pin 2 on UNO
  pinMode(4,OUTPUT); // direction, pin 4 on UNO
  digitalWrite(4,LOW); //low is either clockwise or counterclockwise

  //Initialize the I2C communication. This will set the Arduino up as the 'Master' device.
  Wire.begin();

  //Read the WHO_AM_I register and print the result
  char id=0; 
  id = itgRead(itgAddress, 0x00);  
  Serial.print("ID: ");
  Serial.println(id, HEX);

  //Configure the gyroscope
  //Set the gyroscope scale for the outputs to +/-2000 degrees per second
  itgWrite(itgAddress, DLPF_FS, (DLPF_FS_SEL_0|DLPF_FS_SEL_1|DLPF_CFG_0));
  //Set the sample rate to 100 hz
  itgWrite(itgAddress, SMPLRT_DIV, 9);
}

//The loop section of the sketch will read the X,Y and Z output rates from the gyroscope and output them in the Serial Terminal
void loop()
{
    
  //Create variables to hold the output rates.
  int xRate, yRate, zRate;
  //Read the x,y and z output rates from the gyroscope.
  xRate = readX();
  yRate = readY();
  zRate = readZ();
  
  int zmotor = ((zRate / 14.375) / 6); 
  int derps = 1000000 / ((zmotor * 200 * 2) / 60);
  if (derps < 0);
{
  (derps * (-1));
}

/*
derps per second is Z gyro data
converted to degrees/s,
converted to RPM,
converted to pulses per minute,
converted to pulses per second
converted to microseconds
which equals pulse delay
*/
  
  //motor pulse control loop
  digitalWrite(2,HIGH);
  delayMicroseconds(derps);
  digitalWrite(2,LOW);
  delayMicroseconds(derps);
  
  {
  //Print the output rates to the terminal, seperated by a TAB character.
  Serial.print(xRate);
  Serial.print('\t');
  Serial.print(yRate);
  Serial.print('\t');
  Serial.println(zRate);  

  //Wait 10ms before reading the values again. (Remember, the output rate was set to 100hz and 1reading per 10ms = 100hz.)
  delay(10);
  }
}

And here’s the rest of the gyro’s code:

//This function will write a value to a register on the itg-3200.
//Parameters:
// char address: The I2C address of the sensor. For the ITG-3200 breakout the address is 0x69.
// char registerAddress: The address of the register on the sensor that should be written to.
// char data: The value to be written to the specified register.
void itgWrite(char address, char registerAddress, char data)
{
  //Initiate a communication sequence with the desired i2c device
  Wire.beginTransmission(address);
  //Tell the I2C address which register we are writing to
  Wire.write(registerAddress);
  //Send the value to write to the specified register
  Wire.write(data);
  //End the communication sequence
  Wire.endTransmission();
}

//This function will read the data from a specified register on the ITG-3200 and return the value.
//Parameters:
// char address: The I2C address of the sensor. For the ITG-3200 breakout the address is 0x69.
// char registerAddress: The address of the register on the sensor that should be read
//Return:
// unsigned char: The value currently residing in the specified register
unsigned char itgRead(char address, char registerAddress)
{
  //This variable will hold the contents read from the i2c device.
  unsigned char data=0;

  //Send the register address to be read.
  Wire.beginTransmission(address);
  //Send the Register Address
  Wire.write(registerAddress);
  //End the communication sequence.
  Wire.endTransmission();

  //Ask the I2C device for data
  Wire.beginTransmission(address);
  Wire.requestFrom(address, 1);

  //Wait for a response from the I2C device
  if(Wire.available()){
    //Save the data sent from the I2C device
    data = Wire.read();
  }

  //End the communication sequence.
  Wire.endTransmission();

  //Return the data read during the operation
  return data;
}

//This function is used to read the X-Axis rate of the gyroscope. The function returns the ADC value from the Gyroscope
//NOTE: This value is NOT in degrees per second.
//Usage: int xRate = readX();
int readX(void)
{
  int data=0;
  data = itgRead(itgAddress, GYRO_XOUT_H)<<8;
  data |= itgRead(itgAddress, GYRO_XOUT_L);

  return data;
}

//This function is used to read the Y-Axis rate of the gyroscope. The function returns the ADC value from the Gyroscope
//NOTE: This value is NOT in degrees per second.
//Usage: int yRate = readY();
int readY(void)
{
  int data=0;
  data = itgRead(itgAddress, GYRO_YOUT_H)<<8;
  data |= itgRead(itgAddress, GYRO_YOUT_L);

  return data;
}

//This function is used to read the Z-Axis rate of the gyroscope. The function returns the ADC value from the Gyroscope
//NOTE: This value is NOT in degrees per second.
//Usage: int zRate = readZ();
int readZ(void)
{
  int data=0;
  data = itgRead(itgAddress, GYRO_ZOUT_H)<<8;
  data |= itgRead(itgAddress, GYRO_ZOUT_L);

  return data;
}

I don't immediately see that you print the value of derps anywhere so you can confirm you are getting sensible data from your Gyro and subsequent calculations.

It would almost certainly be better to do the motor code like this

  //motor pulse control loop
  digitalWrite(2,HIGH);
  digitalWrite(2,LOW);
  delayMicroseconds(bigDerps);

as it will give you more control over the timing.

Post a link to the datasheet for your stepper motor.
What stepper motor driver board are you using?
What motor power supply are you using - volts and amps?

...R

Robin2:
I don’t immediately see that you print the value of derps anywhere

I’m not entirely sure what you mean by “print the value”, but I’ve got this running in the void loop(), is that what you’re asking about?

//Create variables to hold the output rates.
  int xRate, yRate, zRate;
  //Read the x,y and z output rates from the gyroscope.
  xRate = readX();
  yRate = readY();
  zRate = readZ();
  
  int zmotor = ((zRate / 14.375) / 6); 
  int derps = 1000000 / ((zmotor * 200 * 2) / 60);
  if (derps < 0);
{
  (derps * (-1));
}

I don’t have access at the moment, but I guess I should add a Serial.print(derps) to see that it’s doing the math correctly.

As far as power, we’re going to be using two 3.7V 2000mAh (10C) batteries in series to power this driver.

But for testing, we’ve been using a DC amplifier.

Here’s our motor.

As far as your suggestion, I’m not sure I understand. Does the “big” prefix mean something? How does this give me more control? Is it because we’re only using derps once, which means there won’t be any disparity in the delay?

Thanks so much for your reply.

I mean that you should put in (temporarily) Serial.println(derps); so you can see what value is being used.

By bigDerps I was just trying to convey the idea that in my version the value would need to be twice as big as yours for the same speed - because your code uses the value twice and mine once.

If you use the value twice subtracting 1 from it adjusts the timing by 2. If you use the value once it only adjusts the timing by 1.

microsBetweenSteps might be a more meaningful name than derps

With those batteries you are only giving 7.4v to the driver. The datasheet says the minimum operating voltage is 8v. And 12v or 24v would be much better. At the very least try a 3rd battery to give 11.1v.

That motor will flatten those batteries very quickly, stepper motors are very inefficient. How long do you want it to run for?

…R

I figured as much about the bigDerps.

Whoops. Yes, you're right! I was thinking of an earlier iteration of our project. Yes, we will actually be powering the motor with four banks of two batteries each in series, then wiring all of it in parallel for 8000mAh @ 14.8V.

I'm also concerned about the batteries' ability to power the motor in addition to the rest of our payload (two pro minis and an uno, and a pair of accelerometers and gyroscopes). What we've observed using the DC amplifier is that when we turn the voltage supply up, the current draw goes down. Is it safe to assume the batteries will behave similarly? I feel like probably not, although I don't really know much about electricity.

The flight is 15 minutes. The rocket will reach apogee at around 3 minutes, which is the bare minimum for running the motor.

blorgon:
I figured as much about the bigDerps.

Does that mean it is now working, or that you have still to modify the code to print the value?

If the batteries are fully charged 15 minutes should not be a problem. It is always wise to assume the sticker overstates the capacity by a factor of 2 - assume 8Ah is really only 4 Ah.

...R

I’m a newbie, but I’m also working on a project with this gyro.
I think it is much better to get the angle based on the angular velocity and then control its stability by the angle.

@brennandt - please do not hijack this thread. If you have a problem then feel free to start your own thread.

Robin2:
Does that mean it is now working, or that you have still to modify the code to print the value?

If the batteries are fully charged 15 minutes should not be a problem. It is always wise to assume the sticker overstates the capacity by a factor of 2 - assume 8Ah is really only 4 Ah.

...R

I still don't have access to try out the changes. I'll report back on Tuesday, when my group meets.

Robin2:
I mean that you should put in (temporarily) Serial.println(derps); so you can see what value is being used.

So I tried this and then realized that the only place "derps" is defined is inside the loop, and I'm getting an error saying that "derps" is not declared.

" devon_z_motor.ino:98:18: error: use of undeclared identifier 'derps'
Serial.print(derps); "

And the motor is also not working as expected. And I'm pretty sure it's because the gyro can only provide data at 100Hz, which is one reading per 10ms. So if the motor loop is trying to determine derps using the equation we have, the gyro needs to be providing data faster than that. But then there's a synchronization problem. So I think what I need to do is have the motor control loop run until a new reading occurs. But I've been reading reference, and I don't know if that's possible.

So TL;DR, I want to run the motor control loop at the value it reads from the gyro until it reads a new value from the gyro. Is that possible?

blorgon:
So I tried this and then realized that the only place "derps" is defined is inside the loop,

Well the simplest thing to do is to declare it as a global variable at the top of the program and not inside the loop.

Do the same with zmotor

blorgon:
And the motor is also not working as expected.

One thing at a time. Find out the DERPS value and then we can consider other stuff.

...R

Robin2:
Well the simplest thing to do is to declare it as a global variable at the top of the program and not inside the loop.
...R

It won't let me move them out of the loop. Everywhere else in the sketch, I get the error that they don't name a type, or weren't declared in this scope.

You need to add
byte derps;
or
int derps;

before setup();
Then any code that is not in a function can access it as a global variable.
If you use
static byte derps;
then code in a function can access it also.
(I think that's the one - I don't use functions much myself so can't readily confirm).

CrossRoads:
You need to add
byte derps;
or
int derps;

before setup();
Then any code that is not in a function can access it as a global variable.
If you use
static byte derps;
then code in a function can access it also.
(I think that's the one - I don't use functions much myself so can't readily confirm).

Derps isn't really the problem, it's that the gyro's code (as provided by sparkfun) doesn't want to be put outside the loop. Derps is dependent on a variable that's dependent on zRate, which is provided by the gyro's read function, which runs in the loop. If I move the read function outside the loop, I won't be getting real time readings from the gyro anymore. At least that's the way I understand the code (but again, very little experience here).

If I move int derps before setup, it tells me that the variables that derps is dependent on are not declared, and then if I move the derp-dependent variables up to where derps is, the compiler tells me the variables don't have a type. I'm sure it has something to do with the itg read void, but I just don't understand this stuff enough to even begin troubleshooting it on my own.

I also tried using static byte and byte, but get this "derp_motor:36: error: expected unqualified-id", and also a "not declared" error.

Here's what I think is going on: the motor loop will run forever, but only if it has a value for derps. Derps is directly read (after some conversion) from the gyroscope. But, the gyroscope can only send a reading every 10ms. So, what I want to know is, is derps defined by whatever variable we get from the gyro, until a new reading comes in and changes the variable? Is derps "latching" for lack of a better term? Will it use that variable until a new one comes down the line, or do I need to program the motor loop to run until it gets new data?

I can't offer much more without being able to play with the code.
Personally, I'd write it without functions, make all the variables global, and run the gyro reading as inline code using blink without delay style coding.
I'm a hardware designer and only play a programmer here. Sometimes libraries are more trouble than they're worth, and just writing some discrete code to read the registers is a lot more straightforward.

At least that's the way I understand the code

No that is not right.

I also tried using static byte and byte, but get this "derp_motor:36: error: expected unqualified-id", and also a "not declared" error.

What version of the IDE are you using?

We need to see ALL your code.

Grumpy_Mike:
No that is not right.

What am I not understanding?

What version of the IDE are you using?

We need to see ALL your code.

All of my code is in the OP. Using Arduino 1.6.4.

What am I not understanding?

The way code works and what functions do what.

All of my code is in the OP

That was in two pieces we need it all in one lump we do not want to stitch it together.

Grumpy_Mike:
The way code works and what functions do what.

Can you explain what they do then? With all due respect, I’d like to understand what’s going on in the sketch and learn, not just have people solve my problem for me.

So after all this advice you have not made any changes?

Well, the only changes suggested so far have been to move my variables to different places in the sketch, and they’ve only produced errors, whereas my original code compiles correctly, but doesn’t control the motor in the expected fashion (which I personally suspect has to do with the gyro’s sample rate, not “derps”)

Here’s the whole code (with one change in the motor loop (“derps”) as suggested by Robin2):

//The Wire library is used for I2C communication
#include <Wire.h>

//This is a list of registers in the ITG-3200. Registers are parameters that determine how the sensor will behave, or they can hold data that represent the
//sensors current status.
//To learn more about the registers on the ITG-3200, download and read the datasheet.
char WHO_AM_I = 0x00;
char SMPLRT_DIV = 0x15;
char DLPF_FS = 0x16;
char GYRO_XOUT_H = 0x1D;
char GYRO_XOUT_L = 0x1E;
char GYRO_YOUT_H = 0x1F;
char GYRO_YOUT_L = 0x20;
char GYRO_ZOUT_H = 0x21;
char GYRO_ZOUT_L = 0x22;

//This is a list of settings that can be loaded into the registers.
//DLPF, Full Scale Register Bits
//FS_SEL must be set to 3 for proper operation
//Set DLPF_CFG to 3 for 1kHz Fint and 42 Hz Low Pass Filter
char DLPF_CFG_0 = 1<<0;
char DLPF_CFG_1 = 1<<1;
char DLPF_CFG_2 = 1<<2;
char DLPF_FS_SEL_0 = 1<<3;
char DLPF_FS_SEL_1 = 1<<4;

//I2C devices each have an address. The address is defined in the datasheet for the device. The ITG-3200 breakout board can have different address depending on how
//the jumper on top of the board is configured. By default, the jumper is connected to the VDD pin. When the jumper is connected to the VDD pin the I2C address
//is 0x69.
char itgAddress = 0x69;

//In the setup section of the sketch the serial port will be configured, the i2c communication will be initialized, and the itg-3200 will be configured.
void setup()
{

   //Create a serial connection using a 9600bps baud rate.
  Serial.begin(9600);
  
  pinMode(2,OUTPUT); // pulse control, pin 2 on UNO
  pinMode(4,OUTPUT); // direction, pin 4 on UNO
  digitalWrite(4,LOW); //low is either clockwise or counterclockwise

  //Initialize the I2C communication. This will set the Arduino up as the 'Master' device.
  Wire.begin();

  //Read the WHO_AM_I register and print the result
  char id=0; 
  id = itgRead(itgAddress, 0x00);  
  Serial.print("ID: ");
  Serial.println(id, HEX);

  //Configure the gyroscope
  //Set the gyroscope scale for the outputs to +/-2000 degrees per second
  itgWrite(itgAddress, DLPF_FS, (DLPF_FS_SEL_0|DLPF_FS_SEL_1|DLPF_CFG_0));
  //Set the sample rate to 100 hz
  itgWrite(itgAddress, SMPLRT_DIV, 9);
}

//The loop section of the sketch will read the X,Y and Z output rates from the gyroscope and output them in the Serial Terminal

/*
derps per second is Z gyro data converted to degrees/s,
converted to RPM, converted to pulses per minute,
converted to pulses per second, converted to microseconds
which equals pulse delay
*/

void loop()
{
    
  //Create variables to hold the output rates.
  static byte xRate, yRate, zRate;
  //Read the x,y and z output rates from the gyroscope.
  xRate = readX();
  yRate = readY();
  zRate = readZ();
      
  int zmotor = ((zRate / 14.375) / 6); 
  int derps = 1000000 / ((zmotor * 400) / 60);
  int dly = (derps * 2);
    
  if (dly < 0);
  {
    (dly * (-1));
  }
  
  //motor pulse control loop
  digitalWrite(2,HIGH);
  digitalWrite(2,LOW);
  delayMicroseconds(dly);
      
      
  //Print the output rates to the terminal, seperated by a TAB character.
  Serial.print(xRate);
  Serial.print('\t');
  Serial.print(yRate);
  Serial.print('\t');
  Serial.println(zRate);  
  
  //Wait 10ms before reading the values again. (Remember, the output rate was set to 100hz and 1reading per 10ms = 100hz.)
  delay(10);
  
}

//This function will write a value to a register on the itg-3200.
//Parameters:
// char address: The I2C address of the sensor. For the ITG-3200 breakout the address is 0x69.
// char registerAddress: The address of the register on the sensor that should be written to.
// char data: The value to be written to the specified register.
void itgWrite(char address, char registerAddress, char data)
{
  //Initiate a communication sequence with the desired i2c device
  Wire.beginTransmission(address);
  //Tell the I2C address which register we are writing to
  Wire.write(registerAddress);
  //Send the value to write to the specified register
  Wire.write(data);
  //End the communication sequence
  Wire.endTransmission();
}

//This function will read the data from a specified register on the ITG-3200 and return the value.
//Parameters:
// char address: The I2C address of the sensor. For the ITG-3200 breakout the address is 0x69.
// char registerAddress: The address of the register on the sensor that should be read
//Return:
// unsigned char: The value currently residing in the specified register
unsigned char itgRead(char address, char registerAddress)
{
  //This variable will hold the contents read from the i2c device.
  unsigned char data=0;

  //Send the register address to be read.
  Wire.beginTransmission(address);
  //Send the Register Address
  Wire.write(registerAddress);
  //End the communication sequence.
  Wire.endTransmission();

  //Ask the I2C device for data
  Wire.beginTransmission(address);
  Wire.requestFrom(address, 1);

  //Wait for a response from the I2C device
  if(Wire.available()){
    //Save the data sent from the I2C device
    data = Wire.read();
  }

  //End the communication sequence.
  Wire.endTransmission();

  //Return the data read during the operation
  return data;
}

//This function is used to read the X-Axis rate of the gyroscope. The function returns the ADC value from the Gyroscope
//NOTE: This value is NOT in degrees per second.
//Usage: int xRate = readX();
int readX(void)
{
  int data=0;
  data = itgRead(itgAddress, GYRO_XOUT_H)<<8;
  data |= itgRead(itgAddress, GYRO_XOUT_L);

  return data;
}

//This function is used to read the Y-Axis rate of the gyroscope. The function returns the ADC value from the Gyroscope
//NOTE: This value is NOT in degrees per second.
//Usage: int yRate = readY();
int readY(void)
{
  int data=0;
  data = itgRead(itgAddress, GYRO_YOUT_H)<<8;
  data |= itgRead(itgAddress, GYRO_YOUT_L);

  return data;
}

//This function is used to read the Z-Axis rate of the gyroscope. The function returns the ADC value from the Gyroscope
//NOTE: This value is NOT in degrees per second.
//Usage: int zRate = readZ();
int readZ(void)
{
  int data=0;
  data = itgRead(itgAddress, GYRO_ZOUT_H)<<8;
  data |= itgRead(itgAddress, GYRO_ZOUT_L);

  return data;
}