NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Troubles connecting gyro to ATmega168

April 12, 2011
by MeekMoe
MeekMoe's Avatar

Hello, I am fairly new to nerd kits and I am currently working on a project where I am connecting a 2 axis gyro to my ATmega168. The gyro I bought (http://www.pololu.com/catalog/product/1266) came on a breakout board and seemed relatively easy to hook up. However when I try to get reading off of it on the LCD i get nothing. It starts up reading all zeros and if i try moving it around nothing happens to the zeros. sometimes there is junk printed out on lines i am not even printing to.

Is that a power issue? I thought that it might, like in the case with a servo, so i powered the gyro using the usb cable (worked for the the servo). Behavior was unchanged.

Another thing i did which i am not sure i should have but also not sure it matters. I miss read the website and used the 3.3V on the gyro and connected it to BOTH Vrefs (on breakout board and on microcontroller). I also connected both ground pins on the breakout board.

I was using code similar to the temperature sensor code but i seems like i am missing something... If anyone has any input it would be greatly appreciated. Ill comment code and post it soon.

MeekMoe

April 12, 2011
by MeekMoe
MeekMoe's Avatar

ok here is my code. hope it amuses some.

// gyro.c
// for NerdKits with ATmega168
//

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC4 -- LED anode
// PB2 -- servo signal (OC1B)
// PC0 -- X axis of gyro
// PC1 -- Y axis of gyro

void pwm_set(uint16_t x) {
  OCR1B = x;
}

#define PWM_MIN 1300
#define PWM_MAX 4450
#define PWM_START 2765
void pwm_init() {
  // setup Timer1 for Fast PWM mode, 16-bit
  // COM1B1 -- for non-inverting output
  // WGM13, WGM12, WGM11, WGM10 -- for Fast PWM with OCR1A as TOP value
  // CS11 -- for CLK/8 prescaling

  OCR1A = 36864;    // sets PWM to repeat pulse every 20.0ms
  pwm_set(PWM_START);
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

  // each count is 8/14745600 = 0.5425us.
  // so 1.0ms = 1843.2
  //    1.5ms = 2764.8
  //    2.0ms = 3686.4
  //   20.0ms = 36864
}

void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}
uint16_t adc_read() {
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
 while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);

  return result;
}
int main() {
  // LED as output
  DDRC |= (1<<PC4);
    // servo as 0utput
  DDRB |= (1<<PB2);

 // init PWM
  pwm_init();

  uint16_t pos = 2000;

  // turn on LED
  PORTC |= (1<<PC4);

  // fire up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // print message to screen
  //             20 columns wide:
  //                     01234567890123456789
  lcd_line_one();
  lcd_write_string(PSTR("    testing gyro    "));
  lcd_line_two();
  lcd_write_string(PSTR("********************"));
  lcd_line_three();
  lcd_write_string(PSTR(" hopefully it works "));
  lcd_line_four();
  lcd_write_string(PSTR("        pray        "));
  delay_ms(5000);
  // turn off that LED
  PORTC &= ~(1<<PC4);
    uint16_t Xaxis, Yaxis;
  // busy loop
  while(1) {
    lcd_line_four();
    lcd_write_string(PSTR("work...work...WORK  "));

    //get data
    ADMUX = 0;
    Xaxis = adc_read();
    Xaxis = Xaxis * (5000.0/1024.0/10);//attempt conversion to deg/s
    ADMUX = 1;
    Yaxis = adc_read();//leave as voltage??

    lcd_line_two();  
    fprintf_P(&lcd_stream, PSTR("gyro X: %d        "), Xaxis);
    lcd_line_three();  
    fprintf_P(&lcd_stream, PSTR("gyro Y: %d        "), Yaxis);

    //LED on
    PORTC |= (1<<PC4);

  }

  return 0;
}
April 12, 2011
by 6ofhalfdozen
6ofhalfdozen's Avatar

Hi MeekMoe,

Any chance you could post a photo of your wiring? I took a look at the link for the gyro, and it sounds like the wiring needs to be done JUST right. There are two things about your description of your wiring that stand out; power, and grounds.

From the way I read the silkscreens and website, if you feed it with 5V (NK or USB), you put power in on Vin pin and do not connect the Vdd/3.3V since the board automatically drops 5V to 3.3V.

