Compare commits

...

6 Commits

Author SHA1 Message Date
Ramon Roche 61904bc14a ci: fix S3 upload so tags don't overwrite stable firmware
Remove the step that uploaded every version tag to the stable/ S3
directory, which caused QGC users selecting "stable" to receive
pre-release firmware (#26340). The stable/ and beta/ directories
are now controlled exclusively by their respective branch pushes,
while version tags only upload to their versioned archive directory
(e.g., v1.16.1/). Pre-release tags are also correctly marked on
GitHub Releases.

Co-authored-by: Julian Oes <julian@oes.ch>

Fixes #26340

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-02-13 07:25:28 -08:00
Matthias Grob fd68d5d043 CI: replace all usage of addnab/docker-run-action
It's unmaintained and the docker version it uses is not supported anymore.
2026-02-13 06:35:09 -08:00
Nick 857b48d57d [1.17] Bugfix: Prevent Offboard to Position without RC (#26391) 2026-01-30 09:35:49 +01:00
AkaiEurus 618995da84 Fix VTOL stuck after back-transition in Mission Fast RTL 2026-01-22 13:55:38 +01:00
Nick 2a78d7dfd1 pwm: Add PWM center support to Wheel and Gimbal (#26211)
* Add Wheel and Gimbal support to PWM center

* Document Center feature for PWM Gimbal
2026-01-22 13:53:45 +01:00
Nick da89d5e939 PWM: Add servo center setting & asymetric deflection (#25897)
Add PWM_*_CENTERx for each servo.
Use a bilinear transform to map actuator_servos to PWM signals.

This solution only works for PWM based servos. Other types of servos are not affected.

* PWM: Add servo trim option

* PWM: Improve documentation of PWM trim feature

* PWM: cleaner clamping and docs typo

* update documentation & safety

* add migration formula

* rename param from trim to center

* docs with center instead of trim

* move clamping and reorder values

* improve documentation

* adress failing range check

* improve documentation

* CA: add event for setting CENTER with TRIM

Signed-off-by: Silvan <silvan@auterion.com>

---------

Signed-off-by: Silvan <silvan@auterion.com>
Co-authored-by: Silvan <silvan@auterion.com>
2026-01-22 13:53:45 +01:00
24 changed files with 364 additions and 138 deletions
+50 -27
View File
@@ -2,6 +2,37 @@
# - If you want to keep the tests running in GitHub Actions you need to uncomment the "runs-on: ubuntu-latest" lines
# and comment the "runs-on: [runs-on,runner=..." lines.
# - If you would like to duplicate this setup try setting up "RunsOn" on your own AWS account try https://runs-on.com
#
# ===================================================================================
# RELEASE UPLOAD LOGIC
# ===================================================================================
# This workflow handles building firmware and uploading to S3 + GitHub Releases.
#
# S3 Bucket Structure (s3://px4-travis/Firmware/):
# - master/ <- Latest main branch build (for QGC compatibility)
# - stable/ <- Latest stable release, controlled by 'stable' branch
# - beta/ <- Latest pre-release, controlled by 'beta' branch
# - vX.Y.Z/ <- Archived stable release
# - vX.Y.Z-beta1/ <- Archived pre-release
#
# Trigger Behavior:
# - Tag v1.16.1 -> Upload to: v1.16.1/ only (versioned archive)
# - Tag v1.17.0-beta1 -> Upload to: v1.17.0-beta1/ only (versioned archive)
# - Branch main -> Upload to: master/ (for QGC compatibility)
# - Branch stable -> Upload to: stable/ (QGC stable firmware)
# - Branch beta -> Upload to: beta/ (QGC beta firmware)
# - Branch release/** -> Build only, no S3 upload (CI validation)
# - Pull requests -> Build only, no S3 upload (CI validation)
#
# GitHub Releases:
# - All version tags create a draft GitHub Release
# - Pre-releases (alpha/beta/rc suffixes) are automatically marked as such
#
# IMPORTANT: Version tags do NOT upload to stable/ or beta/. Only the
# corresponding branch pushes control those directories. This prevents
# pre-release tags from accidentally overwriting stable firmware (#26340)
# and avoids race conditions between tag and branch builds.
# ===================================================================================
name: Build all targets
@@ -159,6 +190,13 @@ jobs:
path: ~/.ccache
key: ${{ steps.cc_restore.outputs.cache-primary-key }}
# ===========================================================================
# ARTIFACT UPLOAD JOB
# ===========================================================================
# Uploads build artifacts to S3 and creates GitHub Releases.
# Runs for version tags (v*), main, stable, and beta branch pushes.
# See header comments for full upload logic documentation.
# ===========================================================================
artifacts:
name: Upload Artifacts
# runs-on: ubuntu-latest
@@ -177,31 +215,31 @@ jobs:
- name: Choose Upload Location
id: upload-location
run: |
# Determine upload location based on branch or tag with the following considerations:
# Destination: AWS S3 bucket px4-travis in folder Firmware/
# - If branch is main -> upload to master/
# - Older versions of QGC are hardocded to look for master/
# - If branch is stable or beta -> upload to stable/ or beta/
# - If a tag vX.Y.Z -> upload to vX.Y.Z/
# - Also update stable/ to point to the same version
#. - Older versions of QGC are hardocded to look for stable/
# - If a pull request -> do not upload
set -euo pipefail
ref="${GITHUB_REF}"
branch=${{ needs.group_targets.outputs.branchname }}
location="$branch"
is_prerelease="false"
# Main branch uploads to "master" for QGC backward compatibility
if [[ "$branch" == "main" ]]; then
location="master"
fi
# Version tags: upload to versioned directory (e.g., v1.16.1/)
if [[ "$ref" == refs/tags/v[0-9]* ]]; then
tag="${ref#refs/tags/}"
location="$tag"
# Pre-release tags contain -alpha, -beta, or -rc suffix
if [[ "$tag" =~ -(alpha|beta|rc) ]]; then
is_prerelease="true"
fi
fi
echo "uploadlocation=$location" >> $GITHUB_OUTPUT
echo "is_prerelease=$is_prerelease" >> $GITHUB_OUTPUT
- name: Uploading Artifacts to S3 [${{ steps.upload-location.outputs.uploadlocation }}]
uses: jakejarvis/s3-sync-action@master
@@ -215,28 +253,13 @@ jobs:
SOURCE_DIR: artifacts/
DEST_DIR: Firmware/${{ steps.upload-location.outputs.uploadlocation }}/
# if we are uploading artifacts to a versioned folder
# we should also update the stable folder in the s3 bucket
- name: Uploading Artifacts to S3 [stable]
uses: jakejarvis/s3-sync-action@master
if: startsWith(github.ref, 'refs/tags/v')
with:
args: --acl public-read
env:
AWS_S3_BUCKET: 'px4-travis'
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'us-west-1'
SOURCE_DIR: artifacts/
DEST_DIR: Firmware/stable/
# if build is a release triggered by a versioned tag then create a github release
# and upload the build artifacts. A draft release is created so that the release
# can be reviewed before publishing
# Create a draft GitHub Release for all version tags
# Pre-releases are automatically marked as such
- name: Upload Artifacts to GitHub Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/v')
with:
draft: true
prerelease: ${{ steps.upload-location.outputs.is_prerelease == 'true' }}
files: artifacts/*.px4
name: ${{ steps.upload-location.outputs.uploadlocation }}
+9 -8
View File
@@ -19,6 +19,10 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
container:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
strategy:
fail-fast: false
matrix:
@@ -35,20 +39,17 @@ jobs:
"px4_sitl_allyes",
"module_documentation",
]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Building [${{ matrix.check }}]
uses: addnab/docker-run-action@v3
with:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
options: -v ${{ github.workspace }}:/workspace
run: |
cd /workspace
git config --global --add safe.directory /workspace
make ${{ matrix.check }}
run: |
cd "$GITHUB_WORKSPACE"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
make ${{ matrix.check }}
- name: Uploading Coverage to Codecov.io
if: contains(matrix.check, 'coverage')
@@ -15,21 +15,21 @@ concurrency:
jobs:
unit_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: main test
uses: addnab/docker-run-action@v3
with:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
options: -v ${{ github.workspace }}:/workspace
container:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: main test
run: |
cd /workspace
git config --global --add safe.directory /workspace
cd "$GITHUB_WORKSPACE"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
make tests TESTFILTER=EKF
- name: Check if there is a functional change
run: git diff --exit-code
working-directory: src/modules/ekf2/test/change_indication
- name: Check if there is a functional change
run: git diff --exit-code
working-directory: src/modules/ekf2/test/change_indication
@@ -8,40 +8,47 @@ on:
jobs:
unit_tests:
runs-on: ubuntu-latest
container:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
env:
GIT_COMMITTER_EMAIL: bot@px4.io
GIT_COMMITTER_NAME: PX4BuildBot
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: main test
uses: addnab/docker-run-action@v3
with:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
options: -v ${{ github.workspace }}:/workspace
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: main test
run: |
cd /workspace
git config --global --add safe.directory /workspace
cd "$GITHUB_WORKSPACE"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
make tests TESTFILTER=EKF
- name: Check if there exists diff and save result in variable
id: diff-check
run: echo "CHANGE_INDICATED=$(git diff --exit-code --output=/dev/null || echo $?)" >> $GITHUB_OUTPUT
working-directory: src/modules/ekf2/test/change_indication
- name: Check if there exists diff and save result in variable
id: diff-check
working-directory: src/modules/ekf2/test/change_indication
run: |
if git diff --quiet; then
echo "CHANGE_INDICATED=false" >> $GITHUB_OUTPUT
else
echo "CHANGE_INDICATED=true" >> $GITHUB_OUTPUT
fi
- name: auto-commit any changes to change indication
uses: stefanzweifel/git-auto-commit-action@v4
with:
file_pattern: 'src/modules/ekf2/test/change_indication/*.csv'
commit_user_name: ${GIT_COMMITTER_NAME}
commit_user_email: ${GIT_COMMITTER_EMAIL}
commit_message: |
'[AUTO COMMIT] update change indication'
- name: auto-commit any changes to change indication
if: steps.diff-check.outputs.CHANGE_INDICATED == 'true'
uses: stefanzweifel/git-auto-commit-action@v4
with:
file_pattern: 'src/modules/ekf2/test/change_indication/*.csv'
commit_user_name: ${{ env.GIT_COMMITTER_NAME }}
commit_user_email: ${{ env.GIT_COMMITTER_EMAIL }}
commit_message: |
[AUTO COMMIT] update change indication
See .github/workflopws/ekf_update_change_indicator.yml for more details
See .github/workflows/ekf_update_change_indicator.yml for more details
- name: if there is a functional change, fail check
if: ${{ steps.diff-check.outputs.CHANGE_INDICATED }}
run: exit 1
- name: if there is a functional change, fail check
if: steps.diff-check.outputs.CHANGE_INDICATED == 'true'
run: exit 1
+18 -16
View File
@@ -19,25 +19,27 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
config:
- {vehicle: "iris", mission: "MC_mission_box"}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build SITL and Run Tests
uses: addnab/docker-run-action@v3
with:
image: px4io/px4-dev-ros-melodic:2021-09-08
options: -v ${{ github.workspace }}:/workspace
- name: Build SITL and Run Tests (inside old ROS container)
run: |
cd /workspace
git config --global --add safe.directory /workspace
make px4_sitl_default
make px4_sitl_default sitl_gazebo-classic
./test/rostest_px4_run.sh mavros_posix_test_mission.test mission:=${{matrix.config.mission}} vehicle:=${{matrix.config.vehicle}}
docker run --rm \
-v "${GITHUB_WORKSPACE}:/workspace" \
-w /workspace \
px4io/px4-dev-ros-melodic:2021-09-08 \
bash -c '
git config --global --add safe.directory /workspace
make px4_sitl_default
make px4_sitl_default sitl_gazebo-classic
./test/rostest_px4_run.sh \
mavros_posix_test_mission.test \
mission:=MC_mission_box \
vehicle:=iris
'
+17 -18
View File
@@ -19,27 +19,26 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
strategy:
fail-fast: false
matrix:
config:
- {test_file: "mavros_posix_tests_offboard_posctl.test", vehicle: "iris"}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build PX4 and Run Tests
uses: addnab/docker-run-action@v3
with:
image: px4io/px4-dev-ros-melodic:2021-09-08
options: -v ${{ github.workspace }}:/workspace
- name: Build SITL and Run Tests (inside old ROS container)
run: |
cd /workspace
git config --global --add safe.directory /workspace
make px4_sitl_default
make px4_sitl_default sitl_gazebo-classic
./test/rostest_px4_run.sh ${{matrix.config.test_file}} vehicle:=${{matrix.config.vehicle}}
docker run --rm \
-v "${GITHUB_WORKSPACE}:/workspace" \
-w /workspace \
px4io/px4-dev-ros-melodic:2021-09-08 \
bash -c '
git config --global --add safe.directory /workspace
make px4_sitl_default
make px4_sitl_default sitl_gazebo-classic
./test/rostest_px4_run.sh \
mavros_posix_tests_offboard_posctl.test \
vehicle:=iris
'
+15 -14
View File
@@ -19,27 +19,28 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
container:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
strategy:
matrix:
config: [
px4_fmu-v5_default,
]
config:
- px4_fmu-v5_default
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build PX4 and Run Test [${{ matrix.config }}]
uses: addnab/docker-run-action@v3
with:
image: px4io/px4-dev:v1.16.0-rc1-258-g0369abd556
options: -v ${{ github.workspace }}:/workspace
- name: Build PX4 and Run Test [${{ matrix.config }}]
run: |
cd /workspace
git config --global --add safe.directory /workspace
export PX4_EXTRA_NUTTX_CONFIG="CONFIG_NSH_LOGIN_PASSWORD=\"test\";CONFIG_NSH_CONSOLE_LOGIN=y"
cd "$GITHUB_WORKSPACE"
git config --global --add safe.directory "$GITHUB_WORKSPACE"
export PX4_EXTRA_NUTTX_CONFIG='CONFIG_NSH_LOGIN_PASSWORD="test";CONFIG_NSH_CONSOLE_LOGIN=y'
echo "PX4_EXTRA_NUTTX_CONFIG: $PX4_EXTRA_NUTTX_CONFIG"
make ${{ matrix.config }} nuttx_context
echo "Check that the config option is set"
grep CONFIG_NSH_LOGIN_PASSWORD build/${{ matrix.config }}/NuttX/nuttx/.config
@@ -238,6 +238,7 @@ def get_actuator_output(yaml_config, output_functions, timer_config_file, verbos
( 'disarmed', 'Disarmed', 'DIS', False ),
( 'min', 'Minimum', 'MIN', False ),
( 'max', 'Maximum', 'MAX', False ),
( 'center', 'Center\n(for Servos)', 'CENT', False ),
( 'failsafe', 'Failsafe', 'FAIL', True ),
]
for key, label, param_suffix, advanced in standard_params_array:
+8
View File
@@ -284,6 +284,9 @@ Note that non-motor outputs might already be active in prearm state if COM_PREAR
'''
minimum_description = \
'''Minimum output value (when not disarmed).
'''
center_description = \
'''Servo Center output value (when not disarmed).
'''
maximum_description = \
'''Maxmimum output value (when not disarmed).
@@ -296,6 +299,7 @@ When set to -1 (default), the value depends on the function (see {:}).
standard_params_array = [
( 'disarmed', 'Disarmed', 'DIS', disarmed_description ),
( 'min', 'Minimum', 'MIN', minimum_description ),
( 'center', 'Center', 'CENT', center_description ),
( 'max', 'Maximum', 'MAX', maximum_description ),
( 'failsafe', 'Failsafe', 'FAIL', failsafe_description ),
]
@@ -312,6 +316,10 @@ When set to -1 (default), the value depends on the function (see {:}).
standard_params[key]['default'] = -1
standard_params[key]['min'] = -1
if key == 'center':
standard_params[key]['default'] = -1
standard_params[key]['min'] = -1
param = {
'description': {
'short': channel_label+' ${i} '+label+' Value',
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+1 -1
View File
@@ -74,7 +74,7 @@ For example, you might have the following settings to assign the gimbal roll, pi
![Gimbal Actuator config](../../assets/config/actuators/qgc_actuators_gimbal.png)
The PWM values to use for the disarmed, maximum and minimum values can be determined in the same way as other servo, using the [Actuator Test sliders](../config/actuators.md#actuator-testing) to confirm that each slider moves the appropriate axis, and changing the values so that the gimbal is in the appropriate position at the disarmed, low and high position in the slider.
The PWM values to use for the disarmed, maximum, center and minimum values can be determined in the same way as other servo, using the [Actuator Test sliders](../config/actuators.md#actuator-testing) to confirm that each slider moves the appropriate axis, and changing the values so that the gimbal is in the appropriate position at the disarmed, low, center and high position in the slider.
The values may also be provided in gimbal documentation.
## Gimbal Control in Missions
+47 -6
View File
@@ -159,7 +159,7 @@ The fields are:
- `Yaw Torque`: Effectiveness of actuator around yaw axis (normalised: -1 to 1).
[Generally you should use the default actuator value](#actuator-roll-pitch-and-yaw-scaling).
- `Trim`: An offset added to the actuator so that it is centered without input.
This might be determined by trial and error.
This might be determined by trial and error. Prefer using the improved `PWM_CENT` instead: [PWM control surfaces](./actuators.md#pwm-control-surfaces-that-move-both-directions-about-a-neutral-point)
- <a id="slew_rate"></a>(Advanced) `Slew Rate`: Limits the minimum time in which the motor/servo signal is allowed to pass through its full output range, in seconds.
- The setting limits the rate of change of an actuator (if not specified then no rate limit is applied).
It is intended for actuators that may be damaged or cause flight disturbance if they move too fast — such as the tilting actuators on a tiltrotor VTOL vehicle, or fast moving flaps, respectively.
@@ -535,12 +535,40 @@ If you're using PWM servos, PWM50 is far more common.
If a high rate servo is _really_ needed, DShot offers better value.
:::
#### Control surfaces that move both directions about a neutral point
##### PWM: Control surfaces that move both directions about a neutral point
To facilitate setting the neutral point of the servos, a bilinear curve function can be defined using the following parameters `PWM_MAIM_CENTx` / `PWM_AUX_CENTx` for each servo. This allows for unequal deflections in the positive and negative direction:
![Asymetric Servo Deflections](../../assets/config/actuators/servo_pwm_center.png)
To set this up:
1. Set all surface `Trim` to `0.00` for all surfaces:
![PWM Trimming](../../assets/config/actuators/control_surface_trim.png)
1. Set the `PWM_MAIN_CENTx` / `PWM_AUX_CENTx` value so that the surface will stay at the neutral (aligned with airfoil) position.
This is usually around `1500` for PWM servos (near the center of the servo range).
![Control Surface Trimming](../../assets/config/actuators/pwm_center_output.png)
2. Gradualy increase the `Maximum` for each servo until the desired deflection is reached. Check the deflection with a remote manual mode while [`COM_PREARM_MODE`](../advanced_config/parameter_reference.md#COM_PREARM_MODE) is set to `Always` or use the sliders.
3. Gradualy decrease the `Minimum` for each servo, until the desired deflection is reached.
4. Set `Disarmed` value to the desired value. It is usually desirable to have it the same as the `Center` value.
::: info
If you want to retain the linear behaviour of the servo after setting the `Center`, make sure to adjust the `Minimum` or `Maximum`, such that both invervals (`min` to `cent` & `cent` to `max`) are equally lare.
![Linear PWM Adjustment](../../assets/config/actuators/servo_pwm_linear.png)
:::
#### Non-PWM: Control surfaces that move both directions about a neutral point
Control surfaces that move either direction around a neutral point include: ailerons, elevons, V-tails, A-tails, and rudders.
To set these up:
0. Set all `PWM_MAIN_CENTx` and `PWM_AUX_CENTx` to default (-1), or trimming will not be possible.
1. Set the `Disarmed` value so that the surfaces will stay at neutral position when disarmed.
This is usually around `1500` for PWM servos (near the centre of the servo range).
@@ -559,14 +587,20 @@ To set these up:
3. Move the slider again to the middle and check if the Control Surfaces are aligned in the neutral position of the wing.
- If it is not aligned, you can set the **Trim** value for the control surface.
::: info
This is done in the `Trim` setting of the Geometry panel, usually by "trial and error".
![Control Surface Trimming](../../assets/config/actuators/control_surface_trim.png)
:::
::: info
This is done in the `Trim` setting of the Geometry panel, usually by "trial and error".
![Control Surface Trimming](../../assets/config/actuators/control_surface_trim.png)
:::
- After setting the trim for a control surface, move its slider away from the centre, release, and then back into disarmed (middle) position.
Confirm that surface is in the neutral position.
::: tip
If any servo has a `PWM_MAIN_CENTx` or `PWM_AUX_CENTx` not set to default (-1), the system will automatically remove `Trim` from all surfaces. This is done to prevent mixing of old and new trimming tools.
:::
::: info
Another way to test without using the sliders would be to set the [`COM_PREARM_MODE`](../advanced_config/parameter_reference.md#COM_PREARM_MODE) parameter to `Always`:
@@ -575,6 +609,8 @@ Another way to test without using the sliders would be to set the [`COM_PREARM_M
:::
#### Control surfaces that move from neutral to full deflection
Control surfaces that move only one direction from neutral include: airbrakes, spoilers, and flaps.
@@ -585,6 +621,7 @@ For a flap, that is when the flap is fully retracted and flush with the wing.
One approach for setting these up is:
0. Set all `PWM_MAIN_CENTx` and `PWM_AUX_CENTx` to default (-1), or trimming will not be possible.
1. Set values `Disarmed` to `1500`, `Min` to `1200`, `Max` to `1700` so that the values are around the centre of the servo range.
2. Move the corresponding slider up and check the control moves and that it is extending (moving away from the disarmed position).
If not, click on the `Rev Range` checkbox to reverse the range.
@@ -594,6 +631,7 @@ One approach for setting these up is:
- If the value was increased towards `Max`, then set `Max` to match `Disarmed`.
4. The value that you did _not_ set to match `Disarmed` controls the maximum amount that the control surface can extend.
Set the slider to the top of the control, then change the value (`Max` or `Min`) so that the control surface is fully extended when the slider is at top.
5. (Only PWM servos) Set the `Center` value to the middle between `Min` and `Max`.
::: info Special note for flaps
In some vehicle builds, flaps may be configured such that both flaps are controlled from a single output.
@@ -631,6 +669,9 @@ For each of the tilt servos:
- Standard VTOL : Motors defined as multicopter motors will be turned off
- Tiltrotors : Motors that have no associated tilt servo will turn off
- Tailsitters do not turn off any motors in fixed-wing flight
- The following formula can be used to migrate from surface trim to PWM trim:
`PWM_MAIN_CENTx = ((PWM_MAX - PWM_MIN) / 2) * CA_SV_CSx_TRIM + PWM_MIN + ((PWM_MAX - PWM_MIN) / 2)`
### Reversing Motors
+1
View File
@@ -8,6 +8,7 @@ actuator_output:
disarmed: { min: 800, max: 2200, default: 1000 }
min: { min: 800, max: 1400, default: 1000 }
max: { min: 1600, max: 2200, default: 2000 }
center: { min: 800, max: 2200}
failsafe: { min: 800, max: 2200 }
extra_function_groups: [ pwm_fmu ]
pwm_timer_param:
+45 -4
View File
@@ -120,6 +120,8 @@ void MixingOutput::initParamHandles(const uint8_t instance_start)
_param_handles[i].disarmed = param_find(param_name);
snprintf(param_name, sizeof(param_name), "%s_%s%d", _param_prefix, "MIN", i + instance_start);
_param_handles[i].min = param_find(param_name);
snprintf(param_name, sizeof(param_name), "%s_%s%d", _param_prefix, "CENT", i + instance_start);
_param_handles[i].center = param_find(param_name);
snprintf(param_name, sizeof(param_name), "%s_%s%d", _param_prefix, "MAX", i + instance_start);
_param_handles[i].max = param_find(param_name);
snprintf(param_name, sizeof(param_name), "%s_%s%d", _param_prefix, "FAIL", i + instance_start);
@@ -142,9 +144,9 @@ void MixingOutput::printStatus() const
PX4_INFO_RAW("Channel Configuration:\n");
for (unsigned i = 0; i < _max_num_outputs; i++) {
PX4_INFO_RAW("Channel %i: func: %3i, value: %i, failsafe: %d, disarmed: %d, min: %d, max: %d\n", i,
PX4_INFO_RAW("Channel %i: func: %3i, value: %i, failsafe: %d, disarmed: %d, min: %d, max: %d, center: %d\n", i,
(int)_function_assignment[i], _current_output_value[i],
actualFailsafeValue(i), _disarmed_value[i], _min_value[i], _max_value[i]);
actualFailsafeValue(i), _disarmed_value[i], _min_value[i], _max_value[i], _center_value[i]);
}
}
@@ -173,6 +175,10 @@ void MixingOutput::updateParams()
_min_value[i] = val;
}
if (_param_handles[i].center != PARAM_INVALID && param_get(_param_handles[i].center, &val) == 0) {
_center_value[i] = val;
}
if (_param_handles[i].max != PARAM_INVALID && param_get(_param_handles[i].max, &val) == 0) {
_max_value[i] = val;
}
@@ -183,6 +189,7 @@ void MixingOutput::updateParams()
_max_value[i] = tmp;
}
if (_param_handles[i].failsafe != PARAM_INVALID && param_get(_param_handles[i].failsafe, &val) == 0) {
_failsafe_value[i] = val;
}
@@ -372,6 +379,14 @@ void MixingOutput::setAllMinValues(uint16_t value)
}
}
void MixingOutput::setAllCenterValues(uint16_t value)
{
for (unsigned i = 0; i < MAX_ACTUATORS; i++) {
_param_handles[i].center = PARAM_INVALID;
_center_value[i] = value;
}
}
void MixingOutput::setAllMaxValues(uint16_t value)
{
for (unsigned i = 0; i < MAX_ACTUATORS; i++) {
@@ -528,10 +543,36 @@ uint16_t MixingOutput::output_limit_calc_single(int i, float value) const
value = -1.f * value;
}
const float output = math::interpolate(value, -1.f, 1.f,
static_cast<float>(_min_value[i]), static_cast<float>(_max_value[i]));
float output = _disarmed_value[i];
if (((_function_assignment[i] >= OutputFunction::Servo1
&& _function_assignment[i] <= OutputFunction::ServoMax) || _function_assignment[i] == OutputFunction::Landing_Gear_Wheel
|| (_function_assignment[i] >= OutputFunction::Gimbal_Roll
&& _function_assignment[i] <= OutputFunction::Gimbal_Yaw))
&& _param_handles[i].center != PARAM_INVALID
&& _center_value[i] >= 800
&& _center_value[i] <= 2200) {
/* bi-linear interpolation */
if (value < 0.0f) {
output = math::interpolate(value, -1.f, 0.0f,
static_cast<float>(_min_value[i]), static_cast<float>(_center_value[i]));
} else {
output = math::interpolate(value, 0.0f, 1.0f,
static_cast<float>(_center_value[i]), static_cast<float>(_max_value[i]));
}
}
// Everything except servos, or if center is not set
else {
output = math::interpolate(value, -1.f, 1.f,
static_cast<float>(_min_value[i]), static_cast<float>(_max_value[i]));
}
return math::constrain(lroundf(output), 0L, static_cast<long>(UINT16_MAX));
}
void
+5
View File
@@ -167,16 +167,19 @@ public:
void setAllFailsafeValues(uint16_t value);
void setAllDisarmedValues(uint16_t value);
void setAllMinValues(uint16_t value);
void setAllCenterValues(uint16_t value);
void setAllMaxValues(uint16_t value);
/** Disarmed values: disarmedValue < minValue needs to hold */
uint16_t &disarmedValue(int index) { return _disarmed_value[index]; }
uint16_t &minValue(int index) { return _min_value[index]; }
uint16_t &centerValue(int index) { return _center_value[index]; }
uint16_t &maxValue(int index) { return _max_value[index]; }
param_t functionParamHandle(int index) const { return _param_handles[index].function; }
param_t disarmedParamHandle(int index) const { return _param_handles[index].disarmed; }
param_t minParamHandle(int index) const { return _param_handles[index].min; }
param_t centerParamHandle(int index) const { return _param_handles[index].center; }
param_t maxParamHandle(int index) const { return _param_handles[index].max; }
/**
@@ -228,6 +231,7 @@ private:
param_t function{PARAM_INVALID};
param_t disarmed{PARAM_INVALID};
param_t min{PARAM_INVALID};
param_t center{PARAM_INVALID};
param_t max{PARAM_INVALID};
param_t failsafe{PARAM_INVALID};
};
@@ -240,6 +244,7 @@ private:
uint16_t _failsafe_value[MAX_ACTUATORS] {};
uint16_t _disarmed_value[MAX_ACTUATORS] {};
uint16_t _min_value[MAX_ACTUATORS] {};
uint16_t _center_value[MAX_ACTUATORS] {};
uint16_t _max_value[MAX_ACTUATORS] {};
uint16_t _current_output_value[MAX_ACTUATORS] {}; ///< current output values (reordered)
uint16_t _reverse_output_mask{0}; ///< reverses the interval [min, max] -> [max, min], NOT motor direction
@@ -54,6 +54,7 @@ static constexpr int MAX_NUM_OUTPUTS = 8;
static constexpr int DISARMED_VALUE = 900;
static constexpr int FAILSAFE_VALUE = 800;
static constexpr int MIN_VALUE = 1000;
static constexpr int CENTER_VALUE = 1500;
static constexpr int MAX_VALUE = 2000;
class MixerModuleTest : public ::testing::Test
@@ -188,6 +189,7 @@ TEST_F(MixerModuleTest, basic)
mixing_output.setAllDisarmedValues(DISARMED_VALUE);
mixing_output.setAllFailsafeValues(FAILSAFE_VALUE);
mixing_output.setAllMinValues(MIN_VALUE);
mixing_output.setAllCenterValues(CENTER_VALUE);
mixing_output.setAllMaxValues(MAX_VALUE);
EXPECT_EQ(test_module.num_updates, 0);
@@ -281,6 +283,7 @@ TEST_F(MixerModuleTest, arming)
mixing_output.setAllDisarmedValues(DISARMED_VALUE);
mixing_output.setAllFailsafeValues(FAILSAFE_VALUE);
mixing_output.setAllMinValues(MIN_VALUE);
mixing_output.setAllCenterValues(CENTER_VALUE);
mixing_output.setAllMaxValues(MAX_VALUE);
test_module.sendMotors({1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f});
@@ -488,6 +491,7 @@ TEST_F(MixerModuleTest, OutputLimitCalcSingle)
mixing_output.setAllMinValues(MIN_VALUE); // default range [1000,2000]
mixing_output.setAllMaxValues(MAX_VALUE);
mixing_output.setAllCenterValues(CENTER_VALUE); // Set center to middle value
EXPECT_EQ(mixing_output.output_limit_calc_single(0, -1.f), 1000); // In range
EXPECT_EQ(mixing_output.output_limit_calc_single(0, -.5f), 1250);
EXPECT_EQ(mixing_output.output_limit_calc_single(0, 0.f), 1500);
@@ -503,6 +507,7 @@ TEST_F(MixerModuleTest, OutputLimitCalcSingle)
mixing_output.setAllMinValues(0); // lower range [0,20]
mixing_output.setAllMaxValues(20);
mixing_output.setAllCenterValues(10); // Set center to middle value
EXPECT_EQ(mixing_output.output_limit_calc_single(0, -1.f), 0); // In range
EXPECT_EQ(mixing_output.output_limit_calc_single(0, -.5f), 5);
EXPECT_EQ(mixing_output.output_limit_calc_single(0, 0.f), 10);
@@ -518,6 +523,7 @@ TEST_F(MixerModuleTest, OutputLimitCalcSingle)
mixing_output.setAllMinValues(20); // inverted range [20,0]
mixing_output.setAllMaxValues(0);
mixing_output.setAllCenterValues(10); // Set center to middle value
EXPECT_EQ(mixing_output.output_limit_calc_single(0, -1.f), 20); // In range
EXPECT_EQ(mixing_output.output_limit_calc_single(0, -.5f), 15);
EXPECT_EQ(mixing_output.output_limit_calc_single(0, 0.f), 10);
@@ -669,6 +669,15 @@ FailsafeBase::Action Failsafe::checkModeFallback(const failsafe_flags_s &status_
if (action == Action::Disarm) {
return action;
}
if (action == Action::FallbackPosCtrl || action == Action::FallbackAltCtrl || action == Action::FallbackStab) {
// Check if RC is available, if not use the mode specified in NAV_RCL_ACT
if (status_flags.manual_control_signal_lost) {
ActionOptions rc_loss_action = fromNavDllOrRclActParam(_param_nav_rcl_act.get());
action = rc_loss_action.action;
}
}
}
// PosCtrl/PositionSlow -> AltCtrl
@@ -32,6 +32,7 @@
****************************************************************************/
#include <px4_platform_common/log.h>
#include <px4_platform_common/events.h>
#include "ActuatorEffectivenessControlSurfaces.hpp"
@@ -74,6 +75,40 @@ void ActuatorEffectivenessControlSurfaces::updateParams()
return;
}
// Helper to check if a PWM center parameter is enabled, and clamp it to valid range
auto check_pwm_center = [](const char *prefix, int channel) -> bool {
char param_name[20];
snprintf(param_name, sizeof(param_name), "%s_CENT%d", prefix, channel);
param_t param = param_find(param_name);
if (param != PARAM_INVALID)
{
int32_t value;
if (param_get(param, &value) == PX4_OK && value != -1) {
// Clamp PWM center to valid range [800, 2200]
if (value < 800 || value > 2200) {
int32_t clamped = (value < 800) ? 800 : 2200;
PX4_WARN("%s_CENT%d (%d) out of range, clamping to %d", prefix, channel, (int)value, (int)clamped);
param_set(param, &clamped);
}
return true;
}
}
return false;
};
// Check if any PWM_MAIN or PWM_AUX center is configured
bool pwm_center_set = false;
for (int i = 1; i <= 8; i++) {
if (check_pwm_center("PWM_MAIN", i) || check_pwm_center("PWM_AUX", i)) {
pwm_center_set = true;
}
}
for (int i = 0; i < _count; i++) {
param_get(_param_handles[i].type, (int32_t *)&_params[i].type);
@@ -84,6 +119,20 @@ void ActuatorEffectivenessControlSurfaces::updateParams()
}
param_get(_param_handles[i].trim, &_params[i].trim);
// If PWM center is set and CA_SV_CS trim is non-zero, warn and reset to 0
if (pwm_center_set && fabsf(_params[i].trim) > FLT_EPSILON) {
/* EVENT
* @description Display warning in GCS when TRIM settings were present and now CENTER are set.
*/
events::send<uint8_t, float>(events::ID("control_surfaces_reset_trim"), events::Log::Warning,
"CA_SV_CS{1}_TRIM ({2}) is reset to 0 as PWM CENTER is used", i, _params[i].trim);
_params[i].trim = 0.0f;
// Update the parameter storage
param_set(_param_handles[i].trim, &_params[i].trim);
}
param_get(_param_handles[i].scale_flap, &_params[i].scale_flap);
param_get(_param_handles[i].scale_spoiler, &_params[i].scale_spoiler);
+5 -1
View File
@@ -312,7 +312,11 @@ parameters:
CA_SV_CS${i}_TRIM:
description:
short: Control Surface ${i} trim
long: Can be used to add an offset to the servo control.
long: |
Can be used to add an offset to the servo control.
NOTE: Do not use for PWM servos. Use the PWM CENTER parameters instead (e.g., PWM_MAIN_CENT, PWM_AUX_CENT) instead.
This parameter can only be set if all PWM Center parameters are set to default.
type: float
decimal: 2
min: -1.0
+8 -2
View File
@@ -169,8 +169,14 @@ void RtlMissionFast::setActiveMissionItems()
mission_item_to_position_setpoint(_mission_item, &pos_sp_triplet->current);
if (_vehicle_status_sub.get().vehicle_type == vehicle_status_s::VEHICLE_TYPE_FIXED_WING && isLanding() &&
_mission_item.nav_cmd == NAV_CMD_WAYPOINT) {
const bool fw_on_mission_landing = _vehicle_status_sub.get().vehicle_type == vehicle_status_s::VEHICLE_TYPE_FIXED_WING
&& isLanding() &&
_mission_item.nav_cmd == NAV_CMD_WAYPOINT;
const bool mc_landing_after_transition = _vehicle_status_sub.get().vehicle_type ==
vehicle_status_s::VEHICLE_TYPE_ROTARY_WING && _vehicle_status_sub.get().is_vtol &&
new_work_item_type == WorkItemType::WORK_ITEM_TYPE_MOVE_TO_LAND;
if (fw_on_mission_landing || mc_landing_after_transition) {
pos_sp_triplet->current.alt_acceptance_radius = FLT_MAX;
}
}
+22
View File
@@ -352,6 +352,28 @@ actuator_output:
# ui only shows the param if this condition is true
type: string
regex: *condition_regex
center:
type: dict
schema:
min:
# Minimum center value
type: integer
min: 0
max: 65536
max:
# Maximum center value
type: integer
min: 0
max: 65536
default:
# Default center value
type: integer
min: 0
max: 65536
show_if:
# ui only shows the param if this condition is true
type: string
regex: *condition_regex
failsafe:
type: dict
schema: