State machine + some functions

Dependencies:   HIDScope MODSERIAL QEI biquadFilter mbed Servo

main.cpp

Committer:
Spekdon
Date:
2018-11-01
Revision:
46:9b60a3c1acab
Parent:
45:829a3992d689

File content as of revision 46:9b60a3c1acab:

#include "mbed.h"
#include "MODSERIAL.h"
#include "QEI.h"
#include "HIDScope.h"
#include "BiQuad.h"
#include "PID_controller.h"
//#include "kinematics.h"
#include "Servo.h"
#include "processing_chain_emg.h"
#include <vector>

//pc
MODSERIAL pc(USBTX, USBRX);

// emg inputs
AnalogIn    emg0( A0 );
AnalogIn    emg1( A1 );
AnalogIn    emg2( A2 );
AnalogIn    emg3( A3 );
Servo       gripper(A5);
//InterruptIn button1(SW2);
DigitalIn   buttongripper(D1);
double range;

// motor ouptuts
PwmOut      motor1_pwm(D5);
DigitalOut  motor1_dir(D4);
PwmOut      motor2_pwm(D6);
DigitalOut  motor2_dir(D7);
AnalogIn vx_adjustment(A4);
AnalogIn vy_adjustment(A5);

// defining encoders
QEI motor_1_encoder(D12,D13,NC,32);
QEI motor_2_encoder(D10,D11,NC,32);

// other objects
DigitalIn   button(D0);
DigitalIn   emgstop(SW2);
DigitalOut  led_R(LED_RED);
DigitalOut  led_B(LED_BLUE);
DigitalOut  led_G(LED_GREEN);

// tickers and timers
Ticker loop_ticker;
Timer state_timer;
Timer emg_timer;

// Timeouts and related variables
Timeout make_button_active;
bool button_suppressed = false;
bool gripperopen = true;

// States
enum States {failure, waiting, calib_emg, calib_enc, operational, demo, homing}; //All possible robot states
enum Emg_measures_states {not_in_calib_emg, calib_right_bicep, calib_right_tricep, calib_left_bicep, calib_left_tricep}; // States inside
enum calib_enc_states {not_in_calib_enc, calib_enc_q1, calib_enc_q2};

//Global variables/objects
States current_state;
Emg_measures_states current_emg_calibration_state = not_in_calib_emg;
calib_enc_states calib_enc_state = not_in_calib_enc;

double des_vx, des_vy, x, y, q1, q2, e1, e2, x_norm, y_norm; //will be set by the motor_controller function
double u1, u2;
double vxmax = 1.0, vymax = 1.0;
double right_bicep_max = 0.0, right_tricep_max = 0.0, left_bicep_max= 0.0, left_tricep_max = 0.0;
double scaling_right_bicep = 1.0, scaling_right_tricep = 1.0, scaling_left_bicep = 1.0, scaling_left_tricep = 1.0;

// Variables for emg
double raw_emg_0, process_emg_0;
double raw_emg_1, process_emg_1;
double raw_emg_2, process_emg_2;
double raw_emg_3, process_emg_3;


// Variables for calibration
double calib_q1 = 3.1415926535f;
double calib_q2 = double(7)/double(6) * 3.1415926535f;
double off_set_q1 = 0.5f*3.1415926535f; // This variable is used to determine the off set from our definition from q1 and q2.
double off_set_q2 = 3.1415926535f;

// Variables defined for the homing state
double q1_homing = 0.5f*3.1415926535f, q2_homing = 3.1415926535f;
double qref1 = 0.5f*3.1415926535f, qref2 = 3.1415926535f;
double beta = 0.05;
double k_hom = 0.05;

// For the state calib_enc
double q1old;
double q2old;

vector<double> currentconfig;
vector<double> newconfig;
vector<double> ref;
vector<double> testconfig;

double currentx;
double currenty;

double L1 = 0.4388;
double L2 = 0.4508;

// Meaning of process_emg_0 and such
// - process_emg_0 is right biceps
// - process_emg_1 is right triceps
// - process_emg_2 is left biceps
// - process_emg_3 is left triceps

int counts_per_rotation = 32;
bool state_changed = false;
const double T = 0.001;

