RC Boat using a C# GUI over XBee communication.

Dependencies:   MODSERIAL LSM9DS0 Motordriver PID mbed-rtos mbed

RC Boat Wiki Page

main.cpp

Committer:
mitchpang
Date:
2015-05-01
Revision:
9:b537a858040e
Parent:
8:1aeb13d290db

File content as of revision 9:b537a858040e:

#include "mbed.h"
#include "LSM9DS0.h"
#include "rtos.h"
#include "PID.h"
#include "motordriver.h"
#include "MODSERIAL.h"


//Gravity at Earth's surface in m/s/s
#define g0 9.812865328
//Number of samples to average.
#define SAMPLES 4
//Number of samples to be averaged for a null bias calculation
//during calibration.
#define CALIBRATION_SAMPLES 1024
//Convert from radians to degrees.
#define toDegrees(x) (x * 57.2957795)
//Convert from degrees to radians.
#define toRadians(x) (x * 0.01745329252)
//LSM9DS0 gyroscope sensitivity is 8.75 (millidegrees/sec)/digit when set to +-2G
#define GYROSCOPE_GAIN (0.007476806640625)
//Full scale resolution on the accelerometer is 0.0001220703125g/LSB.
#define ACCELEROMETER_GAIN (0.00006103515625 * g0)
//Sampling gyroscope at 100Hz.
#define GYRO_RATE   0.01
//Sampling accelerometer at 200Hz.
#define ACC_RATE    0.005
// SDO_XM and SDO_G are both grounded, so our addresses are:
#define LSM9DS0_XM  0x1E // Would be 0x1E if SDO_XM is LOW
#define LSM9DS0_G   0x6A // Would be 0x6A if SDO_G is LOW
#define RATE  0.05 // Define rate for PID loop
//Multiply the linear and angular speed to change from [-1,1] to [-Multiplier, Multiplier]
#define LINEAR_MULTIPLIER 5
#define ANGULAR_MULTIPLIER 90

#define LINEAR_MAX_SPEED 3.0
#define ANGULAR_MAX_SPEED 90.0

Serial pc(USBTX, USBRX);
LSM9DS0 imu(p28, p27, LSM9DS0_G, LSM9DS0_XM);
Mutex pcMutex;
Mutex setPointMutex;
Mutex processValueMutex;
MODSERIAL xbee(p13, p14, 64, 128);
PID linearController(1.0, 0.0, 0.0, RATE);
PID angularController(1.0, 0.0, 0.0, RATE);
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);
DigitalOut led4(LED4);
int usePID = 1;
int commReceived = 0;

//Gloabal Variables for comm system below
Ticker commTicker;


//Global Variables for motor control below
Motor  leftMotor(p21, p22, p23, 1); // pwm, fwd, rev
Motor rightMotor(p26, p25, p24, 1); // pwm, fwd, rev

//Offsets for the gyroscope.
//The readings we take when the gyroscope is stationary won't be 0, so we'll
//average a set of readings we do get when the gyroscope is stationary and
//take those away from subsequent readings to ensure the gyroscope is offset
//or "biased" to 0.
double w_xBias;
double w_yBias;
double w_zBias;

//Offsets for the accelerometer.
//Same as with the gyroscope.
double a_xBias;
double a_yBias;
double a_zBias;

//Accumulators used for oversampling and then averaging.
double a_xAccumulator = 0;
double a_yAccumulator = 0;
double a_zAccumulator = 0;
double w_xAccumulator = 0;
double w_yAccumulator = 0;
double w_zAccumulator = 0;

//Accelerometer and gyroscope readings for x, y, z axes.
double a_x;
double a_y;
double a_z;
double w_x;
double w_y;
double w_z;

//Previous Acceleromerter and gyroscope readings for integration
double last_a_x;
double last_a_y;
double last_a_z;

//Buffer for accelerometer readings.
int readings[3];
//Number of accelerometer samples we're on.
int accelerometerSamples = 0;
//Number of gyroscope samples we're on.
int gyroscopeSamples = 0;

