A quadcopter control Software (Still in development). achieved single axis stability!!!!! released for others benefit. if you'd like to help co-develop this code, then please let me know
Dependencies: MovingAverageFilter MyI2C PID RC mbed-rtos mbed
Currently on hold, due to the fact that i don't own a RX/TX system
Revision 1:e08a4f517989, committed 2013-08-27
- Comitter:
- KarimAzzouz
- Date:
- Tue Aug 27 09:38:49 2013 +0000
- Parent:
- 0:54b67cd15a5b
- Commit message:
- initial commit, achieved single axis stability
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/BMP085/BMP085.cpp Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,129 @@
+#include "BMP085.h"
+
+BMP085::BMP085(){}
+
+void BMP085::write(char reg,char data){
+
+ char tx[2]={reg,data};
+
+ I2C0.blockwrite((BMP_ADDRESS << 1)&0xFE, tx, 2);
+
+ }
+
+int16 BMP085::read (char reg){
+
+ char tx = reg;
+ char rx[2];
+
+ I2C0.blockwrite((BMP_ADDRESS << 1)&0xFE, &tx, 1);
+
+ I2C0.blockread((BMP_ADDRESS << 1)|0x01, rx, 2);
+
+ int16 output = ((int16) rx[0] << 8) | ((int16) rx[1]);
+ return output;
+}
+
+uint8 BMP085::readInt (char reg){
+
+ char tx = reg;
+ char rx;
+
+ I2C0.blockwrite((BMP_ADDRESS << 1)&0xFE, &tx, 1);
+
+ I2C0.blockread((BMP_ADDRESS << 1)|0x01, &rx, 1);
+
+ return rx;
+}
+
+// Get Calibration Values
+void BMP085::Calibrate(void){
+
+ AC1 = read(0xAA);
+ AC2 = read(0xAC);
+ AC3 = read(0xAE);
+ AC4 = read(0xB0);
+ AC5 = read(0xB2);
+ AC6 = read(0xB4);
+ B1 = read(0xB6);
+ B2 = read(0xB8);
+ MB = read(0xBA);
+ MC = read(0xBC);
+ MD = read(0xBE);
+
+}
+
+//Uncompensated temperature takes 4.5ms to read, so we'll just send a read flag and read the data the next cycle since our control loop repeats every 5 ms
+
+void BMP085::readUT_Flag(void){
+
+ write(0xF4,0x2E);
+}
+
+// reading uncompensated Pressure depends on our resolution setting, we'll select ultra high resolution setting so the delay will be 25.5ms (5 iterations);
+
+void BMP085::readUP_Flag(void){
+
+ write(0xF4,(0x34 + (OSS<<6)));
+}
+
+uint16 BMP085::readUT(void){
+
+ uint16 ut = (uint16)read(0xF6);
+
+ return ut;
+
+}
+uint32 BMP085::readUP(void){
+
+ uint32 up = (((uint32) readInt(0xF6) << 16) | ((uint32) readInt(0xF7) << 8) | (uint32) readInt(0xF8)) >> (8-OSS);
+
+ return up;
+}
+
+int16 BMP085::read_Temperature(void){
+
+ int32 X1,X2;
+
+ X1 = (((int32)readUT() - (int32)AC6)*(int32)AC5) >> 15;
+ X2 = ((int32)MC << 11)/(X1 + MD);
+
+ B5 = X1+X2;
+
+ return ((B5 + 8)>>4);
+}
+
+int32 BMP085::read_Pressure(void){
+
+ int32 X1, X2, X3, B3, B6, p;
+ uint32 B4, B7;
+
+ B6 = B5 - 4000;
+ // Calculate B3
+ X1 = (B2 * (B6 * B6)>>12)>>11;
+ X2 = (AC2 * B6)>>11;
+ X3 = X1 + X2;
+ B3 = (((((int32)AC1)*4 + X3)<<OSS) + 2)>>2;
+
+ // Calculate B4
+ X1 = (AC3 * B6)>>13;
+ X2 = (B1 * ((B6 * B6)>>12))>>16;
+ X3 = ((X1 + X2) + 2)>>2;
+ B4 = (AC4 * (uint32)(X3 + 32768))>>15;
+
+ B7 = ((uint32)(readUP() - B3) * (50000>>OSS));
+
+ if (B7 < (uint32)0x80000000)
+ {
+ p = (B7<<1)/B4;
+ }
+ else{
+ p = (B7/B4)<<1;
+ }
+
+ X1 = (p>>8) * (p>>8);
+ X1 = (X1 * 3038)>>16;
+ X2 = (-7357 * p)>>16;
+ p += (X1 + X2 + 3791)>>4;
+
+ return p;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/BMP085/BMP085.h Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,37 @@
+#ifndef BMP085_H
+#define BMP085_H
+
+#include "Std_Types.h"
+#include "MyI2C.h"
+
+#define BMP_ADDRESS 0x77
+#define OSS 3
+
+extern MyI2C I2C0;
+
+class BMP085{
+
+ public:
+ BMP085();
+ void Calibrate(void);
+ void readUT_Flag(void);
+ void readUP_Flag(void);
+ uint16 readUT(void);
+ uint32 readUP(void);
+ int16 read_Temperature(void);
+ int32 read_Pressure(void);
+ private:
+
+ int16 AC1,AC2,AC3,AC4,AC5,AC6;
+ int16 B1,B2;
+ int16 MB,MC,MD;
+ int32 B5;
+
+ void write(char reg,char data);
+
+ int16 read (char reg);
+
+ uint8 readInt (char reg);
+
+};
+#endif
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/DCM/DCM.cpp Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,197 @@
+#include"DCM.h"
+#include "HelperMath.h"
+
+float DCM::constrain(float x, float a, float b)
+{
+ float result = x;
+
+ if (x < a) result = a;
+ if (x > b) result = b;
+
+ return result;
+}
+
+DCM::DCM(){
+
+ G_Dt=0.005;
+
+ for(int i=0;i<3;i++){
+ Accel_Vector[i]=0;
+ Gyro_Vector[i]= 0;
+ Omega_Vector[i]=0;
+ Omega_P[i]=0;
+ Omega_I[i]=0;
+ Omega[i]= 0;
+ errorRollPitch[i]=0;
+ }
+
+ DCM_Matrix[0][0]=1;
+ DCM_Matrix[0][1]=0;
+ DCM_Matrix[0][2]=0;
+ DCM_Matrix[1][0]=0;
+ DCM_Matrix[1][1]=1;
+ DCM_Matrix[1][2]=0;
+ DCM_Matrix[2][0]=0;
+ DCM_Matrix[2][1]=0;
+ DCM_Matrix[2][2]=1;
+
+ Temporary_Matrix[0][0]=0;
+ Temporary_Matrix[0][1]=0;
+ Temporary_Matrix[0][2]=0;
+ Temporary_Matrix[1][0]=0;
+ Temporary_Matrix[1][1]=0;
+ Temporary_Matrix[1][2]=0;
+ Temporary_Matrix[2][0]=0;
+ Temporary_Matrix[2][1]=0;
+ Temporary_Matrix[2][2]=0;
+
+ Update_Matrix[0][0]=0;
+ Update_Matrix[0][1]=1;
+ Update_Matrix[0][2]=2;
+ Update_Matrix[1][0]=3;
+ Update_Matrix[1][1]=4;
+ Update_Matrix[1][2]=5;
+ Update_Matrix[2][0]=6;
+ Update_Matrix[2][1]=7;
+ Update_Matrix[2][2]=8;
+}
+/**************************************************/
+void DCM::Normalize(void)
+{
+ float error=0;
+ float temporary[3][3];
+ float renorm=0;
+ bool problem=false;
+
+ error= -Vector_Dot_Product(&DCM_Matrix[0][0],&DCM_Matrix[1][0])*.5; //eq.19
+
+ Vector_Scale(&temporary[0][0], &DCM_Matrix[1][0], error); //eq.19
+ Vector_Scale(&temporary[1][0], &DCM_Matrix[0][0], error); //eq.19
+
+ Vector_Add(&temporary[0][0], &temporary[0][0], &DCM_Matrix[0][0]);//eq.19
+ Vector_Add(&temporary[1][0], &temporary[1][0], &DCM_Matrix[1][0]);//eq.19
+
+ Vector_Cross_Product(&temporary[2][0],&temporary[0][0],&temporary[1][0]); // c= a x b //eq.20
+
+ renorm= Vector_Dot_Product(&temporary[0][0],&temporary[0][0]);
+ if (renorm < 1.5625f && renorm > 0.64f) {
+ renorm= .5 * (3-renorm); //eq.21
+ } else if (renorm < 100.0f && renorm > 0.01f) {
+ renorm= 1. / sqrt(renorm);
+ }
+ Vector_Scale(&DCM_Matrix[0][0], &temporary[0][0], renorm);
+
+ renorm= Vector_Dot_Product(&temporary[1][0],&temporary[1][0]);
+ if (renorm < 1.5625f && renorm > 0.64f) {
+ renorm= .5 * (3-renorm); //eq.21
+ } else if (renorm < 100.0f && renorm > 0.01f) {
+ renorm= 1. / sqrt(renorm);
+ } else {
+ problem = true;
+ }
+ Vector_Scale(&DCM_Matrix[1][0], &temporary[1][0], renorm);
+
+ renorm= Vector_Dot_Product(&temporary[2][0],&temporary[2][0]);
+ if (renorm < 1.5625f && renorm > 0.64f) {
+ renorm= .5 * (3-renorm); //eq.21
+ } else if (renorm < 100.0f && renorm > 0.01f) {
+ renorm= 1. / sqrt(renorm);
+
+ } else {
+ problem = true;
+ }
+ Vector_Scale(&DCM_Matrix[2][0], &temporary[2][0], renorm);
+
+ if (problem) { // Our solution is blowing up and we will force back to initial condition. Hope we are not upside down!
+ DCM_Matrix[0][0]= 1.0f;
+ DCM_Matrix[0][1]= 0.0f;
+ DCM_Matrix[0][2]= 0.0f;
+ DCM_Matrix[1][0]= 0.0f;
+ DCM_Matrix[1][1]= 1.0f;
+ DCM_Matrix[1][2]= 0.0f;
+ DCM_Matrix[2][0]= 0.0f;
+ DCM_Matrix[2][1]= 0.0f;
+ DCM_Matrix[2][2]= 1.0f;
+ problem = false;
+ }
+}
+
+/**************************************************/
+void DCM::Drift_correction(void)
+{
+ //Compensation the Roll, Pitch and Yaw drift.
+ static float Scaled_Omega_I[3];
+ float Accel_magnitude;
+ float Accel_weight;
+
+ //*****Roll and Pitch***************
+
+ // Calculate the magnitude of the accelerometer vector
+ Accel_magnitude = sqrt(Accel_Vector[0]*Accel_Vector[0] + Accel_Vector[1]*Accel_Vector[1] + Accel_Vector[2]*Accel_Vector[2]);
+ Accel_magnitude /=GRAVITY; // Scale to gravity.
+ // Dynamic weighting of accelerometer info (reliability filter)
+ // Weight for accelerometer info (<0.5G = 0.0, 1G = 1.0 , >1.5G = 0.0)
+ Accel_weight = constrain(1 - 2*abs(1 - Accel_magnitude),0,1); //
+
+
+ Vector_Cross_Product(&errorRollPitch[0],&Accel_Vector[0],&DCM_Matrix[2][0]); //adjust the ground of reference
+ Vector_Scale(&Omega_P[0],&errorRollPitch[0],Kp_ROLLPITCH*Accel_weight);
+
+ Vector_Scale(&Scaled_Omega_I[0],&errorRollPitch[0],Ki_ROLLPITCH*Accel_weight);
+ Vector_Add(Omega_I,Omega_I,Scaled_Omega_I);
+
+}
+
+void DCM::Matrix_update(float GyroX,float GyroY,float GyroZ, float AccX, float AccY, float AccZ)
+{
+ Gyro_Vector[0]=GyroX; //gyro x roll
+ Gyro_Vector[1]=GyroY; //gyro y pitch
+ Gyro_Vector[2]=GyroZ; //gyro Z yaw
+
+ Accel_Vector[0]=AccX*GRAVITY; // acc x
+ Accel_Vector[1]=AccY*GRAVITY; // acc y
+ Accel_Vector[2]=AccZ*GRAVITY; // acc z
+
+ Vector_Add(&Omega[0], &Gyro_Vector[0], &Omega_I[0]); //adding proportional term
+ Vector_Add(&Omega_Vector[0], &Omega[0], &Omega_P[0]); //adding Integrator term
+
+ Update_Matrix[0][0]=0;
+ Update_Matrix[0][1]=-G_Dt*Omega_Vector[2];//-z
+ Update_Matrix[0][2]=G_Dt*Omega_Vector[1];//y
+ Update_Matrix[1][0]=G_Dt*Omega_Vector[2];//z
+ Update_Matrix[1][1]=0;
+ Update_Matrix[1][2]=-G_Dt*Omega_Vector[0];//-x
+ Update_Matrix[2][0]=-G_Dt*Omega_Vector[1];//-y
+ Update_Matrix[2][1]=G_Dt*Omega_Vector[0];//x
+ Update_Matrix[2][2]=0;
+
+ Matrix_Multiply(DCM_Matrix,Update_Matrix,Temporary_Matrix); //a*b=c
+
+ for(int x=0; x<3; x++) //Matrix Addition (update)
+ {
+ for(int y=0; y<3; y++)
+ {
+ DCM_Matrix[x][y]+=Temporary_Matrix[x][y];
+ }
+ }
+}
+
+void DCM::Euler_angles(void)
+{
+ pitch = -asin(DCM_Matrix[2][0]);
+ roll = atan2(DCM_Matrix[2][1],DCM_Matrix[2][2]);
+ yaw = atan2(DCM_Matrix[1][0],DCM_Matrix[0][0]);
+}
+
+void DCM::Update_DCM(float dt,float a, float b, float c, float d, float e, float f){
+
+ G_Dt = dt;
+
+ Matrix_update(a,b,c,d,e,f);
+
+ Normalize();
+
+ Drift_correction();
+
+ Euler_angles();
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/DCM/DCM.h Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,56 @@
+#ifndef DCM_H
+#define DCM_H
+
+#include "mbed.h"
+
+#define GRAVITY 4096
+#define Kp_ROLLPITCH 1.515/GRAVITY //1.515
+#define Ki_ROLLPITCH 0.00101/GRAVITY //0.00101
+
+
+class DCM{
+
+public:
+
+ DCM(void);
+
+float G_Dt;
+
+float roll;
+
+float pitch;
+
+float yaw;
+
+void Update_DCM(float dt,float a, float b, float c, float d, float e, float f);
+
+float DCM_Matrix[3][3];
+
+float Update_Matrix[3][3];
+
+float Temporary_Matrix[3][3];
+
+private:
+
+float Accel_Vector[3]; //Store the acceleration in a vector
+float Gyro_Vector[3];//Store the gyros rutn rate in a vector
+float Omega_Vector[3]; //Corrected Gyro_Vector data
+float Omega_P[3];//Omega Proportional correction
+float Omega_I[3];//Omega Integrator
+float Omega[3];
+float errorRollPitch[3];
+
+
+float constrain(float x, float a, float b);
+
+void Normalize(void);
+
+void Drift_correction(void);
+
+void Matrix_update(float GyroX,float GyroY,float GyroZ, float AccX, float AccY, float AccZ);
+
+void Euler_angles(void);
+
+};
+
+#endif
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/DCM/HelperMath.cpp Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,59 @@
+#include "HelperMath.h"
+/**************************************************/
+//Multiply two 3x3 matrixs. This function developed by Jordi can be easily adapted to multiple n*n matrix's. (Pero me da flojera!).
+void Matrix_Multiply(float a[3][3], float b[3][3],float mat[3][3])
+{
+ float op[3];
+ for(int x=0; x<3; x++)
+ {
+ for(int y=0; y<3; y++)
+ {
+ for(int w=0; w<3; w++)
+ {
+ op[w]=a[x][w]*b[w][y];
+ }
+ mat[x][y]=0;
+ mat[x][y]=op[0]+op[1]+op[2];
+
+ }
+ }
+}
+
+
+//Computes the dot product of two vectors
+float Vector_Dot_Product(float vector1[3],float vector2[3])
+{
+ float op=0;
+
+ for(int c=0; c<3; c++)
+ {
+ op+=vector1[c]*vector2[c];
+ }
+
+ return op;
+}
+
+//Computes the cross product of two vectors
+void Vector_Cross_Product(float vectorOut[3], float v1[3],float v2[3])
+{
+ vectorOut[0]= (v1[1]*v2[2]) - (v1[2]*v2[1]);
+ vectorOut[1]= (v1[2]*v2[0]) - (v1[0]*v2[2]);
+ vectorOut[2]= (v1[0]*v2[1]) - (v1[1]*v2[0]);
+}
+
+//Multiply the vector by a scalar.
+void Vector_Scale(float vectorOut[3],float vectorIn[3], float scale2)
+{
+ for(int c=0; c<3; c++)
+ {
+ vectorOut[c]=vectorIn[c]*scale2;
+ }
+}
+
+void Vector_Add(float vectorOut[3],float vectorIn1[3], float vectorIn2[3])
+{
+ for(int c=0; c<3; c++)
+ {
+ vectorOut[c]=vectorIn1[c]+vectorIn2[c];
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DCM/HelperMath.h Tue Aug 27 09:38:49 2013 +0000 @@ -0,0 +1,19 @@ +#ifndef HELPERMATH_H +#define HELPERMATH_H +/**************************************************/ +//Multiply two 3x3 matrixs. This function developed by Jordi can be easily adapted to multiple n*n matrix's. (Pero me da flojera!). +void Matrix_Multiply(float a[3][3], float b[3][3],float mat[3][3]); + +//Computes the dot product of two vectors +float Vector_Dot_Product(float vector1[3],float vector2[3]); + +//Computes the cross product of two vectors +void Vector_Cross_Product(float vectorOut[3], float v1[3],float v2[3]); + +//Multiply the vector by a scalar. +void Vector_Scale(float vectorOut[3],float vectorIn[3], float scale2); + +void Vector_Add(float vectorOut[3],float vectorIn1[3], float vectorIn2[3]); + + +#endif \ No newline at end of file
--- a/FastPWM.lib Sun Dec 23 23:50:21 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -http://mbed.org/users/Sissors/code/FastPWM/#3094d3806cfc
--- a/GlobalDefines.h Sun Dec 23 23:50:21 2012 +0000 +++ b/GlobalDefines.h Tue Aug 27 09:38:49 2013 +0000 @@ -1,23 +1,26 @@ -#include "mbed.h" -#include "PulseIn.h" -#include "FastPWM.h" - -#define ToRad(x) (x*0.01745329252) // *pi/180 -#define ToDeg(x) (x*57.2957795131) // *180/pi +#include "Std_Types.h" -//// Motor Pins ///// -FastPWM FrontMotor(p21); // Front Motor Pin -FastPWM RearMotor(p22); // Rear Motor Pin -FastPWM LeftMotor(p23); // Left Motor Pin -FastPWM RightMotor(p24);// Right Motor Pin -///////////////////// - +////////////////Motors.h ////////////////// +PwmOut FrontMotor(p24); // Front Motor Pin +PwmOut RearMotor(p21); // Rear Motor Pin +PwmOut LeftMotor(p23); // Left Motor Pin +PwmOut RightMotor(p22);// Right Motor Pin +uint16 Mspeed[4]; +uint16 Throttle= 1000; +int16 PitchSetpoint=0,RollSetpoint=0,YawSetpoint=0; +int16 PitchCorrection,RollCorrection,YawCorrection; +/////////////////////////////////////////// +Serial pc(USBTX, USBRX); DigitalOut one(LED1); -PulseIn Throttle(p11); - +DigitalOut two(LED2); Timer Global; +/* +RC Aileron(p11); +RC Elevator(p12); +RC Throttle(p13); +RC Rudder(p14); +*/ +//float pitch,roll; +float deltaTime; +float pitch,roll; -int16_t RX_Data[4]; -float pitch,roll,deltaTime; -/////PID Variables///// -float PID[3],error[3],I[3],D[3]; \ No newline at end of file
--- a/MPU.h Sun Dec 23 23:50:21 2012 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-// Contains all Sensor related functions : initializing the sensor, calibrating sensors, getting data , accelerometer angles , and filtering gyro/acc data
-
-#define MPU_ADDRESS 0x68
-#define GyroScale 16.4
-#define AccScale 4096
-
-I2C i2c(p9,p10);
-
-float filtered_AccX[4],filtered_AccY[4],filtered_AccZ[4],filtered_GyroX[4],filtered_GyroY[4],filtered_GyroZ[4];
-float Scaled_GyroX,Scaled_GyroY,Scaled_GyroZ;
-float Scaled_AccX,Scaled_AccY,Scaled_AccZ;
-float GyroOffset[3],AccOffset[3];
-float accangle[2];
-
-void write(char reg,char data){
-
- char tx[2]={reg,data};
-
- i2c.write((MPU_ADDRESS << 1)&0xFE, tx, 2);
-
- }
-
-int read (char reg){
-
- char tx = reg;
- char rx[2];
-
- i2c.write((MPU_ADDRESS << 1)&0xFE , &tx,1);
-
- i2c.read((MPU_ADDRESS << 1)|0x01, rx, 2);
-
-
- int16_t output = ((int) rx[0] << 8) | ((int) rx[1]);
-
- return output;
-}
-
-void MPU_Setup(){
-
-
- write(0x6B,0x80); // Restart
- wait_ms(5);
- write(0x6B,0x03); //PWR_MGMT_1 -- SLEEP 0; CYCLE 0; TEMP_DIS 0; CLKSEL 1 (PLL with Z Gyro reference)
- write(0x1B,0x18); //GYRO_CONFIG -- FS_SEL = 2 : Scale = +/-2000 deg/sec
- write(0x1C,0x10); //ACCEL_CONFIG -- AFS_SEL=2 : Scale = +/-8G
- write(0x1A,0x03); //DLPF Settings -- 44Hz Accelerometer Bandwidth 42Hz Gyroscope Bandwidth
- wait_ms(60);
-}
-
-void ScaledGyro(){
-
- Scaled_GyroX = -read(0x45)/GyroScale;
- Scaled_GyroY = read(0x43)/GyroScale;
- Scaled_GyroZ = read(0x47)/GyroScale;
-}
-
-void ScaledAcc(){
-
- Scaled_AccX = (float)read(0x3B)/AccScale;
- Scaled_AccY = (float)read(0x3D)/AccScale;
- Scaled_AccZ = (float)read(0x3F)/AccScale;
-}
-
- void CalibrateGyro(){
-
- for(int k=0;k<200;k++){
- ScaledGyro();
- GyroOffset[0]+=Scaled_GyroX;
- GyroOffset[1]+=Scaled_GyroY;
- GyroOffset[2]+=Scaled_GyroZ;
- wait_ms(1);
- }
- GyroOffset[0]/=200;
- GyroOffset[1]/=200;
- GyroOffset[2]/=200;
- }
-
- void CalibrateAcc(){
-
- for(int k=0;k<200;k++){
- ScaledAcc();
- AccOffset[0]+=Scaled_AccX;
- AccOffset[1]+=Scaled_AccY;
- AccOffset[2]+=Scaled_AccZ;
- wait_ms(1);
- }
- AccOffset[0]/=200;
- AccOffset[1]/=200;
- AccOffset[2]/=200;
- AccOffset[2]-=1;
- }
-
- void GyroRate(){
-
- ScaledGyro();
- Scaled_GyroX-=GyroOffset[0];
- Scaled_GyroY-=GyroOffset[1];
- Scaled_GyroZ-=GyroOffset[2];
- }
-
- void Acc(){
-
- ScaledAcc();
- Scaled_AccX-=AccOffset[0];
- Scaled_AccY-=AccOffset[1];
- Scaled_AccZ-=AccOffset[2];
- }
- void filterGyro(void){
-
- GyroRate();
-
- //////////////////////////////////////// Filter X Axis ////////////////////////////////////////////////////////////////
- filtered_GyroX[0] = Scaled_GyroX;
- Scaled_GyroX = filtered_GyroX[0]*0.25 + filtered_GyroX[1]*0.25 + filtered_GyroX[2]*0.25 + filtered_GyroX[3]*0.25;
- filtered_GyroX[3] = filtered_GyroX[2];
- filtered_GyroX[2] = filtered_GyroX[1];
- filtered_GyroX[1] = filtered_GyroX[0];
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /////////////////////////////////////// Filter Y Axis ////////////////////////////////////////////////////////////////
- filtered_GyroY[0] = Scaled_GyroY;
- Scaled_GyroY = filtered_GyroY[0]*0.25 + filtered_GyroY[1]*0.25 + filtered_GyroY[2]*0.25 + filtered_GyroY[3]*0.25;
- filtered_GyroY[3] = filtered_GyroY[2];
- filtered_GyroY[2] = filtered_GyroY[1];
- filtered_GyroY[1] = filtered_GyroY[0];
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /////////////////////////////////////// Filter Z Axis ////////////////////////////////////////////////////////////////
- filtered_GyroZ[0] = Scaled_GyroZ;
- Scaled_GyroZ = filtered_GyroZ[0]*0.25 + filtered_GyroZ[1]*0.25 + filtered_GyroZ[2]*0.25 + filtered_GyroZ[3]*0.25;
- filtered_GyroZ[3] = filtered_GyroZ[2];
- filtered_GyroZ[2] = filtered_GyroZ[1];
- filtered_GyroZ[1] = filtered_GyroZ[0];
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- }
-
-
- void filterAcc(void){
-
- Acc();
-
- //////////////////////////////////////// Filter X Axis///////////////////////////////////////////////////////////
- filtered_AccX[0] = Scaled_AccX;
- Scaled_AccX = filtered_AccX[0]*0.25 + filtered_AccX[1]*0.25 + filtered_AccX[2]*0.25 + filtered_AccX[3]*0.25;
- filtered_AccX[3] = filtered_AccX[2];
- filtered_AccX[2] = filtered_AccX[1];
- filtered_AccX[1] = filtered_AccX[0];
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- //////////////////////////////////////// Filter Y Axis //////////////////////////////////////////////////////////
- filtered_AccY[0] = Scaled_AccY;
- Scaled_AccY = filtered_AccY[0]*0.25 + filtered_AccY[1]*0.25 + filtered_AccY[2]*0.25 + filtered_AccY[3]*0.25;
- filtered_AccY[3] = filtered_AccY[2];
- filtered_AccY[2] = filtered_AccY[1];
- filtered_AccY[1] = filtered_AccY[0];
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- //////////////////////////////////////// Filter Z Axis//////////////////////////////////////////////////////////
- filtered_AccZ[0] = Scaled_AccZ;
- Scaled_AccZ = filtered_AccZ[0]*0.25 + filtered_AccZ[1]*0.25 + filtered_AccZ[2]*0.25 + filtered_AccZ[3]*0.25;
- filtered_AccZ[3] = filtered_AccZ[2];
- filtered_AccZ[2] = filtered_AccZ[1];
- filtered_AccZ[1] = filtered_AccZ[0];
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- }
-
- void AccAngle(){
-
- filterAcc();
-
- accangle[0] = ToDeg(atan2(Scaled_AccX,sqrt(Scaled_AccY*Scaled_AccY+Scaled_AccZ*Scaled_AccZ)));
- accangle[1] = ToDeg(atan2(Scaled_AccY,sqrt(Scaled_AccX*Scaled_AccX+Scaled_AccZ*Scaled_AccZ)));
- }
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/MPU/MPU6050.cpp Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,167 @@
+#include "MPU6050.h"
+
+MPU6050::MPU6050(){
+
+ Panic=false;
+}
+
+void MPU6050::write(char reg,char data){
+
+ char tx[2]={reg,data};
+
+ I2C0.blockwrite((MPU_ADDRESS << 1)&0xFE, tx, 2);
+
+ }
+
+int MPU6050::read (char reg){
+
+ char tx = reg;
+ char rx[2];
+
+ I2C0.blockwrite((MPU_ADDRESS << 1)&0xFE, &tx, 1);
+
+ I2C0.blockread((MPU_ADDRESS << 1)|0x01, rx, 2);
+
+ int16_t output = ((int) rx[0] << 8) | ((int) rx[1]);
+ return output;
+}
+
+bool MPU6050::CheckConnection(void){
+
+ char tx = 0x75; // Who Am I Register
+ char rx;
+
+ I2C0.blockwrite((MPU_ADDRESS << 1)&0xFE, &tx, 1);
+
+ I2C0.blockread((MPU_ADDRESS << 1)|0x01, &rx, 1);
+
+ if(rx==0x68) return true;
+
+ else return false ;
+}
+
+void MPU6050::MPU_Setup(void){
+
+ write(0x6B,0x80); // Restart
+ wait_ms(5);
+ write(0x6B,0x03); //PWR_MGMT_1 -- SLEEP 0; CYCLE 0; TEMP_DIS 0; CLKSEL 3 (PLL with Z Gyro reference)
+ write(0x1B,0x18); //GYRO_CONFIG -- FS_SEL =3 : Scale = +/-2000 deg/sec
+ write(0x1C,0x10); //ACCEL_CONFIG -- AFS_SEL= 2 : Scale = +/-8G
+ write(0x1A,0x03); //DLPF Settings -- 44Hz Accelerometer Bandwidth 42Hz Gyroscope Bandwidth
+ wait_ms(60);
+}
+
+void MPU6050::ScaledGyro(void){
+
+ Scaled_GyroX = -(float)read(0x45)/GyroScale;
+ Scaled_GyroY = (float)read(0x43)/GyroScale;
+ Scaled_GyroZ = (float)read(0x47)/GyroScale;
+}
+
+void MPU6050::ScaledAcc(void){
+
+ Scaled_AccX = (float)read(0x3B)/AccScale;
+ Scaled_AccY = (float)read(0x3D)/AccScale;
+ Scaled_AccZ = (float)read(0x3F)/AccScale;
+}
+
+void MPU6050::RawAcc(void){
+
+ Raw_AccX = read(0x3B);
+ Raw_AccY = read(0x3D);
+ Raw_AccZ = read(0x3F);
+}
+
+ void MPU6050::CalibrateGyro(void){
+
+ for(int k=0;k<100;k++){
+ ScaledGyro();
+ GyroOffset[0]+=Scaled_GyroX;
+ GyroOffset[1]+=Scaled_GyroY;
+ GyroOffset[2]+=Scaled_GyroZ;
+ wait_ms(5);
+ }
+ GyroOffset[0]/=100;
+ GyroOffset[1]/=100;
+ GyroOffset[2]/=100;
+
+ }
+
+ void MPU6050::CalibrateAcc(void){
+
+ for(int k=0;k<100;k++){
+ ScaledAcc();
+ AccOffset[0]+=Scaled_AccX;
+ AccOffset[1]+=Scaled_AccY;
+ AccOffset[2]+=Scaled_AccZ;
+ wait_ms(5);
+ }
+ AccOffset[0]/=100;
+ AccOffset[1]/=100;
+ AccOffset[2]/=100;
+ AccOffset[2]-=1;
+
+ }
+
+ void MPU6050::GyroRate(void){
+
+ ScaledGyro();
+ Scaled_GyroX-=GyroOffset[0];
+ Scaled_GyroY-=GyroOffset[1];
+ Scaled_GyroZ-=GyroOffset[2];
+ }
+
+ void MPU6050::Acc(void){
+
+ ScaledAcc();
+ Scaled_AccX-=AccOffset[0];
+ Scaled_AccY-=AccOffset[1];
+ Scaled_AccZ-=AccOffset[2];
+
+ }
+
+ void MPU6050::filterGyro(void){
+
+ GyroRate();
+
+ // Filter X Axis //
+ filtered_Gyro[0] = filter_GyroX.update(Scaled_GyroX);
+
+ //Filter Y Axis //
+ filtered_Gyro[1] = filter_GyroY.update(Scaled_GyroY);
+
+ // Filter Z Axis //
+ filtered_Gyro[2] = filter_GyroZ.update(Scaled_GyroZ);
+ }
+
+
+ void MPU6050::filterAcc(void){
+
+ Acc();
+
+ // Filter X Axis//
+ filtered_Acc[0] = filter_AccX.update(Scaled_AccX);
+
+ // Filter Y Axis //
+ filtered_Acc[1] = filter_AccY.update(Scaled_AccY);
+
+ // Filter Z Axis//
+ filtered_Acc[2] = filter_AccZ.update(Scaled_AccZ);
+
+ Acceleration_Magnitude = sqrt(filtered_Acc[0]*filtered_Acc[0] + filtered_Acc[1]*filtered_Acc[1] + filtered_Acc[2]*filtered_Acc[2]);
+
+ if(Acceleration_Magnitude==0) Panic = true; // Free-Fall detection;
+ else Panic= false;
+ }
+
+ void MPU6050::AccAngle(void){
+
+ //filterAcc();
+ Acc();
+
+ //accangle[0] = ToDeg(atan2(filtered_Acc[0],sqrt(filtered_Acc[1]*filtered_Acc[1]+filtered_Acc[2]*filtered_Acc[2])));
+ accangle[0] = ToDeg(atan2(Scaled_AccX,sqrt(Scaled_AccY*Scaled_AccY+Scaled_AccZ*Scaled_AccZ)));
+ //accangle[1] = ToDeg(atan2(filtered_Acc[1],sqrt(filtered_Acc[0]*filtered_Acc[0]+filtered_Acc[2]*filtered_Acc[2])));
+ accangle[1] = ToDeg(atan2(Scaled_AccY,sqrt(Scaled_AccX*Scaled_AccX+Scaled_AccZ*Scaled_AccZ)));
+ }
+
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/MPU/MPU6050.h Tue Aug 27 09:38:49 2013 +0000
@@ -0,0 +1,68 @@
+#ifndef MPU6050_H
+#define MPU6050_H
+
+#include "mbed.h"
+#include "MyI2C.h"
+#include "MAF.h"
+
+#define MPU_ADDRESS 0x68
+#define GyroScale 16.4f
+#define AccScale 4096
+#define ToRad(x) (x*0.01745329252) // *pi/180
+#define ToDeg(x) (x*57.2957795131) // *180/pi
+
+extern MyI2C I2C0;
+
+class MPU6050
+{
+
+public:
+
+ MPU6050(void);
+float filtered_Gyro[3],filtered_Acc[3];
+float Scaled_GyroX,Scaled_GyroY,Scaled_GyroZ;
+float Scaled_AccX,Scaled_AccY,Scaled_AccZ;
+int Raw_AccX,Raw_AccY,Raw_AccZ;
+float GyroOffset[3],AccOffset[3];
+float accangle[2];
+float Acceleration_Magnitude;
+bool Panic;
+
+
+bool CheckConnection(void);
+
+void MPU_Setup(void);
+
+void ScaledGyro(void);
+
+void ScaledAcc(void);
+
+void RawAcc(void);
+
+void CalibrateGyro(void);
+
+void CalibrateAcc(void);
+
+void GyroRate(void);
+
+void Acc(void);
+
+void filterGyro(void);
+
+void filterAcc(void);
+
+void AccAngle(void);
+
+private:
+
+MAF filter_AccX,filter_AccY,filter_AccZ; // Moving Average Filter Initiallization
+
+MAF filter_GyroX,filter_GyroY,filter_GyroZ;
+
+void write(char reg,char data);
+
+int read (char reg);
+
+};
+
+#endif
\ No newline at end of file
--- a/Motors.h Sun Dec 23 23:50:21 2012 +0000
+++ b/Motors.h Tue Aug 27 09:38:49 2013 +0000
@@ -1,57 +1,49 @@
-#define Kp 1
-#define Ki 1
-#define Kd 1
-
-#define PITCH 0
-#define ROLL 1
-#define YAW 2
-
- int constrain(int a , int b, int c){
-
+#define RefreshRate 2500 // in us :400Hz
+
+ static uint16 Constrain(uint16 a , uint16 b, uint16 c){
if(a<b) return b;
else if(a>c)return c;
else return a;
}
-
- void InitializeMotors(void){
-
- FrontMotor.period_ms(20);
- RearMotor.period_ms(20);
- LeftMotor.period_ms(20);
- RightMotor.period_ms(20);
-
+
+ void CalibrateESC(void){
+
+ FrontMotor.period_us(RefreshRate); // setting one will by default set all other pins to the same period
+
+ /*FrontMotor.pulsewidth_us(2000);
+ RearMotor.pulsewidth_us(2000);
+ LeftMotor.pulsewidth_us(2000);
+ RightMotor.pulsewidth_us(2000);
+
+ wait(2); still in testing phase */
+
FrontMotor.pulsewidth_us(1000);
RearMotor.pulsewidth_us(1000);
LeftMotor.pulsewidth_us(1000);
RightMotor.pulsewidth_us(1000);
- wait(3); // let the hardware settle
- }
-
- void updatePID(void){
+ wait(2);
+ }
+
+ void updateMotors(int16 PitchCommand,int16 RollCommand){
- error[PITCH]= RX_Data[PITCH] - pitch;
- error[ROLL]= RX_Data[ROLL] - roll ;
- error[YAW]= RX_Data[YAW] - Scaled_GyroZ;
-
- I[PITCH]+= error[PITCH]*Ki;
- I[ROLL]+= error[ROLL]*Ki;
- I[YAW]+= error[YAW]*Ki;
+ if(Throttle>=1200){
+
+ Mspeed[0] = Constrain((Throttle+PitchCommand),1200,2000);
+ Mspeed[1] = Constrain((Throttle-PitchCommand),1200,2000);
+ Mspeed[2] = Constrain((Throttle+RollCommand),1200,2000);
+ Mspeed[3] = Constrain((Throttle-RollCommand),1200,2000);
+ }
- D[PITCH] = Scaled_GyroX;
- D[ROLL] = Scaled_GyroY;
- D[YAW] = Scaled_GyroZ;
+ else{
+ Mspeed[0] = 1000;
+ Mspeed[1] = 1000;
+ Mspeed[2] = 1000;
+ Mspeed[3] = 1000;
+ }
- PID[PITCH] = error[PITCH]*Kp + I[PITCH] + D[PITCH]*Kd ; // Pitch correction
- PID[ROLL] = error[ROLL]*Kp + I[ROLL] + D[ROLL]*Kd ; // Roll correction
- PID[YAW] = error[YAW] ; // Yaw correction
-
- }
-
- void updateMotors(void){
-
- FrontMotor.pulsewidth_us(constrain(RX_Data[3]+PID[PITCH],1000,2000));
- RearMotor.pulsewidth_us(constrain(RX_Data[3]-PID[PITCH],1000,2000));
- LeftMotor.pulsewidth_us(constrain(RX_Data[3]+PID[ROLL],1000,2000));
- RightMotor.pulsewidth_us(constrain(RX_Data[3]-PID[ROLL],1000,2000));
+ FrontMotor.pulsewidth_us(Mspeed[0]);
+ RearMotor.pulsewidth_us(Mspeed[1]);
+ LeftMotor.pulsewidth_us(Mspeed[2]);
+ RightMotor.pulsewidth_us(Mspeed[3]);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MovingAverageFilter.lib Tue Aug 27 09:38:49 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/KarimAzzouz/code/MovingAverageFilter/#fbc57eb4e61d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MyI2C.lib Tue Aug 27 09:38:49 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/KarimAzzouz/code/MyI2C/#1c722325e933
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PID.lib Tue Aug 27 09:38:49 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/KarimAzzouz/code/PID/#2ac3074cfdb3
--- a/PulseIn.lib Sun Dec 23 23:50:21 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -PulseIn#eaf70ff4df07
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RC.lib Tue Aug 27 09:38:49 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/KarimAzzouz/code/RC/#c98b23d53e42
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Std_Types.h Tue Aug 27 09:38:49 2013 +0000 @@ -0,0 +1,16 @@ +#ifndef STD_TYPES_H +#define STD_DEFINES_H + +#include "mbed.h" + +#define TRUE (uint8)1 +#define FALSE(uint8)0 + +typedef uint8_t boolean; +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +#endif \ No newline at end of file
--- a/main.cpp Sun Dec 23 23:50:21 2012 +0000
+++ b/main.cpp Tue Aug 27 09:38:49 2013 +0000
@@ -1,71 +1,147 @@
+/*
+ F +X
+ | |
+ | |
+ L ------|------R -Y------|------+Y
+ | |
+ | |
+ B -X
+*/
+#include "mbed.h"
#include "rtos.h"
#include "GlobalDefines.h"
-#include "MPU.h"
+#include "MPU6050.h"
+#include "BMP085.h"
+//#include "DCM.h" Drifts a lot, needs tuning
+#include "PID.h"
#include "Motors.h"
-#define FlightControlUpdateRate 5
-#define RxUpdateRate 20
-#define Enable_Serial
+#define Debugging
+
+typedef enum{Armed=0,
+UnArmed
+}State;
+
+State MotorState = Armed;
+bool print = false;
+bool P_readingReady=false;
+bool T_readingReady=false;
+char c ;
-#ifdef Enable_Serial
-Serial pc(USBTX, USBRX);
-#endif
+uint8 count=0;
+int16 Temperature=0;
+int32 Pressure=0;
+
-
+PIDClass PitchPID(4,5,2.5,0.1); // This is a nested PID loop
+ // Kp,Ki,Kp,Kd,iLim..... best till now 4,0,2.5,0.1
+MPU6050 Sensor;
+BMP085 BMP;
+//DCM IMU;
+
+void FlightControl(void const *args){
-void RX_thread(void const *args){
+ //Get Sensor Data
+ Sensor.GyroRate();
+ Sensor.AccAngle();
+
+ ///////////////////////BMP085 Contorol switch statement(Work in Progress!!!)//////////////////////////////////////
+ switch(count){
+
+ case 0:
+ BMP.readUT_Flag();
+ count+=1;
+ break;
+
+
+ case 1:
+ Temperature=BMP.read_Temperature();
+ T_readingReady=true;
+ BMP.readUP_Flag();
+ count+=1;
+ break;
+
+
+ case 7:
+ Pressure = BMP.read_Pressure();
+ P_readingReady=true;
+ count=0;
+ break;
+
+ default: count+=1; break;
+ }
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ //Gotta Keep Time
+ deltaTime = Global.read();
+ Global.reset();
- while(true){
- RX_Data[3] = Throttle.read();
- #ifdef Enable_Serial
- //pc.printf("%d\n",RX_Data[3]);
- #endif
- Thread::wait(RxUpdateRate);
- }
+ pitch = (0.99)*(pitch+Sensor.Scaled_GyroX*deltaTime) + (0.01)*(Sensor.accangle[0]);
+ roll = (0.99)*(roll+ Sensor.Scaled_GyroY*deltaTime) + (0.01)*(Sensor.accangle[1]);
+
+
+ //IMU.Update_DCM(deltaTime,ToRad(Sensor.Scaled_GyroX),ToRad(Sensor.Scaled_GyroY),ToRad(Sensor.Scaled_GyroZ),Sensor.Scaled_AccX,Sensor.Scaled_AccY,Sensor.Scaled_AccZ);
+
+ //Update PID
+ if(Throttle>=1200){
+ PitchCorrection= PitchPID.update(PitchSetpoint,pitch,Sensor.Scaled_GyroX,deltaTime);
+ }
+
+ //Motor Commands
+ if(MotorState==Armed){
+ updateMotors(PitchCorrection,0);
+ }
+
+ print = true;
}
+
int main(){
-
- //i2c.frequency(400000);
+
+ #ifdef Debugging
+ pc.baud(115200);
+ #endif
- #ifdef Enable_Serial
- pc.baud(57600);
- #endif
+ //Start-Up functions
+ __disable_irq();
+ CalibrateESC(); // Throttle Sweep on startup
+ two=1; // check sensor connection
+ while(!Sensor.CheckConnection()); //check sensor connection
+ two=0; // sensor okay
+ BMP.Calibrate(); // Calibrations sequence for BMP... TODO: store values in LocalFileSystem
+ Sensor.MPU_Setup(); // Setup MPU6050
+ Sensor.CalibrateGyro(); // Calibrate Gyros (averaging 100 readings)
+ Sensor.CalibrateAcc(); // Calibrate ACC (averaging 100 readings)
+ one=1; // System okay to start
+ __enable_irq();
- one=1;
- MPU_Setup();
- CalibrateGyro();
- CalibrateAcc();
- InitializeMotors();
- one=0;
-
- Thread thread(RX_thread);
-
- Global.start();
+ RtosTimer Fly(FlightControl,osTimerPeriodic,NULL); // Initialize RTOS Timer
+ Global.start(); // Start timer
+ Fly.start(5); // RTOS Timer (200Hz Control Loop)
while(true){
-
- // Get Sensor Data
- filterGyro();
- AccAngle();
-
- // Gotta Keep Time
- deltaTime = Global.read();
- Global.reset();
-
- //Pitch and Roll Calculation using complimentary filter
- //pitch= (0.98)*(pitch+Scaled_GyroX*deltaTime) + (0.02)*(accangle[0]);
- pitch+= Scaled_GyroX*deltaTime;
-
- // update PID and Motor Commands
- //updatePID();
- updateMotors();
-
- #ifdef Enable_Serial
- pc.printf("%f,%f\n",pitch,accangle[0]);
- #endif
-
- Thread::wait(FlightControlUpdateRate);
+
+ if(pc.readable()){
+ c = pc.getc();
+ if(c == 'a'||c=='A') MotorState = Armed;
+ else if(c == 's'||c=='S') MotorState = UnArmed;
+ else if(c == 'u'||c=='U') Throttle+=50 ;
+ else if(c == 'i'||c=='I') Throttle-=50;
+ else if(c=='m'||c=='M') PitchSetpoint+=10;
+ else if(c=='n'||c=='N') PitchSetpoint-=10;
+ //print=true;
+ }
+
+ #ifdef Debugging
+ if(print){
+ //pc.printf("%d\n",Temperature);
+ pc.printf("%6.2f\n",pitch);
+ //T_readingReady=false;
+ print=false;
+ }
+ #endif
+
+ Thread::wait(20);
}
}
--- a/mbed-rtos.lib Sun Dec 23 23:50:21 2012 +0000 +++ b/mbed-rtos.lib Tue Aug 27 09:38:49 2013 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/users/mbed_official/code/mbed-rtos/#88a1a9c26ae3 +http://mbed.org/users/mbed_official/code/mbed-rtos/#869ef732a8a2
--- a/mbed.bld Sun Dec 23 23:50:21 2012 +0000 +++ b/mbed.bld Tue Aug 27 09:38:49 2013 +0000 @@ -1,1 +1,1 @@ -http://mbed.org/users/mbed_official/code/mbed/builds/63cdd78b2dc1 \ No newline at end of file +http://mbed.org/users/mbed_official/code/mbed/builds/e3affc9e7238 \ No newline at end of file