bool gripperclosed = false;

// Resolution of the encoder at the output axle
double resolution = (2.0f*3.1415926535f/double(counts_per_rotation))*(1.0/131.0); // In radians

// Functions

vector<double> forwardkinematics_function(double& q1, double& q2) {
    // input are joint angles, output are x and y position of end effector
    
    //previously +x01 and +y01
    currentx = L1*cos(q1)-L2*cos(q2);
    currenty = L1 * sin(q1) - L2 * sin(q2); 
    
    vector<double> xy;
    xy.push_back(currentx);
    xy.push_back(currenty);
    return xy;
   
}
 
vector<double> inversekinematics_function(const double& T, double ref1, double ref2, double& q1, double& q2, double& des_vx, double& des_vy) {
    // x, y: positions of end effector | T: time to hold current velocity | qref1, qref2: reference thetas | q1, q2: current thetas | vx, vy: desired x, y velocities
    
    // pseudo inverse jacobian to get joint speeds
    // input are desired vx and vy of end effector, output joint angle speeds
 
    double q1_star_des; // desired joint velocity of q1_star
    double q2_star_des; // same as above but then for q2_star
    double C;
 
    // The calculation below assumes that the end effector position is calculated before this function is executed
    // In our case the determinant will not equal zero, hence no problems with singularies I think.
    
    C = L1*L2*sin(ref1)*cos(ref2) - L1*L2*sin(ref2)*cos(ref1);
    q1_star_des = double(1/C)*(-L2*cos(ref2)*des_vx - L2*sin(ref2)*des_vy);
    q2_star_des = double(1/C)*((L2*cos(ref2)-L1*cos(ref1))*des_vx + (L2*sin(ref2)-L1*sin(ref1))*des_vy);
    
    qref1 = T*q1_star_des;
    qref2 = T*(q2_star_des+q1_star_des);
    
    double testq1 = ref1+qref1;
    double testq2 = ref2+qref2;
    newconfig = forwardkinematics_function(testq1, testq2);
    
    if (sqrt(pow(newconfig[0],2)+pow(newconfig[1],2)) > 1)
    {
        qref1 = ref1;
        qref2 = ref2;
    }
    else
    {
        qref1 = ref1 + qref1;
        qref2 = ref2 + qref2;
    }

    vector<double> thetas;
    thetas.push_back(qref1);
    thetas.push_back(qref2);
    return thetas;
}

void measure_all() 
{
    q1 = (motor_1_encoder.getPulses()*2.0f*3.1415926535f/counts_per_rotation)*(1.0/131.0) + off_set_q1; //do this here, and not in the encoder interrupt, to reduce computational load
    q2 = (motor_2_encoder.getPulses()*2.0f*3.1415926535f/counts_per_rotation)*(1.0/131.0) + off_set_q2;
    currentconfig = forwardkinematics_function(q1,q2); //previously x,y also input 
    raw_emg_0 = emg0.read(); //sample analog voltages (all sampling theory applies, you might get aliasing etc.)
    raw_emg_1 = emg1.read();
    raw_emg_2 = emg2.read();
    raw_emg_3 = emg3.read();
    processing_chain_emg(raw_emg_0, raw_emg_1, raw_emg_2, raw_emg_3, process_emg_0, process_emg_1, process_emg_2, process_emg_3);  // processes the emg signals
}

void output_all() {
    motor1_pwm = 0.4+0.6*fabs(u1);
    
    if (motor1_pwm > 1.0f){
        motor1_pwm = 1.0f;
    }
    motor1_dir = u1 > 0.0f;
    motor2_pwm = 0.4+0.6*fabs(u2);
    if (motor2_pwm > 1.0f){
        motor2_pwm = 1.0f;
    }
    motor2_dir = u2 > 0.0f;
    static int output_counter = 0;
    output_counter++;
}

void unsuppressing_button(){
    button_suppressed = false;    
}

