Пример #1
0
/** Computes the servo values from cam_pan_c and cam_tilt_c */
void cam_angles( void ) {
  float cam_pan = 0;
  float cam_tilt = 0;

#ifdef CAM_PAN_NEUTRAL
  float pan_diff = cam_pan_c - RadOfDeg(CAM_PAN_NEUTRAL);
  if (pan_diff > 0)
    cam_pan = MAX_PPRZ * (pan_diff / (RadOfDeg(CAM_PAN_MAX - CAM_PAN_NEUTRAL)));
  else
    cam_pan = MIN_PPRZ * (pan_diff / (RadOfDeg(CAM_PAN_MIN - CAM_PAN_NEUTRAL)));
#endif

#ifdef CAM_TILT_NEUTRAL
  float tilt_diff = cam_tilt_c - RadOfDeg(CAM_TILT_NEUTRAL);
  if (tilt_diff > 0)
    cam_tilt = MAX_PPRZ * (tilt_diff / (RadOfDeg(CAM_TILT_MAX - CAM_TILT_NEUTRAL)));
  else
    cam_tilt = MIN_PPRZ * (tilt_diff / (RadOfDeg(CAM_TILT_MIN - CAM_TILT_NEUTRAL)));
#endif

  cam_pan = TRIM_PPRZ(cam_pan);
  cam_tilt = TRIM_PPRZ(cam_tilt);

  cam_phi_c = cam_pan_c;
  cam_theta_c = cam_tilt_c;

#ifdef COMMAND_CAM_PAN
  ap_state->commands[COMMAND_CAM_PAN] = cam_pan;
#endif
#ifdef COMMAND_CAM_TILT
  ap_state->commands[COMMAND_CAM_TILT] = cam_tilt;
#endif
}
Пример #2
0
void pid_slew_gaz( void ) {
  static pprz_t last_gaz=TRIM_PPRZ(CLIMB_LEVEL_GAZ*MAX_PPRZ);
  pprz_t diff_gaz = desired_gaz - last_gaz;
  Bound(diff_gaz, -TRIM_PPRZ(CLIMB_MAX_DIFF_GAZ*MAX_PPRZ), TRIM_PPRZ(CLIMB_MAX_DIFF_GAZ*MAX_PPRZ));
  desired_gaz = last_gaz + diff_gaz;
  last_gaz = desired_gaz;
}
Пример #3
0
/** \brief Computes ::desired_aileron and ::desired_elevator from attitude estimation and expected attitude. */
void roll_pitch_pid_run( void ) {
  float err =  estimator_phi - desired_roll;
  desired_aileron = TRIM_PPRZ(roll_pgain * err);
  if (pitch_of_roll <0.)
    pitch_of_roll = 0.;
  err = -(estimator_theta - desired_pitch - pitch_of_roll * fabs(estimator_phi));
  desired_elevator = TRIM_PPRZ(pitch_pgain * err);
}
Пример #4
0
/** Computes the servo values from cam_pan_c and cam_tilt_c */
void cam_angles(void)
{
  float cam_pan = 0;
  float cam_tilt = 0;
  if (cam_pan_c > RadOfDeg(CAM_PAN_MAX)) {
    cam_pan_c = RadOfDeg(CAM_PAN_MAX);
  } else {
    if (cam_pan_c < RadOfDeg(CAM_PAN_MIN)) {
      cam_pan_c = RadOfDeg(CAM_PAN_MIN);
    }
  }

  if (cam_tilt_c > RadOfDeg(CAM_TILT_MAX)) {
    cam_tilt_c = RadOfDeg(CAM_TILT_MAX);
  } else {
    if (cam_tilt_c < RadOfDeg(CAM_TILT_MIN)) {
      cam_tilt_c = RadOfDeg(CAM_TILT_MIN);
    }
  }

#ifdef CAM_PAN_NEUTRAL
  float pan_diff = cam_pan_c - RadOfDeg(CAM_PAN_NEUTRAL);
  if (pan_diff > 0) {
    cam_pan = MAX_PPRZ * (pan_diff / (RadOfDeg(CAM_PAN_MAX - CAM_PAN_NEUTRAL)));
  } else {
    cam_pan = MIN_PPRZ * (pan_diff / (RadOfDeg(CAM_PAN_MIN - CAM_PAN_NEUTRAL)));
  }
#else
  cam_pan = ((float)RadOfDeg(cam_pan_c - CAM_PAN_MIN)) * ((float)MAX_PPRZ / (float)RadOfDeg(CAM_PAN_MAX - CAM_PAN_MIN));
#endif

#ifdef CAM_TILT_NEUTRAL
  float tilt_diff = cam_tilt_c - RadOfDeg(CAM_TILT_NEUTRAL);
  if (tilt_diff > 0) {
    cam_tilt = MAX_PPRZ * (tilt_diff / (RadOfDeg(CAM_TILT_MAX - CAM_TILT_NEUTRAL)));
  } else {
    cam_tilt = MIN_PPRZ * (tilt_diff / (RadOfDeg(CAM_TILT_MIN - CAM_TILT_NEUTRAL)));
  }
#else
  cam_tilt = ((float)RadOfDeg(cam_tilt_c - CAM_TILT_MIN))  * ((float)MAX_PPRZ / (float)RadOfDeg(
               CAM_TILT_MAX - CAM_TILT_MIN));
#endif

  cam_pan = TRIM_PPRZ(cam_pan);
  cam_tilt = TRIM_PPRZ(cam_tilt);

  cam_phi_c = cam_pan_c;
  cam_theta_c = cam_tilt_c;

#ifdef COMMAND_CAM_PAN
  ap_state->commands[COMMAND_CAM_PAN] = cam_pan;
#endif
#ifdef COMMAND_CAM_TILT
  ap_state->commands[COMMAND_CAM_TILT] = cam_tilt;
#endif
}
Пример #5
0
void stabilisation_task(void)
{
    /* ---- inlined below:    ir_update(); ---- */
// #ifndef SIMUL
    int16_t x1_mean = buf_ir1.sum/AV_NB_SAMPLE;
    int16_t x2_mean = buf_ir2.sum/AV_NB_SAMPLE;

    /* simplesclar cannot have type decls in the middle of the func */
    float rad_of_ir, err, tmp_sanjit;

    ir_roll = IR_RollOfIrs(x1_mean, x2_mean) - ir_roll_neutral;
    ir_pitch = IR_PitchOfIrs(x1_mean, x2_mean) - ir_pitch_neutral;
    /*
    #else
    extern volatile int16_t simul_ir_roll, simul_ir_pitch;
    ir_roll = simul_ir_roll -  ir_roll_neutral;
    ir_pitch = simul_ir_pitch - ir_pitch_neutral;
    #endif
    */

    /* ---- inlined below    estimator_update_state_infrared();  ---- */
    rad_of_ir = (ir_estim_mode == IR_ESTIM_MODE_ON && EstimatorIrGainIsCorrect()) ?
                estimator_rad_of_ir : ir_rad_of_ir;
    estimator_phi  = rad_of_ir * ir_roll;

    estimator_theta = rad_of_ir * ir_pitch;

    /* --- inlined below    roll_pitch_pid_run(); // Set  desired_aileron & desired_elevator ---- */
    err =  estimator_phi - desired_roll;
    desired_aileron = TRIM_PPRZ(roll_pgain * err);
    if (pitch_of_roll <0.)
        pitch_of_roll = 0.;

    /* line below commented out by sanjit, to avoid use of fabs
       err = -(estimator_theta - desired_pitch - pitch_of_roll * fabs(estimator_phi));
       2 replacement lines are below
    */
    tmp_sanjit = (estimator_phi < 0) ? -estimator_phi : estimator_phi;
    err = -(estimator_theta - desired_pitch - pitch_of_roll * tmp_sanjit);

    desired_elevator = TRIM_PPRZ(pitch_pgain * err);

    /* --- end inline ---- */

    to_fbw.channels[RADIO_THROTTLE] = desired_gaz; // desired_gaz is set upon GPS message reception
    to_fbw.channels[RADIO_ROLL] = desired_aileron;
#ifndef ANTON_T7
    to_fbw.channels[RADIO_PITCH] = desired_elevator;
#endif

    // Code for camera stabilization, FIXME put that elsewhere
    to_fbw.channels[RADIO_GAIN1] = TRIM_PPRZ(MAX_PPRZ/0.75*(-estimator_phi));
}
Пример #6
0
static inline void h_ctl_roll_rate_loop()
{
  float err = stateGetBodyRates_f()->p - h_ctl_roll_rate_setpoint;

  /* I term calculation */
  static float roll_rate_sum_err = 0.;
  static uint8_t roll_rate_sum_idx = 0;
  static float roll_rate_sum_values[H_CTL_ROLL_RATE_SUM_NB_SAMPLES];

  roll_rate_sum_err -= roll_rate_sum_values[roll_rate_sum_idx];
  roll_rate_sum_values[roll_rate_sum_idx] = err;
  roll_rate_sum_err += err;
  roll_rate_sum_idx++;
  if (roll_rate_sum_idx >= H_CTL_ROLL_RATE_SUM_NB_SAMPLES) { roll_rate_sum_idx = 0; }

  /* D term calculations */
  static float last_err = 0;
  float d_err = err - last_err;
  last_err = err;

  float throttle_dep_pgain =
    Blend(h_ctl_hi_throttle_roll_rate_pgain, h_ctl_lo_throttle_roll_rate_pgain,
          v_ctl_throttle_setpoint / ((float)MAX_PPRZ));
  float cmd = throttle_dep_pgain * (err + h_ctl_roll_rate_igain * roll_rate_sum_err / H_CTL_ROLL_RATE_SUM_NB_SAMPLES +
                                    h_ctl_roll_rate_dgain * d_err);

  h_ctl_aileron_setpoint = TRIM_PPRZ(cmd);
}
inline static void h_ctl_pitch_loop( void ) {
  static float last_err;
  /* sanity check */
  if (h_ctl_pitch_of_roll <0.)
    h_ctl_pitch_of_roll = 0.;

  h_ctl_pitch_loop_setpoint = h_ctl_pitch_setpoint + h_ctl_pitch_of_roll * fabs(estimator_phi);
#ifdef USE_PITCH_TRIM
  loiter();
#endif

#ifdef USE_ANGLE_REF
  // Update reference setpoints for pitch
  h_ctl_ref_pitch_angle += h_ctl_ref_pitch_rate * H_CTL_REF_DT;
  h_ctl_ref_pitch_rate += h_ctl_ref_pitch_accel * H_CTL_REF_DT;
  h_ctl_ref_pitch_accel = H_CTL_REF_W*H_CTL_REF_W * (h_ctl_pitch_loop_setpoint - h_ctl_ref_pitch_angle) - 2*H_CTL_REF_XI*H_CTL_REF_W * h_ctl_ref_pitch_rate;
  // Saturation on references
  BoundAbs(h_ctl_ref_pitch_accel, H_CTL_REF_MAX_Q_DOT);
  if (h_ctl_ref_pitch_rate > H_CTL_REF_MAX_Q) {
    h_ctl_ref_pitch_rate = H_CTL_REF_MAX_Q;
    if (h_ctl_ref_pitch_accel > 0.) h_ctl_ref_pitch_accel = 0.;
  }
  else if (h_ctl_ref_pitch_rate < - H_CTL_REF_MAX_Q) {
    h_ctl_ref_pitch_rate = - H_CTL_REF_MAX_Q;
    if (h_ctl_ref_pitch_accel < 0.) h_ctl_ref_pitch_accel = 0.;
  }
#else
  h_ctl_ref_pitch_angle = h_ctl_pitch_loop_setpoint;
  h_ctl_ref_pitch_rate = 0.;
  h_ctl_ref_pitch_accel = 0.;
#endif

  // Compute errors
  float err = estimator_theta - h_ctl_ref_pitch_angle;
  float d_err = (err - last_err)/H_CTL_REF_DT - h_ctl_ref_pitch_rate;
  last_err = err;

  if (pprz_mode == PPRZ_MODE_MANUAL || launch == 0) {
    h_ctl_pitch_sum_err = 0.;
  }
  else {
    if (h_ctl_pitch_igain < 0.) {
      h_ctl_pitch_sum_err += err * H_CTL_REF_DT;
      BoundAbs(h_ctl_pitch_sum_err, (- H_CTL_PITCH_SUM_ERR_MAX / h_ctl_pitch_igain));
    } else {
      h_ctl_pitch_sum_err = 0.;
    }
  }

  float cmd = h_ctl_pitch_Kffa * h_ctl_ref_pitch_accel
    + h_ctl_pitch_Kffd * h_ctl_ref_pitch_rate
    + h_ctl_pitch_pgain * err
    + h_ctl_pitch_dgain * d_err
    + h_ctl_pitch_igain * h_ctl_pitch_sum_err;

  cmd /= airspeed_ratio2;

  h_ctl_elevator_setpoint = TRIM_PPRZ(cmd);
}
Пример #8
0
/** Pitch loop */
inline static void pid_pitch_loop_run(void) {
  /* sanity check */
  if (pitch_of_roll <0.)
    pitch_of_roll = 0.;

  float err = -(estimator_theta - desired_pitch - pitch_of_roll*fabs(estimator_phi));
  Bound(err, -M_PI/2, M_PI/2);
  desired_elevator = TRIM_PPRZ(pitch_pgain * err);
}
Пример #9
0
inline static void h_ctl_cl_loop(void)
{

#if H_CTL_CL_LOOP_INCREASE_FLAPS_WITH_LOADFACTOR
#if (!defined SITL || defined USE_NPS)
  struct Int32Vect3 accel_meas_body, accel_ned;
  struct Int32RMat *ned_to_body_rmat = stateGetNedToBodyRMat_i();
  struct NedCoor_i *accel_tmp = stateGetAccelNed_i();
  VECT3_COPY(accel_ned, (*accel_tmp));
  accel_ned.z -= ACCEL_BFP_OF_REAL(9.81f);
  int32_rmat_vmult(&accel_meas_body, ned_to_body_rmat, &accel_ned);
  float nz = -ACCEL_FLOAT_OF_BFP(accel_meas_body.z) / 9.81f;
  // max load factor to be taken into acount
  // to prevent negative flap movement du to negative acceleration
  Bound(nz, 1.f, 2.f);
#else
  float nz = 1.f;
#endif
#endif

  // Compute a corrected airspeed corresponding to the current load factor nz
  // with Cz the lift coef at 1g, Czn the lift coef at n g, both at the same speed V,
  // the corrected airspeed Vn is so that nz = Czn/Cz = V^2 / Vn^2,
  // thus Vn = V / sqrt(nz)
#if H_CTL_CL_LOOP_USE_AIRSPEED_SETPOINT
  float corrected_airspeed = v_ctl_auto_airspeed_setpoint;
#else
  float corrected_airspeed = stateGetAirspeed_f();
#endif
#if H_CTL_CL_LOOP_INCREASE_FLAPS_WITH_LOADFACTOR
  corrected_airspeed /= sqrtf(nz);
#endif
  Bound(corrected_airspeed, STALL_AIRSPEED, RACE_AIRSPEED);

  float cmd = 0.f;
  // deadband around NOMINAL_AIRSPEED, rest linear
  if (corrected_airspeed > NOMINAL_AIRSPEED + H_CTL_CL_DEADBAND) {
    cmd = (corrected_airspeed - NOMINAL_AIRSPEED) * (H_CTL_CL_FLAPS_RACE - H_CTL_CL_FLAPS_NOMINAL) / (RACE_AIRSPEED - NOMINAL_AIRSPEED);
  }
  else if (corrected_airspeed < NOMINAL_AIRSPEED - H_CTL_CL_DEADBAND) {
    cmd = (corrected_airspeed - NOMINAL_AIRSPEED) * (H_CTL_CL_FLAPS_STALL - H_CTL_CL_FLAPS_NOMINAL) / (STALL_AIRSPEED - NOMINAL_AIRSPEED);
  }

  // no control in manual mode
  if (pprz_mode == PPRZ_MODE_MANUAL) {
    cmd = 0.f;
  }
  // bound max flap angle
  Bound(cmd, H_CTL_CL_FLAPS_RACE, H_CTL_CL_FLAPS_STALL);
  // from percent to pprz
  cmd = cmd * MAX_PPRZ;
  h_ctl_flaps_setpoint = TRIM_PPRZ(cmd);
}
Пример #10
0
inline static void h_ctl_yaw_loop(void)
{

#if H_CTL_YAW_TRIM_NY
  // Actual Acceleration from IMU:
#if (!defined SITL || defined USE_NPS)
  struct Int32Vect3 accel_meas_body, accel_ned;
  struct Int32RMat *ned_to_body_rmat = stateGetNedToBodyRMat_i();
  struct NedCoor_i *accel_tmp = stateGetAccelNed_i();
  VECT3_COPY(accel_ned, (*accel_tmp));
  accel_ned.z -= ACCEL_BFP_OF_REAL(9.81f);
  int32_rmat_vmult(&accel_meas_body, ned_to_body_rmat, &accel_ned);
  float ny = -ACCEL_FLOAT_OF_BFP(accel_meas_body.y) / 9.81f; // Lateral load factor (in g)
#else
  float ny = 0.f;
#endif

  if (pprz_mode == PPRZ_MODE_MANUAL || launch == 0) {
    h_ctl_yaw_ny_sum_err = 0.;
  } else {
    if (h_ctl_yaw_ny_igain > 0.) {
      // only update when: phi<60degrees and ny<2g
      if (fabsf(stateGetNedToBodyEulers_f()->phi) < 1.05 && fabsf(ny) < 2.) {
        h_ctl_yaw_ny_sum_err += ny * H_CTL_REF_DT;
        // max half rudder deflection for trim
        BoundAbs(h_ctl_yaw_ny_sum_err, MAX_PPRZ / (2. * h_ctl_yaw_ny_igain));
      }
    } else {
      h_ctl_yaw_ny_sum_err = 0.;
    }
  }
#endif

#ifdef USE_AIRSPEED
  float Vo = stateGetAirspeed_f();
  Bound(Vo, STALL_AIRSPEED, RACE_AIRSPEED);
#else
  float Vo = NOMINAL_AIRSPEED;
#endif

  h_ctl_ref.yaw_rate = h_ctl_yaw_rate_setpoint // set by RC
                       + 9.81f / Vo * sinf(h_ctl_roll_setpoint); // for turns
  float d_err = h_ctl_ref.yaw_rate - stateGetBodyRates_f()->r;

  float cmd = + h_ctl_yaw_dgain * d_err
#if H_CTL_YAW_TRIM_NY
              + h_ctl_yaw_ny_igain * h_ctl_yaw_ny_sum_err
#endif
              ;
  cmd /= airspeed_ratio2;
  h_ctl_rudder_setpoint = TRIM_PPRZ(cmd);
}
Пример #11
0
inline static void h_ctl_roll_loop( void ) {
  float err = estimator_phi - h_ctl_roll_setpoint;
#ifdef SITL
  static float last_err = 0;
  estimator_p = (err - last_err)/(1/60.);
  last_err = err;
#endif
  float cmd = - h_ctl_roll_attitude_gain * err
    - h_ctl_roll_rate_gain * estimator_p
    + v_ctl_throttle_setpoint * h_ctl_aileron_of_throttle;

  h_ctl_aileron_setpoint = TRIM_PPRZ(cmd);
}
Пример #12
0
void cam_periodic( void ) {
  switch (cam_roll_mode) {
  case MODE_STABILIZED:
    phi_c = cam_roll_phi + estimator_phi;
    break;
  case MODE_MANUAL:
    phi_c = cam_roll_phi;
    break;
  default:
    phi_c = 0;
  }
  ap_state->commands[COMMAND_CAM_ROLL] = TRIM_PPRZ(phi_c * MAX_PPRZ / RadOfDeg(CAM_PHI_MAX_DEG));
}
Пример #13
0
void cam_periodic(void)
{
  switch (cam_roll_mode) {
    case MODE_STABILIZED:
      phi_c = cam_roll_phi + stateGetNedToBodyEulers_f()->phi;
      break;
    case MODE_MANUAL:
      phi_c = cam_roll_phi;
      break;
    default:
      phi_c = 0;
  }
  ap_state->commands[COMMAND_CAM_ROLL] = TRIM_PPRZ(phi_c * MAX_PPRZ / CAM_PHI_MAX);
}
Пример #14
0
inline static void h_ctl_roll_loop( void ) {
  float err = stateGetNedToBodyEulers_f()->phi - h_ctl_roll_setpoint;
  struct FloatRates* body_rate = stateGetBodyRates_f();
#ifdef SITL
  static float last_err = 0;
  body_rate->p = (err - last_err)/(1/60.);
  last_err = err;
#endif
  float cmd = h_ctl_roll_attitude_gain * err
    + h_ctl_roll_rate_gain * body_rate->p
    + v_ctl_throttle_setpoint * h_ctl_aileron_of_throttle;

  h_ctl_aileron_setpoint = TRIM_PPRZ(cmd);
}
Пример #15
0
inline static void pid_roll_loop_run(void) {
  /** Attitude PID */
  float err =  estimator_phi - desired_roll;
  Bound(err, -M_PI/2., M_PI/2);

  float std_desired_aileron = TRIM_PPRZ(roll_pgain * err + desired_gaz * aileron_of_gaz);
#ifndef PID_RATE_LOOP
  desired_aileron = std_desired_aileron;
#else
  float desired_roll_rate = -alt_roll_pgain*err;
  Bound(desired_roll_rate, -GYRO_MAX_RATE, GYRO_MAX_RATE);

  /** Roll rate pid */
  err = roll_rate - desired_roll_rate;
  Bound(err, -GYRO_MAX_RATE, GYRO_MAX_RATE);
  
  static float rollrate_sum_err = 0.;
  static uint8_t rollratesum_idx = 0;
  static float rollratesum_values[ROLLRATESUM_NB_SAMPLES];

  rollrate_sum_err -= rollratesum_values[rollratesum_idx];
  rollratesum_values[rollratesum_idx] = err;
  rollrate_sum_err += err;
  rollratesum_idx++;
  if (rollratesum_idx >= ROLLRATESUM_NB_SAMPLES) rollratesum_idx = 0;
  
  /* D term calculations */
  static float last_roll_rate;
  float d_err = roll_rate - last_roll_rate;
  last_roll_rate = roll_rate;

  float rate_desired_aileron = TRIM_PPRZ(roll_rate_pgain*(err + roll_rate_igain*(rollrate_sum_err/ROLLRATESUM_NB_SAMPLES) + roll_rate_dgain*d_err));

  desired_aileron = rate_mode*rate_desired_aileron+(1-rate_mode)*std_desired_aileron;
#endif

}
Пример #16
0
void attitude_loop( void ) {

#if USE_INFRARED
  ahrs_update_infrared();
#endif /* USE_INFRARED */

  if (pprz_mode >= PPRZ_MODE_AUTO2)
  {
    if (v_ctl_mode == V_CTL_MODE_AUTO_THROTTLE) {
      v_ctl_throttle_setpoint = nav_throttle_setpoint;
      v_ctl_pitch_setpoint = nav_pitch;
    }
    else if (v_ctl_mode >= V_CTL_MODE_AUTO_CLIMB)
    {
      v_ctl_climb_loop();
    }

#if defined V_CTL_THROTTLE_IDLE
    Bound(v_ctl_throttle_setpoint, TRIM_PPRZ(V_CTL_THROTTLE_IDLE*MAX_PPRZ), MAX_PPRZ);
#endif

#ifdef V_CTL_POWER_CTL_BAT_NOMINAL
    if (vsupply > 0.) {
      v_ctl_throttle_setpoint *= 10. * V_CTL_POWER_CTL_BAT_NOMINAL / (float)vsupply;
      v_ctl_throttle_setpoint = TRIM_UPPRZ(v_ctl_throttle_setpoint);
    }
#endif

    h_ctl_pitch_setpoint = v_ctl_pitch_setpoint; // Copy the pitch setpoint from the guidance to the stabilization control
    Bound(h_ctl_pitch_setpoint, H_CTL_PITCH_MIN_SETPOINT, H_CTL_PITCH_MAX_SETPOINT);
    if (kill_throttle || (!estimator_flight_time && !launch))
      v_ctl_throttle_setpoint = 0;
  }

  h_ctl_attitude_loop(); /* Set  h_ctl_aileron_setpoint & h_ctl_elevator_setpoint */
  v_ctl_throttle_slew();
  ap_state->commands[COMMAND_THROTTLE] = v_ctl_throttle_slewed;
  ap_state->commands[COMMAND_ROLL] = -h_ctl_aileron_setpoint;

  ap_state->commands[COMMAND_PITCH] = h_ctl_elevator_setpoint;

#if defined MCU_SPI_LINK
  link_mcu_send();
#elif defined INTER_MCU && defined SINGLE_MCU
  /**Directly set the flag indicating to FBW that shared buffer is available*/
  inter_mcu_received_ap = TRUE;
#endif

}
Пример #17
0
inline static void h_ctl_pitch_loop(void)
{
  static float last_err;
  struct FloatEulers *att = stateGetNedToBodyEulers_f();
  /* sanity check */
  if (h_ctl_elevator_of_roll < 0.) {
    h_ctl_elevator_of_roll = 0.;
  }

  if (v_ctl_mode == V_CTL_MODE_LANDING) {
    h_ctl_pitch_loop_setpoint =  h_ctl_pitch_setpoint;
  }
  else {
    h_ctl_pitch_loop_setpoint =  h_ctl_pitch_setpoint + h_ctl_elevator_of_roll / h_ctl_pitch_pgain * fabs(att->phi);
  }

  float err = 0;

#ifdef USE_AOA
  switch (h_ctl_pitch_mode) {
    case H_CTL_PITCH_MODE_THETA:
      err = att->theta - h_ctl_pitch_loop_setpoint;
      break;
    case H_CTL_PITCH_MODE_AOA:
      err = stateGetAngleOfAttack_f() - h_ctl_pitch_loop_setpoint;
      break;
    default:
      err = att->theta - h_ctl_pitch_loop_setpoint;
      break;
  }
#else //NO_AOA
  err = att->theta - h_ctl_pitch_loop_setpoint;
#endif


  float d_err = err - last_err;
  last_err = err;
  float cmd = -h_ctl_pitch_pgain * (err + h_ctl_pitch_dgain * d_err);
#ifdef LOITER_TRIM
  cmd += loiter();
#endif
  h_ctl_elevator_setpoint = TRIM_PPRZ(cmd);
}
Пример #18
0
inline static void h_ctl_pitch_loop( void ) {
  static float last_err;
  /* sanity check */
  if (h_ctl_elevator_of_roll <0.)
    h_ctl_elevator_of_roll = 0.;

  h_ctl_pitch_loop_setpoint =
    h_ctl_pitch_setpoint
    - h_ctl_elevator_of_roll / h_ctl_pitch_pgain * fabs(estimator_phi);

  float err = estimator_theta - h_ctl_pitch_loop_setpoint;
  float d_err = err - last_err;
  last_err = err;
  float cmd = h_ctl_pitch_pgain * (err + h_ctl_pitch_dgain * d_err);
#ifdef LOITER_TRIM
  cmd += loiter();
#endif
  h_ctl_elevator_setpoint = TRIM_PPRZ(cmd);
}
Пример #19
0
/** Computes h_ctl_aileron_setpoint from h_ctl_roll_setpoint */
inline static void h_ctl_roll_loop( void ) {
  float err = estimator_phi - h_ctl_roll_setpoint;
  float cmd = h_ctl_roll_pgain * err
    + v_ctl_throttle_setpoint * h_ctl_aileron_of_throttle;
  h_ctl_aileron_setpoint = TRIM_PPRZ(cmd);

#ifdef H_CTL_RATE_LOOP
  if (h_ctl_auto1_rate) {
    /** Runs only the roll rate loop */
    h_ctl_roll_rate_setpoint = h_ctl_roll_setpoint * 10.;
    h_ctl_roll_rate_loop();
  } else {
    h_ctl_roll_rate_setpoint = h_ctl_roll_rate_setpoint_pgain * err;
    BoundAbs(h_ctl_roll_rate_setpoint, H_CTL_ROLL_RATE_MAX_SETPOINT);

    float saved_aileron_setpoint = h_ctl_aileron_setpoint;
    h_ctl_roll_rate_loop();
    h_ctl_aileron_setpoint = Blend(h_ctl_aileron_setpoint, saved_aileron_setpoint, h_ctl_roll_rate_mode) ;
  }
#endif
}
Пример #20
0
void airborne_ant_point_periodic(void)
{
  float airborne_ant_pan_servo = 0;

  static VECTOR svPlanePosition,
         Home_Position,
         Home_PositionForPlane,
         Home_PositionForPlane2;

  static MATRIX smRotation;

  svPlanePosition.fx = stateGetPositionEnu_f()->y;
  svPlanePosition.fy = stateGetPositionEnu_f()->x;
  svPlanePosition.fz = stateGetPositionUtm_f()->alt;

  Home_Position.fx = WaypointY(WP_HOME);
  Home_Position.fy = WaypointX(WP_HOME);
  Home_Position.fz = waypoints[WP_HOME].a;

  /* 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.;

  vMultiplyMatrixByVector(&Home_PositionForPlane2, smRotation, Home_PositionForPlane);


  /*
   * This is for one axis pan antenna mechanisms. The default is to
   * circle clockwise so view is right. The pan servo neutral makes
   * the antenna look to the right with 0� given, 90� is to the back and
   * -90� is to the front.
   *
   *
   *
   *   plane front
   *
   *                  90
                      ^
   *                  I
   *             135  I  45�
   *                \ I /
   *                 \I/
   *        180-------I------- 0�
   *                 /I\
   *                / I \
   *            -135  I  -45�
   *                  I
   *                -90
   *             plane back
   *
   *
   */

  /* fPan =   0  -> antenna looks along the wing
             90  -> antenna looks in flight direction
            -90  -> antenna looks backwards
  */
  /* fixed to the plane*/
  airborne_ant_pan = (float)(atan2(Home_PositionForPlane2.fx, (Home_PositionForPlane2.fy)));

  // I need to avoid oscillations around the 180 degree mark.
  if (airborne_ant_pan > 0 && airborne_ant_pan <= RadOfDeg(175)) { ant_pan_positive = 1; }
  if (airborne_ant_pan < 0 && airborne_ant_pan >= RadOfDeg(-175)) { ant_pan_positive = 0; }

  if (airborne_ant_pan > RadOfDeg(175) && ant_pan_positive == 0) {
    airborne_ant_pan = RadOfDeg(-180);

  } else if (airborne_ant_pan < RadOfDeg(-175) && ant_pan_positive) {
    airborne_ant_pan = RadOfDeg(180);
    ant_pan_positive = 0;
  }

#ifdef ANT_PAN_NEUTRAL
  airborne_ant_pan = airborne_ant_pan - RadOfDeg(ANT_PAN_NEUTRAL);
  if (airborne_ant_pan > 0) {
    airborne_ant_pan_servo = MAX_PPRZ * (airborne_ant_pan / (RadOfDeg(ANT_PAN_MAX - ANT_PAN_NEUTRAL)));
  } else {
    airborne_ant_pan_servo = MIN_PPRZ * (airborne_ant_pan / (RadOfDeg(ANT_PAN_MIN - ANT_PAN_NEUTRAL)));
  }
#endif

  airborne_ant_pan_servo = TRIM_PPRZ(airborne_ant_pan_servo);

#ifdef COMMAND_ANT_PAN
  ap_state->commands[COMMAND_ANT_PAN] = airborne_ant_pan_servo;
#endif


  return;
}
Пример #21
0
/** \brief Computes slewed throttle from throttle setpoint
    called at 20Hz
 */
