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-04-30
Revision:
7:bde99b0b3a86
Parent:
6:c0a2ac7a8c26
Child:
8:1aeb13d290db

File content as of revision 7:bde99b0b3a86:

#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

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(0.00001, 0.0, 0.0, RATE);
PID angularController(0.0000000001, 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;


/**
 * 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("linearMagnitude: %f     angularSpeed: %f\n", v_mag2, w_z);
        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;
}

//Variables for storing commands
char commBuffer [64];
int currentBuff = 0;


void parseCommand()
{
    float *tempForward, *tempAngular;
    volatile char forwardBuffer [4];
    volatile char angularBuffer [4];
    int tempEnabled, tempPID;
    char statusChar;
    
    
    // First char (commBuffer[0]) is header info. commBuffer[1-4] is forward direction in raw float value. CommBUffer[5-8] is reverse direction in raw float value.
    //memcpy(&tempForward, (commBuffer + 1), 4);
    //memcpy(&tempAngular, (commBuffer + 5), 4);
    

    
    // now convert the char arrays into floats
    tempForward = (float*) (commBuffer + 1);
    tempAngular = (float*) (commBuffer + 5);
    
    
    //tempForward = (float*) forwardBuffer;
    //tempAngular = (float*) angularBuffer;
    
    statusChar = commBuffer[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
    setLinear = *tempForward * LINEAR_MULTIPLIER;
    setAngular = *tempAngular * ANGULAR_MULTIPLIER;
    enabled = tempEnabled;

    if (tempPID) {
        startPID();
    } else {
        stopPID();
    }

}

void parseInput(void const *args)
{
    static int i = 0;
    
    while (1) {
        /*
        while (xbee.readable()) {
            commBuffer[1 - currentBuff][i] = xbee.getc();
            if (commBuffer[1 - currentBuff][i] == 0xAA) {
                i = 0;
                currentBuff = 1 - currentBuff;
                if (commBuffer[currentBuff][0] == 0xFF) {
                    parseCommand();
                    commReceived = 1;
                }
            } else {
                ++i;
            }
        }
        */
        
        while (xbee.readable()) {
            commBuffer[i] = xbee.getc();
            if (commBuffer[i] == 0xAA) { //end of message char
                i = 0;
                if (commBuffer[0] == 0xFF) { // buffer recieved is correct length and bit. parse the data
                    parseCommand();
                    commReceived = 1;
                }
            } else {
                ++i;
            }
        }
        





        /*
        while (xbee.readable()) {// something arrived from the xbee
            commBuffer [0] = xbee.getc(); //[1 - currentBuff][i] = xbee.getc();
            commBuffer [1] = xbee.getc();
            if (commBuffer [0] == 0x5 && commBuffer[1] == 0x5) { //the frame from xbee has ended // [1 - currentBuff][i] == '\r') {
                // next 4 chars (32 bits) are going to be the forward speed
                for (int fBi = 0; fBi < 4; ++fBi) {
                    forwardBuffer[fBi] = xbee.getc();
                }
                for (int rBi = 0; rBi < 4; ++rBi) {
                    reverseBuffer[rBi] = xbee.getc();
                }
                enabled = (int) xbee.getc();
                tempPID = (int) xbee.getc();
                if (tempPID) {
                    startPID();
                } else {
                    stopPID();
                }
                commBuffer [2] = xbee.getc();
                commBuffer [3] = xbee.getc();
                if (commBuffer [3] == 0xA && commBuffer [4] == 0xA) {
                commReceived = 1;
                }

            }
        }
        */
        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(0, LINEAR_MULTIPLIER);
    angularController.setInputLimits(0, ANGULAR_MULTIPLIER);
    linearController.setOutputLimits(0, 1.0);
    angularController.setOutputLimits(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;
    int lin_reverse;
    int ang_reverse;
    /*
    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) {
            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;
            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(abs(v_x));
            angularController.setProcessValue(abs(w_z)); // w_z = degrees per second
            processValueMutex.unlock();
            if (lin_reverse == 0 | ang_reverse == 0) {
                linearOutput = linearController.compute();
                angularOutput = angularController.compute();
            } else {
                linearOutput = -linearController.compute();
                angularOutput = -angularController.compute();
            }
        } else {
            linearOutput = setLinear/LINEAR_MULTIPLIER;
            angularOutput = setAngular/ANGULAR_MULTIPLIER;
        }

        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();
        */
    }

}