-
Notifications
You must be signed in to change notification settings - Fork 1
Add tailsitter heading alignment phase during forward transition (Pr yaw alignment) #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: airbound-develop
Are you sure you want to change the base?
Changes from all commits
b61029f
1206984
d6d469a
9bae2ea
b9be9bc
be8846e
46b98b4
439dcd3
b42218b
de2de56
6a0e179
5185062
cd141fd
717e0c5
8f5952c
57a78d7
93bb8d2
8d25f52
3642cad
07200d7
a81a5c6
990981f
305ebb6
7615a90
102f425
e30dab2
befdd50
aedaa74
ca70950
644d922
bdc4ae9
96dd024
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -903,6 +903,139 @@ void Tailsitter_Transition::update() | |||
| switch (transition_state) { | ||||
|
|
||||
| case TRANSITION_ANGLE_WAIT_FW: { | ||||
|
|
||||
| const uint32_t now_ = AP_HAL::millis(); | ||||
| // ----------------------------------------------------------- | ||||
| // 1. INITIALIZATION | ||||
| // ----------------------------------------------------------- | ||||
| quadplane.set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED); | ||||
|
|
||||
| // ----------------------------------------------------------- | ||||
| // 2. TAILSITTER HEADING ALIGNMENT & WAIT | ||||
| // ----------------------------------------------------------- | ||||
| if (quadplane.tailsitter.enabled()) { | ||||
|
|
||||
| // Constants | ||||
| const int32_t ALIGN_TOLERANCE_CD = 1000; // 10.0 degrees | ||||
| const float MAX_SPIN_RATE_DEG = 3.0f; // Max yaw rate allowed | ||||
| const uint32_t ALIGN_PHASE_LIMIT_MS = 10000; // 10s Total Timeout | ||||
| const uint32_t WAIT_DELAY_MS = 2000; // 2s Wait | ||||
|
|
||||
| // Static Variables (State Tracking) | ||||
| static uint32_t align_phase_start_ms = 0; | ||||
| static uint32_t alignment_done_ms = 0; | ||||
| static uint32_t last_run_ms = 0; | ||||
| static uint32_t last_log_ms = 0; | ||||
| // NEW: One-Shot Flag to prevent infinite loops | ||||
| static bool alignment_completed_for_this_flight = false; | ||||
|
|
||||
| // --- DETECT NEW FLIGHT/TRANSITION ENTRY --- | ||||
| // If this function hasn't run for >200ms, assume it's a new attempt. | ||||
| if (now_ - last_run_ms > 200) { | ||||
| align_phase_start_ms = now_; | ||||
| alignment_done_ms = 0; | ||||
| last_log_ms = 0; | ||||
| alignment_completed_for_this_flight = false; // Reset flag for new transition | ||||
| } | ||||
| last_run_ms = now_; | ||||
|
|
||||
| // Check Validity & Only run if we haven't finished aligning yet | ||||
| bool should_run_alignment = !alignment_completed_for_this_flight && | ||||
| (plane.control_mode == &plane.mode_auto || plane.control_mode == &plane.mode_guided) && | ||||
| (plane.nav_controller != nullptr); | ||||
|
|
||||
| if (should_run_alignment) { | ||||
| // Disable weathervane during alignment | ||||
| if (quadplane.weathervane != nullptr) { | ||||
| quadplane.weathervane->allow_weathervaning(false); | ||||
| } | ||||
|
|
||||
| int32_t target_bearing_cd = plane.nav_controller->target_bearing_cd(); | ||||
| int32_t current_yaw_cd = quadplane.ahrs.yaw_sensor; | ||||
| int32_t error_cd = wrap_180_cd(target_bearing_cd - current_yaw_cd); | ||||
| Vector3f gyro = quadplane.ahrs.get_gyro(); | ||||
| float yaw_rate_deg = degrees(gyro.z); | ||||
|
|
||||
| // LOGIC: Are we aligned right now? | ||||
| bool is_aligned = (abs(error_cd) <= ALIGN_TOLERANCE_CD) && (abs(yaw_rate_deg) <= MAX_SPIN_RATE_DEG); | ||||
|
|
||||
| // TIMER LOGIC | ||||
| if (is_aligned) { | ||||
| if (alignment_done_ms == 0) alignment_done_ms = now_; | ||||
| } else { | ||||
| alignment_done_ms = 0; // Reset if we drift out | ||||
| } | ||||
|
|
||||
| // EXIT CRITERIA | ||||
| bool wait_complete = (alignment_done_ms != 0) && (now_ - alignment_done_ms >= WAIT_DELAY_MS); | ||||
| bool timeout_expired = (now_ - align_phase_start_ms >= ALIGN_PHASE_LIMIT_MS); | ||||
|
|
||||
| // --- BLOCKING CONTROL LOOP --- | ||||
| // Run this ONLY if we are NOT done waiting AND haven't timed out | ||||
| if (!wait_complete && !timeout_expired) { | ||||
|
|
||||
| // Log (1Hz) | ||||
| if (now_ - last_log_ms > 1000) { | ||||
| if (is_aligned) { | ||||
| float remaining = (WAIT_DELAY_MS - (now_ - alignment_done_ms)) * 0.001f; | ||||
| gcs().send_text(MAV_SEVERITY_INFO, "Aligned. Waiting: %.1fs", (double)remaining); | ||||
| } else { | ||||
| gcs().send_text(MAV_SEVERITY_INFO, "Aligning: Err %.1f", error_cd * 0.01f); | ||||
| } | ||||
| last_log_ms = now_; | ||||
| } | ||||
| //Force zero climb rate (Altitude Hold) | ||||
| quadplane.pos_control->set_pos_target_z_from_climb_rate_cm(0.0f); | ||||
|
|
||||
| // Control | ||||
| quadplane.pos_control->update_z_controller(); | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are manually calling ardupilot/ArduPlane/quadplane.cpp Line 1040 in a095c6c
Here is a relevant PR from when the comment was added: ArduPilot#19286 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can try calling Will try this when I have time. |
||||
| quadplane.pos_control->update_xy_controller(); | ||||
| quadplane.attitude_control->input_euler_angle_roll_pitch_yaw( | ||||
| quadplane.pos_control->get_roll_cd(), | ||||
| quadplane.pos_control->get_pitch_cd(), | ||||
| (float)target_bearing_cd, | ||||
| true | ||||
| ); | ||||
|
|
||||
| quadplane.motors_output(); | ||||
| set_last_fw_pitch(); // Keep tracking pitch so we don't snap later | ||||
| return; // BLOCK TRANSITION | ||||
| } | ||||
|
|
||||
| // --- HANDOVER LOGIC (Runs ONCE when done) --- | ||||
|
|
||||
| if (timeout_expired) { | ||||
| gcs().send_text(MAV_SEVERITY_WARNING, "Align Timeout: Proceeding"); | ||||
| } else { | ||||
| gcs().send_text(MAV_SEVERITY_INFO, "Alignment Complete: Transitioning"); | ||||
| } | ||||
|
|
||||
| // 1. Mark as complete so we NEVER enter this 'if' block again for this flight | ||||
| alignment_completed_for_this_flight = true; | ||||
|
|
||||
| // 2. Reset Standard Transition Timer to now | ||||
| fw_transition_start_ms = now_; | ||||
|
|
||||
| // 3. Reset Integrators and Pitch Target | ||||
| quadplane.attitude_control->reset_rate_controller_I_terms(); | ||||
| plane.nav_pitch_cd = constrain_float(quadplane.ahrs.pitch_sensor, -8500, 8500); | ||||
| plane.nav_roll_cd = 0; | ||||
| set_last_fw_pitch(); | ||||
| // --- RE-ENABLE WEATHERVANE AFTER ALIGNMENT --- | ||||
| if (quadplane.weathervane != nullptr) { | ||||
| quadplane.weathervane->allow_weathervaning(true); | ||||
| } | ||||
| } | ||||
| else { | ||||
| // --- ENSURE WEATHERVANE IS ENABLED WHEN NOT ALIGNING --- | ||||
| if (quadplane.weathervane != nullptr) { | ||||
| quadplane.weathervane->allow_weathervaning(true); | ||||
| } | ||||
| } | ||||
| } | ||||
|
|
||||
| // Normal transition code continues here for non-tailsitters | ||||
| // or after tailsitter alignment completes... | ||||
| if (tailsitter.transition_fw_complete()) { | ||||
| transition_state = TRANSITION_DONE; | ||||
| if (plane.arming.is_armed_and_safety_off()) { | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -226,6 +226,12 @@ class AC_AttitudeControl { | |||||||||||||||||||||||||||
| // Return the angle between the target thrust vector and the current thrust vector. | ||||||||||||||||||||||||||||
| float get_att_error_angle_deg() const { return degrees(_thrust_error_angle); } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Return the body-to-NED error attitude used by the quadplane-specific attitude control input methods | ||||||||||||||||||||||||||||
| Quaternion get_attitude_error_quat() const { return _attitude_ang_error; } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Return the body-to-NED attitude used by the quadplane-specific attitude control input methods | ||||||||||||||||||||||||||||
| Quaternion get_attitude_body_quat() const { return _attitude_body; } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+229
to
+234
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: fd -t f "AC_AttitudeControl" | head -20Repository: AirboundInc/ardupilot Length of output: 734 🏁 Script executed: find . -name "*.h" -o -name "*.cpp" | xargs grep -l "AC_AttitudeControl" | grep -E "AC_AttitudeControl\.(h|cpp)$"Repository: AirboundInc/ardupilot Length of output: 170 🏁 Script executed: git ls-files | grep -i "attitude" | grep -E "\.(h|cpp)$"Repository: AirboundInc/ardupilot Length of output: 1220 🏁 Script executed: wc -l libraries/AC_AttitudeControl/AC_AttitudeControl.hRepository: AirboundInc/ardupilot Length of output: 118 🏁 Script executed: sed -n '220,240p' libraries/AC_AttitudeControl/AC_AttitudeControl.hRepository: AirboundInc/ardupilot Length of output: 1262 🏁 Script executed: sed -n '500,520p' libraries/AC_AttitudeControl/AC_AttitudeControl.hRepository: AirboundInc/ardupilot Length of output: 1017 🏁 Script executed: grep -n "AC_AttitudeControl::AC_AttitudeControl" libraries/AC_AttitudeControl/AC_AttitudeControl.cpp | head -5Repository: AirboundInc/ardupilot Length of output: 47 🏁 Script executed: grep -n "relax_attitude_controllers" libraries/AC_AttitudeControl/AC_AttitudeControl.cppRepository: AirboundInc/ardupilot Length of output: 122 🏁 Script executed: grep -n "attitude_controller_run_quat" libraries/AC_AttitudeControl/AC_AttitudeControl.cppRepository: AirboundInc/ardupilot Length of output: 561 🏁 Script executed: sed -n '168,190p' libraries/AC_AttitudeControl/AC_AttitudeControl.cppRepository: AirboundInc/ardupilot Length of output: 840 🏁 Script executed: sed -n '708,750p' libraries/AC_AttitudeControl/AC_AttitudeControl.cppRepository: AirboundInc/ardupilot Length of output: 2304 🏁 Script executed: grep -n "_attitude_body" libraries/AC_AttitudeControl/AC_AttitudeControl.cpp | head -20Repository: AirboundInc/ardupilot Length of output: 104 🏁 Script executed: sed -n '750,760p' libraries/AC_AttitudeControl/AC_AttitudeControl.cppRepository: AirboundInc/ardupilot Length of output: 515 🏁 Script executed: grep -n "get_attitude_body_quat\|get_attitude_target_quat\|get_attitude_error_quat" libraries/AC_AttitudeControl/AC_AttitudeControl.cppRepository: AirboundInc/ardupilot Length of output: 47 🏁 Script executed: grep -rn "get_attitude_body_quat" --include="*.cpp" --include="*.h" | head -20Repository: AirboundInc/ardupilot Length of output: 307 🏁 Script executed: grep -n "AC_AttitudeControl::" libraries/AC_AttitudeControl/AC_AttitudeControl.cpp | grep -E "(constructor|AC_AttitudeControl\()" | head -5Repository: AirboundInc/ardupilot Length of output: 47 🏁 Script executed: sed -n '1,100p' libraries/AC_AttitudeControl/AC_AttitudeControl.cpp | grep -A 20 "AC_AttitudeControl::"Repository: AirboundInc/ardupilot Length of output: 907 🏁 Script executed: cat libraries/AC_AttitudeControl/AC_AttitudeControl.h | grep -A 5 "class AC_AttitudeControl"Repository: AirboundInc/ardupilot Length of output: 570 🏁 Script executed: grep -n "Quaternion.*initialise\|Quaternion()" libraries/AC_AttitudeControl/AC_AttitudeControl.cpp | head -10Repository: AirboundInc/ardupilot Length of output: 47 🏁 Script executed: sed -n '1,50p' libraries/AC_AttitudeControl/AC_AttitudeControl.cpp | grep -A 30 "AC_AttitudeControl::" | head -40Repository: AirboundInc/ardupilot Length of output: 1296 🏁 Script executed: grep -n "AC_AttitudeControl.*{" libraries/AC_AttitudeControl/AC_AttitudeControl.h | head -5Repository: AirboundInc/ardupilot Length of output: 190 🏁 Script executed: sed -n '1,100p' libraries/AC_AttitudeControl/AC_AttitudeControl.h | grep -A 50 "AC_AttitudeControl("Repository: AirboundInc/ardupilot Length of output: 2361 Initialize The getter Add Suggested initialization void AC_AttitudeControl::relax_attitude_controllers()
{
// Initialize the attitude variables to the current attitude
_ahrs.get_quat_body_to_ned(_attitude_target);
_attitude_target.to_euler(_euler_angle_target);
_attitude_ang_error.initialise();
+ _attitude_body = _attitude_target;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| // Set x-axis angular velocity in centidegrees/s | ||||||||||||||||||||||||||||
| void rate_bf_roll_target(float rate_cds) { _ang_vel_body.x = radians(rate_cds * 0.01f); } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -498,6 +504,9 @@ class AC_AttitudeControl { | |||||||||||||||||||||||||||
| // This represents a quaternion attitude error in the body frame, used for inertial frame reset handling. | ||||||||||||||||||||||||||||
| Quaternion _attitude_ang_error; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // This represents a quaternion attitude in the body frame | ||||||||||||||||||||||||||||
| Quaternion _attitude_body; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // The angle between the target thrust vector and the current thrust vector. | ||||||||||||||||||||||||||||
| float _thrust_angle; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Start the alignment timeout only when alignment is actually active.
align_phase_start_msis set on entry regardless ofshould_run_alignment. If the vehicle entersTRANSITION_ANGLE_WAIT_FWin a non-AUTO/GUIDED mode and later switches into AUTO/GUIDED, the timeout can already be expired, causing alignment to be skipped immediately. Consider arming the timer when alignment becomes active (or clearing it while inactive).💡 One way to gate the timer
🤖 Prompt for AI Agents