void v_ctl_throttle_slew( void ) {
  pprz_t diff_throttle = v_ctl_throttle_setpoint - v_ctl_throttle_slewed;
  BoundAbs(diff_throttle, TRIM_PPRZ(V_CTL_THROTTLE_SLEW*MAX_PPRZ));
  v_ctl_throttle_slewed += diff_throttle;
}
inline static void h_ctl_roll_loop( void ) {

  static float cmd_fb = 0.;

#ifdef USE_ANGLE_REF
  // Update reference setpoints for roll
  h_ctl_ref_roll_angle += h_ctl_ref_roll_rate * H_CTL_REF_DT;
  h_ctl_ref_roll_rate += h_ctl_ref_roll_accel * H_CTL_REF_DT;
  h_ctl_ref_roll_accel = H_CTL_REF_W*H_CTL_REF_W * (h_ctl_roll_setpoint - h_ctl_ref_roll_angle) - 2*H_CTL_REF_XI*H_CTL_REF_W * h_ctl_ref_roll_rate;
  // Saturation on references
  BoundAbs(h_ctl_ref_roll_accel, H_CTL_REF_MAX_P_DOT);
  if (h_ctl_ref_roll_rate > H_CTL_REF_MAX_P) {
    h_ctl_ref_roll_rate = H_CTL_REF_MAX_P;
    if (h_ctl_ref_roll_accel > 0.) h_ctl_ref_roll_accel = 0.;
  }
  else if (h_ctl_ref_roll_rate < - H_CTL_REF_MAX_P) {
    h_ctl_ref_roll_rate = - H_CTL_REF_MAX_P;
    if (h_ctl_ref_roll_accel < 0.) h_ctl_ref_roll_accel = 0.;
  }
#else
  h_ctl_ref_roll_angle = h_ctl_roll_setpoint;
  h_ctl_ref_roll_rate = 0.;
  h_ctl_ref_roll_accel = 0.;
#endif

#ifdef USE_KFF_UPDATE
  // update Kff gains
  h_ctl_roll_Kffa -= KFFA_UPDATE * h_ctl_ref_roll_accel * cmd_fb / (H_CTL_REF_MAX_P_DOT*H_CTL_REF_MAX_P_DOT);
  h_ctl_roll_Kffd -= KFFD_UPDATE * h_ctl_ref_roll_rate  * cmd_fb / (H_CTL_REF_MAX_P*H_CTL_REF_MAX_P);
#ifdef SITL
  printf("%f %f %f\n", h_ctl_roll_Kffa, h_ctl_roll_Kffd, cmd_fb);
#endif
  h_ctl_roll_Kffa = Min(h_ctl_roll_Kffa, 0);
  h_ctl_roll_Kffd = Min(h_ctl_roll_Kffd, 0);
#endif

  // Compute errors
  float err = estimator_phi - h_ctl_ref_roll_angle;
#ifdef SITL
  static float last_err = 0;
  estimator_p = (err - last_err)/(1/60.);
  last_err = err;
#endif
  float d_err = estimator_p - h_ctl_ref_roll_rate;

  if (pprz_mode == PPRZ_MODE_MANUAL || launch == 0) {
    h_ctl_roll_sum_err = 0.;
  }
  else {
    if (h_ctl_roll_igain < 0.) {
      h_ctl_roll_sum_err += err * H_CTL_REF_DT;
      BoundAbs(h_ctl_roll_sum_err, (- H_CTL_ROLL_SUM_ERR_MAX / h_ctl_roll_igain));
    } else {
      h_ctl_roll_sum_err = 0.;
    }
  }

  cmd_fb = h_ctl_roll_attitude_gain * err;// + h_ctl_roll_rate_gain * d_err;
  float cmd = h_ctl_roll_Kffa * h_ctl_ref_roll_accel
    + h_ctl_roll_Kffd * h_ctl_ref_roll_rate
    - cmd_fb
    - h_ctl_roll_rate_gain * d_err
    - h_ctl_roll_igain * h_ctl_roll_sum_err
    + v_ctl_throttle_setpoint * h_ctl_aileron_of_throttle;
//  float cmd = h_ctl_roll_Kffa * h_ctl_ref_roll_accel
//    + h_ctl_roll_Kffd * h_ctl_ref_roll_rate
//    - h_ctl_roll_attitude_gain * err
//    - h_ctl_roll_rate_gain * d_err
//    - h_ctl_roll_igain * h_ctl_roll_sum_err
//    + v_ctl_throttle_setpoint * h_ctl_aileron_of_throttle;
//
  cmd /= airspeed_ratio2;

  // Set aileron commands
  h_ctl_aileron_setpoint = TRIM_PPRZ(cmd);
}
Пример #23
0
void v_ctl_climb_loop ( void )
{
  // Integrators
  static float v_ctl_throttle_trim = 0;
  static float v_ctl_pitch_trim = 0;

  static float v_ctl_last_e_tot = 0;
  static float v_ctl_last_e_dis = 0;

  // Local
  float v_ctl_vz_error = 0.;
  float v_ctl_gamma_error = 0.;
  float throttle = 0.;

  // Energy: Potential and Kinetic
  float v_ctl_energy_error_pot = 0;
  float v_ctl_energy_error_kin = 0;

  // Energy: Total and distribution
  float v_ctl_energy_error_tot = 0;
  float v_ctl_energy_error_dis = 0;

  static float v_ctl_energy_rate_tot = 0;
  static float v_ctl_energy_rate_dis = 0;

  // Altitude Error
  v_ctl_altitude_error = v_ctl_altitude_setpoint - estimator_z;

  // Commanded Climb Rate
  v_ctl_climb_setpoint = v_ctl_altitude_pgain * v_ctl_altitude_error
    + v_ctl_altitude_pre_climb;
  BoundAbs(v_ctl_climb_setpoint, V_CTL_ALTITUDE_MAX_CLIMB);

  // Actual Climb Rate
  v_ctl_vz_error = v_ctl_climb_setpoint + estimator_z_dot;
  BoundAbs(v_ctl_vz_error, V_CTL_ALTITUDE_MAX_CLIMB);

  // Commanded Flight Path
  v_ctl_gamma_error = atan2(v_ctl_vz_error, estimator_hspeed_mod);
  BoundAbs(v_ctl_gamma_error, 0.5);

  // Massless Energy
  v_ctl_energy_error_pot = (v_ctl_altitude_error * 9.81) / 1000.;
  v_ctl_energy_error_kin = (v_ctl_auto_airspeed_setpoint * v_ctl_auto_airspeed_setpoint/2.
                         - estimator_hspeed_mod * estimator_hspeed_mod/2.) / 1000.;

  BoundAbs( v_ctl_energy_error_pot, v_ctl_pot_energy_bounds * 9.81);
  BoundAbs( v_ctl_energy_error_kin, v_ctl_kin_energy_bounds * v_ctl_kin_energy_bounds / 2.);

  // Total en Distribution
  v_ctl_energy_error_tot = v_ctl_energy_error_pot + v_ctl_energy_error_kin;
  v_ctl_energy_error_dis = v_ctl_energy_error_pot - v_ctl_energy_error_kin;

  // Energy Rate
  v_ctl_energy_rate_tot = ((v_ctl_energy_error_tot - v_ctl_last_e_tot) - v_ctl_energy_rate_tot) / 5.;
  v_ctl_last_e_tot = v_ctl_energy_error_tot;
  v_ctl_energy_rate_dis = ((v_ctl_energy_error_dis - v_ctl_last_e_dis) - v_ctl_energy_rate_dis) / 5.;
  v_ctl_last_e_dis = v_ctl_energy_error_dis;

  // Actual Control
  // integrators
  v_ctl_pitch_trim += -v_ctl_energy_error_kin * v_ctl_energy_dis_i;
  if (v_ctl_pitch_trim >= v_ctl_pitch_max)
  {
    v_ctl_pitch_trim = v_ctl_pitch_max;
  }
  else if (v_ctl_pitch_trim < v_ctl_pitch_min)
  {
    v_ctl_pitch_trim = v_ctl_pitch_min;
  }

  // proportional
  throttle  = v_ctl_throttle_trim + v_ctl_energy_error_tot * v_ctl_energy_tot_p
                                  - v_ctl_energy_rate_tot * v_ctl_energy_tot_d;
  nav_pitch = v_ctl_pitch_trim    + v_ctl_energy_error_dis * v_ctl_energy_dis_p
                                  - v_ctl_energy_rate_dis * v_ctl_energy_dis_d;

  if (nav_pitch >= v_ctl_pitch_max)
  {
    nav_pitch = v_ctl_pitch_max;
  }
  else if (nav_pitch < v_ctl_pitch_min)
  {
    nav_pitch = v_ctl_pitch_min;
  }

  if (throttle >= v_ctl_throttle_max)
  {
    throttle = v_ctl_throttle_max;
  }
  else if (throttle < v_ctl_throttle_min)
  {
    throttle = v_ctl_throttle_min;
  }
  else
  {
    v_ctl_throttle_trim += v_ctl_energy_error_tot * v_ctl_energy_tot_i;
    // too little energy and not too much speed
/*    if ((nav_pitch < H_CTL_PITCH_MAX_SETPOINT) && (v_ctl_energy_error_tot > 0))
    {
      v_ctl_throttle_trim += v_ctl_energy_error_tot * v_ctl_energy_tot_i;
    }
    else if ((nav_pitch > H_CTL_PITCH_MIN_SETPOINT) && (v_ctl_energy_error_tot < 0))
    {
      v_ctl_throttle_trim += v_ctl_energy_error_tot * v_ctl_energy_tot_i;
    }
*/
    if (v_ctl_throttle_trim >= v_ctl_throttle_max)
    {
      v_ctl_throttle_trim = v_ctl_throttle_max;
    }
    else if (v_ctl_throttle_trim < v_ctl_throttle_min)
    {
      v_ctl_throttle_trim = v_ctl_throttle_min;
    }
  }

  v_ctl_throttle_setpoint = TRIM_PPRZ(throttle * MAX_PPRZ);

  DOWNLINK_SEND_VERTICAL_ENERGY(DefaultChannel, &v_ctl_energy_error_tot, &v_ctl_throttle_trim, &v_ctl_energy_error_kin, &v_ctl_pitch_trim, &throttle, &nav_pitch, &v_ctl_auto_airspeed_setpoint);

}
inline static void h_ctl_roll_loop( void ) {

  static float cmd_fb = 0.;

#if USE_ANGLE_REF
  // Update reference setpoints for roll
  h_ctl_ref.roll_angle += h_ctl_ref.roll_rate * H_CTL_REF_DT;
  h_ctl_ref.roll_rate += h_ctl_ref.roll_accel * H_CTL_REF_DT;
  h_ctl_ref.roll_accel = H_CTL_REF_W_P*H_CTL_REF_W_P * (h_ctl_roll_setpoint - h_ctl_ref.roll_angle) - 2*H_CTL_REF_XI_P*H_CTL_REF_W_P * h_ctl_ref.roll_rate;
  // Saturation on references
  BoundAbs(h_ctl_ref.roll_accel, h_ctl_ref.max_p_dot);
  if (h_ctl_ref.roll_rate > h_ctl_ref.max_p) {
    h_ctl_ref.roll_rate = h_ctl_ref.max_p;
    if (h_ctl_ref.roll_accel > 0.) h_ctl_ref.roll_accel = 0.;
  }
  else if (h_ctl_ref.roll_rate < - h_ctl_ref.max_p) {
    h_ctl_ref.roll_rate = - h_ctl_ref.max_p;
    if (h_ctl_ref.roll_accel < 0.) h_ctl_ref.roll_accel = 0.;
  }
#else
  h_ctl_ref.roll_angle = h_ctl_roll_setpoint;
  h_ctl_ref.roll_rate = 0.;
  h_ctl_ref.roll_accel = 0.;
#endif

#ifdef USE_KFF_UPDATE
  // update Kff gains
  h_ctl_roll_Kffa += KFFA_UPDATE * h_ctl_ref.roll_accel * cmd_fb / (h_ctl_ref.max_p_dot*h_ctl_ref.max_p_dot);
  h_ctl_roll_Kffd += KFFD_UPDATE * h_ctl_ref.roll_rate  * cmd_fb / (h_ctl_ref.max_p*h_ctl_ref.max_p);
#ifdef SITL
  printf("%f %f %f\n", h_ctl_roll_Kffa, h_ctl_roll_Kffd, cmd_fb);
#endif
  h_ctl_roll_Kffa = Max(h_ctl_roll_Kffa, 0);
  h_ctl_roll_Kffd = Max(h_ctl_roll_Kffd, 0);
#endif

  // Compute errors
  float err = h_ctl_ref.roll_angle - stateGetNedToBodyEulers_f()->phi;
  struct FloatRates* body_rate = stateGetBodyRates_f();
  float d_err = h_ctl_ref.roll_rate - body_rate->p;

  if (pprz_mode == PPRZ_MODE_MANUAL || launch == 0) {
    h_ctl_roll_sum_err = 0.;
  }
  else {
    if (h_ctl_roll_igain > 0.) {
      h_ctl_roll_sum_err += err * H_CTL_REF_DT;
      BoundAbs(h_ctl_roll_sum_err, H_CTL_ROLL_SUM_ERR_MAX / h_ctl_roll_igain);
    } else {
      h_ctl_roll_sum_err = 0.;
    }
  }

  cmd_fb = h_ctl_roll_attitude_gain * err;
  float cmd = - h_ctl_roll_Kffa * h_ctl_ref.roll_accel
    - h_ctl_roll_Kffd * h_ctl_ref.roll_rate
    - cmd_fb
    - h_ctl_roll_rate_gain * d_err
    - h_ctl_roll_igain * h_ctl_roll_sum_err
    + v_ctl_throttle_setpoint * h_ctl_aileron_of_throttle;

  cmd /= airspeed_ratio2;

  // Set aileron commands
  h_ctl_aileron_setpoint = TRIM_PPRZ(cmd);
}
Пример #25
0
/** \fn void navigation_task( void )
 *  \brief Compute desired_course
 */
