diff --git a/conf/airframes/matek_f405_wing/Easyglider_matek_f405_autoload.xml b/conf/airframes/matek_f405_wing/Easyglider_matek_f405_autoload.xml index 5544a96a32..79d27ad291 100644 --- a/conf/airframes/matek_f405_wing/Easyglider_matek_f405_autoload.xml +++ b/conf/airframes/matek_f405_wing/Easyglider_matek_f405_autoload.xml @@ -51,13 +51,39 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -69,12 +95,13 @@ - - + + + - - - + + + @@ -82,6 +109,7 @@ + @@ -92,21 +120,27 @@ + +
- +
+ + + - + + + - @@ -126,27 +160,28 @@ Normalized Local magnetic field obtained from http://www.ngdc.noaa.gov/geomag-web/#igrfwmm Magnetic field intensity (North-East-vertical) / total field strength H_x = INTENSITY_x / Total Magnetic Field - Total Magnetic Field = (intensityEAST^2+intensityNORTH^2+intensityVERTICAL^2) - Calculated for LAGONISI ATTIKI 21 AUGUST 2020 + Total Magnetic Field = (intensityEAST^2+intensityNORTH^2+intensityVERTICAL^2) + Calculated for LAGONISI ATTIKI 23 October 2020 -->
- - - - + + + +
+ + + - - @@ -154,23 +189,31 @@ - - - - + + + + + + + - + + + + + + +
-
@@ -243,9 +286,11 @@
+ + - - + +
@@ -263,16 +308,16 @@ - + - - +
- +
diff --git a/conf/airframes/matek_f405_wing/Easyglider_matek_f405_wing.xml b/conf/airframes/matek_f405_wing/Easyglider_matek_f405_wing.xml index 07391d8bd4..c1eb065afd 100644 --- a/conf/airframes/matek_f405_wing/Easyglider_matek_f405_wing.xml +++ b/conf/airframes/matek_f405_wing/Easyglider_matek_f405_wing.xml @@ -27,11 +27,9 @@ - - - + @@ -47,28 +45,53 @@ - - + + - + + - + - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -80,12 +103,13 @@ - - + + + - - - + + + @@ -93,6 +117,7 @@ + @@ -103,22 +128,27 @@ - + +
- +
+ + + - + + + - @@ -128,7 +158,6 @@ --> - @@ -139,26 +168,27 @@ Magnetic field intensity (North-East-vertical) / total field strength H_x = INTENSITY_x / Total Magnetic Field Total Magnetic Field = (intensityEAST^2+intensityNORTH^2+intensityVERTICAL^2) - Calculated for LAGONISI ATTIKI 21 AUGUST 2020 + Calculated for LAGONISI ATTIKI 23 October 2020 -->
- - - - + + + +
+ + + - - @@ -166,22 +196,37 @@ - - - - + + + + + + + + + + + + + + +
+
+ + +
@@ -198,7 +243,7 @@ - + @@ -255,9 +300,11 @@
+ + - - + +
@@ -275,16 +322,16 @@ - + - - +
- +
@@ -335,7 +382,7 @@ - +
diff --git a/conf/flight_plans/lagonisi_beach_survey.xml b/conf/flight_plans/lagonisi_beach_survey.xml new file mode 100644 index 0000000000..78730414c9 --- /dev/null +++ b/conf/flight_plans/lagonisi_beach_survey.xml @@ -0,0 +1,155 @@ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/conf/gcs/gcs_with_video.xml b/conf/gcs/gcs_with_video.xml new file mode 100644 index 0000000000..69125dc906 --- /dev/null +++ b/conf/gcs/gcs_with_video.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/modules/board_matek_wing.xml b/conf/modules/board_matek_wing.xml index 8a1b014b83..bdd8b30050 100644 --- a/conf/modules/board_matek_wing.xml +++ b/conf/modules/board_matek_wing.xml @@ -27,10 +27,8 @@ - - - + + - diff --git a/conf/modules/pca9685.xml b/conf/modules/pca9685.xml new file mode 100644 index 0000000000..21304c21d0 --- /dev/null +++ b/conf/modules/pca9685.xml @@ -0,0 +1,22 @@ + + + + + + pca9685 additional servo driver + in a c code file write pca9865_set_servo(12, 1500); to set servo number 12. + In the flight plan you can use call_once fun="pca9865_set_servo(12, 1500)" + + + + +
+ +
+ + + + + + +
diff --git a/conf/modules/uav_recovery.xml b/conf/modules/uav_recovery.xml new file mode 100644 index 0000000000..899c29b78c --- /dev/null +++ b/conf/modules/uav_recovery.xml @@ -0,0 +1,32 @@ + + + + + Parachute + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + +
diff --git a/conf/radios/openTX_radio_8ch.xml b/conf/radios/openTX_radio_8ch.xml index febe4959fe..ac62be1e88 100644 --- a/conf/radios/openTX_radio_8ch.xml +++ b/conf/radios/openTX_radio_8ch.xml @@ -38,14 +38,14 @@ -- Note: a command may be reversed by exchanging min and max values --> - - - - - - - - - + + + + + + + + + diff --git a/conf/telemetry/telemetry_openlrsng_19200.xml b/conf/telemetry/telemetry_openlrsng_19200.xml index 619a007e01..37f082a4e5 100644 --- a/conf/telemetry/telemetry_openlrsng_19200.xml +++ b/conf/telemetry/telemetry_openlrsng_19200.xml @@ -3,32 +3,35 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + + + - - + - + @@ -40,13 +43,14 @@ - + + - - + + diff --git a/sw/airborne/boards/matek_f405_wing_v1.h b/sw/airborne/boards/matek_f405_wing_v1.h index 74b9a73a6c..08e57b3ae8 100644 --- a/sw/airborne/boards/matek_f405_wing_v1.h +++ b/sw/airborne/boards/matek_f405_wing_v1.h @@ -251,8 +251,8 @@ #define ADC_CHANNEL_VSUPPLY ADC_1 #endif -#ifndef CURRENT_ADC_IN -#define CURRENT_ADC_IN ADC_2 +#ifndef ADC_CHANNEL_CURRENT +#define ADC_CHANNEL_CURRENT ADC_2 #endif /* no voltage divider on board, adjust VoltageOfAdc in airframe file */ diff --git a/sw/airborne/modules/display/max7456.c b/sw/airborne/modules/display/max7456.c index 8479645d32..eb9044bad0 100644 --- a/sw/airborne/modules/display/max7456.c +++ b/sw/airborne/modules/display/max7456.c @@ -47,6 +47,9 @@ #else #include "firmwares/rotorcraft/navigation.h" #endif +#if DOWNLINK +#include "subsystems/datalink/telemetry.h" +#endif // Peripherials #include "modules/display/max7456.h" @@ -55,6 +58,49 @@ #define OSD_STRING_SIZE 31 #define osd_sprintf _osd_sprintf +#if !defined(OSD_USE_FLOAT_LOW_PASS_FILTERING) +#define OSD_USE_FLOAT_LOW_PASS_FILTERING +#endif + +//LOW PASS filter strength, cannot be 0, MAX=16 +#if !defined(AMPS_LOW_PASS_FILTER_STRENGTH) || AMPS_LOW_PASS_FILTER_STRENGTH == 0 +#define AMPS_LOW_PASS_FILTER_STRENGTH 6 +#endif + +#if !defined(SPEED_LOW_PASS_FILTER_STRENGTH) || SPEED_LOW_PASS_FILTER_STRENGTH == 0 +#define SPEED_LOW_PASS_FILTER_STRENGTH 6 +#endif + +#if !defined(BAT_CAPACITY) +#pragma message "BAT_CAPACITY not defined, 5000 mah will be used." +#define BAT_CAPACITY 5000.0 +#endif + +#if AP + +#if !defined(LOITER_BAT_CURRENT) +#pragma message "LOITER_BAT_CURRENT not defined, 10 Amps will be used for LOITER current draw." +#define LOITER_BAT_CURRENT 10.0 +#endif + +#if !defined(STALL_AIRSPEED) +#pragma message "STALL_AIRSPEED not defined, 10 m/s will be used." +#define STALL_AIRSPEED 10.0 +#endif + +#endif + +#if !defined(IMU_MAG_X_SIGN) +#define IMU_MAG_X_SIGN 1 +#endif +#if !defined(IMU_MAG_X_SIGN) +#define IMU_MAG_Y_SIGN 1 +#endif +#if !defined(IMU_MAG_X_SIGN) +#define IMU_MAG_Z_SIGN 1 +#endif + + typedef struct { float fx; float fy; @@ -67,11 +113,16 @@ typedef struct { float fz1; float fz2; float fz3; } MATRIX; - +#if AP +static void mag_compass(void); +#endif +static void send_mag_heading(struct transport_tx *trans, struct link_device *dev); static void vSubtractVectors(VECTOR *svA, VECTOR svB, VECTOR svC); static void vMultiplyMatrixByVector(VECTOR *svA, MATRIX smB, VECTOR svC); static float home_direction(void); static char ascii_to_osd_c(char c); +static void calc_flight_time_left(void); +static void draw_osd(void); static void osd_put_s(char *string, uint8_t attributes, uint8_t char_nb, uint8_t row, uint8_t column); static bool _osd_sprintf(char *buffer, char *string, float value); @@ -113,8 +164,95 @@ uint8_t max7456_osd_status = OSD_UNINIT; uint8_t osd_enable = true; uint8_t osd_enable_val = OSD_IMAGE_ENABLE; uint8_t osd_stat_reg = 0; -float home_dir = 0; +uint32_t max_flyable_distance_left = 0; +float mag_course_deg = 0; +float mag_heading_rad = 0; +float gps_course_deg = 0; +float home_dir_deg = 0; + +#if AP +// Periodic function called with a frequency defined in the module .xml file +void mag_compass(void) +{ + + struct FloatEulers *att = stateGetNedToBodyEulers_f(); + struct Int32Vect3 mag; + struct Int32Vect3 mag_neutrals; + int32_t x = 0, y = 0, z = 0; + float cos_roll; float sin_roll; float cos_pitch; float sin_pitch; float mag_x; float mag_y; + static float mag_declination = 0; + static bool declination_calculated = false; + + VECT3_COPY(mag, imu.mag_unscaled); + VECT3_COPY(mag_neutrals, imu.mag_neutral); +#if (defined(IMU_MAG_X_SENS) && defined(IMU_MAG_Y_SENS) && defined(IMU_MAG_Z_SENS)) && !defined(USE_MAGNETOMETER) + x = ((mag.x - mag_neutrals.x) * IMU_MAG_X_SIGN * IMU_MAG_X_SENS_NUM) / IMU_MAG_X_SENS_DEN; + y = ((mag.y - mag_neutrals.y) * IMU_MAG_Y_SIGN * IMU_MAG_Y_SENS_NUM) / IMU_MAG_Y_SENS_DEN; + z = ((mag.z - mag_neutrals.z) * IMU_MAG_Z_SIGN * IMU_MAG_Z_SENS_NUM) / IMU_MAG_Z_SENS_DEN; +#else + x = (mag.x - mag_neutrals.x) * IMU_MAG_X_SIGN; + y = (mag.y - mag_neutrals.y) * IMU_MAG_Y_SIGN; + z = (mag.z - mag_neutrals.z) * IMU_MAG_Z_SIGN; +#endif + + cos_roll = cosf(att->phi); + sin_roll = sinf(att->phi); + cos_pitch = cosf(att->theta); + sin_pitch = sinf(att->theta); + // Pitch&Roll Compensation: + mag_x = x * cos_pitch + y * sin_roll * sin_pitch + z * cos_roll * sin_pitch; + mag_y = y * cos_roll - z * sin_roll; + + // Magnetic Heading N = 0, E = 90, S = +-180, W = -90 + mag_heading_rad = atan2(-mag_y, mag_x); +#if defined(AHRS_MAG_DECLINATION) + if (AHRS_MAG_DECLINATION != 0.0) { + //conversion from degrees to radians is done in the airframe.h file + mag_heading_rad = mag_heading_rad + AHRS_MAG_DECLINATION; + } +#endif + if (mag_heading_rad > M_PI) { // Angle normalization (-180 deg to 180 deg) + mag_heading_rad -= (2.0 * M_PI); + } else if (mag_heading_rad < -M_PI) { mag_heading_rad += (2.0 * M_PI); } + + if (declination_calculated == false) { +#if defined(NOMINAL_AIRSPEED) + if (gps.fix == GPS_FIX_3D && stateGetHorizontalSpeedNorm_f() > (float)NOMINAL_AIRSPEED) { +#else + if (gps.fix == GPS_FIX_3D && stateGetHorizontalSpeedNorm_f() > 10.0) { +#endif + mag_declination = stateGetHorizontalSpeedDir_f(); + if (mag_declination > M_PI) { // Angle normalization (-180 deg to 180 deg) + mag_declination -= (2.0 * M_PI); + } else if (mag_declination < -M_PI) { mag_declination += (2.0 * M_PI); } + mag_declination -= mag_heading_rad; + declination_calculated = true; + if (fabs(mag_declination) > RadOfDeg(10.)) { mag_declination = 0; declination_calculated = false; } + } + } + mag_heading_rad = mag_heading_rad + mag_declination; + if (mag_heading_rad > M_PI) { // Angle normalization (-180 deg to 180 deg) + mag_heading_rad -= (2.0 * M_PI); + } else if (mag_heading_rad < -M_PI) { mag_heading_rad += (2.0 * M_PI); } + // Magnetic COMPASS Heading N = 0, E = 90, S = 180, W = 270 + mag_course_deg = DegOfRad(mag_heading_rad); + if (mag_course_deg < 0) { mag_course_deg += 360; } + + return; +} +#endif + + +static void send_mag_heading(struct transport_tx *trans, struct link_device *dev) +{ + +#if DOWNLINK + pprz_msg_send_IMU_MAG(trans, dev, AC_ID, &mag_course_deg, &gps_course_deg, &home_dir_deg); +#endif + + return; +} //******************************************************************* // function name: vSubtractVectors @@ -144,12 +282,10 @@ static void vMultiplyMatrixByVector(VECTOR *svA, MATRIX smB, VECTOR svC) static float home_direction(void) { - static VECTOR svPlanePosition, - Home_Position, - Home_PositionForPlane, - Home_PositionForPlane2; - + static VECTOR svPlanePosition, Home_Position, Home_PositionForPlane, Home_PositionForPlane2; static MATRIX smRotation; + float home_dir = 0; + /* By swapping coordinates (fx=fPlaneNorth, fy=fPlaneEast) we make the the circle angle go from 0 (0 is to the top of the circle) @@ -157,67 +293,72 @@ static float home_direction(void) applied to the rotation matrices (in radians). In standard mathematical notation 0 is to the right (East) of the circle, -90 is to the bottom, +-180 is to the left and +90 is to the top (counterclockwise rotation). - When reading back the actual rotated coordinates fx has the y coordinate and fy has the x + When reading back the actual rotated coordinates fx has the y coordinate and fy has the x when represented on a circle in standard mathematical notation. */ - - svPlanePosition.fx = stateGetPositionEnu_f()->y; - svPlanePosition.fy = stateGetPositionEnu_f()->x; - svPlanePosition.fz = stateGetPositionUtm_f()->alt; + if (gps.fix == GPS_FIX_3D && stateGetHorizontalSpeedNorm_f() > 5.0) { //Only when flying + svPlanePosition.fx = stateGetPositionEnu_f()->y; + svPlanePosition.fy = stateGetPositionEnu_f()->x; + svPlanePosition.fz = stateGetPositionUtm_f()->alt; #ifdef AP - Home_Position.fx = WaypointY(WP_HOME); - Home_Position.fy = WaypointX(WP_HOME); - Home_Position.fz = ground_alt; + Home_Position.fx = WaypointY(WP_HOME); + Home_Position.fy = WaypointX(WP_HOME); + Home_Position.fz = GetAltRef(); #else - Home_Position.fx = waypoint_get_x(WP_HOME); - Home_Position.fy = waypoint_get_y(WP_HOME); - Home_Position.fz = 0; + Home_Position.fx = waypoint_get_x(WP_HOME); + Home_Position.fy = waypoint_get_y(WP_HOME); + Home_Position.fz = 0; #endif - /* distance between plane and object */ - vSubtractVectors(&Home_PositionForPlane, Home_Position, svPlanePosition); + /* distance between plane and object */ + vSubtractVectors(&Home_PositionForPlane, Home_Position, svPlanePosition); - /* yaw */ - smRotation.fx1 = cosf(stateGetHorizontalSpeedDir_f()); - smRotation.fx2 = sinf(stateGetHorizontalSpeedDir_f()); - smRotation.fx3 = 0.; - smRotation.fy1 = -smRotation.fx2; - smRotation.fy2 = smRotation.fx1; - smRotation.fy3 = 0.; - smRotation.fz1 = 0.; - smRotation.fz2 = 0.; - smRotation.fz3 = 1.; + /* yaw */ + smRotation.fx1 = cosf(stateGetHorizontalSpeedDir_f()); + smRotation.fx2 = sinf(stateGetHorizontalSpeedDir_f()); + smRotation.fx3 = 0.; + smRotation.fy1 = -smRotation.fx2; + smRotation.fy2 = smRotation.fx1; + smRotation.fy3 = 0.; + smRotation.fz1 = 0.; + smRotation.fz2 = 0.; + smRotation.fz3 = 1.; - vMultiplyMatrixByVector(&Home_PositionForPlane2, smRotation, Home_PositionForPlane); + vMultiplyMatrixByVector(&Home_PositionForPlane2, smRotation, Home_PositionForPlane); - /* DEFAULT ORIENTATION IS 0 = FRONT, 90 = RIGHT, 180 = BACK, -90 = LEFT - * - * WHEN home_dir = (float)(atan2(Home_PositionForPlane2.fy, (Home_PositionForPlane2.fx))); - * - * plane front - * 0˚ - * ^ - * I - * -45˚ I 45˚ - * \ I / - * \I/ - * -90˚-------I------- 90˚ - * /I\ - * / I \ - * -135˚ I 135˚ - * I - * 180 - * plane back - * - * - */ + /* DEFAULT ORIENTATION IS 0 = FRONT, 90 = RIGHT, 180 = BACK, -90 = LEFT + * + * WHEN home_dir = (float)(atan2(Home_PositionForPlane2.fy, (Home_PositionForPlane2.fx))); + * + * plane front + * 0˚ + * ^ + * I + * -45˚ I 45˚ + * \ I / + * \I/ + * -90˚-------I------- 90˚ + * /I\ + * / I \ + * -135˚ I 135˚ + * I + * 180 + * plane back + * + * + * When the home_dir variable goes to 0 the aircraft is headed straight back home + */ - /* fixed to the plane*/ - home_dir = (float)(atan2(Home_PositionForPlane2.fy, (Home_PositionForPlane2.fx))); - home_dir = DegOfRad(home_dir); - if (home_dir < 0) { home_dir += 360; } + /* fixed to the plane*/ + home_dir = atan2(Home_PositionForPlane2.fy, Home_PositionForPlane2.fx); + if (home_dir > M_PI) { // Angle normalization (-180 deg to 180 deg but still in radians) + home_dir -= (2.0 * M_PI); + } else if (home_dir < -M_PI) { home_dir += (2.0 * M_PI); } + home_dir_deg = DegOfRad(home_dir); // Now convert radians to degrees. - return (home_dir); + } // END OF if (gps.fix == GPS_FIX_3D) statement. + + return (home_dir_deg); } static char ascii_to_osd_c(char c) @@ -229,6 +370,7 @@ static char ascii_to_osd_c(char c) return (c); #else + PRINT_CONFIG_MSG("OSD USES THE STANDARF MAX7456 OSD CHIP") if (c >= '0' && c <= '9') { if (c == '0') { c -= 38; } else { c -= 48; } @@ -246,6 +388,7 @@ static char ascii_to_osd_c(char c) case (';'): c = 0x43; break; case (':'): c = 0x44; break; case (','): c = 0x45; break; + //case('''): c = 0x46; break; case ('/'): c = 0x47; break; case ('"'): c = 0x48; break; case ('-'): c = 0x49; break; @@ -263,6 +406,77 @@ static char ascii_to_osd_c(char c) #endif } +static void calc_flight_time_left(void) +{ + float current_amps = 0; + float horizontal_speed = 0; + float bat_capacity_left = 0; + static float bat_capacity_used = 0; + + + current_amps = electrical.current; + bat_capacity_used += (current_amps * 1000.) / (3600. * (float)MAX7456_PERIODIC_FREQ); + bat_capacity_left = (float)BAT_CAPACITY - bat_capacity_used; + if (bat_capacity_left < 0) { bat_capacity_left = 0; } + horizontal_speed = stateGetHorizontalSpeedNorm_f(); + +#if AP + if (stateGetHorizontalSpeedNorm_f() < 5.0 || autopilot.launch == false) { + current_amps = LOITER_BAT_CURRENT; + horizontal_speed = MINIMUM_AIRSPEED; + } +#else // #if AP + current_amps = 1.0; // FIXME, Find how to tell if the rotorcraft is on the ground or it is flying. + horizontal_speed = 10.0; +#endif + +#if defined(OSD_USE_FLOAT_LOW_PASS_FILTERING) + + static double current_amps_sum = 0; + static double horizontal_speed_sum = 0; + static float current_amps_filtered = 0; + static float horizontal_speed_filtered = 0; + + current_amps_sum = (current_amps_sum - current_amps_filtered) + current_amps; + current_amps_filtered = current_amps_sum / (1 << AMPS_LOW_PASS_FILTER_STRENGTH); + current_amps = current_amps_filtered; + + horizontal_speed_sum = (horizontal_speed_sum - horizontal_speed_filtered) + horizontal_speed; + horizontal_speed_filtered = horizontal_speed_sum / (1 << SPEED_LOW_PASS_FILTER_STRENGTH); + horizontal_speed = horizontal_speed_filtered; + +#else + + uint32_t current_amps_int = 0; + uint32_t horizontal_speed_int = 0; + static uint64_t current_amps_sum = 0; + static uint64_t horizontal_speed_sum = 0; + static uint32_t current_amps_filtered = 0; + static uint32_t horizontal_speed_filtered = 0; + + // LOW PASS FILTERS for making the OSD 'max_flyable_distance_left' var change more gently. + current_amps_int = (uint32_t)(current_amps * 1000.0); + horizontal_speed_int = (uint32_t)(horizontal_speed * 1000.0); + + current_amps_sum = (current_amps_sum - current_amps_filtered) + current_amps_int; + current_amps_filtered = (current_amps_sum + (1 << (AMPS_LOW_PASS_FILTER_STRENGTH - 1))) >> + (AMPS_LOW_PASS_FILTER_STRENGTH); + + horizontal_speed_sum = (horizontal_speed_sum - horizontal_speed_filtered) + horizontal_speed_int; + horizontal_speed_filtered = (horizontal_speed_sum + (1 << (SPEED_LOW_PASS_FILTER_STRENGTH - 1))) >> + (SPEED_LOW_PASS_FILTER_STRENGTH); + + current_amps = (float)(current_amps_filtered) / 1000.; + horizontal_speed = (float)(horizontal_speed_filtered) / 1000.; + +#endif + + max_flyable_distance_left = ((bat_capacity_left / (current_amps * 1000)) * 3600) * horizontal_speed; + + return; +} + + static void osd_put_s(char *string, uint8_t attributes, uint8_t char_nb, uint8_t row, uint8_t column) { @@ -328,7 +542,6 @@ static void osd_put_s(char *string, uint8_t attributes, uint8_t char_nb, uint8_t static bool _osd_sprintf(char *buffer, char *string, float value) { - uint8_t param_start = 0; uint8_t param_end = 0; uint8_t frac_nb = 0; @@ -347,28 +560,27 @@ static bool _osd_sprintf(char *buffer, char *string, float value) //copy the string passed as parameter to a buffer for (x = 0; x < sizeof(string_buf); x++) { string_buf[x] = *(string + x); if (string_buf[x] == '\0') { break; } } - x = 0; - param_start = 0; - param_end = 0; -//do { - //Now check for any special character - while (string_buf[x] != '\0') { - // EXAMPLE: in "%160c"x is '%' x+4 = 'c' and x+1='1', x+2='6' and x+3='0' - if (string_buf[x] == '%') { if (string_buf[x + 4] == 'c') { (param_start = x + 1); param_end = x + 3; break; } } - x++; - } - if (param_end - param_start) { - //load the special character value where the % character was - string_buf[x] = ((string_buf[param_start] - 48) * 100) + ((string_buf[param_start + 1] - 48) * 10) + - (string_buf[param_start + 2] - 48); - x++; // increment x to the next character which should be the first special character's digit - //Move the rest of the buffer forward so only the special character remains, - // for example in %170c '%' now has the special character's code and x now points to '1' - // which will be overwritten with the rest of the string after the 'c' - for (y = (x + 4); y <= sizeof(string_buf); y++) { string_buf[x++] = string_buf[y]; } - } - -//}while((param_end-param_start > 0)); + do { + x = 0; + param_start = 0; + param_end = 0; + //Now check for any special character + while (string_buf[x] != '\0') { + // EXAMPLE: in "%160c"x is '%' x+4 = 'c' and x+1='1', x+2='6' and x+3='0' + if (string_buf[x] == '%') { if (string_buf[x + 4] == 'c') { (param_start = x + 1); param_end = x + 3; break; } } + x++; + } + if (param_end - param_start) { + //load the special character value where the % character was + string_buf[x] = ((string_buf[param_start] - 48) * 100) + ((string_buf[param_start + 1] - 48) * 10) + + (string_buf[param_start + 2] - 48); + x++; // increment x to the next character which should be the first special character's digit + //Move the rest of the buffer forward so only the special character remains, + // for example in %170c '%' now has the special character's code and x now points to '1' + // which will be overwritten with the rest of the string after the 'c' + for (y = (x + 4); y <= sizeof(string_buf); y++) { string_buf[x++] = string_buf[y]; } + } + } while ((param_end - param_start > 0)); // RESET THE USED VARIABLES JUST TO BE SAFE. x = 0; @@ -436,11 +648,287 @@ static bool _osd_sprintf(char *buffer, char *string, float value) } while (string_buf[param_end] != '\0'); //Write the rest of the string including the terminating char. - } // End of if (param_end - param_start) + // End of if (param_end - param_start) + } else { + for (x = 0; x < sizeof(string_buf); x++) { + *(buffer + x) = string_buf[x]; //Write the rest of the string including the terminating char. + if (*(buffer + x) == '\0') { break; } + } + } return (0); } +void draw_osd(void) +{ + float temp = 0; + float altitude = 0; + float distance_to_home = 0; + static float home_direction_degrees = 0; +#if defined(BARO_ALTITUDE_VAR) + static float baro_alt_correction = 0; +#endif + + struct FloatEulers *att = stateGetNedToBodyEulers_f(); + struct EnuCoor_f *pos = stateGetPositionEnu_f(); +#if AP + float ph_x = waypoints[WP_HOME].x - pos->x; + float ph_y = waypoints[WP_HOME].y - pos->y; + float stall_speed = STALL_AIRSPEED; +#else // FOR ROTORCRAFTS + float ph_x = waypoint_get_x(WP_HOME) - pos->x; + float ph_y = waypoint_get_y(WP_HOME) - pos->y; +#endif // #if AP + + distance_to_home = (float)(sqrt(ph_x * ph_x + ph_y * ph_y)); + calc_flight_time_left(); +#if AP + mag_compass(); +#endif + + //THE SWITCH STATEMENT ENSURES THAT ONLY ONE SPI TRANSACTION WILL OCUUR IN EVERY PERIODIC FUNCTION CALL + switch (step) { + + case (0): + if (gps.fix == GPS_FIX_3D && stateGetHorizontalSpeedNorm_f() > 10.0) { //Only when flying + gps_course_deg = (float)gps.course; + gps_course_deg = DegOfRad(gps_course_deg / 1e7); + } else { +#if AP + gps_course_deg = mag_course_deg; +#else + gps_course_deg = stateGetNedToBodyEulers_f()->psi; +#endif + } + osd_sprintf(osd_string, "%.0f", gps_course_deg); + osd_put_s(osd_string, C_JUST, 3, 1, 15); + step = 10; + break; + + case (10): + //Only when flying because i need this indication to remain stable if the GPS is lost. + // This way i can still have the synchronized magnetic compass heading and the last bearing home. + if (gps.fix == GPS_FIX_3D && gps.pdop < 1000 && stateGetHorizontalSpeedNorm_f() > 5.0) { //Only when flying + home_direction_degrees = gps_course_deg + home_direction(); //home_direction returns degrees -180 to +180 + if (home_direction_degrees < 0) { home_direction_degrees += 360; } // translate the -180, +180 to 0-360. + if (home_direction_degrees >= 360) { home_direction_degrees -= 360; } + } +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + // All special character codes must be in 3 digit format! + osd_sprintf(osd_string, "%191c%.0f", home_direction_degrees); // 0 when heading straight back home. + osd_put_s(osd_string, C_JUST, 5, 2, 15); // "false = L_JUST +#else + osd_sprintf(osd_string, "H%.0f", home_direction_degrees); + osd_put_s(osd_string, C_JUST, 5, 2, 15); // "false = L_JUST + +#endif + step = 20; + break; + + case (20): + temp = ((float)electrical.vsupply); + osd_sprintf(osd_string, "%.1fV", temp); + if (temp > LOW_BAT_LEVEL) { + osd_put_s(osd_string, L_JUST, 5, 1, 1); + + } else { osd_put_s(osd_string, (L_JUST | BLINK | INVERT), 5, 1, 1); } + step = 30; + break; + + case (30): + if (gps.fix == GPS_FIX_3D) { +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + //Since we only send one special character the float variable is replaced by a zero. + osd_sprintf(osd_string, "%030c%031c", 0); + osd_put_s(osd_string, false, 2, 2, 1); +#else + osd_put_s("**", false, 2, 2, 1); +#endif + } else { +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + //Since we only send one special character the float variable is replaced by a zero. + osd_sprintf(osd_string, "%030c%031c", 0); // ALL special osd chars must have 3 digits. + osd_put_s(osd_string, (L_JUST | BLINK), 2, 2, 1); +#else + osd_put_s("**", (L_JUST | BLINK), 2, 2, 1); +#endif + } + step = 40; + break; + + case (40): +#if AP + if (autopilot.mode == AP_MODE_AUTO2) { + osd_put_s("A2", L_JUST, 2, 2, 3); + } else if (autopilot.mode == AP_MODE_AUTO1) { + osd_put_s("A1", L_JUST, 2, 2, 3); + } else { + osd_put_s("MAN", L_JUST, 3, 2, 3); + } +#endif + step = 50; + break; + + case (50): +#if AP + osd_sprintf(osd_string, "THR%.0f", (((float)ap_state->commands[COMMAND_THROTTLE] / (float)MAX_PPRZ) * 100.)); +#else + osd_sprintf(osd_string, "THR%.0fTHR", (((float)stabilization_cmd[COMMAND_THRUST] / (float)MAX_PPRZ) * 100.)); +#endif + osd_put_s(osd_string, L_JUST, 6, 3, 1); + step = 60; + break; + + case (60): +#if AP +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + if ((fabs(stateGetHorizontalSpeedNorm_f() * cos(att->phi))) < stall_speed) { + osd_sprintf(osd_string, "STALL!", 0); + osd_put_s(osd_string, (R_JUST | BLINK), 6, 1, 30); + } else { + osd_sprintf(osd_string, "%.0f%161c", (stateGetHorizontalSpeedNorm_f() * 3.6)); + osd_put_s(osd_string, R_JUST, 6, 1, 30); + } +#else + if ((fabs(stateGetHorizontalSpeedNorm_f() * cos(att->phi))) < stall_speed) { + osd_sprintf(osd_string, "STALL!", 0); + osd_put_s(osd_string, (R_JUST | BLINK), 6, 1, 30); + } else { + osd_sprintf(osd_string, "%.0fKM", (stateGetHorizontalSpeedNorm_f() * 3.6)); + osd_put_s(osd_string, R_JUST, 6, 1, 30); + } +#endif + +#else // #if AP + +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + osd_sprintf(osd_string, "%.0f%161c", (stateGetHorizontalSpeedNorm_f() * 3.6)); +#else + osd_sprintf(osd_string, "%.0fKM", (stateGetHorizontalSpeedNorm_f() * 3.6)); +#endif + osd_put_s(osd_string, R_JUST, 6, 1, 30); + +#endif + step = 70; + break; + + case (70): +#if defined(BARO_ALTITUDE_VAR) + if (gps.fix == GPS_FIX_3D && gps.pdop < 1000) { + RunOnceEvery((MAX7456_PERIODIC_FREQ * 300), { baro_alt_correction = GetPosAlt() - BARO_ALTITUDE_VAR;}); + altitude = GetPosAlt(); + } else { + altitude = BARO_ALTITUDE_VAR + baro_alt_correction; + } +#else + altitude = GetPosAlt(); +#endif +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + osd_sprintf(osd_string, "%.0f%177c", altitude); +#else + osd_sprintf(osd_string, "%.0fM", altitude); +#endif + osd_put_s(osd_string, R_JUST, 6, 2, 30); // "false = L_JUST + step = 80; + break; + + case (80): +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + osd_sprintf(osd_string, "%.1f%159c", stateGetSpeedEnu_f()->z); +#else + osd_sprintf(osd_string, "%.1fVZ", stateGetSpeedEnu_f()->z); +#endif + if (stateGetSpeedEnu_f()->z > 3.0) { + osd_put_s(osd_string, (R_JUST | BLINK), 6, 3, 30); + } else { + osd_put_s(osd_string, R_JUST, 6, 3, 30); + } + step = 90; + break; + + case (90): +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + // ANY SPECIAL CHARACTER CODE MUST BE A 3 DIGIT NUMBER WITH THE LEADING ZEROS!!!! + // THE SPECIAL CHARACTER CAN BE PLACED BEFORE OR AFTER THE FLOAT OR ANY OTHER CHARACTER + osd_sprintf(osd_string, "%160c%.1fK%012c", (distance_to_home / 1000)); + osd_put_s(osd_string, L_JUST, 7, 15, 1); +#else + osd_sprintf(osd_string, "%.1fKM", (distance_to_home / 1000)); + osd_put_s(osd_string, L_JUST, 7, 15, 1); +#endif + step = 100; + break; + + case (100): +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + osd_sprintf(osd_string, "%147c%.1fK%012c", (max_flyable_distance_left / 1000)); +#else + osd_sprintf(osd_string, "%.1fKM", (max_flyable_distance_left / 1000)); +#endif + if ((max_flyable_distance_left + 1000.0) < distance_to_home) { + osd_put_s(osd_string, (R_JUST | BLINK), 7, 15, 30); + } else { + osd_put_s(osd_string, (R_JUST), 7, 15, 30); + } + step = 110; + break; + + // A Text PFD as graphics are not the strong point of the MAX7456 + // In order to level the aircraft while fpving + // just move the stick to the opposite direction from the angles shown on the osd + // and that's why positive pitch (UP) is shown below the OSD center + case (110): + if (DegOfRad(att->theta) > 3) { + osd_sprintf(osd_string, "%.0f", DegOfRad(att->theta)); + osd_put_s(osd_string, C_JUST, 5, 6, 15); + + } else { osd_put_s(" ", C_JUST, 5, 6, 15); } + step = 112; + break; + + case (112): + if (DegOfRad(att->theta) < -3) { + osd_sprintf(osd_string, "%.0f", DegOfRad(att->theta)); + osd_put_s(osd_string, C_JUST, 5, 10, 15); + + } else { osd_put_s(" ", C_JUST, 5, 10, 15); } + step = 114; + break; + + case (114): + if (DegOfRad(att->phi) > 3) { + osd_sprintf(osd_string, "%.0f>", DegOfRad(att->phi)); + osd_put_s(osd_string, false, 5, 8, 18); + + } else { osd_put_s(" ", false, 5, 8, 18); } + step = 116; + break; + + case (116): + if (DegOfRad(att->phi) < -3) { + osd_sprintf(osd_string, "<%.0f", DegOfRad(fabs(att->phi))); + osd_put_s(osd_string, R_JUST, 5, 8, 13); + + } else { osd_put_s(" ", R_JUST, 5, 8, 13); } + step = 120; + break; + + case (120): +#if defined(USE_MATEK_TYPE_OSD_CHIP) && USE_MATEK_TYPE_OSD_CHIP == 1 + osd_sprintf(osd_string, "%126c", 0); + osd_put_s(osd_string, false, 1, 8, 15); // false = L_JUST +#else + osd_put_s("+", false, 1, 8, 15); // false = L_JUST +#endif + step = 0; + break; + + default: step = 0; break; + } // End of switch statement. + + return; +} + void max7456_init(void) { @@ -462,24 +950,19 @@ void max7456_init(void) osd_enable_val = OSD_IMAGE_ENABLE; max7456_osd_status = OSD_UNINIT; +#if DOWNLINK + register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_IMU_MAG, send_mag_heading); +#endif + home_direction(); + return; } void max7456_periodic(void) { - float temp = 0; - struct FloatEulers *att = stateGetNedToBodyEulers_f(); - struct EnuCoor_f *pos = stateGetPositionEnu_f(); -#if AP - float ph_x = waypoints[WP_HOME].x - pos->x; - float ph_y = waypoints[WP_HOME].y - pos->y; -#else - float ph_x = waypoint_get_x(WP_HOME) - pos->x; - float ph_y = waypoint_get_y(WP_HOME) - pos->y; -#endif -//This code is executed always and checks if the "osd_enable" var has been changed by telemetry. -//If yes then it commands a reset but this time turns on or off the osd overlay, not the video. + //This code is executed always and checks if the "osd_enable" var has been changed by telemetry. + //If yes then it commands a reset but this time turns on or off the osd overlay, not the video. if (max7456_osd_status == OSD_IDLE) { if (osd_enable > 1) { osd_enable = 1; @@ -498,6 +981,7 @@ void max7456_periodic(void) //This operation needs at least 100us but when the periodic function will be invoked again //sufficient time will have elapsed even with at a periodic frequency of 1000 Hz max7456_trans.output_buf[1] = OSD_RESET; + //We give an extra delay step by going to the event function and back here for the Reset to complete. max7456_osd_status = OSD_INIT1; spi_submit(&(MAX7456_SPI_DEV), &max7456_trans); } else if (max7456_osd_status == OSD_INIT2) { @@ -507,144 +991,11 @@ void max7456_periodic(void) max7456_osd_status = OSD_INIT3; spi_submit(&(MAX7456_SPI_DEV), &max7456_trans); } else if (max7456_osd_status == OSD_IDLE && osd_enable > 0) { - switch (step) { - case (0): - osd_put_s("HDG", FALSE, 3, 1, 14); - step = 1; - break; - case (1): -#if !defined USE_MATEK_TYPE_OSD_CHIP || USE_MATEK_TYPE_OSD_CHIP == 0 - osd_put_s("DISTANCE", FALSE, 8, 14, 12); -#endif - step = 10; - break; - case (10): - osd_put_s("( )", FALSE, 3, 8, 14); - step = 20; - break; - case (20): - temp = ((float)electrical.vsupply); - osd_sprintf(osd_string, "%.1fV", temp); - if (temp > LOW_BAT_LEVEL) { - osd_put_s(osd_string, L_JUST, 5, 1, 1); - - } else { osd_put_s(osd_string, L_JUST | BLINK | INVERT, 5, 1, 1); } - step = 30; - break; - case (30): -#if OSD_USE_MAG_COMPASS && !defined(SITL) - PRINT_CONFIG_MSG("OSD USES THE MAGNETIC HEADING") - temp = DegOfRad(MAG_Heading); - if (temp < 0) { temp += 360; } -#else - PRINT_CONFIG_MSG("OSD USES THE GPS HEADING") - temp = DegOfRad(state.h_speed_dir_f); - if (temp < 0) { temp += 360; } -#endif - osd_sprintf(osd_string, "%.0f", temp); - osd_put_s(osd_string, C_JUST, 3, 1, 15); - step = 40; - break; - case (40): - osd_sprintf(osd_string, "%.0f KM", (state.h_speed_norm_f * 3.6)); - osd_put_s(osd_string, R_JUST, 6, 1, 29); - step = 42; - break; - case (42): -#if AP - osd_sprintf(osd_string, "%.0fTHR", (((float)ap_state->commands[COMMAND_THROTTLE] / MAX_PPRZ) * 100)); -#else - osd_sprintf(osd_string, "%.0fTHR", (((float)stabilization_cmd[COMMAND_THRUST] / MAX_PPRZ) * 100)); -#endif - osd_put_s(osd_string, R_JUST, 5, 2, 29); - step = 50; - break; - case (50): -#if OSD_USE_BARO_ALTITUDE && !defined(SITL) - PRINT_CONFIG_MSG("OSD ALTITUDE IS COMING FROM BAROMETER") -#if defined BARO_ALTITUDE_VAR - osd_sprintf(osd_string, "%.0fM", BARO_ALTITUDE_VAR); -#else - PRINT_CONFIG_MSG("OSD USES THE DEFAULT BARO ALTITUDE VARIABLE") - osd_sprintf(osd_string, "%.0fM", baro_alt); -#endif -#else - PRINT_CONFIG_MSG("ALTITUDE IS COMING FROM GPS") - osd_sprintf(osd_string, "%.0fM", GetPosAlt()); -#endif - osd_put_s(osd_string, L_JUST, 6, 14, 1); // "FALSE = L_JUST - step = 52; - break; - case (52): -#if defined USE_MATEK_TYPE_OSD_CHIP && USE_MATEK_TYPE_OSD_CHIP == 1 - // ANY SPECIAL CHARACTER CODE MUST BE A 3 DIGIT NUMBER WITH THE LEADING ZEROS!!!! - // THE SPECIAL CHARACTER CAN BE PLACED BEFORE OR AFTER THE FLOAT OR ANY OTHER CHARACTER - osd_sprintf(osd_string, "%191c%.0f", home_direction()); - osd_put_s(osd_string, C_JUST, 4, 2, 15); // "FALSE = L_JUST -#else - osd_sprintf(osd_string, "H%.0f", home_direction()); - osd_put_s(osd_string, C_JUST, 4, 2, 15); // "FALSE = L_JUST - -#endif - step = 60; - break; - - case (60): -#if defined USE_MATEK_TYPE_OSD_CHIP && USE_MATEK_TYPE_OSD_CHIP == 1 - // ANY SPECIAL CHARACTER CODE MUST BE A 3 DIGIT NUMBER WITH THE LEADING ZEROS!!!! - // THE SPECIAL CHARACTER CAN BE PLACED BEFORE OR AFTER THE FLOAT OR ANY OTHER CHARACTER - osd_sprintf(osd_string, "%160c%.0fM", (float)(sqrt(ph_x * ph_x + ph_y * ph_y))); - osd_put_s(osd_string, C_JUST, 6, 14, 15); -#else - osd_sprintf(osd_string, "%.0fM", (float)(sqrt(ph_x * ph_x + ph_y * ph_y))); - osd_put_s(osd_string, C_JUST, 6, 14, 15); -#endif - step = 70; - break; - case (70): - osd_sprintf(osd_string, "%.1fVZ", stateGetSpeedEnu_f()->z); - osd_put_s(osd_string, R_JUST, 9, 14, 29); - step = 80; - break; - // A Text PFD as graphics are not the strong point of the MAX7456 - // In order to level the aircraft while fpving - // just move the stick to the opposite direction from the angles shown on the osd - // and that's why positive pitch (UP) is shown below the OSD center - case (80): - if (DegOfRad(att->theta) > 2) { - osd_sprintf(osd_string, "%.0f", DegOfRad(att->theta)); - osd_put_s(osd_string, C_JUST, 5, 6, 15); - - } else { osd_put_s(" ", C_JUST, 5, 6, 15); } - step = 90; - break; - case (90): - if (DegOfRad(att->theta) < -2) { - osd_sprintf(osd_string, "%.0f", DegOfRad(att->theta)); - osd_put_s(osd_string, C_JUST, 5, 10, 15); - - } else { osd_put_s(" ", C_JUST, 5, 10, 15); } - step = 100; - break; - case (100): - if (DegOfRad(att->phi) > 2) { - osd_sprintf(osd_string, "%.0f>", DegOfRad(att->phi)); - osd_put_s(osd_string, FALSE, 5, 8, 18); - - } else { osd_put_s(" ", FALSE, 5, 8, 18); } - step = 110; - break; - case (110): - if (DegOfRad(att->phi) < -2) { - osd_sprintf(osd_string, "<%.0f", DegOfRad(fabs(att->phi))); - osd_put_s(osd_string, R_JUST, 5, 8, 13); - - } else { osd_put_s(" ", R_JUST, 5, 8, 13); } - step = 10; - break; - default: step = 10; break; - } // End of switch statement. + draw_osd(); } // End of if (max7456_osd_status == OSD_UNINIT) + + + return; } @@ -664,13 +1015,16 @@ void max7456_event(void) max7456_trans.output_length = 2; max7456_trans.input_length = 0; max7456_trans.output_buf[0] = OSD_OSDBL_REG; - max7456_trans.output_buf[1] = max7456_trans.input_buf[0] & (~(1 << 4)); + //Max7456 requires that you read first and then change only bit4 of the OSDBL register. + //Reading was started in the periodic function and now we rerwrite the OSDBL register. + max7456_trans.output_buf[1] = max7456_trans.input_buf[1] & (~(1 << 4)); max7456_osd_status = OSD_INIT4; spi_submit(&(MAX7456_SPI_DEV), &max7456_trans); break; case (OSD_INIT4): max7456_trans.output_buf[0] = OSD_VM0_REG; #if USE_PAL_FOR_OSD_VIDEO +#pragma message "Camera and OSD must be both PAL or NTSC otherwise only the camera picture will be visible." max7456_trans.output_buf[1] = OSD_VIDEO_MODE_PAL | osd_enable_val; #else max7456_trans.output_buf[1] = osd_enable_val; diff --git a/sw/airborne/modules/display/max7456.h b/sw/airborne/modules/display/max7456.h index b2e47a0af6..b23e3e3438 100644 --- a/sw/airborne/modules/display/max7456.h +++ b/sw/airborne/modules/display/max7456.h @@ -36,6 +36,9 @@ extern void max7456_periodic(void); extern void max7456_event(void); extern uint8_t osd_enable; +extern float mag_course_deg; +extern float mag_heading_rad; +extern float home_dir_deg; #endif //MAX7456_H diff --git a/sw/airborne/modules/pca9685/pca9685_i2c.c b/sw/airborne/modules/pca9685/pca9685_i2c.c new file mode 100644 index 0000000000..ebb5e96178 --- /dev/null +++ b/sw/airborne/modules/pca9685/pca9685_i2c.c @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2013-2020 Chris Efstathiou hendrixgr@gmail.com + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * @file modules/pca9685/pca9685.c + * PCA9685 LED DRIVER USED AS A 16 ADDITIONAL PWM SERVO DRIVER. + * + */ + + +//################################################################################################### +// I N C L U D E D H E A D E R F I L E S +//################################################################################################### +#include "modules/pca9685/pca9685_i2c.h" +#include "math/pprz_algebra_int.h" +#include "mcu_periph/i2c.h" +#include "mcu_periph/sys_time.h" + +#include "math/pprz_isa.h" +#include "mcu_periph/sys_time.h" +#include "subsystems/abi.h" +#include "mcu_periph/uart.h" + +#if DOWNLINK +#include "subsystems/datalink/telemetry.h" +#endif + + + + + +//################################################################################################### +// P R E P R O C E S S O R D I R E C T I V E S A N D D E F I N I T I O N S +//################################################################################################### +// BIG ENDIAN DEFINITIONS +//#define BUF2INT(_buf,_idx) ((int16_t)((_buf[_idx]<<8) | _buf[_idx+1])) +//#define INT2BUF(_int,_buf,_idx) { _buf[_idx] = (_int>>8); _buf[_idx+1] = _int; } + +// LITTLE ENDIAN DEFINITIONS USED FOR AvR CPU DUE TO MORE EFFICIENT DATA TRANSFERS +#define BUF2INT(_buf,_idx) ((int16_t)((_buf[_idx+1]<<8) | _buf[_idx])) +#define INT2BUF(_int,_buf,_idx) { _buf[_idx] = _int; _buf[_idx+1] = (_int>>8); } + +#ifndef PCA9685_I2C_DEV +#define PCA9685_I2C_DEV i2c2 +#endif +PRINT_CONFIG_VAR(PCA9685_I2C_DEV) + +#ifndef PCA9865_SRV_RESOLUTION +#define PCA9865_SRV_RESOLUTION 1. // in microseconds +#endif + +#ifndef PCA9865_SRV_DEFAULT_VAL_US +#define PCA9865_SRV_DEFAULT_VAL_US 1000. // in microseconds +#endif + +#ifndef PCA9865_SRV_NUMBER +#define PCA9865_SRV_NUMBER 16 // in microseconds +#endif + +// I2C Addreses +#ifndef PCA9685_I2C_SLAVE_ADDR +#define PCA9685_I2C_SLAVE_ADDR 0xE0 +#endif +PRINT_CONFIG_VAR(PCA9685_I2C_SLAVE_ADDR) + +#ifndef PCA9685_I2C_ALLCALL_ADDR +#define PCA9685_I2C_ALLCALL_ADDR 0xE0 +#endif +#ifndef PCA9685_I2C_RESET_ADDR +#define PCA9685_I2C_RESET_ADDR 0x06 +#endif +#ifndef PCA9685_I2C_GEN_CALL_ADDR +#define PCA9685_I2C_GEN_CALL_ADDR 0x00 +#endif + +// Register addresses +#define PCA9685_MODE1_REG_ADDR 0x00 +#define PCA9685_MODE2_REG_ADDR 0x01 +#define PCA9685_ALLCALL_ADDR 0x05 + +#define PCA9685_LED0_ON_L_REG_ADDR 0X06 +#define PCA9685_LED0_ON_H_REG_ADDR 0X07 +#define PCA9685_LED0_OFF_L_REG_ADDR 0X08 +#define PCA9685_LED0_OFF_H_REG_ADDR 0X09 + +#define PCA9685_LED1_ON_L_REG_ADDR 0X0A +#define PCA9685_LED1_ON_H_REG_ADDR 0X0B +#define PCA9685_LED1_OFF_L_REG_ADDR 0X0C +#define PCA9685_LED1_OFF_H_REG_ADDR 0X0D + +#define PCA9685_LED2_ON_L_REG_ADDR 0X0E +#define PCA9685_LED2_ON_H_REG_ADDR 0X0F +#define PCA9685_LED2_OFF_L_REG_ADDR 0X10 +#define PCA9685_LED2_OFF_H_REG_ADDR 0X11 + +#define PCA9685_LED3_ON_L_REG_ADDR 0X12 +#define PCA9685_LED3_ON_H_REG_ADDR 0X13 +#define PCA9685_LED3_OFF_L_REG_ADDR 0X14 +#define PCA9685_LED3_OFF_H_REG_ADDR 0X15 + +#define PCA9685_LED4_ON_L_REG_ADDR 0X16 +#define PCA9685_LED4_ON_H_REG_ADDR 0X17 +#define PCA9685_LED4_OFF_L_REG_ADDR 0X18 +#define PCA9685_LED4_OFF_H_REG_ADDR 0X19 + +#define PCA9685_LED5_ON_L_REG_ADDR 0X1A +#define PCA9685_LED5_ON_H_REG_ADDR 0X1B +#define PCA9685_LED5_OFF_L_REG_ADDR 0X1C +#define PCA9685_LED5_OFF_H_REG_ADDR 0X1D + +#define PCA9685_LED6_ON_L_REG_ADDR 0X1E +#define PCA9685_LED6_ON_H_REG_ADDR 0X1F +#define PCA9685_LED6_OFF_L_REG_ADDR 0X20 +#define PCA9685_LED6_OFF_H_REG_ADDR 0X21 + +#define PCA9685_LED7_ON_L_REG_ADDR 0X22 +#define PCA9685_LED7_ON_H_REG_ADDR 0X23 +#define PCA9685_LED7_OFF_L_REG_ADDR 0X24 +#define PCA9685_LED7_OFF_H_REG_ADDR 0X25 + + +#define PCA9685_LED8_ON_L_REG_ADDR 0X26 +#define PCA9685_LED8_ON_H_REG_ADDR 0X27 +#define PCA9685_LED8_OFF_L_REG_ADDR 0X28 +#define PCA9685_LED8_OFF_H_REG_ADDR 0X29 + +#define PCA9685_LED9_ON_L_REG_ADDR 0X2A +#define PCA9685_LED9_ON_H_REG_ADDR 0X2B +#define PCA9685_LED9_OFF_L_REG_ADDR 0X2C +#define PCA9685_LED9_OFF_H_REG_ADDR 0X2D + +#define PCA9685_LED10_ON_L_REG_ADDR 0X2E +#define PCA9685_LED10_ON_H_REG_ADDR 0X2F +#define PCA9685_LED10_OFF_L_REG_ADDR 0X30 +#define PCA9685_LED10_OFF_H_REG_ADDR 0X31 + +#define PCA9685_LED11_ON_L_REG_ADDR 0X32 +#define PCA9685_LED11_ON_H_REG_ADDR 0X33 +#define PCA9685_LED11_OFF_L_REG_ADDR 0X34 +#define PCA9685_LED11_OFF_H_REG_ADDR 0X35 + +#define PCA9685_LED12_ON_L_REG_ADDR 0X36 +#define PCA9685_LED12_ON_H_REG_ADDR 0X37 +#define PCA9685_LED12_OFF_L_REG_ADDR 0X38 +#define PCA9685_LED12_OFF_H_REG_ADDR 0X39 + +#define PCA9685_LED13_ON_L_REG_ADDR 0X3A +#define PCA9685_LED13_ON_H_REG_ADDR 0X3B +#define PCA9685_LED13_OFF_L_REG_ADDR 0X3C +#define PCA9685_LED13_OFF_H_REG_ADDR 0X3D + +#define PCA9685_LED14_ON_L_REG_ADDR 0X3E +#define PCA9685_LED14_ON_H_REG_ADDR 0X3F +#define PCA9685_LED14_OFF_L_REG_ADDR 0X40 +#define PCA9685_LED14_OFF_H_REG_ADDR 0X41 + +#define PCA9685_LED15_ON_L_REG_ADDR 0X42 +#define PCA9685_LED15_ON_H_REG_ADDR 0X43 +#define PCA9685_LED15_OFF_L_REG_ADDR 0X44 +#define PCA9685_LED15_OFF_H_REG_ADDR 0X45 + +#define PCA9685_ALL_LED_ON_L_REG_ADDR 0XFA +#define PCA9685_ALL_LED_ON_H_REG_ADDR 0XFB +#define PCA9685_ALL_LED_OFF_L_REG_ADDR 0XFC +#define PCA9685_ALL_LED_OFF_H_REG_ADDR 0XFD + +#define PCA9685_PRESCALER_REG_ADDR 0XFE + +//Bit positions +//MODE 0 REGISTER +#define PCA9865_RESTART_BIT 7 // 1= RESTART ENABLE, DEFAULT = 0 +#define PCA9865_AUTO_INCREMENT_BIT 5 // 1 = AUTO INCREMENT, DEFAULT = 0 +#define PCA9865_SLEEP_BIT 4 // 1 = LOW POWER MODE (OSC OFF), 0=NORMAL MODE, DEFAULT=0 +#define PCA9865_ALLCALL_BIT 0 // 1 = ALL LED ENABLE, DEFAULT = 1 +//MODE 1 REGISTER +#define PCA9865_OUTDRV_BIT 2 // 1 = TOTEM POLE, 0 = OPEN DRAIN, DEFAULT = 1 + + + + +#if !defined(SITL) +//################################################################################################### +// P R I V A T E F U N C T I O N P R O T O T Y P E S +//################################################################################################### +#if defined(PCA9685_SEND_SERVO_VALUES) && PCA9685_SEND_SERVO_VALUES == 1 +static void pca9685_send_servo_values(struct transport_tx *trans, struct link_device *dev); +#endif + + + + +//################################################################################################### +// G L O B A L V A R I A B L E S +//################################################################################################### + +enum { + PCA9685_I2C_STATUS_UNINIT, + PCA9685_I2C_STATUS_INITIALIZED, + PCA9865_I2C_STATUS_WRITE_LED_REG_BUSY, + PCA9865_I2C_STATUS_WRITE_LED_REG_READY, + PCA9865_I2C_STATUS_READ_LED_REG_BUSY, + PCA9865_I2C_STATUS_CHANGE_LED_REG_FINISHED +}; + +struct i2c_transaction pca9685_i2c_trans; +uint8_t pca9685_i2c_status = 0; +uint8_t srv_cnt = 0; +uint16_t pca9865_write_servo_vals[PCA9865_SRV_NUMBER]; +#if defined(PCA9685_SEND_SERVO_VALUES) && PCA9685_SEND_SERVO_VALUES == 1 +PRINT_CONFIG_MSG("PCA9685 servo values can be found in the ""BARO_WORDS"" message") +uint16_t pca9865_read_servo_vals[PCA9865_SRV_NUMBER]; +#endif +uint8_t pca9865_reg_nb[16]; + + +//################################################################################################### +// P R I V A T E F U N C T I O N D E F I N I T I O N S +//################################################################################################### + +//111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +#if defined(PCA9685_SEND_SERVO_VALUES) && PCA9685_SEND_SERVO_VALUES == 1 +static void pca9685_send_servo_values(struct transport_tx *trans, struct link_device *dev) +{ + +// For debugging purposes we send anly the first 4 servo values using the BARO_WORDS message. +// Those values are read back from PCA9865 so they indicate if the module is working as intended. + pprz_msg_send_CSC_SERVO_CMD( + trans, dev, AC_ID, + &pca9865_read_servo_vals[0], &pca9865_read_servo_vals[1], + &pca9865_read_servo_vals[2], &pca9865_read_servo_vals[3] + ); + + + return; +} +#endif + + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// P U B L I C F U N C T I O N D E F I N I T I O N S +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +//111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 +bool pca9865_set_servo(uint8_t srv_nb, uint16_t srv_val) +{ + uint16_t servo_value = 0; + + if (srv_nb < 16) { + servo_value = (uint16_t)((float)srv_val / (float)PCA9865_SRV_RESOLUTION); + pca9865_write_servo_vals[srv_nb] = servo_value; + pca9865_reg_nb[srv_nb] = (srv_nb * 4) + 8; + + } + + return (FALSE); +} + + + +//222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 +void pca9685_i2c_init(void) +{ + + uint16_t reg_value = 0; + uint32_t timer0 = 0; + +// clear all arrays to zero. + for (srv_cnt = 0; srv_cnt < PCA9865_SRV_NUMBER; srv_cnt++) { + pca9865_write_servo_vals[srv_cnt] = 0; +#if defined(PCA9685_SEND_SERVO_VALUES) && PCA9685_SEND_SERVO_VALUES == 1 + pca9865_read_servo_vals[srv_cnt] = 0; +#endif + pca9865_reg_nb[srv_cnt] = 0; + } + srv_cnt = 0; + pca9685_i2c_trans.status = I2CTransDone; + pca9685_i2c_status = PCA9685_I2C_STATUS_UNINIT; + + pca9685_i2c_trans.buf[0] = PCA9685_I2C_RESET_ADDR; + i2c_transmit(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_GEN_CALL_ADDR, 1); + timer0 = sys_time.nb_tick + (SYS_TIME_FREQUENCY / 2); + while (pca9685_i2c_trans.status == I2CTransPending) { + if (sys_time.nb_tick > timer0) { break; } + } + +// Internal Oscillator Off, the oscillator is off by default on power up. + pca9685_i2c_trans.buf[0] = PCA9685_MODE1_REG_ADDR; + pca9685_i2c_trans.buf[1] = (1 << PCA9865_SLEEP_BIT) | (1 << PCA9865_ALLCALL_BIT); // OSC off + i2c_transmit(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_SLAVE_ADDR, 2); + timer0 = sys_time.nb_tick + (SYS_TIME_FREQUENCY / 2); + while (pca9685_i2c_trans.status == I2CTransPending) { + if (sys_time.nb_tick > timer0) { break; } + } + + reg_value = (uint16_t)((float)PCA9865_SRV_RESOLUTION / 0.04); //0.04 = 1/25 Mhz + if (reg_value > 0xFF) { reg_value = 0xFF; } // Sanity check. + pca9685_i2c_trans.buf[0] = PCA9685_PRESCALER_REG_ADDR; + pca9685_i2c_trans.buf[1] = (uint8_t)reg_value; + i2c_transmit(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_SLAVE_ADDR, 2); + timer0 = sys_time.nb_tick + (SYS_TIME_FREQUENCY / 2); + while (pca9685_i2c_trans.status == I2CTransPending) { + if (sys_time.nb_tick > timer0) { break; } + } + +// Internal Oscillator On, Auto Increment on, AllCall enabled, 500 microseconds needed. + pca9685_i2c_trans.buf[0] = PCA9685_MODE1_REG_ADDR; + pca9685_i2c_trans.buf[1] = (1 << PCA9865_AUTO_INCREMENT_BIT) | (1 << PCA9865_ALLCALL_BIT); + i2c_transmit(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_SLAVE_ADDR, 2); + timer0 = sys_time.nb_tick + (SYS_TIME_FREQUENCY / 2); + while (pca9685_i2c_trans.status == I2CTransPending) { + if (sys_time.nb_tick > timer0) { break; } + } + + do { + pca9685_i2c_trans.buf[0] = PCA9685_MODE1_REG_ADDR; + i2c_transceive(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_SLAVE_ADDR, 1, 1); + timer0 = sys_time.nb_tick + (SYS_TIME_FREQUENCY / 2); + while (pca9685_i2c_trans.status == I2CTransPending) { + if (sys_time.nb_tick > timer0) { break; } + } + + } while ((pca9685_i2c_trans.buf[0] & (1 << PCA9865_RESTART_BIT)) != 0); + + pca9685_i2c_trans.buf[0] = PCA9685_ALL_LED_ON_L_REG_ADDR; + pca9685_i2c_trans.buf[1] = 0; + pca9685_i2c_trans.buf[2] = 0; + reg_value = (uint16_t)((float)PCA9865_SRV_DEFAULT_VAL_US / (float)PCA9865_SRV_RESOLUTION); + pca9685_i2c_trans.buf[3] = (uint8_t)reg_value; + pca9685_i2c_trans.buf[4] = (uint8_t)((reg_value >> 8) & 0x000F); + i2c_transmit(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_ALLCALL_ADDR, 5); + timer0 = sys_time.nb_tick + (SYS_TIME_FREQUENCY / 2); + while (pca9685_i2c_trans.status == I2CTransPending) { + if (sys_time.nb_tick > timer0) { break; } + } + + + pca9685_i2c_trans.status = I2CTransDone; + pca9685_i2c_status = PCA9685_I2C_STATUS_INITIALIZED; + +#if DOWNLINK +#if defined(PCA9685_SEND_SERVO_VALUES) && PCA9685_SEND_SERVO_VALUES == 1 + register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_CSC_SERVO_CMD, pca9685_send_servo_values); +#endif +#endif + +// Initialize all 16 servo with the default values + pca9865_set_servo(0, 1500); + pca9865_set_servo(1, 1500); + pca9865_set_servo(2, 1000); + pca9865_set_servo(3, 1500); + for (srv_cnt = 4; srv_cnt < PCA9865_SRV_NUMBER; srv_cnt++) { + pca9865_set_servo(srv_cnt, 1000); + } + srv_cnt = 0; + + return; +} + + + +//333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 +void pca9685_i2c_periodic(void) +{ +// If a servo change was requested write the appropriate pca9685 registers. +// Since those servos are not intented for flight surface control they can be updated slower +// at about one servo each module cycle. This way i2c load is minimized. +// +// Start searching for a servo change request either after intialization or after all +// servo channels have been searched (looping). +// This loop will be executed always until a servo value change is requested. +// Then it will break with "srv_cnt" having the servo number to be changed. + +// DELAY IN ORDER TO GIVE TIME TO THE INTERNAL PCA9865 OSCILLATOR TO STABILIZE. +//if (sys_time.nb_sec > 3){ + if (srv_cnt >= 16 || pca9685_i2c_status == PCA9685_I2C_STATUS_INITIALIZED) { + for (srv_cnt = 0; srv_cnt < 16; srv_cnt++) { + if (pca9865_reg_nb[srv_cnt] > 0) { + pca9685_i2c_status = PCA9865_I2C_STATUS_CHANGE_LED_REG_FINISHED; + break; + } + } + } + + // Change the above found register in order to update the servo pwm value. + // The search for another servo to change is completed in the "event" function. + if (srv_cnt < 16) { + if (pca9685_i2c_status == PCA9865_I2C_STATUS_CHANGE_LED_REG_FINISHED) { + pca9685_i2c_trans.buf[0] = pca9865_reg_nb[srv_cnt]; + pca9685_i2c_trans.buf[1] = (uint8_t)pca9865_write_servo_vals[srv_cnt]; + pca9685_i2c_trans.buf[2] = (uint8_t)(pca9865_write_servo_vals[srv_cnt] >> 8) & 0x0F; + i2c_transmit(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_SLAVE_ADDR, 3); + pca9685_i2c_status = PCA9865_I2C_STATUS_WRITE_LED_REG_BUSY; + } + } +//} + + return; +} + + + +//444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 +void pca9685_i2c_event(void) +{ + +//-------------------------------------------------------------------------------------------------- +// IF SUCCESFULL +//-------------------------------------------------------------------------------------------------- + if (pca9685_i2c_trans.status == I2CTransSuccess) { + + if (pca9685_i2c_status == PCA9865_I2C_STATUS_WRITE_LED_REG_BUSY) { + // Read back the changed value just to be sure... + pca9685_i2c_trans.buf[0] = pca9865_reg_nb[srv_cnt]; + i2c_transceive(&PCA9685_I2C_DEV, &pca9685_i2c_trans, PCA9685_I2C_SLAVE_ADDR, 1, 2); + pca9685_i2c_status = PCA9865_I2C_STATUS_READ_LED_REG_BUSY; + + } else if (pca9685_i2c_status == PCA9865_I2C_STATUS_READ_LED_REG_BUSY) { +#if defined(PCA9685_SEND_SERVO_VALUES) && PCA9685_SEND_SERVO_VALUES == 1 + pca9865_read_servo_vals[srv_cnt] = BUF2INT(pca9685_i2c_trans.buf, 0); +#endif + if (BUF2INT(pca9685_i2c_trans.buf, 0) != pca9865_write_servo_vals[srv_cnt]) { + //pca9685_i2c_init(); + return; + } + pca9685_i2c_status = PCA9865_I2C_STATUS_CHANGE_LED_REG_FINISHED; + // Delete this request. + pca9865_reg_nb[srv_cnt] = 0; + // Search for the next changed servo value. + // If a value > 0 is detected x will contain the new servo number to be changed. + srv_cnt++; + for (; srv_cnt < 16; srv_cnt++) { if (pca9865_reg_nb[srv_cnt] > 0) { break; } } + } +//------------------------------------------------------------------------------------------------- +// ELSE IF NOT SUCCESFULL +//------------------------------------------------------------------------------------------------- + }//else{ pca9685_i2c_init(); } + + return; +} + +#else +// just to stop the compiler complaining i manipulte srv_nb and srv_val a little. +bool pca9865_set_servo(uint8_t srv_nb, uint16_t srv_val) { srv_nb = srv_val; srv_nb /= 2; return (FALSE); } +void pca9685_i2c_init(void) { return; } +void pca9685_i2c_periodic(void) { return; } +void pca9685_i2c_event(void) { return; } + +#endif // #if !defined(SITL) + +/************************************************************************************************/ +// T H E E N D +/************************************************************************************************/ + diff --git a/sw/airborne/modules/pca9685/pca9685_i2c.h b/sw/airborne/modules/pca9685/pca9685_i2c.h new file mode 100644 index 0000000000..7fbc590214 --- /dev/null +++ b/sw/airborne/modules/pca9685/pca9685_i2c.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013-2020 Chris Efstathiou hendrixgr@gmail.com + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * @file modules/pca9685/pca9685.h + * PCA9685 LED DRIVER USED AS A 16 ADDITIONAL PWM SERVO DRIVER. + * + */ + + +#ifndef PCA9685_I2C_H +#define PCA9685_I2C_H + +#include "std.h" + +#define PCA9865_SRV0 PCA9685_LED0_OFF_L_REG_ADDR +#define PCA9865_SRV1 PCA9685_LED1_OFF_L_REG_ADDR +#define PCA9865_SRV2 PCA9685_LED2_OFF_L_REG_ADDR +#define PCA9865_SRV3 PCA9685_LED3_OFF_L_REG_ADDR +#define PCA9865_SRV4 PCA9685_LED4_OFF_L_REG_ADDR +#define PCA9865_SRV5 PCA9685_LED5_OFF_L_REG_ADDR +#define PCA9865_SRV6 PCA9685_LED6_OFF_L_REG_ADDR +#define PCA9865_SRV7 PCA9685_LED7_OFF_L_REG_ADDR +#define PCA9865_SRV8 PCA9685_LED8_OFF_L_REG_ADDR +#define PCA9865_SRV9 PCA9685_LED9_OFF_L_REG_ADDR +#define PCA9865_SRV10 PCA9685_LED10_OFF_L_REG_ADDR +#define PCA9865_SRV11 PCA9685_LED11_OFF_L_REG_ADDR +#define PCA9865_SRV12 PCA9685_LED12_OFF_L_REG_ADDR +#define PCA9865_SRV13 PCA9685_LED13_OFF_L_REG_ADDR +#define PCA9865_SRV14 PCA9685_LED14_OFF_L_REG_ADDR +#define PCA9865_SRV15 PCA9685_LED15_OFF_L_REG_ADDR + +extern void pca9685_i2c_init(void); +extern void pca9685_i2c_periodic(void); +extern void pca9685_i2c_event(void); + +bool pca9865_set_servo(uint8_t srv_nb, uint16_t srv_val); + +#endif diff --git a/sw/airborne/modules/sensors/baro_bmp280_i2c.c b/sw/airborne/modules/sensors/baro_bmp280_i2c.c index e62d522b93..4840ac204a 100644 --- a/sw/airborne/modules/sensors/baro_bmp280_i2c.c +++ b/sw/airborne/modules/sensors/baro_bmp280_i2c.c @@ -19,7 +19,7 @@ */ /** - * @file modules/sensors/baro_bmp280.c + * @file modules/sensors/baro_bmp280_i2c.c * Bosch BMP280 I2C sensor interface. * * This reads the values for pressure and temperature from the Bosch BMP280 sensor through I2C. @@ -34,19 +34,44 @@ #include "subsystems/datalink/downlink.h" #include "math/pprz_isa.h" +#if DOWNLINK && !defined(BMP280_SYNC_SEND) +#include "subsystems/datalink/telemetry.h" +#endif + /** default slave address */ #ifndef BMP280_SLAVE_ADDR #define BMP280_SLAVE_ADDR BMP280_I2C_ADDR #endif float baro_alt = 0; +float baro_temp = 0; +float baro_press = 0; bool baro_alt_valid = 0; + struct Bmp280_I2c baro_bmp280; +#if DOWNLINK && !defined(BMP280_SYNC_SEND) +static void send_baro_bmp_data(struct transport_tx *trans, struct link_device *dev) +{ + int32_t baro_altitude = (int32_t)(baro_alt * 100); + int32_t baro_pressure = (int32_t)(baro_press); + int32_t baro_temperature = (int32_t)(baro_temp * 100); + + pprz_msg_send_BMP_STATUS(trans, dev, AC_ID, &baro_altitude, &baro_altitude , &baro_pressure, & baro_temperature); + + return; +} +#endif + + void baro_bmp280_init(void) { + bmp280_i2c_init(&baro_bmp280, &BMP280_I2C_DEV, BMP280_SLAVE_ADDR); +#if DOWNLINK && !defined(BMP280_SYNC_SEND) + register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_BMP_STATUS, send_baro_bmp_data); +#endif } void baro_bmp280_periodic(void) @@ -64,12 +89,14 @@ void baro_bmp280_event(void) AbiSendMsgBARO_ABS(BARO_BMP_SENDER_ID, now_ts, baro_bmp280.pressure); AbiSendMsgTEMPERATURE(BARO_BMP_SENDER_ID, baro_bmp280.temperature); baro_bmp280.data_available = false; - baro_alt = pprz_isa_altitude_of_pressure((float)baro_bmp280.pressure); - baro_alt_valid = TRUE; + baro_alt = pprz_isa_altitude_of_pressure(baro_bmp280.pressure); + baro_alt_valid = true; + baro_press = (float)baro_bmp280.pressure; + baro_temp = ((float)baro_bmp280.temperature) / 100; -#ifdef BMP280_SYNC_SEND - int32_t up = (int32_t)(baro_alt * 100.0); - int32_t ut = (int32_t) baro_bmp280.raw_temperature; +#if defined(BMP280_SYNC_SEND) + int32_t up = (int32_t)(baro_bmp280.raw_pressure); + int32_t ut = (int32_t)(baro_bmp280.raw_temperature); int32_t p = (int32_t) baro_bmp280.pressure; int32_t t = (int32_t)(baro_bmp280.temperature); DOWNLINK_SEND_BMP_STATUS(DefaultChannel, DefaultDevice, &up, &ut, &p, &t); diff --git a/sw/airborne/modules/sensors/baro_bmp280_i2c.h b/sw/airborne/modules/sensors/baro_bmp280_i2c.h index d6487e6647..0dfb5c66c7 100644 --- a/sw/airborne/modules/sensors/baro_bmp280_i2c.h +++ b/sw/airborne/modules/sensors/baro_bmp280_i2c.h @@ -20,14 +20,14 @@ */ /** - * @file modules/sensors/baro_bmp280.h + * @file modules/sensors/baro_bmp280_i2c.h * Bosch BMP280 I2C sensor interface. * * This reads the values for pressure and temperature from the Bosch BMP280 sensor through I2C. */ -#ifndef BARO_BMP280_H -#define BARO_BMP280_H +#ifndef BARO_BMP280_I2C_H +#define BARO_BMP280_I2C_H #include "peripherals/bmp280_i2c.h" diff --git a/sw/airborne/modules/uav_recovery/uav_recovery.c b/sw/airborne/modules/uav_recovery/uav_recovery.c new file mode 100644 index 0000000000..0215854ac4 --- /dev/null +++ b/sw/airborne/modules/uav_recovery/uav_recovery.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2013-2020 Chris Efstathiou hendrixgr@gmail.com + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * @file modules/uav_recovery/uav_recovery.c + * + */ + + +#include "autopilot.h" +#include "state.h" +#include "firmwares/fixedwing/nav.h" +#include "subsystems/navigation/common_nav.h" +#include "generated/flight_plan.h" +#include "generated/airframe.h" +#include "inter_mcu.h" +#include "subsystems/datalink/datalink.h" +#include "modules/multi/traffic_info.h" +#include "uav_recovery.h" + +#if defined(USE_PARACHUTE) && USE_PARACHUTE == 1 + +#ifndef GLUE_DEFINITIONS_STEP2 +#define GLUE_DEFINITIONS_STEP2(a, b) (a##b) +#endif +#ifndef GLUE_DEFINITIONS +#define GLUE_DEFINITIONS(a, b) GLUE_DEFINITIONS_STEP2(a, b) +#endif + +#if defined(PARACHUTE_SERVO_CHANNEL) && PARACHUTE_SERVO_CHANNEL != -1 +#define PARACHUTE_OUTPUT_COMMAND GLUE_DEFINITIONS(COMMAND_, PARACHUTE_SERVO_CHANNEL) +#else +#warning YOU HAVE NOT DEFINED A PARACHUTE RELEASE AUTOPILOT SERVO +#endif + +#endif + +/** TERMINAL VELOCITY OF THE PARACHUTE PLUS PAYLOAD */ +#ifndef PARACHUTE_TRIGGER_DELAY +#define PARACHUTE_TRIGGER_DELAY 2. +#endif + +#ifndef PARACHUTE_DESCENT_RATE +#define PARACHUTE_DESCENT_RATE 3.0 +#endif +#ifndef PARACHUTE_WIND_CORRECTION +#define PARACHUTE_WIND_CORRECTION 1.0 +#endif +#ifndef PARACHUTE_LINE_LENGTH +#define PARACHUTE_LINE_LENGTH 3.0 +#endif + +float parachute_start_qdr; +float parachute_z; +float airborne_wind_dir = 0; +float airborne_wind_speed = 0; +float calculated_wind_dir = 0; +bool wind_measurements_valid = true; +bool wind_info_valid = false; +bool deploy_parachute_var = 0; +bool land_direction = 0; + +#if PERIODIC_TELEMETRY +#include "subsystems/datalink/telemetry.h" + +static void send_wind_info(struct transport_tx *trans, struct link_device *dev) +{ + + float foo1 = 0, foo2 = 0, foo3 = 0; + pprz_msg_send_WEATHER(trans, dev, AC_ID, &foo1, &foo2, &airborne_wind_speed, &airborne_wind_dir, &foo3); + + return; +} +#endif + +void uav_recovery_init(void) +{ + + deploy_parachute_var = 0; + airborne_wind_dir = 0; + airborne_wind_speed = 0; + wind_measurements_valid = true; + wind_info_valid = true; +#if defined(PARACHUTE_OUTPUT_COMMAND) + ap_state->commands[PARACHUTE_OUTPUT_COMMAND] = MIN_PPRZ; +#endif + +#if PERIODIC_TELEMETRY + register_periodic_telemetry(DefaultPeriodic, PPRZ_MSG_ID_WEATHER, send_wind_info); +#endif + + + return; +} + + +void uav_recovery_periodic(void) +{ +#if defined(PARACHUTE_OUTPUT_COMMAND) + if (autopilot.mode != AP_MODE_MANUAL) { + if (deploy_parachute_var == 1) { + ap_state->commands[PARACHUTE_OUTPUT_COMMAND] = MAX_PPRZ; + + } else { ap_state->commands[PARACHUTE_OUTPUT_COMMAND] = MIN_PPRZ; } + } +#else +#warning PARACHUTE COMMAND NOT FOUND +#endif + + return; +} + +uint8_t LockParachute(void) +{ + + deploy_parachute_var = 0; + + return (0); +} + +uint8_t DeployParachute(void) +{ + + deploy_parachute_var = 1; + + return (0); +} + + +uint8_t calculate_wind_no_airspeed(uint8_t wp, float radius, float height) +{ + +//struct EnuCoor_f* pos = stateGetPositionEnu_f(); +//struct UtmCoor_f * pos = stateGetPositionUtm_f(); + + static bool init = false; + static float circle_count = 0; + static bool wind_measurement_started = false; + +//Legacy estimator values + float estimator_hspeed_dir = 0; + float estimator_hspeed_mod = 0; + estimator_hspeed_dir = stateGetHorizontalSpeedDir_f(); + estimator_hspeed_mod = stateGetHorizontalSpeedNorm_f(); + + float speed_max_buf = 0; + float speed_min_buf = 1000.0; + float heading_angle_buf = 0; + + if (init == false) { + airborne_wind_dir = 0; + airborne_wind_speed = 0; + wind_measurements_valid = false; + speed_max_buf = 0; + speed_min_buf = 1000.; + heading_angle_buf = 0; + wind_measurement_started = false; + init = true; + } + + NavVerticalAutoThrottleMode(RadOfDeg(0.000000)); + NavVerticalAltitudeMode(Height(height), 0.); + NavCircleWaypoint(wp, radius); + if (nav_in_circle == false || GetPosAlt() < Height(height - 10)) { + return (true); + } + NavVerticalThrottleMode(MAX_PPRZ * (float)V_CTL_AUTO_THROTTLE_NOMINAL_CRUISE_THROTTLE); + // Wait until we are in altitude and in circle. + if (wind_measurement_started == false) { + circle_count = NavCircleCount(); + wind_measurement_started = true; + } + if (estimator_hspeed_mod > speed_max_buf) { + speed_max_buf = estimator_hspeed_mod; + } + // WHEN THE GROUND SPEED IS LOWEST THIS IS WHERE THE WIND IS BLOWING FROM. + if (estimator_hspeed_mod < speed_min_buf) { + speed_min_buf = estimator_hspeed_mod; + heading_angle_buf = estimator_hspeed_dir; //180 to -180 in radians, 0 is to the NORTH! + } + + if ((NavCircleCount() - circle_count) >= 1.1) { + //Update the wind direction and speed in the WEATHER message. + airborne_wind_speed = ((speed_max_buf - speed_min_buf) / 2) * AIRBORNE_WIND_CORRECTION; +#if defined(FIXED_WIND_SPEED_FOR_TESTING) && defined(SITL) +#pragma message "Wind SPEED for UAV recovery is fixed for the simulation" +#pragma message "You can change the value by editing 'uav_recovery.xml' file." + airborne_wind_speed = FIXED_WIND_SPEED_FOR_TESTING; // FOR TESTING IN SIMULATION ONLY! +#endif +#if defined(FIXED_WIND_DIRECTION_FOR_TESTING) && defined(SITL) +#pragma message "Wind direction for UAV recovery is fixed for the simulation" +#pragma message "You can change the value by editing 'uav_recovery.xml' file." + if (FIXED_WIND_DIRECTION_FOR_TESTING > 180.) { + heading_angle_buf = RadOfDeg(FIXED_WIND_DIRECTION_FOR_TESTING - 360.); + } else { + heading_angle_buf = RadOfDeg(FIXED_WIND_DIRECTION_FOR_TESTING); + } +#endif + airborne_wind_dir = DegOfRad(heading_angle_buf); //For use with the WEATHER message + //Convert from 180, -180 to 0, 360 in Degrees. 0=NORTH, 90=EAST + if (airborne_wind_dir < 0) { airborne_wind_dir += 360; } + calculated_wind_dir = heading_angle_buf; + wind_measurements_valid = true; + wind_info_valid = true; + + //NavVerticalAutoThrottleMode(RadOfDeg(0.000000)); + // EXIT and continue the flight plan. + init = false; + return (false); + } + + return (true); +} + + +// Compute an approximation for the RELEASE waypoint from expected descent and altitude +unit_t parachute_compute_approach(uint8_t baseleg, uint8_t release, uint8_t wp_target) +{ + + float x = 0, y = 0; + float approach_dir = 0; + float calculated_wind_east = 0; + float calculated_wind_north = 0; + + struct { + float x; + float y; + float a; + } start_wp; + + // Calculate a starting waypoint opposite to the wind. + //180 TO -180 in Radians. Convert from 0 = NORTH to 0 = EAST + approach_dir = RadOfDeg(90.) - calculated_wind_dir; + if (approach_dir > M_PI) { approach_dir -= 2 * M_PI; } + approach_dir -= M_PI; // reverse the direction as we must release the parachute against the wind! + + start_wp.x = (cos(approach_dir) * nav_radius) + waypoints[wp_target].x; + start_wp.y = (sin(approach_dir) * nav_radius) + waypoints[wp_target].y; + start_wp.a = waypoints[release].a; + + // We approximate vx and vy, taking into account that RELEASE is close to the TARGET + float x_0 = waypoints[wp_target].x - start_wp.x; + float y_0 = waypoints[wp_target].y - start_wp.y; + + // Unit vector from the calculated starting waypoint to TARGET + float d = sqrt(x_0 * x_0 + y_0 * y_0); + float x1 = x_0 / d; + float y_1 = y_0 / d; + + waypoints[baseleg].x = start_wp.x + y_1 * nav_radius; + waypoints[baseleg].y = start_wp.y - x1 * nav_radius; + waypoints[baseleg].a = start_wp.a; + parachute_start_qdr = M_PI - atan2(-y_1, -x1); + if (nav_radius < 0) { + parachute_start_qdr += M_PI; + } + + parachute_z = waypoints[release].a - ground_alt - PARACHUTE_LINE_LENGTH; + + calculated_wind_north = cosf(calculated_wind_dir) * airborne_wind_speed; + calculated_wind_east = sinf(calculated_wind_dir) * airborne_wind_speed; + x = (parachute_z / PARACHUTE_DESCENT_RATE) * calculated_wind_east * PARACHUTE_WIND_CORRECTION; + y = (parachute_z / PARACHUTE_DESCENT_RATE) * calculated_wind_north * PARACHUTE_WIND_CORRECTION; + + waypoints[release].x = (waypoints[wp_target].x + x); + waypoints[release].y = waypoints[wp_target].y + y; + + + return 0; +} + diff --git a/sw/airborne/modules/uav_recovery/uav_recovery.h b/sw/airborne/modules/uav_recovery/uav_recovery.h new file mode 100644 index 0000000000..68e12f90a0 --- /dev/null +++ b/sw/airborne/modules/uav_recovery/uav_recovery.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013-2020 Chris Efstathiou hendrixgr@gmail.com + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * @file modules/uav_recovery/uav_recovery.h + * + */ + +#if !defined(UAV_RECOVERY_H) +#define UAV_RECOVERY_H +#define MY_PARACHUTE_RADIUS DEFAULT_CIRCLE_RADIUS + +extern unit_t parachute_compute_approach(uint8_t baseleg, uint8_t release, uint8_t wp_target); +extern float parachute_start_qdr; +extern bool deploy_parachute_var; +extern float airborne_wind_dir; +extern float airborne_wind_speed; +extern bool wind_measurements_valid; +extern bool wind_info_valid; + +extern void uav_recovery_init(void); +extern void uav_recovery_periodic(void); +extern uint8_t DeployParachute(void); +extern uint8_t LockParachute(void); +extern uint8_t calculate_wind_no_airspeed(uint8_t wp, float radius, float height); + + +#define ParachuteComputeApproach(_baseleg, _release, _target) parachute_compute_approach(_baseleg, _release, _target) + +#endif