//current readings of linear velocity
double v_x = 0;
double v_y = 0;
double v_z = 0;
double v_mag2 = 0; // linear velocity squared. sqrt() is comupationally difficult


//values for pwm wave to motors

float linearOutput = 0;
float angularOutput = 0;

float leftMotorCO = 0;
float rightMotorCO = 0;


//setpoints from xbee
float setLinear = 0;
float setAngular = 0;
int enabled = 0;
int lin_reverse;
int ang_reverse;

/**
 * Prototypes
 */
//Calculate the null bias.
void calibrateAccelerometer(void);
//Take a set of samples and average them.
void sampleAccelerometer(void);
//Calculate the null bias.
void calibrateGyroscope(void);
//Take a set of samples and average them.
void sampleGyroscope(void);



void debug(void const *args)
{
    while(1) {
        //pc.printf("linearOutput: %f    angularOutput: %f\n",linearOutput, angularOutput);
        //pc.printf("leftMotorCO: %f     rightMotorCO: %f\n", leftMotorCO, rightMotorCO);
        pc.printf("setLinear: %f     setAngular: %f\n", setLinear, setAngular);
        pc.printf("v_x: %f     angularSpeed: %f\n", v_x, w_z);
        pc.printf("linearOutput: %f     angularOutput: %f\n", linearOutput, angularOutput);
        pc.printf("leftMotorCO: %f     rightMotorCO: %f\n\n", leftMotorCO, rightMotorCO);
        Thread::wait(2000);
    }
}


void startPID()
{
    linearController.setMode(AUTO_MODE);
    angularController.setMode(AUTO_MODE);
    usePID = 1;
}

void stopPID()
{
    linearController.setMode(MANUAL_MODE);
    angularController.setMode(MANUAL_MODE);
    usePID = 0;
}


void parseCommand(char buff[], int length) {
    float *tempForward, *tempAngular;
    volatile char forwardBuffer [4];
    volatile char angularBuffer [4];
    int tempEnabled, tempPID;
    char statusChar;
    
    float *tempLinearPGain, *tempLinearIGain, *tempLinearDGain;
    float *tempAngularPGain, *tempAngularIGain, *tempAngularDGain;
    
    // Main control parsing
    if (0xCC == buff[0] && 10 == length) {
    
        tempForward = (float*) (buff + 1);
        tempAngular = (float*) (buff + 5);
        statusChar = buff[9]; //contains data for enable and enablePID
        tempPID = (int) (statusChar & 0x0F); //extract the last 4 bits and type cast to an int
        tempEnabled = (int) (statusChar & 0xF0); //extract the first 4 bits and type cast to an int

        //Change global variables
        setPointMutex.lock();
        setLinear = *tempForward;
        setAngular = *tempAngular;
        
        /*
        if (setLinear < 0) {
            setLinear = -setLinear; // make pos
            lin_reverse = 1;
        } else lin_reverse = 0;

        if (setAngular < 0) {
            setAngular = -setAngular; //make pos
            ang_reverse = 1;
        } else ang_reverse = 0;
        */
        enabled = tempEnabled;
        if (tempPID) {
            startPID();
        } else {
            stopPID();
        }
        setPointMutex.unlock();
    // Parsing change PID values command
    } else if (0x55 == buff[0] && 25 == length) {
        tempLinearPGain = (float*) (buff + 1);
        tempLinearIGain = (float*) (buff + 5);
        tempLinearDGain = (float*) (buff + 9);
        tempAngularPGain = (float*) (buff + 13);
        tempAngularIGain = (float*) (buff + 17);
        tempAngularDGain = (float*) (buff + 21);
        linearController.setTunings(*tempLinearPGain, *tempLinearIGain, *tempLinearDGain);
        angularController.setTunings(*tempAngularPGain, *tempAngularIGain, *tempAngularDGain);
    }

}

char waitForChar() {
    while (!xbee.readable()) {
        Thread::wait(10);
    }
    return xbee.getc();
}

