PWM Tutorial - Accelerometer

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey everyone,

I'm trying to read values from a Memsic 2125 accelerometer however I can't seem to find a good tutorial on PWM usage. Abcminiuser's timer tutorial was great but unfortunately hasn't been completed quite yet. Can anyone point me in the right direction?

edit: title

Last Edited: Mon. Dec 31, 2007 - 02:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ooooh... I just got one of those at Radio Shack..... here's what I've got so far....

int xhi,xpd,yhi,ypd;
float xGs,yGs;
int xbias,ybias;
//-------------------------
void waitforpb0togohi(void){
unsigned int n;

	n=65000;
	while((PINB & 1)==0){
	  n--;
		if(n==0) break;
	}
}

//-------------------------
void waitforpb0togolo(void){
unsigned int n;

	n=65000;
	while((PINB & 1)==1){
	  n--;
		if(n==0) break;
	}
}

//-------------------------
void waitforpb1togohi(void){
unsigned int n;

	n=65000;
	while((PINB & 2)==0){
	  n--;
		if(n==0) break;
	}
}

//-------------------------
void waitforpb1togolo(void){
unsigned int n;

	n=65000;
	while((PINB & 2)==2){
	  n--;
		if(n==0) break;
	}
}

//-------------------------
void readxgs(void){
//read xgs  t1 counts at 2mhz 500ns

  waitforpb0togolo(); //pb0 goes lo after pulse  
  waitforpb0togohi(); //new pulse is starting
	TCNT1=0;            //reset counter

  waitforpb0togolo(); //end of pulse 
	xhi=TCNT1;          //read hi time   0= -3gs  10000=0gs   20000=3gs
  waitforpb0togohi(); //low pd at end of pulse
	xpd=TCNT1;          //read period
//	xGs=xhi*300L/xpd - xbias; //hundredth Gs
  xGs=(6.0/xpd)*xhi-3.0;
}

//-------------------------
void readygs(void){
//read ygs  t1 counts at 2mhz 500ns   10000usec=20000tics=0x4e20
  
  waitforpb1togolo();  
  waitforpb1togohi();  
	TCNT1=0; //reset counter

  waitforpb1togolo();  
	yhi=TCNT1; //0= -3gs  10000=0gs   20000=3gs
  waitforpb1togohi();
	ypd=TCNT1;  
//	yGs=yhi*300L/ypd -ybias; //hundredth Gs
  yGs=(6.0/ypd)*yhi-3.0;
}


Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice! This should give me a great start. Thanks Bob!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello again!

I attempted to use your example to get a reading and I think I'm having a little bit of an issue, here is my code:

#include 
#include 
#include 
#include 
#include 

#include 
#include 

#define F_CPU 3686400
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

void usart_init(void);
void usart_tx(unsigned char c);
void usart_tx_string(unsigned char *string);
void wait_pb0_hi(void);
void wait_pb0_low(void);
void wait_pb1_hi(void);
void wait_pb1_low(void);
int get_x_g(void);
int get_y_g(void);

unsigned char buffer[15];

int x_hi;
int x_pd;
int y_hi;
int y_pd;

int xbias;
int ybias;

float x_g;
float y_g;

int main (void) {
    // Enable input on PB0 & PB1
    DDRB =  0x00;
    PORTB |= (1 << PB0) | (1 << PB1);

    usart_init();

    while(1) {
        // TX x_g
        itoa(get_x_g(), buffer, 10);
        //usart_tx_string('x: ');
        usart_tx_string(buffer);

        // TX y_g
        itoa(get_y_g(), buffer, 10);
        //usart_tx_string('y: ');
        usart_tx_string(buffer);

        // TX newline
        usart_tx('\r');
        usart_tx('\n');
    }   
}

void usart_tx_string(unsigned char *string) {
    while(*string) {
        usart_tx(*string++);
    }
}

void usart_tx(unsigned char c) {
    // Wait until UDR is ready for more data
    while((UCSRA & (1 << UDRE)) == 0) {};

    UDR = c;
}

void usart_init(void) {
    // Enable USART rx and tx
    UCSRB |= (1 << RXEN) | (1 << TXEN);

    // Use 8-bit character sizes
    UCSRC |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1);

    // Load lower 8-bits of the baud rate value into the low byte of the UBBR register
    UBRRL = BAUD_PRESCALE;

    // Load upper 8-bits of the baude rate value into the high byte of the UBRR register
    UBRRH = (BAUD_PRESCALE >> 8);
}

void wait_pb0_hi(void) {
    unsigned int n;

    n = 65000;
    while((PINB & 1) == 0) {
        n--;

        if(n == 0) {
            break;
        }
    }
}