void state_machine() {
    switch(current_state) { //States can be: failure, wait, calib_enc, calib_emg, operational, demo,
        case waiting:  //Nothing useful here, maybe a blinking LED for fun and communication to the user
            if (button.read()==false) 
            {
                current_state = calib_emg; //the NEXT loop we will be in calib_enc state
                current_emg_calibration_state = calib_right_bicep;
                calib_enc_state = calib_enc_q2;
                state_changed = true;
            }
            
            break; //to avoid falling through to the next state, although this can sometimes be very useful.
        
        case calib_enc:
        
            if (state_changed==true) 
            {
                state_timer.reset();
                state_timer.start();
                state_changed = false;
                led_G = 0;
                led_B = 1;
                led_R = 1;
                //u1 = 0.2;
                //u2 = -0.65f;
                q1old = 0; 
                q2old = 0;
            }
            
            switch(calib_enc_state){
                case calib_enc_q2:
                    if (q2 - q2old == 0.0 && state_timer.read() > 5.0f)
                    {
                        calib_enc_state = calib_enc_q1;
                        off_set_q2 = calib_q2 - q2;
                        //u2 = -0.4;
                        //u1 = -0.75;
                        state_timer.reset();
                        state_timer.start();
                    }    
                    q2old = q2;
                    break;
                case calib_enc_q1:
                    if (q1 - q1old == 0.0 && state_timer.read() > 5.0f)
                    {
                        calib_enc_state = not_in_calib_enc;
                        current_emg_calibration_state = calib_right_bicep;
                        current_state = calib_emg;
                        state_changed = true;
                        off_set_q1 = calib_q1 - q1;
                        //u1 = 0;
                        state_timer.reset();
                        state_timer.start();
                    }    
                    q1old = q1;
                    break;
                default:
                    current_state = failure;
            }
            break;
            
        case calib_emg:     //calibrate emg-signals
            if (state_changed == true){
                state_timer.reset();
                state_timer.start();
                emg_timer.reset();
                emg_timer.start();
                state_changed = false;
                }
            // first the led will be purple, during this time the right biceps should be maximally contracted
            // after five seconds and when the right biceps are relaxed, it switches to a blue led
            // when the blue led is active the right triceps should be maximally contracted
            // then again it switches to purple for left biceps and after that blue for left triceps
            // after this, the emg signals are calibrated
            switch(current_emg_calibration_state){
                case calib_right_bicep:
                    led_R = 0;
                    led_G = 1;
                    led_B = 0;
                    if(emg_timer < 5.0f){
                        if (process_emg_0 > right_bicep_max){
                            right_bicep_max = process_emg_0;
                            }
                        }
                    else if ((process_emg_0 < 0.1*right_bicep_max) || (emgstop.read() == false)){
                            scaling_right_bicep = 1.0/ right_bicep_max;
                            current_emg_calibration_state = calib_right_tricep;
                            emg_timer.reset();
                            emg_timer.start();
                        }
                    break;
                case calib_right_tricep:
                    led_R = 1;
                    led_G = 1;
                    led_B = 0;
                    if(emg_timer < 5.0f){
                            if (process_emg_1 > right_tricep_max){
                                right_tricep_max = process_emg_1;
                                }
                            }
                    else if ((process_emg_1 < 0.1*right_tricep_max) || (emgstop.read() == false)){
                            scaling_right_tricep = 1.0/ right_tricep_max;
                            current_emg_calibration_state = calib_left_bicep;
                            emg_timer.reset();
                            emg_timer.start();
                        }
                    break;
                case calib_left_bicep:
                    led_R = 0;
                    led_G = 1;
                    led_B = 0;
                    if(emg_timer < 5.0f){
                        if (process_emg_2 > left_bicep_max){
                            left_bicep_max = process_emg_2;
                            }
                        }
                    else if ((process_emg_2 < 0.1*left_bicep_max) || (emgstop.read() == false)){
                            scaling_left_bicep = 1.0/ left_bicep_max;
                            current_emg_calibration_state = calib_left_tricep;
                            emg_timer.reset();
                            emg_timer.start();
                        }
                    break;
                case calib_left_tricep:
                    led_R = 1;
                    led_G = 1;
                    led_B = 0;
                    if(emg_timer < 5.0f){
                       if (process_emg_3 > left_tricep_max){
                           left_tricep_max = process_emg_3;
                       }
                    }
                    else if ((process_emg_3 < 0.1*left_tricep_max) || (emgstop.read() == false)){
                      scaling_left_tricep = 1.0/ left_tricep_max;
                      current_emg_calibration_state = not_in_calib_emg;
                      current_state = operational;
                      state_changed = true;
                      emg_timer.reset();
                      led_R = 1;
                      led_G = 1;
                      led_B = 1;
                    }
                    break;
                default:
                    current_state = failure;
                    state_changed = true;
            }
            
            break;
                    
        case homing: 
            if (state_changed == true){
                    state_timer.reset();
                    state_timer.start();
                    qref1 = q1; //NOT SURE IF WE NEED THIS. I do not think so, but just to be sure.
                    qref2 = q2;
                }
            des_vx = min(beta, k_hom*(q1 - q1_homing)); // Little bit different then that Arvid told us, but now it works with the motor controller
            des_vy = min(beta, k_hom*(q2 - q2_homing));
                
            // The value of 3.0 and 2*resolution can be changed
            if (fabs(q1-q1_homing) <= 2*resolution && fabs(q2-q2_homing) <= 2 * resolution ){
                    if (state_timer > 3.0f){
                        current_state = operational;
                        state_changed = true;
                        des_vx = 0; // Not sure if needed but added it anyways.
                        des_vy = 0;
                    }
                }
            else{
                state_timer.reset();    
            }
            break;
        
        case operational:       //interpreting emg-signals to move the end effector
            if (state_changed == true){
                    state_changed = false;
            }
            
            // normalization 
            x_norm = process_emg_0 * scaling_right_bicep - process_emg_1 * scaling_right_tricep;
            y_norm = process_emg_2 * scaling_left_bicep - process_emg_3 * scaling_left_tricep;
               
             //x_norm = -1+2*vx_adjustment.read();
             //y_norm = -1+2*vy_adjustment.read();  
                            
            // here we have to determine the desired velocity based on the processed emg signals and calibration
            // x velocity
            if (x_norm >= 0.8) { des_vx = 0.2; }
            else if(x_norm >= 0.6) { des_vx = 0.15; }
            else if(x_norm >= 0.4) { des_vx = 0.1; }
            else if(x_norm >= 0.2) { des_vx = 0.05; } 
            else if(x_norm <= -0.8) { des_vx = -0.2; }
            else if(x_norm <= -0.6) { des_vx = -0.15; }
            else if(x_norm <= -0.4) { des_vx = -0.1; }
            else if(x_norm <= -0.2) { des_vx = -0.05; }
            else { des_vx = 0; }
            
            // y velocity
            if (y_norm >= 0.8) { des_vy = 0.2; }
            else if(y_norm >= 0.6) { des_vy = 0.15; }
            else if(y_norm >= 0.4) { des_vy = 0.1; }
            else if(y_norm >= 0.2) { des_vy = 0.05; } 
            else if(y_norm <= -0.8) { des_vy = -0.2; }
            else if(y_norm <= -0.6) { des_vy = -0.15; }
            else if(y_norm <= -0.4) { des_vy = -0.1; }
            else if(y_norm <= -0.2) { des_vy = -0.05; }
            else { des_vy = 0; }
    
            if (button.read() == false && button_suppressed == false ) {
                current_state = demo; 
                state_changed = true;
                button_suppressed = true;
                
                make_button_active.attach(&unsuppressing_button,0.5);

            } 
            
            if (buttongripper.read() == false && button_suppressed == false ) {
                button_suppressed = true;
                if (gripperopen){gripper = 0; gripperopen = false;}
                else {gripper = 1; gripperopen = true;}

                make_button_active.attach(&unsuppressing_button,0.5);
            }
            
            break;
            
        case demo: //moving according to a specified trajectory
            // We want to draw a square. Hence, first move to a certain point and then start moving a square.
            if (state_changed == true){
                    state_changed = false;
                    state_timer.reset();
                    state_timer.start();
                    des_vx = 0.1;
                    des_vy = 0;
            }
            double old_vx;
            double old_vy;        
            if (state_timer > 2.0f){
                old_vx = des_vx;
                old_vy = des_vy;
                
                des_vy = -old_vx;
                des_vx = old_vy;
                
                state_timer.reset();  
                state_timer.start(); 
            }
            
            
            //        des_vx = 0.1;           // first we move to the right
            //        des_vy = 0.0;
            
            //static double new_vx, new_vy;
                        
            //if(state_timer > 3.0f){         // after waiting an extra second we start on another side of the square
            //    state_timer.reset();
            //    state_timer.start();
            //    des_vx = new_vx;
            //    des_vy = new_vy;
            //}
            //else if(state_timer > 2.0f){                          // we have a velocity of 0.1 m/s for 2 seconds, so we make a square of 20 by 20 cm
            //    if(des_vx > 0){new_vx = 0.0; new_vy = 0.1;}       // here we check which side of the square we were on
            //    else if(des_vy > 0){new_vx = -0.1; new_vy = 0.0;}
            //    else if(des_vx < 0){new_vx = 0.0; new_vy = -0.1;}
            //    else if(des_vy < 0){new_vx = 0.1; new_vy = 0.0;}
            //    des_vx = 0;
            //    des_vy = 0;
            //}
       
                
            if (button.read() == false && button_suppressed == false ) {
                current_state = operational;
                state_changed = true;
                
                button_suppressed = true;
                
                make_button_active.attach(&unsuppressing_button,0.5);
            }
            
            break;
        
        case failure: //no way to get out
            u1 = 0.0f;
            u2 = 0.0f;
            led_R = 0;
            led_G = 1;
            led_B = 1;
            break;
    }
}