You make mention of putting both grounds on the breakout board. I might be miss reading you, but there is only one ground on the breakoutboard and it should be connected to breadboard ground and not to another pin on the breakout board.

The website mentions that the powerdown and selftest should never be held high at 5V, and sounds like tieing them to ground it best if you aren't using them. Also, it mentions that with no rotation, it should output 1.23V. Since you get no reading what so ever, I am thinking it is a wiring problem first then maybe a code issue. You just posted your code, so I haven't read that yet.

my suggestion is check your wiring. It looks like a cool gyro, hopefully you can get it working with just a minor tweak.

April 12, 2011
by bretm
bretm's Avatar

"sometimes there is junk printed out on lines i am not even printing to."

It looks like you're printing to all 4 lines, so what does the junk look like and on which lines/columns does it appear?

The adc_read() routine starts a new conversion before it returns the value from the previous conversion. So when you do this:

ADMUX = 0;
Xaxis = adc_read();

it will start another ADC0 conversion before giving you the value back. So when later you do this

ADMUX = 1;

it may still be in the middle of that conversion. That means that when you later do

Yaxis = adc_read();

you're actually reading the ADC0 value that it was still working on. adc_read then starts another conversion (using ADMUX=1 which you just set) so that when you loop around again you're going to be getting the ADC1 value back when you set Xaxis = adc_read(). Does that make sense?

But that doesn't help explain why you're getting zeros.

Remove the ADC and LCD stuff from the picture first. Just attach a voltmeter to the gyro outputs. What voltage range does it give you when you move the gyro around?

April 12, 2011
by MeekMoe
MeekMoe's Avatar

bottom side

On the left you can see that there are two GND. I connected them both. in the side view red is Vin, yellow 3.3V to refs, Blue is signals.

So i tested out the pins on the breakout board and now im convinced its a code issue. Everything was working on breakout board,3.3V and ~1.23V on signals out.

The junk printed out is u :"2.222""
and is usually on the first line. on closer inspection it looks like the entire LCD screen flickers when i flick the gyro.

As far as interrupting the ADC, should i just ad a Delay before ADMUX = 0,1 to allow time for the adc to convert? also i was wondering do i need to define PC0 and PC1 as in input like i did with the LED as an output?

April 12, 2011
by MeekMoe
MeekMoe's Avatar

OOOOOO it just clicked in my mind. i now get what you mean about the reading of the ADC values and thats because the conversions get started at the end of ADC_read function right?? Ill adapt code and see if it changes anything. but your right, it still doesnt explain the zeros.

April 13, 2011
by MeekMoe
MeekMoe's Avatar

Ok so i figured it out, silly me, i forgot to adc_init in the main function silly me. After i got that figured out it has started to work. thank you both for you quick response, they were both helpful.

But now i have another question as far as my project goes. Is there a way to get time stamps or find out how long processes take? I want to integrate the gyro over time in order to track the rough orientation. Really all i want to do is make the gyro recognize when its nearly turned over, or at least past 90 degrees. Anyone have any advice? i think there are time stamps in C but it requires the time.h library..

MeekMoe

April 13, 2011
by Ralphxyz
Ralphxyz's Avatar

Hi MeekMoe, please post a project outline and your working code. Lurking in the back of my mind is a balance bot project that will need gyros.

In order to do a time stamp you will need a time reference. I think the time.h library references a pc's clock you will need to furnish the clock.

Of course you could reference a mcu timer and show an elapse time.

There is a Nerdkits Crystal RTC project and then there is Rick's great I2C Real Time Clock project.

This adds a bit of complications beyond just calling a library function, but hey.

Ralph

April 13, 2011
by 6ofhalfdozen
6ofhalfdozen's Avatar

MeekMoe, Glad to hear you got it working.

As for finding out how long processes take, if you are talking about how long it takes to run X lines of mcu code, there are several threads on here talking about that. If you are talking of macroscopic timing for data monitoring, ie seconds/minutes/etc, there are several threads on that as well, usually based around the real time clocks.

