Version for EMG Threshold finding

Dependencies:   HIDScope MODSERIAL QEI biquadFilter mbed

Fork of prog_BioRobotics_Group9_StampRobot by Gerhard Berman

main.cpp

Committer:
GerhardBerman
Date:
2016-11-07
Revision:
48:73a5f7f62aec
Parent:
47:13b4a318a3d0

File content as of revision 48:73a5f7f62aec:

#include "mbed.h"
#include <math.h>
#include "MODSERIAL.h"
#include "QEI.h"
#include "HIDScope.h"
#include "BiQuad.h"
 
/*
THINGS TO CONSIDER
- Motor action of motor 1 is inverted because it is mounted
opposite to motor 2 in the tower. Check if the clockwise directions of the 
motors correspond to the positive q1, q2-directions (both counterclockwise)
 in the original IK-sketch.
- Robot arms should be placed manually into reference position before resetting board.
*/
 
//set pins
DigitalIn encoder1A (D13);                          //Channel A from Encoder 1
DigitalIn encoder1B (D12);                              //Channel B from Encoder 1
DigitalIn encoder2A (D11);                                  //Channel A from Encoder 2
DigitalIn encoder2B (D10);                                      //Channel B from Encoder 2
AnalogIn emg0( A0 );
AnalogIn emg1( A1 );
DigitalIn button1(D3);
DigitalIn button2(D9);
 
DigitalOut motor1DirectionPin(D7);
PwmOut motor1MagnitudePin(D6);
DigitalOut motor2DirectionPin(D4);
PwmOut motor2MagnitudePin(D5);
DigitalOut ledGrn(LED_GREEN);
DigitalOut ledRed(LED_RED);
DigitalOut ledBlue(LED_BLUE);
 
//library settings
Serial pc(USBTX,USBRX);
HIDScope    scope(6);
 
//go-Ticker settings
Ticker MeasureTicker;
volatile bool MeasureTicker_go=false;
void MeasureTicker_act(){MeasureTicker_go=true;};               // Activates go-flag
 
//constant values
const float PI = 3.141592653589793;
const int cw = 0;               //values for cw and ccw are inverted! cw=0 and ccw=1
const int ccw = 1;
double threshold_l=0.09;                                        //left arm EMG threshold
double threshold_r = 0.08;                                      //right arm EMG threshold
float EMGgain = 1.0;
 
//set lengths
//float L0 = 0.232;                     //height of motor axes above table surface
float L1 = 0.45;                        //length of proximal arm
float L2 = 0.35;                            //length of distal arm
float TowerHeight = 0.232;                  //height of motor axes above table surface
float StampHeight = 0.056;              // height of end effector
float y_stampup = 0.1;                  //height stamp while not stamping: 10cm above                           table surface
float y_stampdown = -0.04;                  //height stamp while    stamping: at table surface
 
float MotorGain = 8.4;          // rad/s for PWM, is max motor speed
float MotorMaxSpeed = 0.1;                  //define a maximum PWM speed for the motor
float t_sample = 0.002;                                     //seconds
const float maxStampDistance = 0.7;
const float minStampDistance = 0.3;
float Kp = 4.0;                          //proportional controller constant
float Ki = 0.04;                            //integrative controller constant
float Kd = 0.02;                        //derivative controller constant
float N = 25;                               //PIDF filter constant
//(Higher N is faster derivative action but more sensitive to noise)
 
//set initial conditions for inputs and positions
float biceps_l = 0, biceps_r = 0;
double envelopeL = 0, envelopeR = 0;
int T=0;                                //EMG 'switch' variable
float ReferencePosition_x = 0.35;       //starting position for x reference position        
float ReferencePosition_y = L1 + TowerHeight - StampHeight; 
//starting position for y reference position
float ReferencePosition_xnew = 0.35;     
float ReferencePosition_ynew = L1 + TowerHeight - StampHeight;
float Position_x = 0.0;
float Position_y = 0.0;
 
//set initial conditions for angles, errors and motor values
float q1 = 0,q2 = PI/2;
float q1_ref = 0, q2_ref = 0;
float q1start = 0; 
float q12start = PI/2;
float q1Encoder = 0, q12Encoder = 0;
float q12Out = 0;
 
float q1_error_prev = 0, q2_error_prev = 0;
float DerTotalError1 = 0, DerTotalError2 = 0;
float q1IntError = 0, q2IntError = 0;
float TotalError1_prev = 0, TotalError2_prev = 0;
float TotalError1= 0, TotalError2= 0;
 
