Code used to localize a robot using IMU with PID control

Dependencies:   IMUfilter LSM9DS0 Motordriver mbed

Fork of Robot_feedback_ir by Adrian Winata

main.cpp

Committer:
cardenb
Date:
2015-10-20
Revision:
1:e71cba217586
Parent:
0:98baffd99476

File content as of revision 1:e71cba217586:

#include "mbed.h"
#include "motordriver.h"
#include "LSM9DS0.h"
#include "IMUfilter.h"

#define RATE  0.1

// Callback to update the PID controller
Ticker pid_updater;
// Set a PID update rate of 10 Hz
const float PID_RATE = 0.1;
// Call back to update the IMU samples
Ticker imu_updater;
Ticker heading_reseter;
// Set a sample rate of 200 Hz for the IMU
const float IMU_RATE = 0.025;
const int NUM_IMU_SAMPLES = 4;
// Count the number of times the IMU is sampled to average every 100 samples.
int imu_count = 0;
Ticker dist_updater;
const float DIST_RATE = 0.1;
const float DIST_LIMIT = 0.5;

const float INITIAL_SPEED_LEFT = 0.7;
const float INITIAL_SPEED_RIGHT = 0.609;

const float TURN_TIME = 0.55;

/*/////// PID control gains
 / These values need to be adjusted in accordance with the individual
 / actuators (motors) either by trial and error or computation. The information
 / here should help: 
*////////
/*
 * Done with Ziegler-Nichols method
 
#define Kp    0.002
#define Ki    0.00002
#define Kd    0.075
 */
/*
 * Done with random method from stack overflow which is more upvoted than Ziegler-Nichols
 
#define Kp 0.192
#define Ki 0.0
#define Kd 0.048
 */

#define Kp 0.0001
#define Ki 0.0
#define Kd 0.00001
/*
 */
// SDO_XM and SDO_G are pulled up, so our addresses are:
#define LSM9DS0_XM_ADDR  0x1D // Would be 0x1E if SDO_XM is LOW
#define LSM9DS0_G_ADDR   0x6B // Would be 0x6A if SDO_G is LOW

// peripheral objects
DigitalOut f(LED1); // forward
DigitalOut b(LED2); // backward
DigitalOut l(LED3); // left
DigitalOut r(LED4); // right
//See http://mbed.org/cookbook/Motor
//Connections to dual H-brdige driver for the two drive motors
Motor left(p21, p22, p23, 1); // pwm, fwd, rev, has brake feature
Motor right(p26, p25, p24, 1);
LSM9DS0 imu(p9, p10, LSM9DS0_G_ADDR, LSM9DS0_XM_ADDR); // imu sensor
Serial pc(USBTX, USBRX); // usb serial for debugging

// function prototypes
void setup();
void initIMU();
float getHeading();
void updatePID();
void updateIMU();
float GetAvgHeading();
void UpdateMotorSpeed(float pid_output);
void updateDist();
void headingReset();
void turnRight();
void turnLeft();
void calibrateAccel();
void turnToAngle(float angle);

// global variables
int count; // counter for IR

float calcHeading; // calculated heading
float heading[1]; // index 0 = goal, index 1 = current
float heading_avg[NUM_IMU_SAMPLES]; // moving average array
float sum;

float previousError; // previous error
float pidError; // error
float steadyError; // steady-state error

float P; // proportional error
float I; // integral error
float D; // derivative error

float kp; // proportional constant
float ki; // integral constant
float kd; // derivative constant

float output; // PID controller output

float dt; // update frequency

float motorSpeed1; // motor speed for left motor
float motorSpeed2; // motor speed for right motor
float dist[2]; // 0 = x, 1 = y
float vel[2]; // 0 = x, 1 = y
float accel[2]; // 0 = x, 1 = y
float cur_dist; // resultant distance