void motor_controller() 
{
    if (current_state >= operational) { // we can (ab)use the fact that an enum is actually an integer, so math/logic rules still apply
        ref = inversekinematics_function(T,qref1,qref2,q1,q2,des_vx,des_vy); //many different states can modify your reference position, so just do the inverse kinematics once, here
        qref1 = ref[0];
        qref2 = ref[1];
        e1 = qref1 - q1; //tracking error (q_ref - q_meas)
        e2 = qref2 - q2;
        PID_controller(e1*(180/3.14),e2*(180/3.14),u1,u2,T); //feedback controller or with possibly fancy controller additions...; pass by reference
        } //otherwise we just don’t mess with the value of control variable ‘u’ that is set somewhere in the state-machine.
}


void loop_function() {
    measure_all();              //measure all signals
    state_machine();            //Do relevant state dependent things
    motor_controller();         //Do not put different motor controllers in the states, because every state can re-use the same motor-controller!
    output_all();               //Output relevant signals, messages, screen outputs, LEDs etc.
}


int main() 
{
    pc.baud(115200);
    motor1_pwm.period_us(60);
    motor2_pwm.period_us(60);
    current_state = waiting; //we start in state ‘waiting’ and current_state can be accessed by all functions
    u1 = 0.0f; //initial output to motors is 0.
    u2 = 0.0f;
    bqc0.add(&bq0high).add(&bq0notch).add(&bq0notch2).add(&bq0notch3).add(&bq0notch4);        // filter cascade for emg 0
    bqc1.add(&bq1high).add(&bq1notch).add(&bq1notch2).add(&bq1notch3).add(&bq1notch4);        // filter cascade for emg 1
    bqc2.add(&bq2high).add(&bq2notch).add(&bq2notch2).add(&bq2notch3).add(&bq2notch4);        // filter cascade for emg 2
    bqc3.add(&bq3high).add(&bq3notch).add(&bq3notch2).add(&bq3notch3).add(&bq3notch4);        // filter cascade for emg 3
    loop_ticker.attach(&loop_function, T); //Run the function loop_function 1000 times per second
    led_R = 1;
    led_B = 1;
    led_G = 1;
    
    gripper = 1;
    int m = 0;
    while (true) {
    m++;
    if (m%1000)
    {   pc.printf("error1: %f, error2: %f, pwm1: %f, pwm2: %f, qref1: %f, qref2: %f, vx: %f, vy: %f, q1: %f, q2: %f, gripper: \r\n",  e1, e2, u1, u2, qref1, qref2, des_vx, des_vy, q1, q2);}
    //testconfig = forwardkinematics_function(qref1,qref2);
    //double x = testconfig[0];
    //double y = testconfig[1];
    //pc.printf("x: %f, y: %f \r\n", x,y);
    }  //Do nothing here (timing purposes)
}