float motorValue1 = 0.0, motorValue2 = 0.0;
int counts1 = 0, counts2 = 0;
int counts1Prev = 0, counts2Prev = 0;
 
//set reference angle boundaries
float q1_refOutNew = 0, q2_refOutNew = 0;
float q1_refOutMin = 0;                 //Physical min angle 0.00 radians
float q1_refOutMax = 1.37;                  //Physical max angle 1.47 radians - 0.1 rad
float q2_refOutMin = 0.91;              //Physical  min angle 0.81 radians + 0.1 rad
float q2_refOutMax = 2.07;              //Physical max angle 2.17 radians - 0.1 rad
 
//set BiQuad filters/filter chains
BiQuad pidf;                    //PID Filter
 
BiQuadChain bcq1R;              //Right EMG filter chain 1: notch filter+highpass
BiQuadChain bcq2R;                  //Right EMG filter chain 2: lowpass
BiQuad bq1R(9.9110e-01,-1.6036e+00,9.9110e-01,-1.6036e+00,9.8221e-01);      
// Notch filter wo=50; bandwidth=wo/35
BiQuad bq2R(9.1497e-01,-1.8299e+00,9.1497e-01,-1.8227e+00,8.3718e-01);      
// High pass Butterworth filter 2nd order, Fc=10;
BiQuad bq3R(1.3487e-03,2.6974e-03,1.3487e-03,-1.8935e+00,8.9886e-01);       
// Low pass Butterworth filter 2nd order, Fc = 8;
 
BiQuadChain bcq1L;              //Left EMG filter chain 1: notch filter+highpass
BiQuadChain bcq2L;                  //Left EMG filter chain 2: lowpass
BiQuad bq1L(9.9110e-01,-1.6036e+00,9.9110e-01,-1.6036e+00,9.8221e-01);      
// Notch filter wo=50; bandwidth=wo/35
BiQuad bq2L(9.1497e-01,-1.8299e+00,9.1497e-01,-1.8227e+00,8.3718e-01);      
// High pass Butterworth filter 2nd order, Fc=10;
BiQuad bq3L(1.3487e-03,2.6974e-03,1.3487e-03,-1.8935e+00,8.9886e-01);       
// Low pass Butterworth filter 2nd order, Fc = 8;
// In the following: R is used for right arm EMG, L is used for left arm EMG
 
//define encoder counts and degrees
QEI Encoder1(D12, D13, NC, 32);                     // turns on encoder
QEI Encoder2(D10, D11, NC, 32);                         // turns on encoder
const int counts_per_revolution = 4200;                 //counts per motor axis revolution
const int inverse_gear_ratio = 131;
const float resolution = counts_per_revolution/(2*PI/inverse_gear_ratio);   //87567.0496892 counts per radian, encoder axis
 
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
 
void FilteredSample(int &Tout, double &envelopeLout, double &envelopeRout)
{   
//This function reads EMG, filters it to acquire an EMG envelope and generates a 
//T-switch value which specifies the movement of the robot
 
    double inLout = emg0.read();
    double inRout = emg1.read();
    
    double outRfilter1 = bcq1R.step(inRout);
    double outRrect= fabs(outRfilter1);
    envelopeRout = bcq2R.step(outRrect);
    
    double outLfilter1 = bcq1L.step(inLout);
    double outLrect = fabs(outLfilter1);
    envelopeLout = bcq2L.step(outLrect);
  
    double biceps_l = (double) envelopeLout * EMGgain; //emg0.read();
//left biceps filtered EMG signal with a gain

    double biceps_r = (double) envelopeRout * EMGgain;  
//right biceps filtered EMG signal with a gain
    if (biceps_l > threshold_l && biceps_r > threshold_r){
        //both arms activated: stamp moves down
        Tout = -2;
        }
    else if (biceps_l > threshold_l && biceps_r <= threshold_r){
        //arm 1 activated, move left
        Tout  = -1;
        }
    else if (biceps_l <= threshold_l && biceps_r > threshold_r){
        //arm 1 activated, move right
        Tout = 1;
        }
    else{
        //wait(0.2);
        Tout = 0;
        }   
}
 
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
 