void parseInput(void const *args)
{
    static int i = 0;
    static int length = 0;
    static char commBuffer [64];
    
    while (xbee.readable()) {
        xbee.getc();
    }
    
    while (1) {
        if (0x55 != waitForChar()) {
            continue;
        }
        if (0x55 != waitForChar()) {
            continue;
        }
        length = waitForChar();
        
        for (i = 0; i < length; ++i) {
            commBuffer[i] = waitForChar();
        }
        if (waitForChar() != 0xAA) {
            continue;
        }
        commReceived = 1;
        parseCommand(commBuffer, length);
        //Thread::wait(100);
    }
}


void checkCOMRecieved()
{
    if (commReceived == 0) {
        stopPID();
        enabled = 0;
        led1 = led2 = led3 = led4 = 1;
    } else {
        led1 = led2 = led3 = led4 = 0;
    }
    commReceived = 0;
}

void sampleAccelerometer(void const *args)
{
    while(1) {
        while(usePID != 1) {
            Thread::wait(1000); //wait 1 sec and check if we are using the PID loop again.
        }
        //Have we taken enough samples?
        if (accelerometerSamples == SAMPLES) {

            //Average the samples, remove the bias, and calculate the acceleration
            //in m/s/s.
            a_x = -((a_xAccumulator / SAMPLES) - a_xBias) * g0;// * ACCELEROMETER_GAIN;
            a_y = -((a_yAccumulator / SAMPLES) - a_yBias) * g0;// * ACCELEROMETER_GAIN;
            a_z = -((a_zAccumulator / SAMPLES) - a_zBias) * g0;// * ACCELEROMETER_GAIN;

            a_xAccumulator = 0;
            a_yAccumulator = 0;
            a_zAccumulator = 0;
            accelerometerSamples = 0;
            //pcMutex.lock();
            //pc.printf("a_x: %f  a_y: %f  a_z: %f \n",a_x,a_y,a_z);
            //pcMutex.unlock();

            // integrate to get velocity. make sure to subtract gravity downwards!

            v_x = v_x + (a_x + last_a_x)/2 * ACC_RATE * SAMPLES;
            v_y = v_y + (a_y + last_a_y)/2 * ACC_RATE * SAMPLES;
            v_z = v_z + (a_z + last_a_z)/2 * ACC_RATE * SAMPLES;

            last_a_x = a_x;
            last_a_y = a_y;
            last_a_z = a_z;
            //pcMutex.lock();
            //pc.printf("v_x: %f  v_y: %f  v_z: %f \n",v_x,v_y,v_z);
            //pcMutex.unlock();

        } else {
            //Take another sample.
            imu.readAccel();
            /*
            pcMutex.lock();
            pc.printf("A: ");
            pc.printf("%d", imu.ax);
            pc.printf(", ");
            pc.printf("%d",imu.ay);
            pc.printf(", ");
            pc.printf("%d\n",imu.az);
            pcMutex.unlock();
            */
            a_xAccumulator += imu.ax;
            a_yAccumulator += imu.ay;
            a_zAccumulator += imu.az;

            accelerometerSamples++;
            Thread::wait(ACC_RATE * 1000);
        }


    }
}

void calibrateAccelerometer(void)
{

    led1 = 1;

    a_xAccumulator = 0;
    a_yAccumulator = 0;
    a_zAccumulator = 0;


    //Take a number of readings and average them
    //to calculate the zero g offset.
    for (int i = 0; i < CALIBRATION_SAMPLES; i++) {

        imu.readAccel();

        a_xAccumulator += (double) imu.ax;
        a_yAccumulator += (double) imu.ay;
        a_zAccumulator += (double) imu.az;

        wait(ACC_RATE);

    }

    a_xAccumulator /= CALIBRATION_SAMPLES;
    a_yAccumulator /= CALIBRATION_SAMPLES;
    a_zAccumulator /= CALIBRATION_SAMPLES;

    //At 4mg/LSB, 250 LSBs is 1g.
    a_xBias = a_xAccumulator;
    a_yBias = a_yAccumulator;
    //a_zBias = (a_zAccumulator - (1/0.00006103515625));
    a_zBias = a_zAccumulator; // calibrate so that gravity is already out of the equation
    pc.printf("A_Bias     ax: %f     ay: %f     az: %f \n", a_xBias,a_yBias,a_zBias);
    /*
    pcMutex.lock();
    pc.printf("A_Bias     ax: %f     ay: %f     az: %f \n", a_xBias,a_yBias,a_zBias);
    pc.printf("%f", a_xBias);
    pc.printf(", ");
    pc.printf("%f",a_yBias);
    pc.printf(", ");
    pc.printf("%f\n",a_zBias);
    pcMutex.unlock();
    */
    a_xAccumulator = 0;
    a_yAccumulator = 0;
    a_zAccumulator = 0;
    pc.printf("done calibrating accel\n");
    led1 = 0;
}


