Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
main.cpp
00001 #include "mbed.h" 00002 #include "PPM.h" 00003 00004 I2C i2c(PB_11, PB_10); // sda, scl 00005 Serial pc(PA_9, PA_10, 115200); // tx, rx 00006 DigitalOut led_green(PB_3); 00007 DigitalOut led_red(PB_4); 00008 PPM ppm(PA_0); 00009 00010 DigitalOut motorPin1(PA_8); 00011 DigitalOut motorPin2(PA_11); 00012 DigitalOut motorPin3(PB_6); 00013 DigitalOut motorPin4(PB_7); 00014 DigitalOut motorPin5(PB_8); 00015 DigitalOut motorPin6(PB_9); 00016 00017 DigitalOut buzzer(PA_12); 00018 00019 #define ROLL 0 00020 #define PITCH 1 00021 #define YAW 2 00022 00023 #define RC_ROLL (ppm.channels[0]) 00024 #define RC_PITCH (ppm.channels[1]) 00025 #define RC_THROTTLE (ppm.channels[2]) 00026 #define RC_YAW (ppm.channels[3]) 00027 #define RC_DEADBAND_US 8 00028 00029 #define GYRO_CALIB_SAMPLES 4000 00030 #define MPU6050 (0x68 << 1) 00031 00032 float pid_p_gain_roll = 1.3; //Gain setting for the roll P-controller 00033 float pid_i_gain_roll = 0.04; //Gain setting for the roll I-controller 00034 float pid_d_gain_roll = 18.0; //Gain setting for the roll D-controller 00035 int pid_max_roll = 400; //Maximum output of the PID-controller (+/-) 00036 00037 float pid_p_gain_pitch = pid_p_gain_roll; //Gain setting for the pitch P-controller. 00038 float pid_i_gain_pitch = pid_i_gain_roll; //Gain setting for the pitch I-controller. 00039 float pid_d_gain_pitch = pid_d_gain_roll; //Gain setting for the pitch D-controller. 00040 int pid_max_pitch = pid_max_roll; //Maximum output of the PID-controller (+/-) 00041 00042 float pid_p_gain_yaw = 4.0; //Gain setting for the pitch P-controller. //4.0 00043 float pid_i_gain_yaw = 0.02; //Gain setting for the pitch I-controller. //0.02 00044 float pid_d_gain_yaw = 0.0; //Gain setting for the pitch D-controller. 00045 int pid_max_yaw = 400; //Maximum output of the PID-controller (+/-) 00046 00047 int cal_int; 00048 double gyro_cal[3]; 00049 int16_t acc_raw[3], gyro_raw[3]; 00050 float acc_smoothed[3], gyro_smoothed[3]; 00051 00052 Timer armingTimer; 00053 00054 Timer timer; 00055 00056 float angle_roll_acc, angle_pitch_acc, angle_pitch, angle_roll; 00057 float acc_total_vector; 00058 00059 float roll_level_adjust, pitch_level_adjust; 00060 float pid_i_mem_roll, pid_roll_setpoint, pid_output_roll, pid_last_roll_d_error; 00061 float pid_i_mem_pitch, pid_pitch_setpoint, pid_output_pitch, pid_last_pitch_d_error; 00062 float pid_i_mem_yaw, pid_yaw_setpoint, pid_output_yaw, pid_last_yaw_d_error; 00063 00064 int constrain( int val, int lower, int upper ) 00065 { 00066 if ( val < lower ) return lower; 00067 if ( val > upper ) return upper; 00068 return val; 00069 } 00070 00071 void beepStart() 00072 { 00073 buzzer = 0; 00074 } 00075 00076 void beepStop() 00077 { 00078 buzzer = 1; 00079 } 00080 00081 void beep(int ms) 00082 { 00083 beepStart(); 00084 wait_ms(ms); 00085 beepStop(); 00086 } 00087 00088 void setIMURegisters() 00089 { 00090 char cmd[2]; 00091 00092 cmd[0] = 0x6B; //We want to write to the PWR_MGMT_1 register (6B hex) 00093 cmd[1] = 0x00; //Set the register bits as 00000000 to activate the gyro 00094 i2c.write(MPU6050, cmd, 2); 00095 00096 cmd[0] = 0x1B; //We want to write to the GYRO_CONFIG register (1B hex) 00097 cmd[1] = 0x08; //Set the register bits as 00001000 (500dps full scale) 00098 i2c.write(MPU6050, cmd, 2); 00099 00100 cmd[0] = 0x1C; //We want to write to the ACCEL_CONFIG register (1A hex) 00101 cmd[1] = 0x10; //Set the register bits as 00010000 (+/- 8g full scale range) 00102 i2c.write(MPU6050, cmd, 2); 00103 00104 //Let's perform a random register check to see if the values are written correct 00105 cmd[0] = 0x1B; //Start reading at register 0x1B 00106 i2c.write(MPU6050, cmd, 1); 00107 i2c.read(MPU6050, cmd, 1); 00108 if (cmd[0] != 0x08) { //Check if the value is 0x08 00109 led_red = 0; //Turn on the warning led 00110 pc.printf("Gyro init failed! (%d)\n", cmd[0]); 00111 while (true) 00112 wait(1); //Stay in this loop for ever 00113 } 00114 00115 cmd[0] = 0x1A; //We want to write to the CONFIG register (1A hex) 00116 cmd[1] = 0x03; //Set the register bits as 00000011 (Set Digital Low Pass Filter to ~43Hz) 00117 i2c.write(MPU6050, cmd, 2); 00118 } 00119 00120 void readIMU(bool calibrated = true) 00121 { 00122 char cmd[14]; 00123 00124 cmd[0] = 0x3B; //Start reading at register 43h and auto increment with every read. 00125 i2c.write(MPU6050, cmd, 1); 00126 i2c.read(MPU6050, cmd, 14); //Read 14 bytes from the gyro. 00127 00128 acc_raw[0] = (cmd[0] << 8) | cmd[1]; //Add the low and high byte to the acc_x variable. 00129 acc_raw[1] = (cmd[2] << 8) | cmd[3]; //Add the low and high byte to the acc_y variable. 00130 acc_raw[2] = (cmd[4] << 8) | cmd[5]; //Add the low and high byte to the acc_z variable. 00131 00132 gyro_raw[0] = (cmd[8] << 8) | cmd[9]; //Read high and low part of the angular data. 00133 gyro_raw[1] = (cmd[10] << 8) | cmd[11]; //Read high and low part of the angular data. 00134 gyro_raw[2] = (cmd[12] << 8) | cmd[13]; //Read high and low part of the angular data. 00135 00136 if ( calibrated ) { 00137 for (int i = 0; i < 3; i++) 00138 gyro_raw[i] -= gyro_cal[i]; 00139 } 00140 } 00141 00142 void calibrateGyro() 00143 { 00144 for (int i = 0; i < 3; i++) 00145 gyro_cal[i] = 0; 00146 00147 for (int s = 0; s < GYRO_CALIB_SAMPLES; s++) { //Take GYRO_CALIB_SAMPLES readings for calibration. 00148 if (s % 50 == 0) 00149 led_green = 1 - led_green; //Change the led status to indicate calibration. 00150 readIMU(false); //Read the gyro output. 00151 for (int i = 0; i < 3; i++) 00152 gyro_cal[i] += gyro_raw[i]; //Ad roll value to gyro_roll_cal. 00153 wait_us(3); //Wait 3 milliseconds before the next loop. 00154 } 00155 00156 for (int i = 0; i < 3; i++) 00157 gyro_cal[i] /= (float)GYRO_CALIB_SAMPLES; //Divide the roll total by GYRO_CALIB_SAMPLES. 00158 } 00159 00160 bool checkArmStateChange(bool armed) 00161 { 00162 static int armingState = 0; 00163 00164 if ( RC_THROTTLE > 1100 ) 00165 return false; 00166 00167 if ( ! armed && RC_YAW > 1900 ) { 00168 if ( armingState == 0 ) { 00169 armingState = 1; 00170 armingTimer.reset(); 00171 armingTimer.start(); 00172 pc.printf("Started arming\n"); 00173 } 00174 else { 00175 uint16_t time = armingTimer.read_ms(); 00176 if ( time > 2000 ) { 00177 armed = true; 00178 armingState = 0; 00179 pc.printf("ARMED\n"); 00180 //beep(300); 00181 return true; 00182 } 00183 } 00184 } 00185 else if ( armed && RC_YAW < 1100 ) { 00186 if ( armingState == 0 ) { 00187 armingState = -1; 00188 armingTimer.reset(); 00189 armingTimer.start(); 00190 pc.printf("Started disarming\n"); 00191 } 00192 else { 00193 uint16_t time = armingTimer.read_ms(); 00194 if ( time > 2000 ) { 00195 armed = false; 00196 armingState = 0; 00197 pc.printf("DISARMED\n"); 00198 //beep(50); 00199 //wait_ms(50); 00200 //beep(50); 00201 return false; 00202 } 00203 } 00204 } 00205 else { 00206 armingState = 0; 00207 armingTimer.reset(); 00208 } 00209 00210 return armed; 00211 } 00212 00213 bool getAutoLevelState() 00214 { 00215 return ppm.channels[5] > 1600; 00216 } 00217 00218 int afterDeadband(int in) 00219 { 00220 int lower = 1500 - RC_DEADBAND_US; 00221 int upper = 1500 + RC_DEADBAND_US; 00222 if ( in < lower ) 00223 return in - lower; 00224 if ( in > upper ) 00225 return in - upper; 00226 return 0; 00227 } 00228 00229 void resetPID() 00230 { 00231 pid_i_mem_roll = 0; 00232 pid_last_roll_d_error = 0; 00233 pid_i_mem_pitch = 0; 00234 pid_last_pitch_d_error = 0; 00235 pid_i_mem_yaw = 0; 00236 pid_last_yaw_d_error = 0; 00237 } 00238 00239 void calculatePID() 00240 { 00241 //Roll calculations 00242 float pid_error_temp = gyro_smoothed[ROLL] - pid_roll_setpoint; 00243 pid_i_mem_roll += pid_i_gain_roll * pid_error_temp; 00244 if(pid_i_mem_roll > pid_max_roll)pid_i_mem_roll = pid_max_roll; 00245 else if(pid_i_mem_roll < pid_max_roll * -1)pid_i_mem_roll = pid_max_roll * -1; 00246 00247 pid_output_roll = pid_p_gain_roll * pid_error_temp + pid_i_mem_roll + pid_d_gain_roll * (pid_error_temp - pid_last_roll_d_error); 00248 if(pid_output_roll > pid_max_roll)pid_output_roll = pid_max_roll; 00249 else if(pid_output_roll < pid_max_roll * -1)pid_output_roll = pid_max_roll * -1; 00250 00251 pid_last_roll_d_error = pid_error_temp; 00252 00253 //Pitch calculations 00254 pid_error_temp = gyro_smoothed[PITCH] - pid_pitch_setpoint; 00255 pid_i_mem_pitch += pid_i_gain_pitch * pid_error_temp; 00256 if(pid_i_mem_pitch > pid_max_pitch)pid_i_mem_pitch = pid_max_pitch; 00257 else if(pid_i_mem_pitch < pid_max_pitch * -1)pid_i_mem_pitch = pid_max_pitch * -1; 00258 00259 pid_output_pitch = pid_p_gain_pitch * pid_error_temp + pid_i_mem_pitch + pid_d_gain_pitch * (pid_error_temp - pid_last_pitch_d_error); 00260 if(pid_output_pitch > pid_max_pitch)pid_output_pitch = pid_max_pitch; 00261 else if(pid_output_pitch < pid_max_pitch * -1)pid_output_pitch = pid_max_pitch * -1; 00262 00263 pid_last_pitch_d_error = pid_error_temp; 00264 00265 //Yaw calculations 00266 pid_error_temp = gyro_smoothed[YAW] - pid_yaw_setpoint; 00267 pid_i_mem_yaw += pid_i_gain_yaw * pid_error_temp; 00268 if(pid_i_mem_yaw > pid_max_yaw)pid_i_mem_yaw = pid_max_yaw; 00269 else if(pid_i_mem_yaw < pid_max_yaw * -1)pid_i_mem_yaw = pid_max_yaw * -1; 00270 00271 pid_output_yaw = pid_p_gain_yaw * pid_error_temp + pid_i_mem_yaw + pid_d_gain_yaw * (pid_error_temp - pid_last_yaw_d_error); 00272 if(pid_output_yaw > pid_max_yaw)pid_output_yaw = pid_max_yaw; 00273 else if(pid_output_yaw < pid_max_yaw * -1)pid_output_yaw = pid_max_yaw * -1; 00274 00275 pid_last_yaw_d_error = pid_error_temp; 00276 } 00277 00278 int main() { 00279 00280 i2c.frequency(400000); 00281 00282 buzzer = 1; 00283 led_red = 1; 00284 led_green = 1; 00285 00286 motorPin1 = 0; 00287 motorPin2 = 0; 00288 motorPin3 = 0; 00289 motorPin4 = 0; 00290 motorPin5 = 0; 00291 motorPin6 = 0; 00292 00293 armingTimer.reset(); 00294 00295 beep(50); 00296 00297 pc.printf("setIMURegisters\n"); 00298 setIMURegisters(); 00299 00300 pc.printf("calibrateGyro\n"); 00301 calibrateGyro(); 00302 00303 pc.printf("Finished gyro calibration (%f, %f, %f)\n", gyro_cal[0], gyro_cal[1], gyro_cal[2]); 00304 00305 timer.reset(); 00306 timer.start(); 00307 00308 for (int i = 0; i < 3; i++) 00309 gyro_smoothed[i] = 0; 00310 angle_pitch = 0; 00311 angle_roll = 0; 00312 00313 beep(50); 00314 wait_ms(50); 00315 beep(50); 00316 00317 bool armed = false; 00318 bool armedLastTime = armed; 00319 00320 bool autoLevel = getAutoLevelState(); 00321 bool autoLevelLastTime = autoLevel; 00322 00323 unsigned long oneShotCounter = 0; 00324 00325 unsigned long loopCounter = 0; 00326 00327 float initialAccUptake = 0.8; 00328 00329 while (true) { 00330 00331 //pc.printf("Gyro (%f, %f, %f)\n", gyro_smoothed[0], gyro_smoothed[1], gyro_smoothed[2]); 00332 //pc.printf("Accel (%f, %f)\n", angle_pitch, angle_roll); 00333 00334 armed = checkArmStateChange(armed); 00335 led_green = ! armed; 00336 00337 if ( ! armedLastTime && armed ) 00338 resetPID(); 00339 if ( armed != armedLastTime ) { 00340 oneShotCounter = 20; 00341 beepStart(); 00342 } 00343 armedLastTime = armed; 00344 00345 autoLevel = getAutoLevelState(); 00346 if ( autoLevel != autoLevelLastTime ) { 00347 oneShotCounter = 4; 00348 beepStart(); 00349 } 00350 autoLevelLastTime = autoLevel; 00351 00352 if ( oneShotCounter > 0 ) { 00353 if ( --oneShotCounter == 0 ) { 00354 beepStop(); 00355 } 00356 } 00357 00358 const float uptake = 0.3; 00359 for (int i = 0; i < 3; i++) 00360 gyro_smoothed[i] = (gyro_smoothed[i] * (1-uptake)) + ((gyro_raw[i] / 65.5) * uptake); //Gyro pid input is deg/sec. 00361 00362 00363 //Gyro angle calculations 00364 //0.0000611 = 1 / (250Hz / 65.5) 00365 angle_pitch += gyro_raw[ROLL] * 0.0000611; //Calculate the traveled pitch angle and add this to the angle_pitch variable. 00366 angle_roll += gyro_raw[PITCH] * 0.0000611; //Calculate the traveled roll angle and add this to the angle_roll variable. 00367 00368 //0.000001066 = 0.0000611 * (3.142(PI) / 180degr) The Arduino sin function is in radians 00369 float new_angle_pitch = angle_pitch + angle_roll * sin(gyro_raw[YAW] * 0.000001066); //If the IMU has yawed transfer the roll angle to the pitch angel. 00370 angle_roll -= angle_pitch * sin(gyro_raw[YAW] * 0.000001066); //If the IMU has yawed transfer the pitch angle to the roll angel. 00371 angle_pitch = new_angle_pitch; 00372 00373 //Accelerometer angle calculations 00374 acc_total_vector = sqrtf((acc_raw[0]*acc_raw[0])+(acc_raw[1]*acc_raw[1])+(acc_raw[2]*acc_raw[2])); //Calculate the total accelerometer vector. 00375 00376 if (abs(acc_raw[PITCH]) < acc_total_vector) { //Prevent the asin function to produce a NaN 00377 angle_pitch_acc = asin((float)acc_raw[PITCH]/acc_total_vector)* 57.296; //Calculate the pitch angle. 00378 } 00379 if (abs(acc_raw[ROLL]) < acc_total_vector) { //Prevent the asin function to produce a NaN 00380 angle_roll_acc = asin((float)acc_raw[ROLL]/acc_total_vector)* -57.296; //Calculate the roll angle. 00381 } 00382 00383 //Place the MPU-6050 spirit level and note the values in the following two lines for calibration. 00384 angle_pitch_acc -= 0.0; //Accelerometer calibration value for pitch. 00385 angle_roll_acc -= 0.0; //Accelerometer calibration value for roll. 00386 00387 initialAccUptake *= 0.98; 00388 00389 float acc_uptake = 0.0004 + initialAccUptake; 00390 angle_pitch = angle_pitch * (1-acc_uptake) + angle_pitch_acc * acc_uptake; //Correct the drift of the gyro pitch angle with the accelerometer pitch angle. 00391 angle_roll = angle_roll * (1-acc_uptake) + angle_roll_acc * acc_uptake; //Correct the drift of the gyro roll angle with the accelerometer roll angle. 00392 00393 pitch_level_adjust = angle_pitch * 15; //Calculate the pitch angle correction 00394 roll_level_adjust = angle_roll * 15; //Calculate the roll angle correction 00395 00396 if ( ! autoLevel ){ //If the quadcopter is not in auto-level mode 00397 pitch_level_adjust = 0; //Set the pitch angle correction to zero. 00398 roll_level_adjust = 0; //Set the roll angle correcion to zero. 00399 } 00400 00401 00402 00403 //The PID set point in degrees per second is determined by the roll receiver input. 00404 //In the case of deviding by 3 the max roll rate is aprox 164 degrees per second ( (500-8)/3 = 164d/s ). 00405 pid_roll_setpoint = afterDeadband(RC_ROLL); 00406 pid_roll_setpoint -= roll_level_adjust; //Subtract the angle correction from the standardized receiver roll input value. 00407 pid_roll_setpoint /= 3.0; //Divide the setpoint for the PID roll controller by 3 to get angles in degrees. 00408 00409 //The PID set point in degrees per second is determined by the pitch receiver input. 00410 //In the case of deviding by 3 the max pitch rate is aprox 164 degrees per second ( (500-8)/3 = 164d/s ). 00411 pid_pitch_setpoint = afterDeadband(RC_PITCH); 00412 pid_pitch_setpoint -= pitch_level_adjust; //Subtract the angle correction from the standardized receiver pitch input value. 00413 pid_pitch_setpoint /= 3.0; //Divide the setpoint for the PID pitch controller by 3 to get angles in degrees. 00414 00415 //The PID set point in degrees per second is determined by the yaw receiver input. 00416 //In the case of dividing by 3 the max yaw rate is aprox 164 degrees per second ( (500-8)/3 = 164d/s ). 00417 pid_yaw_setpoint = 0; 00418 //We need a little dead band of 16us for better results. 00419 if (RC_THROTTLE > 1050) { //Do not yaw when turning off the motors. 00420 pid_yaw_setpoint = afterDeadband(RC_YAW) / 3.0; 00421 } 00422 00423 calculatePID(); 00424 00425 int pwm[6]; 00426 00427 if (armed) { //The motors are started. 00428 int throttle = RC_THROTTLE; 00429 if (throttle > 1800) 00430 throttle = 1800; //We need some room to keep full control at full throttle. 00431 pwm[0] = throttle - pid_output_pitch + pid_output_roll - pid_output_yaw; //Calculate the pulse for esc 1 (front-right - CCW) 00432 pwm[1] = throttle + pid_output_pitch + pid_output_roll + pid_output_yaw; //Calculate the pulse for esc 2 (rear-right - CW) 00433 pwm[2] = throttle + pid_output_pitch - pid_output_roll - pid_output_yaw; //Calculate the pulse for esc 3 (rear-left - CCW) 00434 pwm[3] = throttle - pid_output_pitch - pid_output_roll + pid_output_yaw; //Calculate the pulse for esc 4 (front-left - CW) 00435 pwm[4] = 1000; 00436 pwm[5] = 1000; 00437 00438 for (int i = 0; i < 6; i++) 00439 pwm[i] = constrain( pwm[i], 1100, 2000); //Keep the motors running. 00440 } 00441 else { 00442 for (int i = 0; i < 6; i++) 00443 pwm[i] = 1000; //If start is not 2 keep a 1000us pulse for ess-1. 00444 } 00445 00446 00447 00448 00449 while ( timer.read_us() < 4000 ) 00450 ; // wait 00451 00452 motorPin1 = motorPin2 = motorPin3 = motorPin4 = 1; 00453 timer.reset(); 00454 00455 readIMU(); 00456 00457 if ( loopCounter++ % 20 == 0 ) { 00458 //pc.printf("%d %d %d %d\n", pwm[0], pwm[1], pwm[2], pwm[3]); 00459 pc.printf("%f %f\n", angle_roll, angle_pitch ); 00460 //pc.printf("%d\n", pwm[0] ); 00461 //pc.printf("Gyro (%f, %f, %f)\n", gyro_smoothed[0], gyro_smoothed[1], gyro_smoothed[2]); 00462 //pc.printf("%d, %d, %d, %d, %d, %d\n", ppm.channels[0], ppm.channels[1], ppm.channels[2], ppm.channels[3], ppm.channels[4], ppm.channels[5]); 00463 } 00464 00465 bool doneOutput = false; 00466 while ( ! doneOutput ) { 00467 int now = timer.read_us(); 00468 int c = 0; 00469 if ( now >= pwm[0] ) { motorPin1 = 0; c++; } 00470 if ( now >= pwm[1] ) { motorPin2 = 0; c++; } 00471 if ( now >= pwm[2] ) { motorPin3 = 0; c++; } 00472 if ( now >= pwm[3] ) { motorPin4 = 0; c++; } 00473 if ( now >= pwm[4] ) { motorPin5 = 0; c++; } 00474 if ( now >= pwm[5] ) { motorPin6 = 0; c++; } 00475 doneOutput = c >= 6; 00476 } 00477 00478 } 00479 00480 /*while (true) { 00481 pc.printf("%d, %d, %d, %d, %d, %d\n", ppm.channels[0], ppm.channels[1], ppm.channels[2], ppm.channels[3], ppm.channels[4], ppm.channels[5]); 00482 }*/ 00483 }
Generated on Sun Jul 31 2022 22:47:51 by
1.7.2