// function implementations
void setup()
{
    // Use the begin() function to initialize the LSM9DS0 library.
    // You can either call it with no parameters (the easy way):
    uint16_t status = imu.begin();

    // Make sure communication is working
    pc.printf("LSM9DS0 WHO_AM_I's returned: 0x%X\n", status);
    pc.printf("Should be 0x49D4\n\n");
    initIMU();
    
    // Initialize motor speeds
    motorSpeed1 = INITIAL_SPEED_LEFT;
    motorSpeed2 = INITIAL_SPEED_RIGHT;
    // Attach the PID Update routine to a callback
    pid_updater.attach(&updatePID, PID_RATE);
    // Attach the IMU update routine to a callback to computer a moving average
    imu_updater.attach(&updateIMU, IMU_RATE);
    dist_updater.attach(&updateDist, DIST_RATE);
    // set motor speeds
    left.speed(motorSpeed1);
    right.speed(motorSpeed2);
}

// obtains the initial heading upon bootup
void initIMU()
{
    imu.readMag();
    imu.readAccel();
    imu.calcBias();
    imu.readMag();
    wait(0.1);
    // initial heading
    float sum = 0;
    for( int i = 0; i < NUM_IMU_SAMPLES; ++i) {
        sum += getHeading();
    }
    float initHeading = sum/NUM_IMU_SAMPLES;
        
    heading[0] = initHeading;
    heading[1] = initHeading;
}

// obtains an updated heading
float getHeading()
{
    imu.readMag(); // reads current value
    calcHeading = imu.calcHeading(); // sets current value

    // pc.printf("Compass Heading (in degrees): ");
    // pc.printf("%2f\r\n",calcHeading);
    // pc.printf("%2f\r\n",heading[0]);

    return calcHeading;
}

// PID update attached to a ticker.
void updatePID()
{
    float current = heading[1];
    float target = heading[0];
    float dt = PID_RATE;
    // pc.printf("current=%0.2f, target=%0.2f, dt=%0.2f\r\n", current, target, dt);
    // calculate difference between actual and goal values
    pidError = target - current;
    if (pidError < -270) pidError += 360;
    if (pidError >  270) pidError -= 360;

    // accumulates error over time
    steadyError += pidError*dt;

    // integrator windup compensation (saturates to actuator's output)
    if (steadyError < -10.0) steadyError = -10.0;
    if (steadyError > 10.0) steadyError = 10.0;

    P = kp*pidError; // proportional error
    I = ki*steadyError; // integral error
    D = kd*(pidError - previousError)/dt;  // derivative error

    // save current error
    previousError = pidError;

    float pidOutput = P + I + D;  // calculate the PID output
    pidOutput /= 300.0;  // scale it down to get it within the range of the actuator - probably needs tuning
    if(pidOutput < -0.1) pidOutput = -0.1;
    if(pidOutput > 0.1) pidOutput = 0.1;

    // pc.printf("P = %f | I = %f | D = %f | Output = %f\r\n",P,I,D,pidOutput);
    UpdateMotorSpeed(pidOutput);
}

void UpdateMotorSpeed(float pid_output) {
    // updates new motor speeds
    motorSpeed1 -= pid_output;
    motorSpeed2 += pid_output;

    // pc.printf("Wheel speed difference = %0.2f\r\n", motorSpeed1-motorSpeed2);
    // set motor speeds
    left.speed(motorSpeed1);
    right.speed(motorSpeed2);
}

float GetAvgHeading() {
    float sum = 0;
    for( int i = 0; i < NUM_IMU_SAMPLES; ++i ) {
        sum += heading_avg[i];
    }
    return sum/NUM_IMU_SAMPLES;
}
        

