/** ****************************************************************************** * @addtogroup OpenPilotModules OpenPilot Modules * @{ * @addtogroup ActuatorModule Actuator Module * @brief Compute servo/motor settings based on @ref ActuatorDesired "desired actuator positions" and aircraft type. * This is where all the mixing of channels is computed. * @{ * * @file actuator.c * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. * @brief Actuator module. Drives the actuators (servos, motors etc). * * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "openpilot.h" #include "actuator.h" #include "actuatorsettings.h" #include "vtolsettings.h" #include "vtolstatus.h" #include "systemsettings.h" #include "actuatordesired.h" #include "actuatorcommand.h" #include "manualcontrolcommand.h" //yaw stabilisation for helis... #include "stabilizationsettings.h" #include "attitudeactual.h" //yaw stabilisation for helis... // Private constants #define MAX_QUEUE_SIZE 2 #define STACK_SIZE configMINIMAL_STACK_SIZE #define TASK_PRIORITY (tskIDLE_PRIORITY+4) #define FAILSAFE_TIMEOUT_MS 100 // Private types //yaw stabilisation for helis... //currently just coppied from simple stabilisation #define YAW_INTEGRAL_LIMIT 0.5 AttitudeActualData attitudeActual; StabilizationSettingsData stabSettings; float ccpmYawError, ccpmYawErrorLast; float ccpmYawDerivative; float ccpmYawIntegral, ccpmYawIntegralLimit; float desiredYaw; static float bound(float val, float min, float max); //yaw stabilisation for helis... // Private variables static xQueueHandle queue; static xTaskHandle taskHandle; // Private functions static void actuatorTask(void *parameters); static int32_t mixerFixedWing(const ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd); static int32_t mixerFixedWingElevon(const ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd); static int32_t mixerVTOL(const ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd); static int32_t InterpolateCCPMCurve(float *Output, float input, const float *Curve); static int32_t mixerCCPM(ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd); static int16_t scaleChannel(float value, int16_t max, int16_t min, int16_t neutral); static void setFailsafe(); static float bound(float val, float min, float max); /** * @brief Module initialization * @return 0 */ int32_t ActuatorInitialize() { // Create object queue queue = xQueueCreate(MAX_QUEUE_SIZE, sizeof(UAVObjEvent)); // Listen for ExampleObject1 updates ActuatorDesiredConnectQueue(queue); ccpmYawErrorLast = 0.0; // Start main task xTaskCreate(actuatorTask, (signed char *)"Actuator", STACK_SIZE, NULL, TASK_PRIORITY, &taskHandle); return 0; } /** * @brief Main module task */ static void actuatorTask(void *parameters) { // UAVObjEvent ev; ActuatorSettingsData settings; SystemSettingsData sysSettings; ActuatorDesiredData desired; ActuatorCommandData cmd; portTickType lastSysTime; // Set servo update frequency (done only on start-up) ActuatorSettingsGet(&settings); PIOS_Servo_SetHz(settings.ChannelUpdateFreq[0], settings.ChannelUpdateFreq[1]); // Go to the neutral (failsafe) values until an ActuatorDesired update is received setFailsafe(); // Main task loop lastSysTime = xTaskGetTickCount(); while (1) { // Wait until the ActuatorDesired object is updated, if a timeout then go to failsafe /*if ( xQueueReceive(queue, &ev, FAILSAFE_TIMEOUT_MS / portTICK_RATE_MS) != pdTRUE ) { setFailsafe(); continue; } */ // Read settings ActuatorSettingsGet(&settings); SystemSettingsGet(&sysSettings); // Reset ActuatorCommand to neutral values for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) { cmd.Channel[n] = settings.ChannelNeutral[n]; } // Read input object ActuatorDesiredGet(&desired); // Call appropriate mixer depending on the airframe configuration if (sysSettings.AirframeType == SYSTEMSETTINGS_AIRFRAMETYPE_FIXEDWING) { if (mixerFixedWing(&settings, &desired, &cmd) == -1) { AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL); } else { AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR); } } else if (sysSettings.AirframeType == SYSTEMSETTINGS_AIRFRAMETYPE_FIXEDWINGELEVON) { if (mixerFixedWingElevon(&settings, &desired, &cmd) == -1) { AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL); } else { AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR); } } else if (sysSettings.AirframeType == SYSTEMSETTINGS_AIRFRAMETYPE_VTOL) { if (mixerVTOL(&settings, &desired, &cmd) == -1) { AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL); } else { AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR); } } else if (sysSettings.AirframeType == SYSTEMSETTINGS_AIRFRAMETYPE_HELICP) { if (mixerCCPM(&settings, &desired, &cmd) == -1) { AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL); } else { AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR); } } // Update output object ActuatorCommandSet(&cmd); // Update in case read only (eg. during servo configuration) ActuatorCommandGet(&cmd); // Update servo outputs for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) { PIOS_Servo_Set(n, cmd.Channel[n]); } // Wait until next update vTaskDelayUntil(&lastSysTime, settings.UpdatePeriod / portTICK_RATE_MS); } } /** * Mixer for Fixed Wing airframes. Converts desired roll,pitch,yaw and throttle to servo outputs. * @return -1 if error, 0 if success */ static int32_t mixerFixedWing(const ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd) { // Check settings if (settings->FixedWingPitch1 == ACTUATORSETTINGS_FIXEDWINGPITCH1_NONE || settings->FixedWingRoll1 == ACTUATORSETTINGS_FIXEDWINGROLL1_NONE || settings->FixedWingThrottle == ACTUATORSETTINGS_FIXEDWINGTHROTTLE_NONE) { return -1; } ManualControlCommandData manualControl; ManualControlCommandGet(&manualControl); // Set pitch servo command cmd->Channel[settings->FixedWingPitch1] = scaleChannel(desired->Pitch, settings->ChannelMax[settings->FixedWingPitch1], settings->ChannelMin[settings->FixedWingPitch1], settings->ChannelNeutral[settings->FixedWingPitch1]); if (settings->FixedWingPitch2 != ACTUATORSETTINGS_FIXEDWINGPITCH2_NONE) { cmd->Channel[settings->FixedWingPitch2] = scaleChannel(desired->Pitch, settings->ChannelMax[settings->FixedWingPitch2], settings->ChannelMin[settings->FixedWingPitch2], settings->ChannelNeutral[settings->FixedWingPitch2]); } // Set roll servo command cmd->Channel[settings->FixedWingRoll1] = scaleChannel(desired->Roll, settings->ChannelMax[settings->FixedWingRoll1], settings->ChannelMin[settings->FixedWingRoll1], settings->ChannelNeutral[settings->FixedWingRoll1]); if (settings->FixedWingRoll2 != ACTUATORSETTINGS_FIXEDWINGROLL2_NONE) { cmd->Channel[settings->FixedWingRoll2] = scaleChannel(desired->Roll, settings->ChannelMax[settings->FixedWingRoll2], settings->ChannelMin[settings->FixedWingRoll2], settings->ChannelNeutral[settings->FixedWingRoll2]); } // Set yaw servo command if (settings->FixedWingYaw != ACTUATORSETTINGS_FIXEDWINGYAW_NONE) { cmd->Channel[settings->FixedWingYaw] = scaleChannel(desired->Yaw, settings->ChannelMax[settings->FixedWingYaw], settings->ChannelMin[settings->FixedWingYaw], settings->ChannelNeutral[settings->FixedWingYaw]); } // Set throttle servo command if (manualControl.Armed == MANUALCONTROLCOMMAND_ARMED_TRUE) cmd->Channel[settings->FixedWingThrottle] = scaleChannel(desired->Throttle, settings->ChannelMax[settings->FixedWingThrottle], settings->ChannelMin[settings->FixedWingThrottle], settings->ChannelNeutral[settings->FixedWingThrottle]); else cmd->Channel[settings->FixedWingThrottle] = -1; // Done return 0; } /** * Mixer for Fixed Wing airframes with elevons. Converts desired roll,pitch,yaw and throttle to servo outputs. * @return -1 if error, 0 if success */ static int32_t mixerFixedWingElevon(const ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd) { // Check settings if (settings->FixedWingRoll1 == ACTUATORSETTINGS_FIXEDWINGROLL1_NONE || settings->FixedWingRoll2 == ACTUATORSETTINGS_FIXEDWINGROLL2_NONE || settings->FixedWingThrottle == ACTUATORSETTINGS_FIXEDWINGTHROTTLE_NONE) { return -1; } ManualControlCommandData manualControl; ManualControlCommandGet(&manualControl); // Set first elevon servo command cmd->Channel[settings->FixedWingRoll1] = scaleChannel(desired->Pitch + desired->Roll, settings->ChannelMax[settings->FixedWingRoll1], settings->ChannelMin[settings->FixedWingRoll1], settings->ChannelNeutral[settings->FixedWingRoll1]); // Set second elevon servo command cmd->Channel[settings->FixedWingRoll2] = scaleChannel(desired->Pitch - desired->Roll, settings->ChannelMax[settings->FixedWingRoll2], settings->ChannelMin[settings->FixedWingRoll2], settings->ChannelNeutral[settings->FixedWingRoll2]); // Set throttle servo command if (manualControl.Armed == MANUALCONTROLCOMMAND_ARMED_TRUE) cmd->Channel[settings->FixedWingThrottle] = scaleChannel(desired->Throttle, settings->ChannelMax[settings->FixedWingThrottle], settings->ChannelMin[settings->FixedWingThrottle], settings->ChannelNeutral[settings->FixedWingThrottle]); else cmd->Channel[settings->FixedWingThrottle] = -1; // Done return 0; } /** * Mixer for VTOL (quads and octo copters). Converts desired roll,pitch,yaw and throttle to servo outputs. * * I will probably add settings to change the weighting for each control to each motor. This will allow more * flexible support for various motor topologies. For now hard coding in simple versions and lettings the * scaling capabilities fix the subtlties. * * Also, because of how the Throttle ranges from 0 to 1, the motors should too! * * @return -1 if error, 0 if success */ static int32_t mixerVTOL(const ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd) { VTOLSettingsData vtolSettings; VTOLStatusData vtolStatus; VTOLSettingsGet(&vtolSettings); VTOLStatusGet(&vtolStatus); ManualControlCommandData manualControl; ManualControlCommandGet(&manualControl); const int vtolMin = -1; int vtolMax = -1; if ((manualControl.Armed == MANUALCONTROLCOMMAND_ARMED_TRUE) && (desired->Throttle >= 0)) vtolMax = 1; else vtolMax = -1; if (((settings->VTOLMotorN != ACTUATORSETTINGS_VTOLMOTORN_NONE) + (settings->VTOLMotorNE != ACTUATORSETTINGS_VTOLMOTORS_NONE) + (settings->VTOLMotorE != ACTUATORSETTINGS_VTOLMOTORE_NONE) + (settings->VTOLMotorSE != ACTUATORSETTINGS_VTOLMOTORSE_NONE) + (settings->VTOLMotorS != ACTUATORSETTINGS_VTOLMOTORS_NONE) + (settings->VTOLMotorSW != ACTUATORSETTINGS_VTOLMOTORSW_NONE) + (settings->VTOLMotorW != ACTUATORSETTINGS_VTOLMOTORW_NONE) + (settings->VTOLMotorNW != ACTUATORSETTINGS_VTOLMOTORNW_NONE)) <= 2) { return -1; // can't fly with 2 or less engines (i believe) } if (settings->VTOLMotorN != ACTUATORSETTINGS_VTOLMOTORN_NONE) { vtolStatus.MotorN = desired->Throttle * vtolSettings.MotorN[VTOLSETTINGS_MOTORN_THROTTLE] + desired->Pitch * vtolSettings.MotorN[VTOLSETTINGS_MOTORN_PITCH] + desired->Roll * vtolSettings.MotorN[VTOLSETTINGS_MOTORN_ROLL] + desired->Yaw * vtolSettings.MotorN[VTOLSETTINGS_MOTORN_YAW]; cmd->Channel[settings->VTOLMotorN] = scaleChannel(bound(vtolStatus.MotorN, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorN], settings->ChannelMin[settings->VTOLMotorN], settings->ChannelNeutral[settings->VTOLMotorN]); } if (settings->VTOLMotorNE != ACTUATORSETTINGS_VTOLMOTORNE_NONE) { vtolStatus.MotorNE = desired->Throttle * vtolSettings.MotorNE[VTOLSETTINGS_MOTORNE_THROTTLE] + desired->Pitch * vtolSettings.MotorNE[VTOLSETTINGS_MOTORNE_PITCH] + desired->Roll * vtolSettings.MotorNE[VTOLSETTINGS_MOTORNE_ROLL] + desired->Yaw * vtolSettings.MotorNE[VTOLSETTINGS_MOTORNE_YAW]; cmd->Channel[settings->VTOLMotorNE] = scaleChannel(bound(vtolStatus.MotorNE, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorNE], settings->ChannelMin[settings->VTOLMotorNE], settings->ChannelNeutral[settings->VTOLMotorNE]); } if (settings->VTOLMotorE != ACTUATORSETTINGS_VTOLMOTORE_NONE) { vtolStatus.MotorE = desired->Throttle * vtolSettings.MotorE[VTOLSETTINGS_MOTORE_THROTTLE] + desired->Pitch * vtolSettings.MotorE[VTOLSETTINGS_MOTORE_PITCH] + desired->Roll * vtolSettings.MotorE[VTOLSETTINGS_MOTORE_ROLL] + desired->Yaw * vtolSettings.MotorE[VTOLSETTINGS_MOTORE_YAW]; cmd->Channel[settings->VTOLMotorE] = scaleChannel(bound(vtolStatus.MotorE, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorE], settings->ChannelMin[settings->VTOLMotorE], settings->ChannelNeutral[settings->VTOLMotorE]); } if (settings->VTOLMotorSE != ACTUATORSETTINGS_VTOLMOTORSE_NONE) { vtolStatus.MotorSE = desired->Throttle * vtolSettings.MotorSE[VTOLSETTINGS_MOTORSE_THROTTLE] + desired->Pitch * vtolSettings.MotorSE[VTOLSETTINGS_MOTORSE_PITCH] + desired->Roll * vtolSettings.MotorSE[VTOLSETTINGS_MOTORSE_ROLL] + desired->Yaw * vtolSettings.MotorSE[VTOLSETTINGS_MOTORSE_YAW]; cmd->Channel[settings->VTOLMotorSE] = scaleChannel(bound(vtolStatus.MotorSE, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorSE], settings->ChannelMin[settings->VTOLMotorSE], settings->ChannelNeutral[settings->VTOLMotorSE]); } if (settings->VTOLMotorS != ACTUATORSETTINGS_VTOLMOTORS_NONE) { vtolStatus.MotorS = desired->Throttle * vtolSettings.MotorS[VTOLSETTINGS_MOTORS_THROTTLE] + desired->Pitch * vtolSettings.MotorS[VTOLSETTINGS_MOTORS_PITCH] + desired->Roll * vtolSettings.MotorS[VTOLSETTINGS_MOTORS_ROLL] + desired->Yaw * vtolSettings.MotorS[VTOLSETTINGS_MOTORS_YAW]; cmd->Channel[settings->VTOLMotorS] = scaleChannel(bound(vtolStatus.MotorS, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorS], settings->ChannelMin[settings->VTOLMotorS], settings->ChannelNeutral[settings->VTOLMotorS]); } if (settings->VTOLMotorSW != ACTUATORSETTINGS_VTOLMOTORSW_NONE) { vtolStatus.MotorSW = bound(desired->Throttle * vtolSettings.MotorSW[VTOLSETTINGS_MOTORSW_THROTTLE] + desired->Pitch * vtolSettings.MotorSW[VTOLSETTINGS_MOTORSW_PITCH] + desired->Roll * vtolSettings.MotorSW[VTOLSETTINGS_MOTORSW_ROLL] + desired->Yaw * vtolSettings.MotorSW[VTOLSETTINGS_MOTORSW_YAW], vtolMin, vtolMax); cmd->Channel[settings->VTOLMotorSW] = scaleChannel(vtolStatus.MotorSW, settings->ChannelMax[settings->VTOLMotorSW], settings->ChannelMin[settings->VTOLMotorSW], settings->ChannelNeutral[settings->VTOLMotorSW]); } if (settings->VTOLMotorW != ACTUATORSETTINGS_VTOLMOTORW_NONE) { vtolStatus.MotorW = desired->Throttle * vtolSettings.MotorW[VTOLSETTINGS_MOTORW_THROTTLE] + desired->Pitch * vtolSettings.MotorW[VTOLSETTINGS_MOTORW_PITCH] + desired->Roll * vtolSettings.MotorW[VTOLSETTINGS_MOTORW_ROLL] + desired->Yaw * vtolSettings.MotorW[VTOLSETTINGS_MOTORW_YAW]; cmd->Channel[settings->VTOLMotorW] = scaleChannel(bound(vtolStatus.MotorW, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorW], settings->ChannelMin[settings->VTOLMotorW], settings->ChannelNeutral[settings->VTOLMotorW]); } if (settings->VTOLMotorNW != ACTUATORSETTINGS_VTOLMOTORNW_NONE) { vtolStatus.MotorNW = desired->Throttle * vtolSettings.MotorNW[VTOLSETTINGS_MOTORNW_THROTTLE] + desired->Pitch * vtolSettings.MotorNW[VTOLSETTINGS_MOTORNW_PITCH] + desired->Roll * vtolSettings.MotorNW[VTOLSETTINGS_MOTORNW_ROLL] + desired->Yaw * vtolSettings.MotorNW[VTOLSETTINGS_MOTORNW_YAW]; cmd->Channel[settings->VTOLMotorNW] = scaleChannel(bound(vtolStatus.MotorNW, vtolMin, vtolMax), settings->ChannelMax[settings->VTOLMotorNW], settings->ChannelMin[settings->VTOLMotorNW], settings->ChannelNeutral[settings->VTOLMotorNW]); } VTOLStatusSet(&vtolStatus); return 0; } static int32_t InterpolateCCPMCurve(float *output, float input, const float *curve) { int8_t curvIndex; float slope; float offset; float value; //determine which section of the 5 point curve we are on if (input <= .25) { curvIndex = 0; offset = 0; } else if (input <= .50) { curvIndex = 1; offset = 0.25; } else if (input <= .75) { curvIndex = 2; offset = 0.50; } else { curvIndex = 3; offset = 0.75; } //calculate the linear interpolation for the selected segment slope = (curve[curvIndex + 1] - curve[curvIndex]) / 0.25; value = curve[curvIndex] + ((input - offset) * slope); //bound the output to valid percentage values if (value > 100.0) value = 100.0; if (value < 0.0) value = 0.0; *output = value; return 0; } /** * Mixer for CCPM (single rotor helicopters). Converts desired roll,pitch,yaw and throttle to servo outputs. * @return -1 if error, 0 if success */ static int32_t mixerCCPM(ActuatorSettingsData * settings, const ActuatorDesiredData * desired, ActuatorCommandData * cmd) { #define degtorad 0.0174532925 //pi/180 // TODO: Implement CCPM mixers int configOK; float curveValue; float throttle; float bladePitch; float CCPMCyclicConstant; float servoW; float servoX; float servoY; float servoZ; configOK = 0; //calculate the throttle value based on the curve - scale to 0.0 -> +1.0 InterpolateCCPMCurve(&curveValue, desired->Throttle, settings->CCPMThrottleCurve); throttle = curveValue / 100.0; //calculate the Blade Pitch value based on the curve - scale to -1.0 -> +1.0 InterpolateCCPMCurve(&curveValue, desired->Throttle, settings->CCPMPitchCurve); bladePitch = (curveValue / 50.0) - 1.0; //calculate how much Cyclic to apply to the mixing CCPMCyclicConstant = 1 - settings->CCPMCollectiveConstant; // Set ServoW servo command if (settings->CCPMServoW != ACTUATORSETTINGS_CCPMSERVOW_NONE) { servoW = (CCPMCyclicConstant * ((sin((settings->CCPMAngleW + settings->CCPMCorrectionAngle) * degtorad) * desired->Roll) + (cos((settings->CCPMAngleW + settings->CCPMCorrectionAngle) * degtorad) * desired->Pitch))) + (settings->CCPMCollectiveConstant * bladePitch); cmd->Channel[settings->CCPMServoW] = scaleChannel(servoW, settings->ChannelMax[settings->CCPMServoW], settings->ChannelMin[settings->CCPMServoW], settings->ChannelNeutral[settings->CCPMServoW]); configOK++; } // Set ServoX servo command if (settings->CCPMServoX != ACTUATORSETTINGS_CCPMSERVOX_NONE) { servoX = (CCPMCyclicConstant * ((sin((settings->CCPMAngleX + settings->CCPMCorrectionAngle) * degtorad) * desired->Roll) + (cos((settings->CCPMAngleX + settings->CCPMCorrectionAngle) * degtorad) * desired->Pitch))) + (settings->CCPMCollectiveConstant * bladePitch); cmd->Channel[settings->CCPMServoX] = scaleChannel(servoX, settings->ChannelMax[settings->CCPMServoX], settings->ChannelMin[settings->CCPMServoX], settings->ChannelNeutral[settings->CCPMServoX]); configOK++; } // Set ServoY servo command if (settings->CCPMServoY != ACTUATORSETTINGS_CCPMSERVOY_NONE) { servoY = (CCPMCyclicConstant * ((sin((settings->CCPMAngleY + settings->CCPMCorrectionAngle) * degtorad) * desired->Roll) + (cos((settings->CCPMAngleY + settings->CCPMCorrectionAngle) * degtorad) * desired->Pitch))) + (settings->CCPMCollectiveConstant * bladePitch); cmd->Channel[settings->CCPMServoY] = scaleChannel(servoY, settings->ChannelMax[settings->CCPMServoY], settings->ChannelMin[settings->CCPMServoY], settings->ChannelNeutral[settings->CCPMServoY]); configOK++; } // Set ServoZ servo command if (settings->CCPMServoZ != ACTUATORSETTINGS_CCPMSERVOZ_NONE) { servoZ = (CCPMCyclicConstant * ((sin((settings->CCPMAngleZ + settings->CCPMCorrectionAngle) * degtorad) * desired->Roll) + (cos((settings->CCPMAngleZ + settings->CCPMCorrectionAngle) * degtorad) * desired->Pitch))) + (settings->CCPMCollectiveConstant * bladePitch); cmd->Channel[settings->CCPMServoZ] = scaleChannel(servoZ, settings->ChannelMax[settings->CCPMServoZ], settings->ChannelMin[settings->CCPMServoZ], settings->ChannelNeutral[settings->CCPMServoZ]); configOK++; } // Set throttle servo command if (settings->CCPMThrottle != ACTUATORSETTINGS_CCPMTHROTTLE_NONE) { cmd->Channel[settings->CCPMThrottle] = scaleChannel(throttle, settings->ChannelMax[settings->CCPMThrottle], settings->ChannelMin[settings->CCPMThrottle], settings->ChannelNeutral[settings->CCPMThrottle]); } else { configOK = 0; } // Set TailRotor servo command if (settings->CCPMYawStabilizationInManualMode == ACTUATORSETTINGS_CCPMYAWSTABILIZATIONINMANUALMODE_FALSE) { //update the servo as per the desired control mode (manual or stabalized) if (settings->CCPMTailRotor != ACTUATORSETTINGS_CCPMTAILROTOR_NONE) { cmd->Channel[settings->CCPMTailRotor] = scaleChannel(desired->Yaw, settings->ChannelMax[settings->CCPMTailRotor], settings->ChannelMin[settings->CCPMTailRotor], settings->ChannelNeutral[settings->CCPMTailRotor]); } else { configOK = 0; } ccpmYawErrorLast = 0.0; } else { //TODO need to implement the stabilization PID loop to provide heading hold gyro functionality in manual control mode //currently just coppied from simple stabilisation AttitudeActualGet(&attitudeActual); StabilizationSettingsGet(&stabSettings); if (desired->Yaw < 0) { desiredYaw = 360 + (desired->Yaw * 180.0); } else { desiredYaw = (desired->Yaw * 180.0); } // Yaw stabilization control loop ccpmYawError = desiredYaw - attitudeActual.Yaw; //this should make it take the quickest path to reach the desired yaw if (ccpmYawError > 180.0) ccpmYawError -= 360; if (ccpmYawError < -180.0) ccpmYawError += 360; ccpmYawDerivative = ccpmYawError - ccpmYawErrorLast; ccpmYawIntegralLimit = YAW_INTEGRAL_LIMIT / stabSettings.YawKi; ccpmYawIntegral = bound(ccpmYawIntegral + ccpmYawError * stabSettings.UpdatePeriod, -ccpmYawIntegralLimit, ccpmYawIntegralLimit); desiredYaw = stabSettings.YawKp * ccpmYawError + stabSettings.YawKi * ccpmYawIntegral + stabSettings.YawKd * ccpmYawDerivative;; desiredYaw = bound(desiredYaw, -1.0, 1.0); ccpmYawErrorLast = ccpmYawError; if (settings->CCPMTailRotor != ACTUATORSETTINGS_CCPMTAILROTOR_NONE) { cmd->Channel[settings->CCPMTailRotor] = scaleChannel(desiredYaw, settings->ChannelMax[settings->CCPMTailRotor], settings->ChannelMin[settings->CCPMTailRotor], settings->ChannelNeutral[settings->CCPMTailRotor]); } else { configOK = 0; } } if (configOK < 2) return -1; return 0; } /** * Convert channel from -1/+1 to servo pulse duration in microseconds */ static int16_t scaleChannel(float value, int16_t max, int16_t min, int16_t neutral) { int16_t valueScaled; // Scale if (value >= 0.0) { valueScaled = (int16_t) (value * ((float)(max - neutral))) + neutral; } else { valueScaled = (int16_t) (value * ((float)(neutral - min))) + neutral; } if (max > min) { if (valueScaled > max) valueScaled = max; if (valueScaled < min) valueScaled = min; } else { if (valueScaled < max) valueScaled = max; if (valueScaled > min) valueScaled = min; } return valueScaled; } /** * Set actuator output to the neutral values (failsafe) */ static void setFailsafe() { ActuatorSettingsData settings; ActuatorCommandData cmd; // Read settings ActuatorSettingsGet(&settings); // Reset ActuatorCommand to neutral values for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) { cmd.Channel[n] = settings.ChannelNeutral[n]; } // Set alarm AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL); // Update servo outputs for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) { PIOS_Servo_Set(n, cmd.Channel[n]); } // Update output object ActuatorCommandSet(&cmd); } /** * Bound input value between limits */ static float bound(float val, float min, float max) { if (val < min) { val = min; } else if (val > max) { val = max; } return val; } /** * @} * @} */