Control up to two motors using filtered EMG signals and a PID controller

Dependencies:   FastPWM HIDScope MODSERIAL QEI Matrix biquadFilter controller errorFetch mbed motorConfig refGen MatrixMath inverseKinematics

Fork of Minor_test_serial by First Last

Revision:
43:dd0888f86357
Parent:
42:ae78ff03d9d6
Child:
44:d157094b48d5
diff -r ae78ff03d9d6 -r dd0888f86357 main.cpp
--- a/main.cpp	Fri Oct 27 10:37:43 2017 +0000
+++ b/main.cpp	Wed Nov 01 14:54:50 2017 +0000
@@ -12,32 +12,33 @@
 #include "motorConfig.h"
 #include "errorFetch.h"
 #include "BiQuad.h"
+#include "inverseKinematics.h"
 
 // ADJUSTABLE PARAMETERS
+// robot dimensions
+const float L1 = 0.391;
+const float L2 = 0.391;
+
 // controller ticker time interval
-const float Ts = 0.01;
+const float Ts = 0.008;
+
+// Defining an inverse-kinematics calculator
+inverseKinematics robotKinematics(L1,L2,Ts);
 
 // EMG filter parameters
 // calibration time
 const int calSamples = 1000;
 
+// KINEMATICS reference motor position
+volatile double Mp1C = 0;
+volatile double Mp2C = 0;
+
 // Initialize average and max EMG value for calibration to 0 and 1 respectively
 volatile float avgEMGvalue = 0;
 volatile double maxEMGvalue = 1;
 
-// high pass
-BiQuadChain HPbqc;
-BiQuad HPbq1(0.9837,-1.9674, 0.9837,1.0000,-1.9769,0.9770);
-BiQuad HPbq2(1.0000, -2.0000, 1.0000, 1.0000, -1.9903, 0.9904);
-
-
-// low pass
-BiQuadChain LPbqc;
-BiQuad LPbq1(1.0e-7*1.2023, 1.0e-7*2.4046, 1.0e-7*1.2023, 1.0000, -1.9313, 0.9327);
-BiQuad LPbq2(1.0000, 2.0000, 1.0000, 1.0000, -1.9702, 0.9716);
-
 // Controller parameters
-const float k_p = 1;
+const float k_p = 0.01;
 const float k_i = 0; // Still needs a reasonable value
 const float k_d = 0; // Again, still need to pick a reasonable value
 
@@ -46,7 +47,7 @@
 
 // LOGISTICS
 // Declaring finite-state-machine states
-enum robotStates {KILLED, ACTIVE, CALIBRATING};
+enum robotStates {KILLED, ACTIVE};
 volatile robotStates currentState = KILLED;
 volatile bool stateChanged = true;
 
@@ -72,9 +73,6 @@
 InterruptIn sw3(SW3);
 InterruptIn button1(D2);
 InterruptIn button2(D3);
-//AnalogIn pot2(A2);
-//AnalogIn emg0( A0 );
-//AnalogIn emg1( A1 );
 
 // Defining LED outputs to indicate robot state-us
 DigitalOut ledG(LED_GREEN);
@@ -90,14 +88,12 @@
 volatile float w;
 
 