// Get reach the minimum number of samples and then use a moving average filter
// Keep the count below 2*num_samples to prevent overflow, but keep at a minimum of 1*num_samples
void updateIMU() {
    // Get current index into the ring buffer (heading_avg)
    int moving_idx = imu_count % NUM_IMU_SAMPLES;
    // Add the current heading into the buffer
    heading_avg[moving_idx] = getHeading();
    
    // Keep the IMU count between num_samples & 2*num_samples
    // Get the average heading as long as the minimum number of samples have been reached.
    if( imu_count >= NUM_IMU_SAMPLES && imu_count <= 2*NUM_IMU_SAMPLES ) {
        if( imu_count == 2*NUM_IMU_SAMPLES ) {
            imu_count = NUM_IMU_SAMPLES;
        }
        heading[1] = GetAvgHeading();
        // pc.printf("av_heading=%0.2f\r\n", heading[1]);
    }
    imu_count += 1;
}

// Update acceleration, velocity, and distance vectors.
void updateDist() {
    // Get latest acceleration reading.
    imu.readAccel();
    
    // Get acceleration vector
    accel[0] = imu.ax - imu.abias[0];
    accel[1] = imu.ay - imu.abias[1];
    
    // Get velocity vector
    vel[0] += accel[0]*RATE;
    vel[1] += accel[1]*RATE;
    
    // Get distance vector
    dist[0] += vel[0]*RATE;
    dist[1] += vel[1]*RATE;
    
    // Get resultant distance
    // d = (dx^2 + dy^2)^(1/2)
    cur_dist = sqrt(dist[0]*dist[0] + dist[1]*dist[1]);
    if( cur_dist > DIST_LIMIT ) {
        turnRight();
        // Reset vel and dist to get accurate distance measurement to next move
        vel[0] = 0;
        vel[1] = 0;
        dist[0] = 0;
        dist[1] = 0;
        cur_dist = 0;
        pidError = 0;
        previousError = 0;
    }
    // pc.printf("ax=%0.2f, ay=%0.2f\r\nvx=%0.2f, vy=%0.2f\r\ndx=%0.2f, dy=%0.2f, d=%0.2f\r\n", accel[0], accel[1], vel[0], vel[1], dist[0], dist[1], cur_dist);
}

void headingReset() {
    motorSpeed1 = INITIAL_SPEED_LEFT;
    motorSpeed2 = INITIAL_SPEED_RIGHT;
    
    // set motor speeds
    left.speed(motorSpeed1);
    right.speed(motorSpeed2);

    heading[0] = getHeading();
    heading[1] = getHeading();
}

void turnRight() {
    
    left.stop(0);
    right.stop(0);
    left.speed(-1);
    right.speed(1);
    
    /*
    int turn_ang = heading[0] + 90;
    turn_ang = turn_ang% 360;
    turnToAngle(turn_ang);
    */
    
    wait(TURN_TIME);
    headingReset();
}

void turnToAngle(float angle) {
    bool hasTurned = false;
    left.stop(0);
    right.stop(0);
    left.speed(-1);
    right.speed(1);
    while(!hasTurned) {
        float cur_head = getHeading();
        pc.printf("%0.2f, %0.2f\r\n", cur_head, angle);
        if( cur_head > angle - 20 && cur_head < angle + 20 ) {
            hasTurned = true;
            heading[0] = cur_head;
            left.stop(0);
            right.stop(0);
        }
        
    }
}
void turnLeft() {
    left.stop(0);
    right.stop(0);
    left.speed(1);
    right.speed(-1);
    
    /*
    int turn_ang = heading[0] - 90;
    turn_ang = turn_ang% 360;
    turnToAngle(turn_ang);
    */
    
    wait(TURN_TIME);
    headingReset();
}

int main()
{
    // initialization functions
    setup();

    // variables
    dt = RATE;
    kp = Kp;
    ki = Ki;
    kd = Kd;
    
    /*
    // Attach the PID Update routine to a callback
    pid_updater.attach(&updatePID, PID_RATE);
    // Attach the IMU update routine to a callback to computer a moving average
    imu_updater.attach(&updateIMU, IMU_RATE);
    dist_updater.attach(&updateDist, DIST_RATE);

    */
    // spin in the main loop. Updates will happen asychronously.
    while(1) {
    }
}