void wait_pb0_low(void) {
    unsigned int n;

    n = 65000;
    while((PINB & 1) == 1) {
        n--;

        if(n == 0) {
            break;
        }
    }
}

void wait_pb1_hi(void) {
    unsigned int n;

    n = 65000;
    while((PINB & 2) == 0) {
        n--;

        if(n == 0) {
            break;
        }
    }
}

void wait_pb1_low(void) {
    unsigned int n;

    n = 65000;
    while((PINB & 2) == 2) {
        n--;

        if(n == 0) {
            break;
        }
    }
}

int get_x_g(void) {
    // PB0 goes low after pulse
    wait_pb0_low();

    // new pulse is starting
    wait_pb0_hi();

    // Reset counter
    TCNT1 = 0;

    // End of pulse
    wait_pb0_low();

    // Read high time (0 = -3g, 10000 = 0g, 20000 = 3g)
    x_hi = TCNT1;

    // Low PD at end of pulse
    wait_pb0_hi();

    // Read period
    x_pd = TCNT1;

    // hundreth G
    // x_g = x_hi & 300L / xpd - xbias;

    x_g = (6.0 / x_pd) * x_hi - 3.0;

    return(x_g);
}

int get_y_g(void) {
    // read y(g) T1 counts at 2mhz (500ns) (10000usec = 20000tics = 0x4e20)
    wait_pb1_low();

    wait_pb1_hi();

    // Reset counter
    TCNT1 = 0;

    wait_pb1_low();

    // 0 = -3g, 10000 = 0g, 20000 = 3g
    y_hi = TCNT1;

    wait_pb1_hi();
    y_pd = TCNT1;

    // hundreth g
    // y_g = y_hi & 300L / ypd - ybias;
    y_g = (6.0 / y_pd) * y_hi - 3.0;

    return(y_g);
}

This is running on an ATMega16 (avr-gcc) - Xout at PB0, Yout at PB1.

I'm attempting to xmit the x/y values via usart but I am getting this output:

00
00
00
00
00
00
...
...

I'm don't completely understand the timing with your code - My clock is set via an external crystal running at 3.6864Mhz could this be the cause?

Also, attached is the waveform I'm seeing:

x:

y:

Any help would be great!

Thanks again,

Ben

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have you initialized timer 1 ?
Bob has (from his comments), but does not show the code.

Why is your scope not displaying a nice square wave?

I have experimented with this chip. I like the principle of operation but the 10ms period of the o/p was the biggest issue for me. Great for tilt sensing. However the jitter I saw on the readings was hard to dial out. Also the current draw was too high for the intended application.

edit:
PWM usually refers to output from the micro. Perhaps this is why you are going astray. The problem here is measuring the duty cycle of an input. Have a look at the ICP pin and how it can stop the timer. (I gated this pin externally according to whether I was reading X or Y. Pretty clumsy but it worked)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Broxbourne wrote:
Have you initialized timer 1 ?
Bob has (from his comments), but does not show the code.

Why is your scope not displaying a nice square wave?

Doh! I knew I forgot something - I setup TCCR1B at a prescale of 64 - this is where I am a bit confused. I'm getting output now

03
12
12
03
-12
03
13
13
03
-13
03
03

However, I'm not exactly sure how to get the 100hz sample rate.

Thanks again guys!

Ben

edit: As for the wave form - I'm not exactly sure what is going on there. I think that most likely - it's my "psudo oscope" ie: Zelscope/Xoscope which is basically just a sound card. Otherwise I guess the accelerometer could be hosed - it seems to respond to x/y movement though.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Whoo-Hoo !!
Well done!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The pulses would look like that if they were running thru caps... as they undoubtedly are on the sound card. They would be nice and straight on a 'real' dc coupled scope I think. My mega32 was clocked at 16mhz and I think I was using prescaler of 8... 2mhz count... so count of 20000 would be 10000usec=10ms. With 3.6864mhz and ps=1, need to figure out what count is xpd and ypd... try printing those out. The x Gs routine just waits for the pulse to come along, then clears the timer, waits for the pulse to end, reads the timer. Could be smarter, but I was just trying to see something. I also think it might need a calibration routine, where you remember the max and min x and y G numbers when the x axis is pointing down, then sideways. The you might need to compute the magnitude of the XGs squared and the YGs squared, and when thats 1, its not moving. When its not 1, its moving. When its not moving, the tilt angle in radians is the atan2(yGs,xGs) and you can cvt to degrees and print that out and see if it reads 45 degrees when you tilt it 45 degrees.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ahhh! Thanks for the info on the caps - I figured something was a little off there. Anyway, I hope to improve on this a bit, if I come up with a better version I'll be sure to let you know. You've been a great help =]