TimeDate stamps can be a tricky one. I am definately planning on putting a couple in my future projects, sadly haven't gotten there yet. The simplest one I can think of is to have a timer interupt set for a certain number and use it to accumulate a variable off of that..(like in the real time clock project, but instead of seconds have it accumulate into a 16bit 20 second variable or something...) then have the accumulator stop once the NK dumps the data off elsewhere with a timestamp and back calculate it on a pc or something. Simple to me, not so simple to others.. just a thought..

Ralph, Rick, Noter, and others who's names I can't recall at the moment, have been fighting with external real time clocks in another post or two or three. Ideally external RTC's are probably the best way to do a "real" timestamp without bogging down your NK mcu, but it seems to be a bit more complicated than one would think. Sadly, just trying to keep up with their marathon posts has gotten me pretty confused. So I will defer to the experts on that topic.

As for finding out if your gyro has gone over 90degrees sounds pretty simple to me, if you are looking for the x and y axis seperately. If I recall, the gyro outputs + and - values for the X and Y axis changes in the gyro. If you were to set a signed integer variable, call it Xaxis and Yaxis just add it to the ADC reading every so often. You would need to zero the variable at a "level" or zero angle for each one. Once you have it zero'd, it should add and subtract(ie add a negative value), as changes occur. Noise might be an issue, so you might need to do some filtering. Anyhow, set an if then in your code so that if Xaxis or Yaxis goes over the ADC value for 90degree and LED or something goes on/off. I think once you have the gyro talking to the NK, the hard part is mostly over for this task.. anyhow, hope that helps a little.

April 13, 2011
by MeekMoe
MeekMoe's Avatar

My project goal it to create a reliable parachute for my pressurized water bottle rocket. I can make the rockets over stable such that at the apex of flight, the rocket will turn to fall back down to the ground. It is at this point I want the gyro to trigger the servo to deploy the parachute. Accelerometers don't work because the rocket is in free fall while it is still traveling upward with great velocity.

here is my most recent working code. As you can see I Have tried to "force" a time stamp in order to try to do the integration process. I was hoping that if i made a delay that was larger than the mcu data collection processing time, yet small enough to integrate, that a accurate integration would be possible, but its not looking like it. Ill try and read up on those time threads you guys mention and see if i can get something better working. Please let me know if yo have questions or comments or advice about the code.

// gyro.c
// for NerdKits with ATmega168
//

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC4 -- LED anode
// PB2 -- servo signal (OC1B)
// PC0 -- X axis of gyro
// PC1 -- Y axis of gyro
/////////////////////////////////////////////////////////////////////////
void pwm_set(uint16_t x) {
  OCR1B = x;
}