void GetReferenceKinematics1(float &q1Out, float &q2Out, float &q1_refOut, float &q2_refOut)
{    
//This function reads current joint angles from encoder, calculates reference position from 
//T-switch value, converts reference position to reference joint angles and checks boundaries.
    
    //get joint positions q feedback from encoder
    float Encoder1Position = counts1/resolution;    //angular encoder position (rad)
    float Encoder2Position = -1*counts2/resolution; //NEGATIVE due to opposite build                                    //up in tower
    
    q1Encoder = Encoder1Position*inverse_gear_ratio;
    q12Encoder = Encoder2Position*inverse_gear_ratio;
    q1Out = q1start + q1Encoder;                    //angular motor axis position (rad)
    q12Out = q12start + q12Encoder;                 //encoder 2 gives sum of q1 and q2!
    q2Out = q12Out - q1Out;             
    //float q1deg = q1Out*360/2/PI;                 //angle in degrees
    //float q2deg = q2Out*360/2/PI;                 //angle in degrees
    
    //get end effector position with trigonometry
    Position_x = (L1*sin(q1) + L2*sin(q1+q2));
    Position_y = (L1*cos(q1) + L2*cos(q1+q2)) + TowerHeight - StampHeight;  
    
    //get reference position from EMG based T-switch values  
    if (T == -2){
        //both EMGs active: stamp moves down
        ReferencePosition_xnew = ReferencePosition_x;
        ReferencePosition_ynew = ReferencePosition_y - 0.015;
        }
    else if (T==-1){
        //left EMG active, move stamp left
        ReferencePosition_xnew = ReferencePosition_x - 0.0009;
        ReferencePosition_ynew = y_stampup;
        }
    else if (T==1){
        //right EMG active, move stamp right
        ReferencePosition_xnew = ReferencePosition_x + 0.0009;
        ReferencePosition_ynew = y_stampup;
        }
    else{ //T==0
        //no EMG active, no x-movement, y-position restored to non-stamping position
        ReferencePosition_ynew = y_stampup;
        }
    
    //check reference position boundaries
    if (ReferencePosition_xnew > maxStampDistance){
        //target too far to be reached
        ReferencePosition_x = maxStampDistance;
        ReferencePosition_y = y_stampup;
        //pc.printf("Target too far! \r\n");
        }
    else if (ReferencePosition_xnew < minStampDistance){
        //target too close
        ReferencePosition_x = minStampDistance;
        ReferencePosition_y = y_stampup;
        //pc.printf("Target too close! \r\n");
        }
    else if (ReferencePosition_ynew < y_stampdown){
        //target under table surface
        ReferencePosition_x = ReferencePosition_xnew;
        ReferencePosition_y = y_stampdown;
        //pc.printf("Target too low! \r\n");
        }
    else {
        //target within reach
        ReferencePosition_x = ReferencePosition_xnew;        
        ReferencePosition_y = ReferencePosition_ynew;
        }
    
    //calculate reference joint angles for the new reference position
    float PointPositionArm2_x = ReferencePosition_x;
    float PointPositionArm2_y = ReferencePosition_y - TowerHeight + StampHeight;
    float PointVectorArm2 = sqrt(pow(PointPositionArm2_x,2)+pow(PointPositionArm2_y,2));
    
    float alpha = atan(PointPositionArm2_y/PointPositionArm2_x);
    float beta = acos((L2*L2-L1*L1-pow(PointVectorArm2,2))/(-2*L1*PointVectorArm2));
    q1_refOutNew = PI/2 - (alpha+beta);
    q2_refOutNew = PI - asin(PointVectorArm2*sin(beta)/L2);
    
    //check reference angle boundaries
    if (q1_refOutNew < q1_refOutMin){
        //new q1_ref too small
        q1_refOut = q1_refOutMin;
        //pc.printf("\r\n Under q1 angle boundaries\r\n");
        }
    else if (q1_refOutNew > q1_refOutMax){
        //new q1_ref too large
        q1_refOut = q1_refOutMax;
        //pc.printf("\r\n Above q1 angle boundaries\r\n");
        }
    else {
        //new q1_ref within reach
        q1_refOut = q1_refOutNew;
        }
        
    if (q2_refOutNew < q2_refOutMin){
        //new q2_ref too small
        q2_refOut = q2_refOutMin;
        pc.printf("\r\n Under q2 angle boundaries");
        }
    else if (q2_refOutNew > q2_refOutMax){
        //new q2_ref too large
        q2_refOut = q2_refOutMax;
        pc.printf("\r\n Above q2 angle boundaries");
        }
    else {
        //new q2_ref within reach
        q2_refOut = q2_refOutNew;
        }        
    
    scope.set(0, ReferencePosition_xnew);
    scope.set(1, ReferencePosition_ynew);
    scope.set(2, envelopeL);
    scope.set(3, envelopeR);
    scope.set(4, T);
    scope.send();
}    
 
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
 