static void navigation_task( void ) {
#if defined FAILSAFE_DELAY_WITHOUT_GPS
  /** This section is used for the failsafe of GPS */
  static uint8_t last_pprz_mode;

  /** If aircraft is launched and is in autonomus mode, go into
      PPRZ_MODE_GPS_OUT_OF_ORDER mode (Failsafe) if we lost the GPS */
  if (launch) {
    if (GpsTimeoutError) {
      if (pprz_mode == PPRZ_MODE_AUTO2 || pprz_mode == PPRZ_MODE_HOME) {
    last_pprz_mode = pprz_mode;
    pprz_mode = PPRZ_MODE_GPS_OUT_OF_ORDER;
    PERIODIC_SEND_PPRZ_MODE(DefaultChannel);
    gps_lost = TRUE;
      }
    } else /* GPS is ok */ if (gps_lost) {
      /** If aircraft was in failsafe mode, come back in previous mode */
      pprz_mode = last_pprz_mode;
      gps_lost = FALSE;

      PERIODIC_SEND_PPRZ_MODE(DefaultChannel);
    }
  }
#endif /* GPS && FAILSAFE_DELAY_WITHOUT_GPS */

  common_nav_periodic_task_4Hz();
  if (pprz_mode == PPRZ_MODE_HOME)
    nav_home();
  else if (pprz_mode == PPRZ_MODE_GPS_OUT_OF_ORDER)
    nav_without_gps();
  else
    nav_periodic_task();

#ifdef TCAS
  CallTCAS();
#endif

#ifndef PERIOD_NAVIGATION_DefaultChannel_0 // If not sent periodically (in default 0 mode)
  SEND_NAVIGATION(DefaultChannel);
#endif

  SEND_CAM(DefaultChannel);

  /* The nav task computes only nav_altitude. However, we are interested
     by desired_altitude (= nav_alt+alt_shift) in any case.
     So we always run the altitude control loop */
  if (v_ctl_mode == V_CTL_MODE_AUTO_ALT)
    v_ctl_altitude_loop();

  if (pprz_mode == PPRZ_MODE_AUTO2 || pprz_mode == PPRZ_MODE_HOME
            || pprz_mode == PPRZ_MODE_GPS_OUT_OF_ORDER) {
#ifdef H_CTL_RATE_LOOP
    /* Be sure to be in attitude mode, not roll */
    h_ctl_auto1_rate = FALSE;
#endif
    if (lateral_mode >=LATERAL_MODE_COURSE)
      h_ctl_course_loop(); /* aka compute nav_desired_roll */
    if (v_ctl_mode >= V_CTL_MODE_AUTO_CLIMB)
      v_ctl_climb_loop();
    if (v_ctl_mode == V_CTL_MODE_AUTO_THROTTLE)
      v_ctl_throttle_setpoint = nav_throttle_setpoint;

#if defined V_CTL_THROTTLE_IDLE
    Bound(v_ctl_throttle_setpoint, TRIM_PPRZ(V_CTL_THROTTLE_IDLE*MAX_PPRZ), MAX_PPRZ);
#endif

#ifdef V_CTL_POWER_CTL_BAT_NOMINAL
    if (vsupply > 0.) {
      v_ctl_throttle_setpoint *= 10. * V_CTL_POWER_CTL_BAT_NOMINAL / (float)vsupply;
      v_ctl_throttle_setpoint = TRIM_UPPRZ(v_ctl_throttle_setpoint);
    }
#endif

    h_ctl_pitch_setpoint = nav_pitch;
    Bound(h_ctl_pitch_setpoint, H_CTL_PITCH_MIN_SETPOINT, H_CTL_PITCH_MAX_SETPOINT);
    if (kill_throttle || (!estimator_flight_time && !launch))
      v_ctl_throttle_setpoint = 0;
  }
  energy += ((float)current) / 3600.0f * 0.25f;	// mAh = mA * dt (4Hz -> hours)
}
inline static void h_ctl_pitch_loop( void ) {
#if !USE_GYRO_PITCH_RATE
  static float last_err;
#endif

  /* sanity check */
  if (h_ctl_pitch_of_roll <0.)
    h_ctl_pitch_of_roll = 0.;

  h_ctl_pitch_loop_setpoint = h_ctl_pitch_setpoint + h_ctl_pitch_of_roll * fabs(stateGetNedToBodyEulers_f()->phi);
#if USE_PITCH_TRIM
  loiter();
#endif

#if USE_ANGLE_REF
  // Update reference setpoints for pitch
  h_ctl_ref.pitch_angle += h_ctl_ref.pitch_rate * H_CTL_REF_DT;
  h_ctl_ref.pitch_rate += h_ctl_ref.pitch_accel * H_CTL_REF_DT;
  h_ctl_ref.pitch_accel = H_CTL_REF_W_Q*H_CTL_REF_W_Q * (h_ctl_pitch_loop_setpoint - h_ctl_ref.pitch_angle) - 2*H_CTL_REF_XI_Q*H_CTL_REF_W_Q * h_ctl_ref.pitch_rate;
  // Saturation on references
  BoundAbs(h_ctl_ref.pitch_accel, h_ctl_ref.max_q_dot);
  if (h_ctl_ref.pitch_rate > h_ctl_ref.max_q) {
    h_ctl_ref.pitch_rate = h_ctl_ref.max_q;
    if (h_ctl_ref.pitch_accel > 0.) h_ctl_ref.pitch_accel = 0.;
  }
  else if (h_ctl_ref.pitch_rate < - h_ctl_ref.max_q) {
    h_ctl_ref.pitch_rate = - h_ctl_ref.max_q;
    if (h_ctl_ref.pitch_accel < 0.) h_ctl_ref.pitch_accel = 0.;
  }
#else
  h_ctl_ref.pitch_angle = h_ctl_pitch_loop_setpoint;
  h_ctl_ref.pitch_rate = 0.;
  h_ctl_ref.pitch_accel = 0.;
#endif

  // Compute errors
  float err =  h_ctl_ref.pitch_angle - stateGetNedToBodyEulers_f()->theta;
#if USE_GYRO_PITCH_RATE
  float d_err = h_ctl_ref.pitch_rate - stateGetBodyRates_f()->q;
#else // soft derivation
  float d_err = (err - last_err)/H_CTL_REF_DT - h_ctl_ref.pitch_rate;
  last_err = err;
#endif

  if (pprz_mode == PPRZ_MODE_MANUAL || launch == 0) {
    h_ctl_pitch_sum_err = 0.;
  }
  else {
    if (h_ctl_pitch_igain > 0.) {
      h_ctl_pitch_sum_err += err * H_CTL_REF_DT;
      BoundAbs(h_ctl_pitch_sum_err, H_CTL_PITCH_SUM_ERR_MAX / h_ctl_pitch_igain);
    } else {
      h_ctl_pitch_sum_err = 0.;
    }
  }

  float cmd = - h_ctl_pitch_Kffa * h_ctl_ref.pitch_accel
    - h_ctl_pitch_Kffd * h_ctl_ref.pitch_rate
    + h_ctl_pitch_pgain * err
    + h_ctl_pitch_dgain * d_err
    + h_ctl_pitch_igain * h_ctl_pitch_sum_err;

  cmd /= airspeed_ratio2;

  h_ctl_elevator_setpoint = TRIM_PPRZ(cmd);
}