On another note; I dumped the counter values and found that Yout is definitely not working properly. I think I had the probe on the wrong pin for the Y waveform above. Like you, I picked this up at radio shack - hopefully they'll take it back for an exchange.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello again,

So radio shack replaced the faulty sensor I had today - so I'm finally getting proper outputs. I'm still a bit confused about the (g) calculation though.

The datasheet states that:

A (g)= (T1/T2 - 0.5)/12.5%
0g = 50% Duty Cycle
T2= 2.5ms or 10ms (factory programmable)

I think I set this up properly in the code above but the resolution seems pretty poor ie:

x: 0 y: 1
----
x: -1 y: 0
----
x: -2 y: 0
----
x: -2 y: 0
----
x: -1 y: 1
----
x: -2 y: -2
----
x: -1 y: 0
----
x: 0 y: 0
----
x: 1 y: 0

Does the code below seem right?
Thanks again for all of your patience and help everyone =]

#include 
#include 
#include 

#define F_CPU 3686400
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

void usart_init(void);
void usart_tx(unsigned char c);
void usart_tx_string(unsigned char *string);
float get_x_g(void);
float get_y_g(void);
unsigned char x_status(void);
unsigned char y_status(void);

unsigned char buffer[15];

int main (void) {
    // Enable input on PB0 & PB1
    DDRB =  0x00;
    PORTB |= (1 << PB0) | (1 << PB1) | (1 << PB2);

    usart_init();

    // prescale = 64
    //TCCR1B |= (1 << CS10) | (1 << CS11);

    // no prescale
    TCCR1B |= (1 << CS10);

    // prescale = 8
    //TCCR1B = (1 << CS11);

    while(1) {
        usart_tx_string((unsigned char *) "----");
        usart_tx('\r');
        usart_tx('\n');

        // TX x_g
        itoa(get_x_g(), buffer, 10);
        usart_tx_string((unsigned char *) "x: ");
        usart_tx_string(buffer);

        usart_tx(' ');

        // TX y_g
        itoa(get_y_g(), buffer, 10);
        usart_tx_string((unsigned char *) "y: ");
        usart_tx_string(buffer);

        // TX newline
        usart_tx('\r');
        usart_tx('\n');
    }   
}

void usart_tx_string(unsigned char *string) {
    while(*string) {
        usart_tx(*string++);
    }
}

void usart_tx(unsigned char c) {
    // Wait until UDR is ready for more data
    while((UCSRA & (1 << UDRE)) == 0) {};

    UDR = c;
}

void usart_init(void) {
    // Enable USART rx and tx
    UCSRB |= (1 << RXEN) | (1 << TXEN);

    // Use 8-bit character sizes
    UCSRC |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1);

    // Load lower 8-bits of the baud rate value into the low byte of the UBBR register
    UBRRL = BAUD_PRESCALE;

    // Load upper 8-bits of the baude rate value into the high byte of the UBRR register
    UBRRH = (BAUD_PRESCALE >> 8);
}

float get_x_g(void) {
    int T1, T2;
    // wait for Xout to go low
    while(x_status() == 1) {}

    // wait for Xout to go high
    while(x_status() == 0) {}

    // Start counter
    TCNT1 = 0;

    // wait for Xout to go low
    // this gives T1 (high pulse length)
    while(x_status() == 1) {}
    T1 = TCNT1;

    // wait for Xout to go high
    // this gives T2 (high pulse/low pulse; high start)
    while(x_status() == 0) {}
    T2 = TCNT1;

    return((T1 / (T2 - 0.5)) / 0.125);
}

float get_y_g(void) {
    int T1, T2;
    // wait for Yout to go low
    while(y_status() == 1) {}

    // wait for Yout to go high
    while(y_status() == 0) {}

    // Start counter
    TCNT1 = 0;

    // wait for Yout to go low
    // this gives T1 (high pulse length)
    while(y_status() == 1) {}
    T1 = TCNT1;

    // wait for Yout to go high
    // this gives T2 (high pulse/low pulse; high start)
    while(y_status() == 0) {}
    T2 = TCNT1;

    return((T1 / (T2 - 0.5)) / 0.125);
}

unsigned char x_status(void) {
    if(PINB & (1 << PB0)) {
        // Xout is low
        return(0);
    } else {
        // Xout is high
        return(1);
    }
}