void FeedbackControl1(float q1_ref, float q2_ref, float q1, float q2, float &motorValue1Out, float &motorValue2Out)
{
//This function calculates the error between angles and refernce angles, and provides
//motor values via a PIDF controller.
    
    //calculate error values
    float q1_error = q1_ref - q1;               // proportional angular error in radians
    float q2_error = q2_ref - q2;               // proportional angular error in radians
    
    //PIDF total error output
    float TotalError1 = pidf.step(q1_error);
    float TotalError2 = pidf.step(q2_error);
    
    //calculate motor values from the total errors    
    motorValue1Out = TotalError1/MotorGain;       
    motorValue2Out = TotalError2/MotorGain;       
    
    //prepare for next ticker cycle
    q1_error_prev = q1_error;
    q2_error_prev = q2_error;
    TotalError1_prev = TotalError1;
    TotalError2_prev = TotalError2;
}
 
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
 
void SetMotor1(float motorValue1, float motorValue2)
{
//This function sets the PWM and direction bits for the motors. 
//motorValues outside range are truncated to within range.
 
    //control motor 1
    if (motorValue1 >=0){ 
        //clockwise rotation
        motor1DirectionPin=cw;
        }
    else{
        //counterclockwise rotation 
        motor1DirectionPin=ccw;
        }
    if (fabs(motorValue1)>MotorMaxSpeed){
        motor1MagnitudePin = MotorMaxSpeed;             //motor values truncated
        }
    else{
        motor1MagnitudePin = fabs(motorValue1);
        }
    
    //control motor 2
    if (motorValue2 >=0){  
        //counterclockwise rotation due to inverse buildup in tower
        motor2DirectionPin=cw;                  //action is ccw, due to faulty                                  motor2DirectionPin (inverted)
        }
    else{    
        //clockwise rotation due to inverse buildup in tower
        motor2DirectionPin=ccw;                         //action is cw, due to faulty                                   motor2DirectionPin (inverted)
        }
    if (fabs(motorValue2)>MotorMaxSpeed){
        motor2MagnitudePin = MotorMaxSpeed;
        }
    else{
        motor2MagnitudePin = fabs(motorValue2);
        }
}
 
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
 
void MeasureAndControl()
{
// This function measures the EMG of both arms, calculates via inverse kinematics
// what the joint reference positions should be, and controls the motor with 
// a Feedback controller. This is called from a Ticker.
    FilteredSample(T, envelopeL, envelopeR);
    GetReferenceKinematics1(q1, q2, q1_ref, q2_ref);
    FeedbackControl1( q1_ref, q2_ref, q1, q2, motorValue1, motorValue2);
    SetMotor1(motorValue1, motorValue2);
}
 
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
 
int main()
{
 //pc.baud(115200);
 //pc.printf("Test putty IK");
 ledRed=1;
 ledBlue=1; 
 ledRed=0;

 //set BiQuad chains
 bcq1R.add(&bq1R).add(&bq2R);                       
 bcq2R.add(&bq3R);
 bcq1L.add(&bq1L).add(&bq2L);
 bcq2L.add(&bq3L);
 pidf.PIDF( Kp, Ki, Kd, N, t_sample );                  //set PID filter
 
 //start up encoders
 counts1 = Encoder1.getPulses();                        //gives position of encoder in counts 
 counts2 = Encoder2.getPulses();                        //gives position of encoder in counts
 wait(20.0);                                            //time to sart up HIDScope and EMG
 MeasureTicker.attach(&MeasureTicker_act, 0.002);       //initialize MeasureTicker, 500 Hz
 
 while(1)
    {
        if (MeasureTicker_go){
            MeasureTicker_go=false;
            ledGrn = 1;
            ledBlue = 0;
            MeasureAndControl();                    //execute MeasureAndControl
            counts1 = Encoder1.getPulses();         //get encoder counts again
            counts2 = Encoder2.getPulses();             //get encoder counts again
            ledBlue = 1;
            ledGrn = 0;
            }
    }
}