-void sendDataToPc(float data1, float data2, float data3, float data4, float data5, float data6){
+void sendDataToPc(float data1, float data2, float data3, float data4){
         // Capture data
         x = data1;
         y = data2;
         z = data3;
         q = data4;
-        k = data5;
-        w = data6;
         scope.set(0, x);   
         scope.set(1, y); 
         scope.set(2, z);
@@ -113,9 +109,11 @@
 const float maxAngle = 1*3.14*posRevRange; // max angle in radians
 
 
-// Function getReferencePosition returns reference angle based on potmeter 1
-refGen ref1(A2, maxAngle);
-refGen ref2(A3, maxAngle);
+// References based on potmeter 1 and 2
+// Set Vx using pot1 = A5
+// Set Vy using pot2 = A4
+refGen ref1(A4, 0.1);
+refGen ref2(A3, 0.1);
 
 // readEncoder reads counts and revs and logs results to serial window
 errorFetch e1(gearRatio, Ts);
@@ -130,39 +128,15 @@
 
 // PROBLEM: if I'm processing the state machine in the endless while loop, how can I adjust robot behavior in the ticker (as it'll keep running)? Do I need to also implement it there? If so, why bother with the while(1) in the main function in the first place?
 void measureAndControl(){
-    // Read encoders and EMG signal (unnfiltered reference)
+    // Read encoders and potmeter signal (unnfiltered reference)
     m1counts = Encoder1.getPulses();
     m2counts = Encoder2.getPulses();
     
     double m1position = e1.fetchMotorPosition(m1counts);
     double m2position = e2.fetchMotorPosition(m2counts);
     
-    // measuring and normalizing EMG signals to use as basis for reference
-
-    double emg1 = ref1.getReference();
-    double emg2 = ref2.getReference();
-    
-    
-    // Filtering the EMG signals   
-
-    double emg1HP = HPbqc.step(emg1);
-    double emg1HP_abs = fabs(emg1HP);
-    double emg1HPLP_abs = LPbqc.step(emg1HP_abs);
-//    thet1 = fabs(thet1);
-//    thet1 = LPbqc.step(thet1);
-    
-
-    double emg2HP = HPbqc.step(emg2);
-    double emg2HP_abs = fabs(emg2);
-    double emg2HPLP_abs = LPbqc.step(emg2HP_abs);
-//    thet2 = LPbqc.step(thet2);
-
-    // Something worth trying: set a position setpoint that constantly changes but will never be reached until EMG value is 0 as it is computed from the robot's current position    
-//    thet1 = m1position + thet1;
-//    thet2 = m1position + thet2;
-    
-    // Other possibility: use thet1 and thet2 directly as reference angles. That'll require the user to hold muscle tension to stay in a certain position though
-    
+    double pot1 = ref1.getReference();
+    double pot2 = ref2.getReference();
     
     // Finite state machine
     switch(currentState){
@@ -175,31 +149,43 @@
                     pc.printf("Killed state \r\n");
                     stateChanged = false;               
                     }
-
-                // Send reference data to pc
                 
                 // Set LED to red
                 ledR = 0;
                 ledG = 1;
                 ledB = 1;
-                // need something here to check if "killswitch" has been pressed (?)
-                // NOTE: state transition is handled using buttons triggering functions motorConfig::kill() and motorConfig::turnMotorOn
-//                e1.fetchError(m1position, thet1);
-//                e2.fetchError(m2position, thet1);
 
-                sendDataToPc(emg1, emg1HP_abs, emg1HPLP_abs, emg2, emg2HP, emg2HPLP_abs); // just send the EMG signal value to HIDscope
-                
+                sendDataToPc(pot1, pot2, m1counts, m2counts); // just send the EMG signal value to HIDscope
                 break;
                 }
+                
+                
             case ACTIVE:
                 {
                 if(stateChanged){
                     pc.printf("Active state \r\n");
+                    Mp1C = m1position;
+                    Mp2C = m2position;
+                    stateChanged = false;
                     }
+                    
+                // Using potmeter signals to define a desired end-effector velocity;
+                
+                double vx = pot1;
+                double vy = pot2;
                 
-                // Compute error
-                e1.fetchError(m1position, emg1);
-                e2.fetchError(m2position, emg2);
+                // Translating vx and vy to angular velocities
+                Matrix q_dot = robotKinematics.computeAngularVelocities(vx,vy,Mp1C,Mp2C);
+                double q_dot1 = q_dot(1,1);
+                double q_dot2 = q_dot(2,1);
+                
+                // Computing position setpoint for next ticker tick using desired end-effector velocity
+                double Mp1N = Mp1C + Ts*q_dot1;
+                double Mp2N = Mp2C + Ts*q_dot2;
+                
+                // Compute error between actual CURRENT motor position and NEXT position setpoint
+                e1.fetchError(m1position, Mp1N);
+                e2.fetchError(m2position, Mp2N);
                 
                 // Compute motor value using controller and set motor
                 float motorValue1 = motorController1.control(e1.e_pos, e1.e_int, e1.e_der);
@@ -208,7 +194,11 @@
                 motor2.setMotor(motorValue2);
                 
                 // Send data to HIDscope
-                sendDataToPc(emg1, emg2, e1.e_pos, e2.e_pos, motorValue1, motorValue2);
+                sendDataToPc(Mp1N, Mp2N, q_dot1, q_dot2);
+                
+                // Prepare for next round
+                Mp1C = Mp1N;
+                Mp2C = Mp2N;
                 
                 // Set LED to blue
                 ledR = 1;
@@ -217,59 +207,7 @@
                 // NOTE: state transition is handled using buttons triggering functions motorConfig::kill() and motorConfig::turnMotorOn
                 break;
                 }
-            case CALIBRATING:
-                {
-                // NOTE: maybe wrap this whole calibrating thing in a library?
-                
-               // Do I need to kill motors here?
-
-                
-                // Initialization of CALIBRATE state
-                static int Ncal = 0;
-                std::vector<float> EMGsamples;
-                
-                if(stateChanged){                                        
-                    // Kill motors
-                    pc.printf("Calibrate state \r\n");
-                    motor1.kill();
-                    motor2.kill();
-                    
-                    // Clear sample value vector and reset counter variable
-                    EMGsamples.clear();
-                    Ncal = 0;
-                    stateChanged = false;
-                    }
-
-                // fill the array with sample data if it is not yet filled
-                if(Ncal < calSamples){
-                    EMGsamples.push_back(emg1);
-                    Ncal++;
-                    }
-                // if array is filled compute the mean value and switch to active state
-                else {
-                    // Set new avgEMGvalue
-                    avgEMGvalue = std::accumulate(EMGsamples.begin(), EMGsamples.end(), 0)/calSamples; // still needs to return this value 
-                    double maxEMGvalue = *std::max_element(EMGsamples.begin(), EMGsamples.end());
-                    
-                    
-                    pc.printf("Avg emg value is %.2f changed state to active", avgEMGvalue);
-                    // State transition logic
-                    currentState = ACTIVE;
-                    stateChanged = true;
-                    Ncal = 0;
-                    }
-                
-                // Set LED to green
-                ledR = 1;
-                ledG = 0;
-                ledB = 1;
-//                sendDataToPc(thet1, thet2, 0.0, 0.0, 0.0, 0.0);
-                break;
-                }
         }
-    
-    
-    
     }
 
 void r1SwitchDirection(){
@@ -292,17 +230,11 @@
     stateChanged = true;
     }
     
-void calibrateRobot(){
-    currentState = CALIBRATING;
-    stateChanged = true;
-    }
 
 int main()
     {
     pc.baud(115200);
     pc.printf("Main function");
-    HPbqc.add(&HPbq1).add(&HPbq2);
-    LPbqc.add(&LPbq1).add(&LPbq2);
     
     // Attaching state change functions to buttons;
     sw2.fall(&killSwitch);