void calibrateGyroscope(void)
{
    led2 = 1;
    w_xAccumulator = 0;
    w_yAccumulator = 0;
    w_zAccumulator = 0;

    //Take a number of readings and average them
    //to calculate the gyroscope bias offset.
    for (int i = 0; i < CALIBRATION_SAMPLES; i++) {
        imu.readGyro();
        w_xAccumulator += (double) imu.gx;
        w_yAccumulator += (double) imu.gy;
        w_zAccumulator += (double) imu.gz;
        Thread::wait(GYRO_RATE * 1000);

    }

    //Average the samples.
    w_xAccumulator /= CALIBRATION_SAMPLES;
    w_yAccumulator /= CALIBRATION_SAMPLES;
    w_zAccumulator /= CALIBRATION_SAMPLES;

    w_xBias = w_xAccumulator;
    w_yBias = w_yAccumulator;
    w_zBias = w_zAccumulator;

    pcMutex.lock();
    pc.printf("G_Bias: ");
    pc.printf("%f", w_xBias);
    pc.printf(", ");
    pc.printf("%f",w_yBias);
    pc.printf(", ");
    pc.printf("%f\n",w_zBias);
    pcMutex.unlock();

    w_xAccumulator = 0;
    w_yAccumulator = 0;
    w_zAccumulator = 0;
    pc.printf("done calibrating gyro\n");
    led2 = 0;
}

void sampleGyroscope(void const *args)
{
    while(1) {
        while(usePID != 1) {
            Thread::wait(1000); //wait 1 sec and check if we are using the PID loop again.
        }
        //Have we taken enough samples?
        if (gyroscopeSamples == SAMPLES) {

            //Average the samples, remove the bias, and calculate the angular
            //velocity in deg/s.
            w_x = ((w_xAccumulator / SAMPLES) - w_xBias); //* GYROSCOPE_GAIN;
            w_y = ((w_yAccumulator / SAMPLES) - w_yBias); //* GYROSCOPE_GAIN;
            w_z = ((w_zAccumulator / SAMPLES) - w_zBias); //* GYROSCOPE_GAIN;
            //pcMutex.lock();
            //pc.printf("w_x: %f  w_y: %f  w_z: %f \n",w_x,w_y,w_z);
            //pcMutex.unlock();
            w_xAccumulator = 0;
            w_yAccumulator = 0;
            w_zAccumulator = 0;
            gyroscopeSamples = 0;

        } else {
            imu.readGyro();
            /*
            pcMutex.lock();
            pc.printf("G: ");
            pc.printf("%d", imu.gx);
            pc.printf(", ");
            pc.printf("%d",imu.gy);
            pc.printf(", ");
            pc.printf("%d\n",imu.gz);
            pcMutex.unlock();
            */
            w_xAccumulator += imu.gx;
            w_yAccumulator += imu.gy;
            w_zAccumulator += imu.gz;

            gyroscopeSamples++;
            Thread::wait(GYRO_RATE * 1000);
        }

        Thread::wait(GYRO_RATE * 1000);
    }
}




