Subversion Repositories Projects

Rev

Rev 964 | Blame | Compare with Previous | Last modification | View Log | RSS feed

//////////////////////////////////////
// LocoHead - Mathias Kreider, 2011 //                 
//                                                                      //
// headtracker.c                                        //
// I2C, angular and filter functions//         
//////////////////////////////////////

#include "vector.h"
#include <math.h>
#include <inttypes.h>
#include <avr/io.h>  
#include <stdlib.h>

extern vector m_max;
extern vector m_min;


void i2c_start() {  
        TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); // send start condition  
        while (!(TWCR & (1 << TWINT)));  
}  
 
void i2c_write_byte(char byte) {  
        TWDR = byte;              
        TWCR = (1 << TWINT) | (1 << TWEN); // start address transmission  
        while (!(TWCR & (1 << TWINT)));  
}  
 
char i2c_read_byte() {  
        TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN); // start data reception, transmit ACK  
        while (!(TWCR & (1 << TWINT)));  
        return TWDR;  
}  

char i2c_read_last_byte() {  
        TWCR = (1 << TWINT) | (1 << TWEN); // start data reception
        while (!(TWCR & (1 << TWINT)));  
        return TWDR;  
}  
 
void i2c_stop() {  
          TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); // send stop condition  
}  


// Returns a set of acceleration and raw magnetic readings from the cmp01a.
void read_data_raw(vector *a, vector *m)
{
        // read accelerometer values
        i2c_start();
        i2c_write_byte(0x30); // write acc
        i2c_write_byte(0xa8); // OUT_X_L_A, MSB set to enable auto-increment
        i2c_start();              // repeated start
        i2c_write_byte(0x31); // read acc
        unsigned char axl = i2c_read_byte();
        unsigned char axh = i2c_read_byte();
        unsigned char ayl = i2c_read_byte();
        unsigned char ayh = i2c_read_byte();
        unsigned char azl = i2c_read_byte();
        unsigned char azh = i2c_read_last_byte();
        i2c_stop();

        // read magnetometer values
        i2c_start();
        i2c_write_byte(0x3C); // write mag
        i2c_write_byte(0x03); // OUTXH_M
        i2c_start();              // repeated start
        i2c_write_byte(0x3D); // read mag
        unsigned char mxh = i2c_read_byte();
        unsigned char mxl = i2c_read_byte();
        unsigned char myh = i2c_read_byte();
        unsigned char myl = i2c_read_byte();
        unsigned char mzh = i2c_read_byte();
        unsigned char mzl = i2c_read_last_byte();
        i2c_stop();

        a->x = axh << 8 | axl;
        a->y = ayh << 8 | ayl;
        a->z = azh << 8 | azl;
        m->x = mxh << 8 | mxl;
        m->y = myh << 8 | myl;
        m->z = mzh << 8 | mzl;
}

float IIR2(float x, float* z)
{

        //const for butterworth lowpass fc 0.5Hz  
//      const float a[3] = {1.0000,   -1.8521,    0.8623};
//      const float b[3] = {0.0026,    0.0051,    0.0026};

        //const for butterworth lowpass fc 2Hz  
        const float a[3] = {1.0000,   -1.4190,    0.5533};
        const float b[3] = {0.0336,    0.0671,    0.0336};


        float y,r;

        r       =       a[1]*z[0]+a[2]*z[1];
        y       =       b[0]*(x-r)+b[1]*z[0]+b[2]*z[1];
        z[1]=   z[0];
        z[0]=   x-r;

        return y;

}


//cancels out movemt below threshold while using step sum to
int thr_filter(int  x, int * x_reg, int * y_reg)
{
        int  y;
        int  diff;
        int  sum = 0;
       
        const int  thr = 4;
        const int  lmt = 5;

        diff = x - *x_reg;

    if(abs(diff) <= thr)
        {
       sum += diff;
       if(abs(sum) >= lmt)
           {
           sum = 0;
           y = x;
                }
        else y = *y_reg;
        }
        else
        {
        y = x;
        sum = 0;
        }
   

        *x_reg = x;
        *y_reg = y;

        return y;
}


// Returns corrected and low-pass filtered magnetometer and accelerometer values
void read_data(vector *a, vector *m)
{
        //interal state buffers for IIR axis filtering
        static float zm_x[2] = {0.0, 0.0};
        static float zm_y[2] = {0.0, 0.0};
        static float zm_z[2] = {0.0, 0.0};
        static float za_x[2] = {0.0, 0.0};
        static float za_y[2] = {0.0, 0.0};
        static float za_z[2] = {0.0, 0.0};
       

        read_data_raw(a, m);
       
        //low pass filter acc
        a->x = IIR2(a->x, za_x);
        a->y = IIR2(a->y, za_y);
        a->z = IIR2(a->z, za_z);

        //compensate scale and offset, low pass filter mag
        m->x = IIR2(((m->x - m_min.x) / (m_max.x - m_min.x) * 2 - 1.0), zm_x);
        m->y = IIR2(((m->y - m_min.y) / (m_max.y - m_min.y) * 2 - 1.0), zm_y);
        m->z = IIR2(((m->z - m_min.z) / (m_max.z - m_min.z) * 2 - 1.0), zm_z);
}



float get_heading(const vector *a, const vector *m, const vector *p)
{
        vector E;
        vector N;

        // cross magnetic vector (magnetic north + inclination) with "down" (acceleration vector) to produce "west"
        // -- right hand rule says

        vector_cross(m, a, &E);
        vector_normalize(&E);

        // cross "down" with "east" to produce "north" (parallel to the ground)
        vector_cross(a, &E, &N);
        vector_normalize(&N);

        // compute heading
       
        float heading = atan2(vector_dot(&E, p), vector_dot(&N, p)) * 180.0 / M_PI;
        return heading;

}

float get_perpendicular(const vector *a, const vector *d, const vector *q)
{
       

        float sign = 0.0;
        vector norma = *a;

        if              (q->x == 0.0) {norma.x = 0.0; sign = norma.y;}// cancel out movement on undesired axis
        else if (q->y == 0.0) {norma.y = 0.0; sign = norma.x;} 
        vector_normalize(&norma);
       

        // compute angle
        float angle = acos(vector_dot(&norma,d)) * 180.0/M_PI;
        if(sign >= 0.0) angle *= -1;
       
        return angle;

}

int get_us(float angle, float deg_min, float deg_max, int pwm_min,int pwm_max)
{
        //adjust sign change of angular function to new zero offset
        if(angle < -180.0) angle += 360.0;
        if(angle >= 180.0) angle -= 360.0;

        //crop
        if(angle < deg_min) angle  = deg_min;
        else if (angle  > deg_max) angle  = deg_max;

        //scale to pwm
        float   ratio = ((float)(pwm_max - pwm_min)) / (deg_max - deg_min);
        int diff = ((int)((angle-deg_min) * ratio));
       
        return pwm_min + diff;
}