unsigned char y_status(void) {
    if(PINB & (1 << PB1)) {
        // Yout is low
        return(0);
    } else {
        // Yout is high
        return(1);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Something fishy about the G computation in the return statements... T2 is a big integer like 20000 or 40000 something, so subtracting .5 from that doesnt make much sense... make T1 and T2 globals so you can print them out and see if T1 is really half of T2 when the thing is sitting level. Record the T1 and T2 values with the x and y axes pointing up and down... then we can work the G equation to give a reasonable number like +1 or -1 using the real numbers.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I think I set this up properly in the code above

    return((T1 / (T2 - 0.5)) / 0.125); 

does not compute

A (g)= (T1/T2 - 0.5)/12.5% 

i.e., you must do the T1/T2 division first then subtract 0.5.

Quote:
but the resolution seems pretty poor ie:

You are only printing ints while the computed g value is a float. As a fix for both problems I would suggest the functions return milli g as an int, i.e., the g value scaled by 1000. This will get rid of all floating point operations also (saves a lot of code space):

    return ((long)T1 * 1000 / T2 - 500) * 8;

(*8 since 1/0.125 is 8 and 500 is 0.5 when scaled*1000). Even better (unless I am too tired today):

    return (long)T1 * 8*1000 / T2 - 8*500;
unsigned char x_status(void) { 
    if(PINB & (1 << PB0)) { 
        // Xout is low 
        return(0); 
    } else { 
        // Xout is high 
        return(1); 
    } 
} 

unsigned char y_status(void) { 
    if(PINB & (1 << PB1)) { 
        // Yout is low 
        return(0); 
    } else { 
        // Yout is high 
        return(1); 
    } 
} 

These functions seem to have the reverse sense (comparing with your comments).

unsigned char x_status(void) { 
    return (PINB & (1 << PB0)) != 0;

} 

unsigned char y_status(void) { 
    return (PINB & (1 << PB1)) != 0;
} 

or similar is what you need (I guess).
/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Happy New Year everyone =]

Thanks for the great feedback. I think I'm getting a little closer now. Here's the output I'm currently getting when the sensor is still:

X - T1: 579 T2: 581 (g) = -4144
Y - T1: 578 T2: 582 (g) = -4160
----
X - T1: 579 T2: 582 (g) = -4144
Y - T1: 579 T2: 583 (g) = -4144
----
X - T1: 579 T2: 581 (g) = -4144
Y - T1: 579 T2: 582 (g) = -4144
----
X - T1: 579 T2: 582 (g) = -4144
Y - T1: 579 T2: 581 (g) = -4144
----
X - T1: 578 T2: 581 (g) = -4160
Y - T1: 579 T2: 582 (g) = -4144
----
X - T1: 578 T2: 582 (g) = -4160
Y - T1: 578 T2: 582 (g) = -4160

The (g) value still seems to be off. I'm still not doing something right here... Using (T1/T2 - 0.5) / 0.125 I get a (g) value of -4 no matter what, and -41xx using (T1 * 1000 / T2 - 500) * 8. I set the prescale to 64 as TCNT1 seemed to be overflowing without it.

Any ideas?

Edit: current code attached

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I notice some problems but, alas, they do not explain how you can have almost the same value for T1 and T2 for X, not sure what that is. Anyway:
* You test the wrong bit in y_status(void).
* You removed the != 0 test in y_status. This is ok if you write the loops (when checking for a "1") like this:

    while(y_status()) {}

* You changed the position of the (long) cast in the expression

    ((long)T1 * 1000 / T2 - 500) * 8

is now

    ((long) (T1 * 1000 / T2 - 500) * 8)

The long cast is needed for the * 1000 and the division (i.e., T1*1000 will not fit in an int). The result (from the function eventually) will however be ok as an int.
/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also, if you need to compare with the float version then you have a similar issue:

    return((T1 / T2 - 0.5) / 0.125);

would have to be

    return(((float)T1 / T2 - 0.5) / 0.125);

or maybe scale it also

return (((float)T1 / T2 - 0.5) / 0.125) * 1000;

if you intend to use itoa when printing this.
/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When the chip is flat on the table, T1 ought to be half of T2, right? When X is pointing straight down, T1 ought to be half T2 + one third T2... 1G in X pointing down out of 3 full scale. So whats that? 5/6 T2 when pointing down? And 1/6 T2 when X is pointing up. Looks like you are checking the same bit in B for X and Y... must be a typo... I wasnt doing that was I?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, I think T1 should definitely at least be near 1/2 of T2. I'm going to setup some tests and see if I can't figure out exactly whats going on here...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I kind of liked the names I had... 'wait for pin to go lo' You just have a status... and why put that one instruction in a subroutine? Takes 4 or 5 cycles... make it a define. You need to start the timer at 0 right when the pulse goes hi, then read it when it goes lo. Obviously. Use the smallest prescaler you can to capture the 10ms period... the more numbers you have, the better G resolution you get... .1G is good for a car I think.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah you're right - I changed things around when I was trying to clarify some things. A method to wait and then return is a much better solution - and cleaner. Hopefully I'll pick up an oscope soon so I can really look at whats going on. Microcarl made an offer for one, still mulling over it though =].