void setup()
{
    pc.baud(9600); // Start serial at 115200 bps
    // Use the begin() function to initialize the LSM9DS0 library.
    // You can either call it with no parameters (the easy way):
    //uint16_t status = dof.begin();

    //Or call it with declarations for sensor scales and data rates:
    //uint16_t status = imu.begin(dof.G_SCALE_2000DPS,
    //                            dof.A_SCALE_2G, dof.M_SCALE_2GS);

    uint16_t status = imu.begin(imu.G_SCALE_245DPS, imu.A_SCALE_2G, imu.M_SCALE_2GS,
                                imu.G_ODR_760_BW_100, imu.A_ODR_400, imu.M_ODR_50);

    // begin() returns a 16-bit value which includes both the gyro
    // and accelerometers WHO_AM_I response. You can check this to
    // make sure communication was successful.
    pc.printf("LSM9DS0 WHO_AM_I's returned: 0x");
    pc.printf("%x\n",status);
    pc.printf("Should be 0x49D4\n");
    pc.printf("\n");

    // Set up PID Loops
    linearController.setInputLimits(-1.0, 1.0);
    angularController.setInputLimits(-1.0, 1.0);
    linearController.setOutputLimits(-1.0, 1.0);
    angularController.setOutputLimits(-1.0, 1.0);
    linearController.setSetPoint(0.0);
    angularController.setSetPoint(0.0);
    linearController.setMode(AUTO_MODE);
    angularController.setMode(AUTO_MODE);

}


int main()
{
    //float linearOutput = 0;
    //float angularOutput = 0;

    /*
    float leftMotorCO = 0;
    float rightMotorCO = 0;
*/
    pc.printf("Starting IMU filter test...\n");
    setup();


    //Initialize inertial sensors.
    calibrateAccelerometer();
    calibrateGyroscope();

    Thread accelThread(sampleAccelerometer);
    Thread gyroThread(sampleGyroscope);
    Thread commThread(parseInput);
    Thread debugThread(debug);
    commTicker.attach(&checkCOMRecieved, 2.0); // the address of the function to be attached (checkCOMRecieved) and the interval (2 seconds)
    /*
    //pcMutex.lock();
    pc.printf("done attaching tickers\n\n");
    //pcMutex.unlock();
    */
    while (1) {
        //pc.printf("in loop\n");
        if(usePID) {
            setPointMutex.lock();
            linearController.setSetPoint(setLinear);
            angularController.setSetPoint(setAngular);
            setPointMutex.unlock();
            if (abs(w_z) < 2.0f) w_z = 0; // if less than 2 deg/sec, assume it is noise and make it eqal to 0.
            //v_mag2 = v_x * v_x + v_y * v_y;
            processValueMutex.lock();
            linearController.setProcessValue(v_x/LINEAR_MAX_SPEED);
            angularController.setProcessValue(w_z/ANGULAR_MAX_SPEED); // w_z = degrees per second
            processValueMutex.unlock();
            //pc.printf("linearOutput: %f",linearOutput);
            linearOutput = linearController.compute();
            angularOutput = angularController.compute();
            
            /*
            if (lin_reverse == 0) {
                linearOutput = linearController.compute();
            } else {
                linearOutput = -linearController.compute();
            }
            if (ang_reverse == 0) {
                angularOutput = angularController.compute();
            } else {
                angularOutput = -angularController.compute();
            }
            */
            
        } else {// PID Disabled
            linearOutput = setLinear;
            angularOutput = setAngular;
        }

        if (enabled) {

            leftMotorCO = (linearOutput - angularOutput);
            rightMotorCO = (linearOutput + angularOutput);

            leftMotor.speed(leftMotorCO);
            rightMotor.speed(rightMotorCO);


            /*
            leftMotor.speed(setLinear);
            rightMotor.speed(setAngular);
            */
        } else {
            leftMotor.coast();
            rightMotor.coast();
        }

        Thread::wait(RATE * 1000);
        /*
               //pcMutex.lock();
               pc.printf("%f,%f,%f\n",toDegrees(imuFilter.getRoll()),
                         toDegrees(imuFilter.getPitch()),
                         toDegrees(imuFilter.getYaw()));
               //pcMutex.unlock();
        */
    }

}