#define PWM_MIN 1300
#define PWM_MAX 4450
#define PWM_START 2765
/////////////////////////////////////////////////////////////////////////
void pwm_init() {
  // setup Timer1 for Fast PWM mode, 16-bit
  // COM1B1 -- for non-inverting output
  // WGM13, WGM12, WGM11, WGM10 -- for Fast PWM with OCR1A as TOP value
  // CS11 -- for CLK/8 prescaling

  OCR1A = 36864;    // sets PWM to repeat pulse every 20.0ms
  pwm_set(PWM_START);
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

  // each count is 8/14745600 = 0.5425us.
  // so 1.0ms = 1843.2
  //    1.5ms = 2764.8
  //    2.0ms = 3686.4
  //   20.0ms = 36864
}
/////////////////////////////////////////////////////////////////////////
void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}
/////////////////////////////////////////////////////////////////////////
uint16_t adc_read() {
  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  // read from ADC, waiting for conversion to finish
  // wait for it to be cleared
 while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  return result;
}
/////////////////////////////////////////////////////////////////////////
double toDPS(uint16_t sample, double bias) {
  // conversion ratio units: (steps)*(mV/steps)*(DPS/mV) = DPS
  // (3300 mV / 1024 steps)*(1 DEG/SEC / 2.5mV) 
  //    ^^^^^^^^^^^             ^^^^^^^^^
  //     from ADC               from gyro
  return (sample * (3300.0 / 1024.0))/2.5-bias;  
  }
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
int main() {

//OUTPUTS
  // LED
  DDRC |= (1<<PC4);
  // Servo 
  DDRB |= (1<<PB2);

//INITIALIZATION 
  // init PWM
  pwm_init();
  // start up the ACD
  adc_init();  
  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

//VARIABLES and CONSTANTS  
    uint16_t pos = 2000;
    uint16_t Xaxis, Yaxis;
    double X;
    double Y;
    double Xt;
    double Yt;
    double biasX;
    double biasY;
    double thetaX;
    double thetaY;
    uint8_t i;

// SET VISUAL DISPLAYS  
  // LED on
  PORTC |= (1<<PC4);

    // print message to screen
  //             20 columns wide:
  //                     01234567890123456789
  lcd_line_one();
  lcd_write_string(PSTR(" Calibrating gyro   "));
  //lcd_line_two();
  //lcd_write_string(PSTR("********************"));
  lcd_line_three();
  lcd_write_string(PSTR(" Please Hold Still  "));
  //lcd_line_four();
  //lcd_write_string(PSTR("        pray        "));

  //Pause for a moment to allow for
  //movement to dampen out
  delay_ms(5000);

//GET GYRO BIAS
  X=0;
  Y=0;
  thetaX=0;
  thetaY=0;
    for(i = 0; i<200; i++){
    ADMUX = 0;
    Xaxis = adc_read();
    ADMUX = 1;
    Yaxis = adc_read();
    Xt = toDPS(Xaxis,0);
    Yt = toDPS(Yaxis,0);
    X = X + Xt/200;
    Y = Y + Yt/200;
    }
  biasX = X;
  biasY = Y;

  // LED off
  PORTC &= ~(1<<PC4);

  // busy loop
  while(1) {

//GET GYRO DATA AVERAGE OVER 100 READINGS
    X=0;
    Y=0;
    for(i = 0; i<100; i++){
    ADMUX = 0;
    Xaxis = adc_read();
    ADMUX = 1;
    Yaxis = adc_read();
    Xt = toDPS(Xaxis,biasX);
    Yt = toDPS(Yaxis,biasY);
    X = X + Xt/100;
    Y = Y + Yt/100;
    }

// PRINT GYRO DATA               01234567890123456789
    lcd_line_one();  
    fprintf_P(&lcd_stream, PSTR("gyro X: %.1f        "), X);
    lcd_line_two();  
    fprintf_P(&lcd_stream, PSTR("gyro Y: %.1f        "), Y);

//"FORCED"  timestep to calculate/integrate theta
    delay_ms(25);
    thetaX = thetaX + X*0.025;
    thetaY = thetaY + Y*0.025;

// PRINT THETA DATA
    lcd_line_three();  
    fprintf_P(&lcd_stream, PSTR("theta X: %.1f       "), thetaX);
    lcd_line_four();  
    fprintf_P(&lcd_stream, PSTR("theta Y: %.1f       "), thetaY);

    //LED on
    PORTC |= (1<<PC4);

  }

  return 0;
}
April 13, 2011
by hevans
(NerdKits Staff)

hevans's Avatar

Hi all,

Just to throw another option for the "timing" question. If what you want is to know the interval between to successive readings of the ADC you can probably get a fairly accurate time by just knowing how long it takes for you to get a new sample. If you take a look at the datasheet you will see it takes 13 ADC clock cycles for you to get a new sample from the ADC, and you know each ADC clock cycle takes 128 MCU clock cycles (assuming you are using a 1/128 prescaler). So assuming you structure your code so you pick up each new conversion as soon as it is ready, and start the new one immediately, you should able to have a very consistent time between collected samples. This approach might certainly be good enough as an easy to implement first pass which will not require you to have any external circuitry to keep track of time.

Humberto

April 14, 2011
by MeekMoe
MeekMoe's Avatar

I like that idea, but that leads me to ask... If I switch to ADMUX = 1 before i read from the ADC, will the ADC update or contain its previous value from ADMUX = 0?

Also I am assuming the ADC runs parallel to the main code?

April 14, 2011
by bretm
bretm's Avatar

Once you read the value from the ADC data register there is no guarantee that it will retain that value after that point, whether you start another conversion or not. Once you read ADCH, technically the MCU is allowed to update the ADC data register at any point after that. I don't think it does unless you do a new conversion, but you can't count on that. Nor do you need to--you'll keep your own copy of the value to use as long as you like.

Yes, it runs in parallel to the main code.

Post a Reply

Please log in to post a reply.

Did you know that Pulse Width Modulation (PWM) can be used to control the speed of a motor digitally? Learn more...