[vtx] Add VTX driver with Tramp and SmartAudio support
Build all targets / Scan for Board Targets (push) Has been cancelled
Build all targets / Build [${{ matrix.runner }}][${{ matrix.group }}] (push) Has been cancelled
Build all targets / Upload Artifacts (push) Has been cancelled
Checks / build (NO_NINJA_BUILD=1 px4_fmu-v5_default) (push) Has been cancelled
Checks / build (NO_NINJA_BUILD=1 px4_sitl_default) (push) Has been cancelled
Checks / build (check_format) (push) Has been cancelled
Checks / build (check_newlines) (push) Has been cancelled
Checks / build (module_documentation) (push) Has been cancelled
Checks / build (px4_fmu-v2_default stack_check) (push) Has been cancelled
Checks / build (px4_sitl_allyes) (push) Has been cancelled
Checks / build (shellcheck_all) (push) Has been cancelled
Checks / build (tests) (push) Has been cancelled
Checks / build (tests_coverage) (push) Has been cancelled
Checks / build (validate_module_configs) (push) Has been cancelled
Clang Tidy / build (push) Has been cancelled
MacOS build / build (px4_fmu-v5_default) (push) Has been cancelled
MacOS build / build (px4_sitl) (push) Has been cancelled
Ubuntu environment build / Build and Test (ubuntu:22.04) (push) Has been cancelled
Ubuntu environment build / Build and Test (ubuntu:24.04) (push) Has been cancelled
Container build / Set Tags and Variables (push) Has been cancelled
Container build / Build Container (amd64) (push) Has been cancelled
Container build / Build Container (arm64) (push) Has been cancelled
Container build / Deploy To Registry (push) Has been cancelled
EKF Update Change Indicator / unit_tests (push) Has been cancelled
Failsafe Simulator Build / build (failsafe_web) (push) Has been cancelled
FLASH usage analysis / Analyzing px4_fmu-v5x (push) Has been cancelled
FLASH usage analysis / Analyzing px4_fmu-v6x (push) Has been cancelled
FLASH usage analysis / Publish Results (push) Has been cancelled
ITCM check / Checking nxp_mr-tropic (push) Has been cancelled
ITCM check / Checking nxp_tropic-community (push) Has been cancelled
ITCM check / Checking px4_fmu-v5x (push) Has been cancelled
ITCM check / Checking px4_fmu-v6xrt (push) Has been cancelled
MAVROS Mission Tests / build (map[mission:MC_mission_box vehicle:iris]) (push) Has been cancelled
MAVROS Offboard Tests / build (map[test_file:mavros_posix_tests_offboard_posctl.test vehicle:iris]) (push) Has been cancelled
Nuttx Target with extra env config / build (px4_fmu-v5_default) (push) Has been cancelled
Python CI Checks / build (push) Has been cancelled
ROS Integration Tests / build (push) Has been cancelled
ROS Translation Node Tests / Build and test (map[ros_version:humble ubuntu:jammy]) (push) Has been cancelled
ROS Translation Node Tests / Build and test (map[ros_version:jazzy ubuntu:noble]) (push) Has been cancelled
SITL Tests / Testing PX4 tailsitter (push) Has been cancelled
SITL Tests / Testing PX4 iris (push) Has been cancelled
SITL Tests / Testing PX4 standard_vtol (push) Has been cancelled
Sync ROS 2 messages to px4_msgs / sync_to_px4_msgs (push) Has been cancelled
Docs - Crowdin - Upload Guide sources (en) / upload-to-crowdin (push) Has been cancelled
Docs - Deploy PX4 User Guide to AWS / build (push) Has been cancelled
Docs - Deploy PX4 User Guide to AWS / deploy (push) Has been cancelled

This commit is contained in:
Niklas Hauser
2026-01-23 13:34:04 +01:00
committed by Niklas Hauser
parent fad4450d7d
commit c0c265cd1f
28 changed files with 3397 additions and 1 deletions
@@ -123,3 +123,9 @@ if(CONFIG_MODULES_TEMPERATURE_COMPENSATION)
rc.thermal_cal
)
endif()
if(CONFIG_DRIVERS_VTXTABLE)
px4_add_romfs_files(
rc.vtxtable
)
endif()
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
#
# VTX table loading script.
#
# NOTE: Script variables are declared/initialized/unset in the rcS script.
#
vtxtable load
+10
View File
@@ -612,6 +612,16 @@ else
fi
unset RC_LOGGING
#
# Start the VTX services.
#
set RC_VTXTABLE ${R}etc/init.d/rc.vtxtable
if [ -f ${RC_VTXTABLE} ]
then
. ${RC_VTXTABLE}
fi
unset RC_VTXTABLE
#
# Set additional parameters and env variables for selected AUTOSTART.
#
+3
View File
@@ -15,6 +15,8 @@ CONFIG_DRIVERS_DSHOT=y
CONFIG_DRIVERS_GNSS_SEPTENTRIO=y
CONFIG_DRIVERS_GPS=y
CONFIG_DRIVERS_PPS_CAPTURE=y
CONFIG_DRIVERS_VTX=y
CONFIG_VTX_CRSF_MSP_SUPPORT=y
CONFIG_DRIVERS_IMU_BOSCH_BMI088=y
CONFIG_BMI088_ACCELEROMETER_INT2=y
CONFIG_COMMON_LIGHT=y
@@ -92,3 +94,4 @@ CONFIG_SYSTEMCMDS_TOPIC_LISTENER=y
CONFIG_SYSTEMCMDS_UORB=y
CONFIG_SYSTEMCMDS_VER=y
CONFIG_SYSTEMCMDS_WORK_QUEUE=y
CONFIG_WQ_TTY_STACKSIZE=2500
@@ -160,7 +160,7 @@ CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_CMDPARMS=y
CONFIG_NSH_CROMFSETC=y
CONFIG_NSH_LINELEN=128
CONFIG_NSH_MAXARGUMENTS=15
CONFIG_NSH_MAXARGUMENTS=24
CONFIG_NSH_NESTDEPTH=8
CONFIG_NSH_QUOTE=y
CONFIG_NSH_ROMFSETC=y
+1
View File
@@ -350,6 +350,7 @@
- [FrSky Telemetry](peripherals/frsky_telemetry.md)
- [TBS Crossfire (CRSF) Telemetry](telemetry/crsf_telemetry.md)
- [Satellite Comms (Iridium/RockBlock)](advanced_features/satcom_roadblock.md)
- [Analog Video Transmitters](vtx/index.md)
- [Power Systems](power_systems/index.md)
- [Battery Estimation Tuning](config/battery.md)
+287
View File
@@ -0,0 +1,287 @@
# Analog Video Transmitters
Analog Video Transmitters (VTX) can be controlled by PX4 via a half-duplex UART connection implementing the SmartAudio v1, v2, and v2.1 and Tramp protocols.
The protocols allow writing and reading:
- device status.
- transmission frequency in MHz or via band and channel index.
- transmission power in dBm or mW.
- operation modes.
VTX settings are controlled by parameters and optionally via RC AUX channels or CRSF MSP commands.
The driver stores frequency and power tables that map band/channel indices to actual transmission values.
Configuration is device-specific and set up using the command line interface.
## Getting Started
Connect the SmartAudio or Tramp pin of the VTX to the TX pin of a free serial port on the flight controller.
Then set the following parameters:
- `VTX_SER_CFG`: Select the serial port used for VTX communication.
- `VTX_DEVICE`: Selects the VTX device (generic SmartAudio/Tramp or a specific device).
Note that since the VTX communication is half-duplex, you can, for example, use the single-pin Radio Controller port for the VTX and use a full duplex TELEM port for CRSF communication.
You should now be able to see the VTX device in the driver status:
```
nsh> vtx status
INFO [vtx] UART device: /dev/ttyS4
INFO [vtx] VTX table "UNINITIALIZED":
INFO [vtx] Power levels:
INFO [vtx] RC mapping: Disabled
INFO [vtx] Parameters:
INFO [vtx] band: 1
INFO [vtx] channel: 1
INFO [vtx] frequency: 0 MHz
INFO [vtx] power level: 1
INFO [vtx] power: 0 = 0 mW
INFO [vtx] pit mode: off
INFO [vtx] SmartAudio v2:
INFO [vtx] band: 1
INFO [vtx] channel: 1
INFO [vtx] frequency: 6110 MHz
INFO [vtx] power level: 1
INFO [vtx] power: 0 mW
INFO [vtx] pit mode: on
INFO [vtx] lock: unlocked
```
:::warning
Without a configured power table, power mappings are unknown and default to 0 mW.
Some VTX devices enter pit mode when power is set to 0, regardless of the `VTX_PIT_MODE` parameter.
:::
## VTX Table Configuration
The VTX table stores frequency and power mappings for your specific device.
The manufacturer usually provides this information in the form of a JSON file that can be translated into a [Betaflight CLI command set](https://www.betaflight.com/docs/development/VTX#vtx-table) that this driver implements for compatibility.
### Power Level Configuration
```
# Set table name ≤16 characters
vtxtable name "Peak THOR T67"
# Set the power values that are sent to the VTX for each power level index
# Note: SmartAudio v1 and v2 use index values!
vtxtable powervalues 0 1 2 3 4
# Note: SmartAudio v2.1 uses dBm values instead!
# vtxtable powervalues 14 23 27 30 35
# Note: Tramp uses mW values instead!
# vtxtable powervalues 25 200 500 1000 3000
# Set the corresponding power labels for each power level index ≤4 characters.
# These are used for status reporting.
vtxtable powerlabels 25 200 500 1W 3W
# Set number of power levels
vtxtable powerlevels 5
# Save configuration
vtxtable save
```
This will create a VTX table with 5 power levels.
```nsh> vtxtable status
INFO [vtxtable] VTX table "Peak THOR T67":
INFO [vtxtable] Power levels:
INFO [vtxtable] 1: 0 = 25
INFO [vtxtable] 2: 1 = 200
INFO [vtxtable] 3: 2 = 500
INFO [vtxtable] 4: 3 = 1W
INFO [vtxtable] 5: 4 = 3W
```
### Frequency Table Configuration
```
# Set the name of each band and the frequencies of each channel
vtxtable band 1 BAND_A A FACTORY 6110 6130 6150 6170 6190 6210 6230 6250
vtxtable band 2 BAND_B B FACTORY 6270 6290 6310 6330 6350 6370 6390 6410
vtxtable band 3 BAND_E E FACTORY 6430 6450 6470 6490 6510 6530 6550 6570
vtxtable band 4 BAND_F F FACTORY 6590 6610 6630 6650 6670 6690 6710 6730
vtxtable band 5 BAND_R R FACTORY 6750 6770 6790 6810 6830 6850 6870 6890
vtxtable band 6 BAND_P P FACTORY 6910 6930 6950 6970 6990 7010 7030 7050
vtxtable band 7 BAND_H H FACTORY 7070 7090 7110 7130 7150 7170 7190 7210
vtxtable band 8 BAND_U U FACTORY 6115 6265 6425 6585 6745 6905 7065 7185
# Set number of bands and channels
vtxtable bands 8
vtxtable channels 8
# Save configuration
vtxtable save
```
This will create a VTX table with 8 bands and 8 channels.
Note that FACTORY sends the band and channel indexes to the VTX device and they use their internal frequency mapping. In this mode the frequency is just for indication purposes.
In contrast, CUSTOM would send the actual frequency values to the VTX device, but not all devices support this mode.
Setting a frequency to zero will skip setting it.
```
nsh> vtxtable status
INFO [vtxtable] VTX table 8x8: Peak THOR T67
INFO [vtxtable] A: BAND_A = 6110 6130 6150 6170 6190 6210 6230 6250
INFO [vtxtable] B: BAND_B = 6270 6290 6310 6330 6350 6370 6390 6410
INFO [vtxtable] E: BAND_E = 6430 6450 6470 6490 6510 6530 6550 6570
INFO [vtxtable] F: BAND_F = 6590 6610 6630 6650 6670 6690 6710 6730
INFO [vtxtable] R: BAND_R = 6750 6770 6790 6810 6830 6850 6870 6890
INFO [vtxtable] P: BAND_P = 6910 6930 6950 6970 6990 7010 7030 7050
INFO [vtxtable] H: BAND_H = 7070 7090 7110 7130 7150 7170 7190 7210
INFO [vtxtable] U: BAND_U = 6115 6265 6425 6585 6745 6905 7065 7185
```
### Table Constraints
Maximum table dimensions:
- ≤24 bands each with ≤16 channels and ≤32GHz frequency values.
- ≤16 power levels.
- ≤16 characters table name.
- ≤12 characters band name and 1 character band letter.
- ≤4 characters power label length (to support "2.5W").
## AUX Channel Mapping
The AUX mapping feature allows you to control VTX settings using RC AUX channels.
Each mapping entry defines an AUX channel range that triggers a specific VTX configuration.
To enable AUX mapping, set `VTX_MAP_CONFIG` to one of the following values:
- `0`: Disabled
- `1`: Disabled (reserved for CRSF MSP integration)
- `2`: Map AUX channels to power level control only
- `3`: Map AUX channels to band and channel control only
- `4`: Map AUX channels to all settings (power, band, and channel)
### Configuring AUX Map Entries
Use the following command format to add mapping entries:
```
vtxtable <index> <aux_channel> <band> <channel> <power> <start_pwm> <end_pwm>
```
Parameters:
- `index`: Map entry index (0-159)
- `aux_channel`: AUX channel number (3-19, where AUX1=3)
- `band`: Target band (1-24, or 0 to leave unchanged)
- `channel`: Target channel (1-16, or 0 to leave unchanged)
- `power`: Power level (1-16, 0 to leave unchanged, or -1 for pit mode)
- `start_pwm`: Start of PWM range in microseconds (typically 900-2100)
- `end_pwm`: End of PWM range in microseconds (typically 900-2100)
:::info
AUX channel numbering starts from 3 (AUX1=channel 3) to account for the first four RC channels 0-3 used for flight control.
:::
Example configuration for a 6-position dial controlling band/channel on AUX4 (channel 7):
```
vtx 0 7 7 1 0 900 1025
vtx 1 7 7 2 0 1025 1100
vtx 2 7 7 4 0 1100 1175
vtx 3 7 7 6 0 1175 1225
vtx 4 7 7 8 0 1225 1300
vtx 5 7 3 8 0 1300 2100
```
Example configuration for power control on AUX3 (channel 6):
```
vtxtable 16 6 0 0 -1 900 1250
vtxtable 17 6 0 0 1 1250 1525
vtxtable 18 6 0 0 2 1525 1650
vtxtable 19 6 0 0 3 1650 1875
vtxtable 20 6 0 0 4 1875 2010
```
Save the configuration with:
```
vtxtable save
```
The map status can be verified with `vtxtable status`.
## CRSF MSP Integration
When using a CRSF receiver with MSP support, you can control VTX settings directly from your transmitter using MSP commands sent over the CRSF link.
This feature must be enabled at compile time with the `VTX_CRSF_MSP_SUPPORT` Kconfig option.
To enable CRSF MSP control, set `VTX_MAP_CONFIG` to one of:
- `1`: MSP controls both frequency (band/channel) and power
- `2`: MSP controls frequency (band/channel) only, AUX controls power
- `3`: MSP controls power only, AUX controls band/channel
When MSP integration is active, the driver responds to `MSP_SET_VTX_CONFIG` (0x59) commands.
The transmitter can send band, channel, frequency, power level, and pit mode settings via MSP, which are automatically mapped to the corresponding PX4 parameters.
:::tip
The MSP integration allows seamless VTX control from transmitters that support VTX configuration via Lua scripts or built-in VTX menus without requiring additional hardware switches.
:::
## Build Configuration
Both the VTX driver and VTX table support are configured via Kconfig options.
Key configuration options:
- `VTX_CRSF_MSP_SUPPORT`: Enables CRSF MSP command support (default: disabled)
- `VTXTABLE_CONFIG_FILE`: File path for persistent configuration (default: `/fs/microsd/vtx_config`)
- `VTXTABLE_AUX_MAP`: Enables AUX channel mapping (default: disabled)
## Parameter Reference
### VTX Settings Parameters
- `VTX_BAND` (0-23): Frequency band selection (Band 1-24 in UI)
- `VTX_CHANNEL` (0-15): Channel within band (Channel 1-16 in UI)
- `VTX_FREQUENCY` (0-32000): Direct frequency in MHz (overrides band/channel when non-zero)
- `VTX_POWER` (0-15): Power level (Level 1-16 in UI, as configured in table)
- `VTX_PIT_MODE` (boolean): Pit mode for reduced power (default: disabled)
### Configuration Parameters
- `VTX_SER_CFG`: Serial port assignment for VTX communication
- `VTX_MAP_CONFIG`: Controls how VTX settings are mapped:
- Without `VTX_CRSF_MSP_SUPPORT`:
- `0`: Disabled
- `1`: Disabled
- `2`: AUX controls power only
- `3`: AUX controls band/channel only
- `4`: AUX controls both power and band/channel
- With `VTX_CRSF_MSP_SUPPORT`:
- `0`: Disabled
- `1`: MSP controls both frequency and power
- `2`: MSP controls frequency, AUX controls power
- `3`: MSP controls power, AUX controls band/channel
- `4`: Not used with MSP support
- `VTX_DEVICE`: Device-specific configuration (see below)
## Device-Specific Configuration
The `VTX_DEVICE` parameter allows device-specific workarounds.
It encodes both the protocol type and device variant:
- Low byte (bits 0-7): Protocol selection
- `0`: SmartAudio (default)
- `1`: Tramp
- High byte (bits 8-15): Device-specific variant
- `0`: Generic device
- `20`: Peak THOR T67
- `40`: Rush Max Solo
### Known Device Workarounds
**Peak THOR T67** (`VTX_DEVICE` = 5120):
This device incorrectly reports pit mode status but otherwise functions normally.
The driver applies a workaround to override the reported status with the actual configured state.
For generic devices, use `VTX_DEVICE` = 0 (SmartAudio) or `VTX_DEVICE` = 1 (Tramp).
+1
View File
@@ -240,6 +240,7 @@ set(msg_files
VehicleThrustSetpoint.msg
VehicleTorqueSetpoint.msg
VelocityLimits.msg
Vtx.msg
WheelEncoders.msg
YawEstimatorStatus.msg
versioned/ActuatorMotors.msg
+32
View File
@@ -0,0 +1,32 @@
uint64 timestamp # time since system start (microseconds)
uint8 BAND_NAME_LENGTH = 12
uint8 POWER_LABEL_LENGTH = 4
uint8 PROTOCOL_NONE = 0 # No protocol is detected, usually an error
uint8 PROTOCOL_SMART_AUDIO_V1 = 10
uint8 PROTOCOL_SMART_AUDIO_V2 = 20
uint8 PROTOCOL_SMART_AUDIO_V2_1 = 21
uint8 PROTOCOL_TRAMP = 100
uint8 protocol
uint8 DEVICE_UNKNOWN = 0
uint8 DEVICE_PEAK_THOR_T67 = 20
uint8 DEVICE_RUSH_MAX_SOLO = 40
uint8 device
uint8 MODE_NORMAL = 0
uint8 MODE_PIT = 1
uint8 mode
# Band and Channel are 0-indexed! But the user expects a 1-indexed display!
int8 band # Band number (0-23), negative values indicate frequency mode
int8 channel # Channel number (0-15), negative values indicate frequency mode
uint16 frequency # Frequency in MHz, zero indicates unknown
uint8 band_letter # Band letter as ASCII
uint8[12] band_name # Band name in ASCII without null termination
# Also 0-indexed, but the user expects a 1-indexed display!
int8 power_level # Current power level (0-15), negative values indicate unknown
uint8[4] power_label # Current power label in ASCII without null termination
+13
View File
@@ -0,0 +1,13 @@
uint64 timestamp # time since system start (microseconds)
# See https://betaflight.com/docs/wiki/guides/current/VTX-CLI-Settings#change-vtx-power-level-using-aux-channel
uint8 MAX_LENGTH = 160
uint8 length
int8[160] aux_channel
int8[160] band
int8[160] channel
int8[160] power_level
int16[160] start_range
int16[160] end_range
+35
View File
@@ -0,0 +1,35 @@
uint64 timestamp # time since system start (microseconds)
uint8 NAME_LENGTH = 16
uint8 BAND_NAME_LENGTH = 12
uint8 POWER_LABEL_LENGTH = 4
uint8 CHANNELS = 16
uint8 BANDS = 24
uint8 POWER_LEVELS = 16
# Configuration name in ASCII without null termination
uint8[16] name
uint8 bands
uint8 channels
# Frequency is in MHz
# Invalid frequencies are set to 0
uint16[384] frequency # 24*16 = 384
# Band names in ASCII without null termination
uint8[288] band_name # 24*12 = 288
# Band letters in ASCII
uint8[24] letter
# Band attributes
uint8 ATTRIBUTE_FACTORY = 0
uint8 ATTRIBUTE_CUSTOM = 1
uint8[24] attribute
uint8 power_levels
# Positive values are the exact value the VTX protocol expects to set the power level
# Negative values are alternative values depending on the VTX protocol and device
int16[16] power_value
# The power labels as ASCII without null termination
uint8[64] power_label # 4*16 = 64
+65
View File
@@ -46,6 +46,9 @@
#include "QueueBuffer.hpp"
#include "CrsfParser.hpp"
#include "Crc8.hpp"
#ifdef CONFIG_VTX_CRSF_MSP_SUPPORT
#include <px4_platform_common/param.h>
#endif
#define CRSF_CHANNEL_VALUE_MIN 172
#define CRSF_CHANNEL_VALUE_MAX 1811
@@ -60,6 +63,7 @@ enum CRSF_PAYLOAD_SIZE {
CRSF_PAYLOAD_SIZE_LINK_STATISTICS_TX = -1,
CRSF_PAYLOAD_SIZE_RC_CHANNELS = 22,
CRSF_PAYLOAD_SIZE_ATTITUDE = 6,
CRSF_PAYLOAD_SIZE_MSP_WRITE = -1, // -1 means variable length
CRSF_PAYLOAD_SIZE_ELRS_STATUS = -1, // unclear how large this message is
};
@@ -127,12 +131,18 @@ static bool ProcessChannelData(const uint8_t *data, const uint32_t size, CrsfPac
static bool ProcessLinkStatistics(const uint8_t *data, const uint32_t size, CrsfPacket_t *const new_packet);
static bool ProcessLinkStatisticsTx(const uint8_t *data, const uint32_t size, CrsfPacket_t *const new_packet);
static bool ProcessElrsStatus(const uint8_t *data, const uint32_t size, CrsfPacket_t *const new_packet);
#ifdef CONFIG_VTX_CRSF_MSP_SUPPORT
static bool ProcessMspWrite(const uint8_t *data, const uint32_t size, CrsfPacket_t *const new_packet);
#endif
static const CrsfPacketDescriptor_t crsf_packet_descriptors[] = {
{CRSF_PACKET_TYPE_RC_CHANNELS_PACKED, CRSF_PAYLOAD_SIZE_RC_CHANNELS, ProcessChannelData},
{CRSF_PACKET_TYPE_LINK_STATISTICS, CRSF_PAYLOAD_SIZE_LINK_STATISTICS, ProcessLinkStatistics},
{CRSF_PACKET_TYPE_LINK_STATISTICS_TX, CRSF_PAYLOAD_SIZE_LINK_STATISTICS_TX, ProcessLinkStatisticsTx},
{CRSF_PACKET_TYPE_ELRS_STATUS, CRSF_PAYLOAD_SIZE_ELRS_STATUS, ProcessElrsStatus},
#ifdef CONFIG_VTX_CRSF_MSP_SUPPORT
{CRSF_PACKET_TYPE_MSP_WRITE, CRSF_PAYLOAD_SIZE_MSP_WRITE, ProcessMspWrite},
#endif
};
#define CRSF_PACKET_DESCRIPTOR_COUNT (sizeof(crsf_packet_descriptors) / sizeof(CrsfPacketDescriptor_t))
@@ -259,6 +269,61 @@ static bool ProcessElrsStatus(const uint8_t *data, const uint32_t size, CrsfPack
return true;
}
#ifdef CONFIG_VTX_CRSF_MSP_SUPPORT
static bool ProcessMspWrite(const uint8_t *data, const uint32_t size, CrsfPacket_t *const)
{
// Write the band/channel into the parameters, so it is thread-safe
// data contains the following:
// 0: CRSF v3: destination
// 1: CRSF v3: origin
// 2: CRSF v3: status
// 3: MSP: size
// 4: MSP: command
// 5: MSP: data[≤57]
// Try: crsf_rc inject 0x7C 0xC8 0xEA 0x30 0x4 0x59 0x22 0x0 0x1 0x0
int32_t map_config{};
param_get(int(px4::params::VTX_MAP_CONFIG), &map_config);
if (map_config == 0) {
// no mapping, just return
return false;
}
if (data[2] == 0x30 && data[4] == 0x59) {
const uint8_t length = data[3];
if (map_config == 1 || map_config == 2) {
// Status = bit4=new frame, bit5,6=MSPv1
// MSP command 0x59 is MSP_SET_VTX_CONFIG
uint32_t frequency = (data[6] << 8) | data[5];
if (frequency <= 0x3f) {
// first byte contains band and channel: 0b00bb'bccc
const int32_t band = (data[5] >> 3) & 0x07;
const int32_t channel = data[5] & 0x07;
param_set_no_notification(int(px4::params::VTX_BAND), &band);
param_set_no_notification(int(px4::params::VTX_CHANNEL), &channel);
frequency = 0; // Disable the frequency override
}
param_set_no_notification(int(px4::params::VTX_FREQUENCY), &frequency);
}
if (length > 2 && (map_config == 1 || map_config == 3)) {
const int32_t pit_mode = (data[8] || (data[7] == 0)) ? 1 : 0;
param_set_no_notification(int(px4::params::VTX_PIT_MODE), &pit_mode);
const int32_t power{pit_mode ? 0 : data[7] - 1};
param_set_no_notification(int(px4::params::VTX_POWER), &power);
}
}
// nothing else is implemented yet
return false;
}
#endif
static CrsfPacketDescriptor_t *FindCrsfDescriptor(const enum CRSF_PACKET_TYPE packet_type)
{
uint32_t i;
+43
View File
@@ -0,0 +1,43 @@
############################################################################
#
# Copyright (c) 2025 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE drivers__vtx
MAIN vtx
COMPILE_FLAGS
SRCS
VTX.cpp
smart_audio.cpp
tramp.cpp
MODULE_CONFIG
module.yaml
)
+18
View File
@@ -0,0 +1,18 @@
menuconfig DRIVERS_VTX
bool "VTX Driver"
default n
select DRIVERS_VTXTABLE
select VTXTABLE_AUX_MAP
---help---
Control VTX devices via serial interface.
if DRIVERS_VTX
config VTX_CRSF_MSP_SUPPORT
bool "Support using CRSF MSP commands to configure VTX"
depends on DRIVERS_RC_CRSF_RC
default n
---help---
Set VTX frequency and power via CRSF MSP messages.
endif # DRIVERS_VTX
+482
View File
@@ -0,0 +1,482 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "VTX.hpp"
#include <fcntl.h>
#include <termios.h>
#include <drivers/drv_hrt.h>
#include "smart_audio.h"
#include "tramp.h"
using namespace time_literals;
VTX::VTX(const char *device) :
ModuleParams(nullptr),
ScheduledWorkItem(MODULE_NAME, px4::serial_port_to_wq(device)),
_perf_cycle(perf_alloc(PC_ELAPSED, MODULE_NAME": cycle time")),
_perf_error(perf_alloc(PC_COUNT, MODULE_NAME": errors")),
_perf_get_settings(perf_alloc(PC_COUNT, MODULE_NAME": errors get settings")),
_perf_set_power(perf_alloc(PC_COUNT, MODULE_NAME": errors set power")),
_perf_set_frequency(perf_alloc(PC_COUNT, MODULE_NAME": errors set frequency")),
_perf_set_pit_mode(perf_alloc(PC_COUNT, MODULE_NAME": errors set pit mode"))
{
if (device) {
strlcpy(_serial_path, device, sizeof(_serial_path));
}
}
VTX::~VTX()
{
delete _protocol;
perf_free(_perf_cycle);
perf_free(_perf_error);
perf_free(_perf_get_settings);
perf_free(_perf_set_power);
perf_free(_perf_set_frequency);
perf_free(_perf_set_pit_mode);
}
int VTX::init()
{
_param_vtx_device.update();
_device = (_param_vtx_device.get() >> 8);
const uint8_t protocol = (_param_vtx_device.get() & 0xff);
if (_protocol == nullptr) {
if (protocol == vtx_s::PROTOCOL_TRAMP) {
_protocol = new vtx::Tramp();
} else {
_protocol = new vtx::SmartAudio();
}
}
if (_protocol == nullptr) {
PX4_ERR("Protocol alloc failed");
return PX4_ERROR;
}
if (_protocol->init(_serial_path) != PX4_OK) {
return PX4_ERROR;
}
return PX4_OK;
}
void VTX::Run()
{
static constexpr auto _INTERVAL{50_ms};
if (should_exit()) {
exit_and_cleanup();
return;
}
perf_begin(_perf_cycle);
switch (_state) {
case STATE_INIT:
if (init() == PX4_OK) {
_state = STATE_UPDATE;
_pending = STATE_SET_ALL;
} else {
perf_count(_perf_error);
}
break;
case STATE_GET_SETTINGS:
if (_protocol->get_settings() == PX4_OK) {
if (!_comms_ok) {
// previous get settings failed, send all settings again
_pending |= STATE_SET_ALL;
}
_pending &= ~STATE_GET_SETTINGS;
_comms_ok = true;
} else {
perf_count(_perf_get_settings);
_comms_ok = false;
}
_update_counter = 1_s / _INTERVAL;
_state = STATE_UPDATE;
break;
case STATE_SET_FREQUENCY:
if ((_frequency ? _protocol->set_frequency(_frequency) : _protocol->set_channel(_band, _channel)) == PX4_OK) {
_pending &= ~STATE_SET_FREQUENCY;
_pending |= STATE_GET_SETTINGS;
} else {
perf_count(_perf_set_frequency);
}
_state = STATE_UPDATE;
break;
case STATE_SET_POWER:
if (_protocol->set_power(_power) == PX4_OK) {
_pending &= ~STATE_SET_POWER;
_pending |= STATE_GET_SETTINGS;
} else {
perf_count(_perf_set_power);
}
_state = STATE_UPDATE;
break;
case STATE_SET_PIT_MODE:
if (_protocol->set_pit_mode(_pit_mode) == PX4_OK) {
_pending &= ~STATE_SET_PIT_MODE;
_pending |= STATE_GET_SETTINGS;
} else {
perf_count(_perf_set_pit_mode);
}
_state = STATE_UPDATE;
break;
default:
// Poll parameters and update settings if needed
int new_band{_band}, new_channel{_channel}, new_frequency{_frequency}, new_power{_power}, new_pit_mode{_pit_mode};
update_params(&new_band, &new_channel, &new_frequency, &new_power, &new_pit_mode);
const vtx::Config::ChangeType current_change = vtxtable().get_change();
if (_last_config_change != current_change) {
_last_config_change = current_change;
_pending |= STATE_SET_ALL;
}
if (_band != new_band || _channel != new_channel) {
_band = new_band;
_channel = new_channel;
_pending |= STATE_SET_FREQUENCY;
}
if (_frequency != new_frequency) {
_frequency = new_frequency;
_pending |= STATE_SET_FREQUENCY;
}
if (_power != new_power) {
_power = new_power;
_pending |= STATE_SET_POWER;
}
if (_pit_mode != new_pit_mode) {
_pit_mode = new_pit_mode;
// Some VTX set power to 0 in pit mode, so also set power again
_pending |= STATE_SET_PIT_MODE | STATE_SET_POWER;
}
if (_update_counter <= 0) {
_pending |= STATE_GET_SETTINGS;
} else {
_update_counter--;
}
// Round robin schedule the next pending flag test
uint8_t current{0};
while (_pending && !current) {
current = uint8_t(_pending & _schedule);
_schedule <<= 1;
if (!(_schedule & STATE_MASK)) {
_schedule = STATE_GET_SETTINGS;
break;
}
}
// Convert pending flags to state
if (current & STATE_GET_SETTINGS) {
_state = STATE_GET_SETTINGS;
} else if (current & STATE_SET_FREQUENCY) {
_state = STATE_SET_FREQUENCY;
} else if (current & STATE_SET_POWER) {
_state = STATE_SET_POWER;
} else if (current & STATE_SET_PIT_MODE) {
_state = STATE_SET_PIT_MODE;
}
handle_uorb();
break;
}
ScheduleDelayed(_INTERVAL);
perf_end(_perf_cycle);
}
void VTX::update_params(int *new_band, int *new_channel, int *new_frequency, int *new_power, int *new_pit_mode)
{
_param_vtx_band.update();
_param_vtx_channel.update();
_param_vtx_power.update();
_param_vtx_frequency.update();
_param_vtx_pit_mode.update();
_param_vtx_map_config.update();
const int map_config = _param_vtx_map_config.get();
// Set transmit channel based on either parameter or manual auxiliary channel input
if (map_config > 1) {
input_rc_s input_rc{};
if (_input_rc_sub.update(&input_rc) && !input_rc.rc_lost) {
int8_t band{0}, channel{0}, power{0};
vtxtable().map_lookup(input_rc.values, input_rc.channel_count, &band, &channel, &power);
if (map_config > 2) {
if (band > 0) { _param_vtx_band.commit_no_notification(band - 1); }
if (channel > 0) { _param_vtx_channel.commit_no_notification(channel - 1); }
}
if (map_config == 2 || map_config == 4) {
if (power == -1) {
_param_vtx_power.commit_no_notification(0);
_param_vtx_pit_mode.commit_no_notification(true);
} else if (power > 0) {
_param_vtx_power.commit_no_notification(power - 1);
_param_vtx_pit_mode.commit_no_notification(false);
}
}
}
}
*new_band = _param_vtx_band.get() % 24;
*new_channel = _param_vtx_channel.get() & 0xF;
*new_power = _param_vtx_power.get() & 0xF;
*new_frequency = _param_vtx_frequency.get();
*new_pit_mode = _param_vtx_pit_mode.get();
}
void VTX::handle_uorb()
{
vtx_s msg{};
msg.band = _band;
msg.channel = _channel;
msg.power_level = _power;
msg.device = _device;
if (!_comms_ok || !_protocol->copy_to(&msg)) {
msg.protocol = vtx_s::PROTOCOL_NONE;
msg.frequency = (_frequency ? _frequency : vtxtable().frequency(_band, _channel));
msg.mode = _pit_mode ? vtx_s::MODE_PIT : vtx_s::MODE_NORMAL;
}
// If frequency is set, overwrite band and channel to -1
if (_frequency) {
msg.band = -1;
msg.channel = -1;
msg.band_letter = 'f';
strncpy((char *)msg.band_name, "FREQUENCY", sizeof(msg.band_name));
} else {
msg.band_letter = vtxtable().band_letter(msg.band);
strncpy((char *)msg.band_name, vtxtable().band_name(msg.band), sizeof(msg.band_name));
}
strncpy((char *)msg.power_label, vtxtable().power_label(msg.power_level), sizeof(msg.power_label));
// Workarounds for specific devices
if (_device == vtx_s::DEVICE_PEAK_THOR_T67) {
// This device always reports pit mode, but still works fine
msg.frequency = vtxtable().frequency(_band, _channel);
msg.mode = _pit_mode ? vtx_s::MODE_PIT : vtx_s::MODE_NORMAL;
}
// Only publish if something changed
if (memcmp(&msg, &_vtx_msg, sizeof(msg)) != 0) {
_vtx_msg = msg;
msg.timestamp = hrt_absolute_time();
_vtx_pub.publish(msg);
}
}
int VTX::custom_command(int argc, char *argv[])
{
if (!strcmp(argv[0], "start")) {
if (is_running()) {
return print_usage("already running");
}
int ret = VTX::task_spawn(argc, argv);
if (ret) {
return ret;
}
}
if (!is_running()) {
return print_usage("not running");
}
// Check if the first argument is a number and delegate to the VtxTable
char *endptr{};
strtol(argv[0], &endptr, 10);
if (endptr != argv[0]) {
extern int vtxtable_custom_command(int argc, char *argv[]);
return vtxtable_custom_command(argc, argv);
}
return print_usage("unknown command");
}
int VTX::task_spawn(int argc, char *argv[])
{
int myoptind = 1;
int ch;
const char *myoptarg = nullptr;
const char *device_name = nullptr;
while ((ch = px4_getopt(argc, argv, "d:", &myoptind, &myoptarg)) != EOF) {
switch (ch) {
case 'd':
device_name = myoptarg;
break;
default:
PX4_WARN("unrecognized flag");
return -1;
break;
}
}
if (device_name && (access(device_name, R_OK | W_OK) == 0)) {
auto *const instance = new VTX(device_name);
if (instance == nullptr) {
PX4_ERR("alloc failed");
return PX4_ERROR;
}
_object.store(instance);
_task_id = task_id_is_work_queue;
instance->ScheduleNow();
return PX4_OK;
} else {
if (device_name) {
PX4_ERR("invalid device (-d) %s", device_name);
} else {
PX4_INFO("valid device required");
}
}
return PX4_ERROR;
}
int VTX::print_status()
{
if (_serial_path[0]) {
PX4_INFO("UART device: %s", _serial_path);
}
#ifdef CONFIG_VTX_CRSF_MSP_SUPPORT
const char *const config_map[] {"Disabled", "MSP", "MSP=Band/Channel, AUX=Power", "MSP=Power, AUX=Band/Channel", "AUX"};
#else
const char *const config_map[] {"Disabled", "Disabled", "Power", "Band/Channel", "Power/Band/Channel"};
#endif
const char *const config_str = config_map[_param_vtx_map_config.get() <= 4 ? _param_vtx_map_config.get() : 0];
PX4_INFO("RC mapping: %s", config_str);
PX4_INFO("Parameters:");
PX4_INFO(" band: %d", (_frequency ? -1 : (_band + 1)));
PX4_INFO(" channel: %d", (_frequency ? -1 : (_channel + 1)));
PX4_INFO(" frequency: %u MHz", (_frequency ? _frequency : vtxtable().frequency(_band, _channel)));
PX4_INFO(" power level: %u", _power + 1);
PX4_INFO(" power: %hi = %s", vtxtable().power_value(_power), vtxtable().power_label(_power));
PX4_INFO(" pit mode: %s", _pit_mode ? "on" : "off");
if (!(_comms_ok && _protocol && _protocol->print_settings())) {
PX4_ERR("%s device not found", _param_vtx_device.get() == 1 ? "Tramp" : "SmartAudio");
}
perf_print_counter(_perf_cycle);
perf_print_counter(_perf_error);
perf_print_counter(_perf_get_settings);
perf_print_counter(_perf_set_power);
perf_print_counter(_perf_set_frequency);
perf_print_counter(_perf_set_pit_mode);
return 0;
}
int VTX::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
This module communicates with a VTX camera via serial port. It can be used to
configure the camera settings and to control the camera's video transmission.
Supported protocols are:
- SmartAudio v1, v2.0, v2.1
- Tramp
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("vtx", "driver");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_PARAM_STRING('d', nullptr, "<file:dev>", "VTX device", false);
PRINT_MODULE_USAGE_COMMAND_DESCR("<int>", "Sets an entry in the mapping table: <index> <aux channel> <band> <channel> <power level> <start range> <end range>");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
extern "C" __EXPORT int vtx_main(int argc, char *argv[])
{
return VTX::main(argc, argv);
}
+134
View File
@@ -0,0 +1,134 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <float.h>
#include <board_config.h>
#include <drivers/drv_hrt.h>
#include <lib/perf/perf_counter.h>
#include <px4_platform_common/Serial.hpp>
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/getopt.h>
#include <px4_platform_common/log.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/px4_work_queue/ScheduledWorkItem.hpp>
#include <uORB/PublicationMulti.hpp>
#include <uORB/Subscription.hpp>
#include <uORB/topics/vtx.h>
#include <uORB/topics/input_rc.h>
#include "protocol.h"
#include "../vtxtable/VtxTable.hpp"
/**
* @author Niklas Hauser <niklas@auterion.com>
*/
class VTX : public ModuleBase<VTX>, public ModuleParams, public px4::ScheduledWorkItem
{
public:
VTX(const char *device);
virtual ~VTX();
/** @see ModuleBase */
static int task_spawn(int argc, char *argv[]);
/** @see ModuleBase */
static int custom_command(int argc, char *argv[]);
/** @see ModuleBase */
static int print_usage(const char *reason = nullptr);
/** @see ModuleBase::print_status() */
int print_status() override;
int init();
private:
void Run() override;
void update_params(int *new_band, int *new_channel, int *new_frequency, int *new_power, int *new_pit_mode);
void handle_uorb();
char _serial_path[20] {};
vtx::Protocol *_protocol{nullptr};
DEFINE_PARAMETERS(
(ParamInt<px4::params::VTX_BAND>) _param_vtx_band,
(ParamInt<px4::params::VTX_CHANNEL>) _param_vtx_channel,
(ParamInt<px4::params::VTX_FREQUENCY>) _param_vtx_frequency,
(ParamInt<px4::params::VTX_POWER>) _param_vtx_power,
(ParamBool<px4::params::VTX_PIT_MODE>) _param_vtx_pit_mode,
(ParamInt<px4::params::VTX_MAP_CONFIG>) _param_vtx_map_config,
(ParamInt<px4::params::VTX_DEVICE>) _param_vtx_device
);
perf_counter_t _perf_cycle;
perf_counter_t _perf_error;
perf_counter_t _perf_get_settings;
perf_counter_t _perf_set_power;
perf_counter_t _perf_set_frequency;
perf_counter_t _perf_set_pit_mode;
uORB::PublicationMulti<vtx_s> _vtx_pub{ORB_ID(vtx)};
uORB::Subscription _input_rc_sub{ORB_ID(input_rc)};
vtx_s _vtx_msg{};
int8_t _band{0};
int8_t _channel{0};
int16_t _frequency{0};
int16_t _power{0};
uint8_t _device{0};
bool _pit_mode{};
enum : uint8_t {
STATE_INIT = 0,
STATE_GET_SETTINGS = 0b0001,
STATE_SET_PIT_MODE = 0b0010,
STATE_SET_FREQUENCY = 0b0100,
STATE_SET_POWER = 0b1000,
STATE_UPDATE = 0b1'0000,
STATE_SET_ALL = STATE_SET_FREQUENCY | STATE_SET_POWER | STATE_SET_PIT_MODE,
STATE_MASK = 0b1111,
} _state{STATE_INIT};
uint8_t _pending{};
uint8_t _schedule{STATE_GET_SETTINGS};
int8_t _update_counter{};
bool _comms_ok{};
vtx::Config::ChangeType _last_config_change{};
};
+133
View File
@@ -0,0 +1,133 @@
module_name: VTX SmartAudio
serial_config:
- command: vtx start -d ${SERIAL_DEV}
port_config_param:
name: VTX_SER_CFG
group: VTX
label: VTX Serial Port
parameters:
- group: VTX
definitions:
VTX_BAND:
description:
short: VTX band
long: VTX table band 1-24
type: enum
values:
0: Band 1
1: Band 2
2: Band 3
3: Band 4
4: Band 5
5: Band 6
6: Band 7
7: Band 8
8: Band 9
9: Band 10
10: Band 11
11: Band 12
12: Band 13
13: Band 14
14: Band 15
15: Band 16
16: Band 17
17: Band 18
18: Band 19
19: Band 20
20: Band 21
21: Band 22
22: Band 23
23: Band 24
default: 0
VTX_CHANNEL:
description:
short: VTX channel
long: VTX table channel 1-16
type: enum
values:
0: Channel 1
1: Channel 2
2: Channel 3
3: Channel 4
4: Channel 5
5: Channel 6
6: Channel 7
7: Channel 8
8: Channel 9
9: Channel 10
10: Channel 11
11: Channel 12
12: Channel 13
13: Channel 14
14: Channel 15
15: Channel 16
default: 0
VTX_FREQUENCY:
description:
short: VTX frequency in MHz
long: |
If the VTX frequency is set, it will overwrite the band and channel
settings. Set to 0 to use band and channel settings.
type: int32
min: 0
max: 32000
default: 0
VTX_POWER:
description:
short: VTX power level
long: VTX transmission power level 1-16
type: enum
values:
0: Level 1
1: Level 2
2: Level 3
3: Level 4
4: Level 5
5: Level 6
6: Level 7
7: Level 8
8: Level 9
9: Level 10
10: Level 11
11: Level 12
12: Level 13
13: Level 14
14: Level 15
15: Level 16
default: 0
VTX_PIT_MODE:
description:
short: VTX pit mode
long: VTX pit mode reduces power to the minimum
type: boolean
default: false
VTX_MAP_CONFIG:
description:
short: VTX mapping configuration
long: |
Configure how VTX settings are controlled. Options include using
MSP commands, AUX channels or a combination of both. To use MSP
commands, you must use a CRSF receiver with MSP support enabled in
the PX4 build.
type: enum
values:
0: Disabled
1: MSP for Power, Band and Channel
2: MSP for Band and Channel, AUX for Power
3: MSP for Power, AUX for Band and Channel
4: AUX for Power, Band and Channel
default: 0
VTX_DEVICE:
description:
short: VTX device
long: Specific VTX device useful for workarounds and optimizations
type: enum
# Note: keep values in sync with msg/Vtx.msg: lower 8-bit is protocol, next 8-bit is device
values:
0: SmartAudio v1, v2, v2.1 Protocol
100: Tramp Protocol
5120: Peak THOR T67
10240: Rush MAX SOLO
default: 0
reboot_required: true
+185
View File
@@ -0,0 +1,185 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <board_config.h>
#include <px4_platform_common/Serial.hpp>
#include <px4_platform_common/log.h>
#include <uORB/topics/vtx.h>
#include "../vtxtable/VtxTable.hpp"
namespace vtx
{
/**
* Parser class for working with Tramp protocol.
* @author Niklas Hauser <niklas@auterion.com>
*/
class Protocol
{
public:
/// @brief Constructor
/// @param baudrate The baud rate for the serial connection
constexpr Protocol(uint16_t baudrate) :
_baudrate(baudrate)
{}
virtual inline ~Protocol()
{
if (_serial) {
_serial->close();
delete _serial;
}
}
inline int init(const char *device)
{
if (_serial == nullptr) {
_serial = new device::Serial(device, _baudrate);
} else {
_serial->close();
}
if (_serial == nullptr) {
PX4_ERR("Serial alloc failed");
return PX4_ERROR;
}
#ifdef CONFIG_BOARD_SERIAL_RC
// All RC serial ports are RX by default, so we need to swap RX and TX
// to get the TX pin on the RX pin.
if (strcmp(device, CONFIG_BOARD_SERIAL_RC) == 0 && !board_rc_swap_rxtx(device)) {
_serial->setSwapRxTxMode();
}
#endif
_serial->setStopbits(device::SerialConfig::StopBits::Two);
_serial->setSingleWireMode();
if (!_serial->open()) {
PX4_ERR("Serial open failed");
delete _serial;
_serial = nullptr;
return PX4_ERROR;
}
if (reset() != 0) {
return PX4_ERROR;
}
return PX4_OK;
}
/// @brief Reset the VTX
/// @return 0 on success, negative error code on failure
virtual int reset() { return 0; }
/// @brief Get the current settings from the VTX
/// @return 0 on success, negative error code on failure
virtual int get_settings() = 0;
/// @brief Set the power
/// @param power_level The power level to set, negative values set dBm or mW depending on the implementation
/// @return 0 on success, negative error code on failure
virtual int set_power(int16_t power_level) = 0;
/// @brief Set the frequency
/// @param frequency_MHz The frequency to set, negative values set pit frequency
/// @return 0 on success, negative error code on failure
virtual int set_frequency(int16_t frequency_MHz) = 0;
/// @brief Set the frequency based on band and channel
/// @param band The band (0-7)
/// @param channel The channel (0-7)
/// @return 0 on success, negative error code on failure
virtual int set_channel(uint8_t band, uint8_t channel)
{
return set_frequency(vtxtable().frequency(band, channel));
}
/// @brief Set the pit mode
/// @param onoff true to enable pit mode, false to disable
/// @return 0 on success, negative error code on failure
virtual int set_pit_mode(bool onoff) = 0;
/// @brief Print the current settings
/// @return true if device is connected and settings are valid
virtual bool print_settings() = 0;
/// @brief Copy the current settings to a uORB message
/// @param msg The message to copy the settings to
/// @return true if data is valid and has been copied
virtual bool copy_to(vtx_s *msg) = 0;
protected:
/// @brief Parse incoming bytes
/// @param value The incoming byte
/// @return Number of remaining bytes to read, 0 if a full message has been received, negative on error
virtual int rx_parser(uint8_t value) = 0;
inline int rx_msg()
{
using namespace time_literals;
memset(_rx_buf, 0, sizeof(_rx_buf));
_read_state = 0;
const hrt_abstime start_time_us = hrt_absolute_time();
int remaining{3};
while (true) {
const int new_bytes = _serial->readAtLeast(_serial_buf, sizeof(_serial_buf), remaining, 20);
for (int i = 0; i < new_bytes; i++) {
remaining = rx_parser(_serial_buf[i]);
if (remaining <= 0) { return remaining; }
}
if (hrt_elapsed_time(&start_time_us) > 200_ms) {
return -ETIMEDOUT;
}
}
return -1;
}
const uint16_t _baudrate{};
device::Serial *_serial{};
uint8_t _tx_buf[32] {};
uint8_t _rx_buf[32] {};
uint8_t _serial_buf[32] {};
uint8_t _read_state{};
};
} // namespace vtx
+368
View File
@@ -0,0 +1,368 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "smart_audio.h"
#include <string.h>
#include <cerrno>
#include <drivers/drv_hrt.h>
#include <px4_platform_common/log.h>
#include <math.h>
namespace vtx
{
using namespace time_literals;
int SmartAudio::get_settings()
{
const uint8_t buf[] = {COMMAND_GET_SETTINGS, 0};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
Settings s;
memcpy(&s, _rx_buf + 2, sizeof(Settings));
// Fix frequency endianess
s.frequency = __builtin_bswap16(s.frequency);
const uint8_t version = _rx_buf[0] >> 3;
if (version == 0) { // v1
s.number_of_power_levels = 4;
s.power_levels[0] = 7;
s.power_levels[1] = 16;
s.power_levels[2] = 25;
s.power_levels[3] = 40;
} else if (version == 1) { // v2
s.number_of_power_levels = 8;
} else if (version == 2) { // v2.1
if (s.number_of_power_levels > 7) {
s.number_of_power_levels = 7;
}
for (int i = 0; i < s.number_of_power_levels; i++) {
s.power_levels[i] = s.power_levels[i + 1];
}
}
s.version = version;
// copy the settings to storage
_settings = s;
return PX4_OK;
}
bool SmartAudio::print_settings()
{
if (_settings.version > 2) { return false; }
PX4_INFO("SmartAudio v%s%s:", ((const char *[]) {"1", "2", "2.1"})[_settings.version],
(_settings.mode & SmartAudio::MODE_SET_FREQUENCY) ? "+fr" : "+ch");
PX4_INFO(" band: %u", ((_settings.channel >> 3) & 0b111) + 1);
PX4_INFO(" channel: %u", (_settings.channel & 0b111) + 1);
PX4_INFO(" frequency: %u MHz", _settings.frequency);
PX4_INFO(" power level: %u", _settings.current_power_level + 1);
const char *power_label = vtxtable().power_label(_settings.current_power_level);
if (_settings.version == 2) {
PX4_INFO(" power: %u dBm = %s", _settings.current_power_dBm, power_label);
} else {
PX4_INFO(" power: %s", power_label);
}
PX4_INFO(" pit mode: %s", (_settings.mode & (SmartAudio::MODE_IN_RANGE_PIT_MODE |
SmartAudio::MODE_OUT_RANGE_PIT_MODE)) ? "on" : "off");
PX4_INFO(" lock: %slocked", (_settings.mode & SmartAudio::MODE_UNLOCKED) ? "un" : "");
return true;
}
bool SmartAudio::copy_to(vtx_s *msg)
{
if (_settings.version > 2) { return false; }
if (_settings.version == 0) { msg->protocol = vtx_s::PROTOCOL_SMART_AUDIO_V1; }
else if (_settings.version == 1) { msg->protocol = vtx_s::PROTOCOL_SMART_AUDIO_V2; }
else { msg->protocol = vtx_s::PROTOCOL_SMART_AUDIO_V2_1; }
msg->band = _settings.channel >> 3;
msg->channel = _settings.channel & 0b111;
msg->frequency = _settings.frequency;
msg->power_level = _settings.current_power_level;
bool pm = (_settings.mode & (SmartAudio::MODE_IN_RANGE_PIT_MODE | SmartAudio::MODE_OUT_RANGE_PIT_MODE));
msg->mode = pm ? vtx_s::MODE_PIT : vtx_s::MODE_NORMAL;
return true;
}
int SmartAudio::set_power(uint8_t power_level, bool is_dBm)
{
if (power_level >= _settings.number_of_power_levels) {
return PX4_ERROR;
}
power_level = vtxtable().power_value(power_level);
if (_settings.version == 2 && is_dBm) { power_level |= 0x80; }
const uint8_t buf[] = {COMMAND_SET_POWER, 1, power_level};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
return ((_rx_buf[0] == COMMAND_SET_POWER) && (_rx_buf[2] == (power_level & ~0x80))) ? PX4_OK : PX4_ERROR;
}
int SmartAudio::set_channel(uint8_t band, uint8_t channel)
{
if (vtxtable().band_attribute(band) == Config::BandAttribute::CUSTOM) {
const uint16_t freq = vtxtable().frequency(band, channel);
if (freq) { return set_frequency(freq, false); }
}
const uint8_t value = ((band & 0b111) << 3) | (channel & 0b111);
const uint8_t buf[] = {COMMAND_SET_CHANNEL, 1, value};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
return ((_rx_buf[0] == COMMAND_SET_CHANNEL) && (_rx_buf[2] == value)) ? PX4_OK : PX4_ERROR;
}
int SmartAudio::set_frequency(uint16_t frequency, bool pit)
{
pit ? frequency |= 0x8000 : frequency &= ~0x8000;
const uint8_t buf[] = {COMMAND_SET_FREQUENCY, 2, uint8_t(frequency >> 8), uint8_t(frequency)};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
const bool success = (_rx_buf[0] == COMMAND_SET_FREQUENCY) && (_rx_buf[2] == (frequency >> 8));
if (_settings.version == 2) {
return (success && (_rx_buf[3] == (frequency & 0xff))) ? PX4_OK : PX4_ERROR;
} else {
// Some v1/v2 firmwares do not echo the low byte back
// But the CRC is still correct, so we assume success
return success ? PX4_OK : PX4_ERROR;
}
}
int SmartAudio::set_operating_mode(uint8_t mode)
{
if (_settings.version == 0) {
return -1;
}
const uint8_t buf[] = {COMMAND_SET_MODE, 1, mode};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
return ((_rx_buf[0] == COMMAND_SET_MODE) && (_rx_buf[2] == mode)) ? PX4_OK : PX4_ERROR;
}
int SmartAudio::set_pit_mode(bool onoff)
{
// only v2 supports pit mode
if (_settings.version == 0) {
return 0;
}
uint8_t mode{SET_MODE_UNLOCKED};
if (onoff) {
if (_settings.mode & MODE_OUT_RANGE_PIT_MODE) {
mode |= SET_MODE_OUT_RANGE;
} else {
mode |= SET_MODE_IN_RANGE;
}
} else {
mode |= SET_MODE_DISABLE_PIT_MODE;
}
return set_operating_mode(mode);
}
int SmartAudio::transmit(const uint8_t *buf, size_t len)
{
if (len > 28) { return -1; }
// copy the message data
memcpy(_tx_buf + 3, buf, len);
// shift the command to the left
// const uint8_t cmd = buf[0];
_tx_buf[3] = (buf[0] << 1) | 0x01;
// compute the CRC
_tx_buf[3 + len] = crc8(_tx_buf + 1, 2 + len);
// some devices need an additional 0 byte at the end
_tx_buf[4 + len] = 0;
// send command
if (_serial->write(_tx_buf, 5 + len) < ssize_t(5 + len)) {
return -errno;
}
_serial->flush();
return rx_msg();
}
bool SmartAudio::is_rsp(uint8_t cmd, uint8_t length)
{
if (length == 0) { return false; }
if (cmd == COMMAND_SET_POWER && length == 0x03) { return true; }
if (cmd == COMMAND_SET_CHANNEL && length == 0x03) { return true; }
if (cmd == COMMAND_SET_FREQUENCY && length == 0x04) { return true; }
if (cmd == COMMAND_SET_MODE && length == 0x03) { return true; }
if (((cmd == COMMAND_GET_SETTINGS) || (cmd == COMMAND_GET_SETTINGS_V2) ||
(cmd == COMMAND_GET_SETTINGS_V21)) && length >= 0x06) { return true; }
return false;
}
int SmartAudio::rx_parser(uint8_t c)
{
enum {
SYNC1 = 0,
SYNC2 = 1,
COMMAND = 2,
LENGTH = 3,
DATA = 4,
CRC = 5,
};
switch (_read_state) {
case SYNC1:
PX4_DEBUG("SYNC1 %x", c);
if (c == 0xAA) {
_read_state = SYNC2;
}
return 1;
case SYNC2:
PX4_DEBUG("SYNC2 %x", c);
if (c == 0x55) {
_read_state = COMMAND;
return 3;
} else {
_read_state = SYNC1;
return 1;
}
case COMMAND:
PX4_DEBUG("COMMAND %x", c);
_rx_buf[0] = c;
_read_state = LENGTH;
return 2;
case LENGTH:
PX4_DEBUG("LENGTH %x", c);
if (c >= 20) {
_read_state = SYNC1;
return 1;
}
_rx_buf[1] = c;
// command response: has one length too many
if (is_rsp(_rx_buf[0], c) && c) { c--; }
_read_length = c;
_read_data_length = 0;
_read_state = _read_length ? DATA : CRC;
return _read_length + 1;
case DATA:
PX4_DEBUG("DATA %x", c);
_rx_buf[2 + _read_data_length] = c;
if (++_read_data_length >= _read_length) {
_read_state = CRC;
}
return _read_length - _read_data_length + 1;
case CRC:
PX4_DEBUG("CRC %x", c);
_read_state = SYNC1;
if (!is_rsp(_rx_buf[0], _rx_buf[1])) { return 1; }
return (c == crc8(_rx_buf, 2u + _read_length)) ? 0 : -CRC;
}
return -6000;
}
uint8_t SmartAudio::crc8(const uint8_t *data, const uint8_t len)
{
// Implementation adapted from lib/crc/crc.c
uint8_t crc{};
const uint8_t poly{0xd5};
for (uint8_t i = 0 ; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & (1u << 7u)) {
crc = (uint8_t)((crc << 1u) ^ poly);
} else {
crc = (uint8_t)(crc << 1u);
}
}
}
return crc;
}
}
+129
View File
@@ -0,0 +1,129 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include "protocol.h"
namespace vtx
{
/**
* Implementation for working with SmartAudio protocol.
* @author Niklas Hauser <niklas@auterion.com>
*/
class SmartAudio final : public Protocol
{
public:
static constexpr int BAUDRATE{4800};
struct Settings {
uint8_t channel;
uint8_t current_power_level;
uint8_t mode;
uint16_t frequency;
uint8_t current_power_dBm;
uint8_t number_of_power_levels;
uint8_t power_levels[8];
uint8_t version;
} __attribute__((packed));
enum {
MODE_SET_FREQUENCY = 0b0001,
MODE_PIT_MODE_ACTIVE = 0b0010,
MODE_IN_RANGE_PIT_MODE = 0b0100,
MODE_OUT_RANGE_PIT_MODE = 0b1000,
MODE_UNLOCKED = 0b10000,
};
const Settings &settings() const { return _settings; }
inline SmartAudio() : Protocol(BAUDRATE)
{
_tx_buf[1] = 0xAA;
_tx_buf[2] = 0x55;
}
virtual ~SmartAudio() = default;
inline int set_power(int16_t power_level) override
{
// negative values do not force dBm mode
if (power_level < 0) { return set_power(-power_level, false); }
return set_power(power_level, true);
}
inline int set_frequency(int16_t frequency_MHz) override
{
if (frequency_MHz < 0) { return set_frequency(-frequency_MHz, true); }
return set_frequency(frequency_MHz, false);
}
int set_channel(uint8_t band, uint8_t channel) override;
int get_settings() override;
int set_pit_mode(bool onoff) override;
bool print_settings() override;
bool copy_to(vtx_s *msg) override;
// Additional methods not part of the Protocol interface
int set_frequency(uint16_t frequency_MHz, bool pit = false);
int set_power(uint8_t power_level, bool is_dBm = true);
int set_operating_mode(uint8_t mode);
private:
int rx_parser(uint8_t c) override;
int transmit(const uint8_t *buf, size_t len);
static uint8_t crc8(const uint8_t *data, uint8_t len);
static bool is_rsp(uint8_t cmd, uint8_t length);
enum {
COMMAND_GET_SETTINGS = 0x01,
COMMAND_SET_POWER = 0x02,
COMMAND_SET_CHANNEL = 0x03,
COMMAND_SET_FREQUENCY = 0x04,
COMMAND_SET_MODE = 0x05,
COMMAND_GET_SETTINGS_V2 = 0x09,
COMMAND_GET_SETTINGS_V21 = 0x11,
};
enum {
SET_MODE_IN_RANGE = 0b0001,
SET_MODE_OUT_RANGE = 0b0010,
SET_MODE_DISABLE_PIT_MODE = 0b0100,
SET_MODE_UNLOCKED = 0b1000,
};
Settings _settings{.version = 99};
uint8_t _read_length{};
uint8_t _read_data_length{};
};
}
+294
View File
@@ -0,0 +1,294 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "tramp.h"
#include <string.h>
#include <cerrno>
#include <drivers/drv_hrt.h>
#include <px4_platform_common/log.h>
#include <math.h>
namespace vtx
{
using namespace time_literals;
int Tramp::get_settings()
{
const int rv = get_status();
if (rv != 0) { return rv; }
px4_usleep(30_ms);
return get_temperature();
}
bool Tramp::print_settings()
{
PX4_INFO("Tramp:");
PX4_INFO(" frequency: %hu MHz", _settings.frequency);
PX4_INFO(" requested power: %hu mW", _settings.requested_power_mW);
PX4_INFO(" power: %hu mW", _settings.power_mW);
PX4_INFO(" pit mode: %s", _settings.pit_mode ? "on" : "off");
PX4_INFO(" temperature: %hi C", _settings.temperature);
PX4_INFO(" min frequency: %hu MHz", _settings.min_frequency);
PX4_INFO(" max frequency: %hu MHz", _settings.max_frequency);
PX4_INFO(" max power: %hu mW", _settings.max_power_mW);
return true;
}
bool Tramp::copy_to(vtx_s *msg)
{
msg->protocol = vtx_s::PROTOCOL_TRAMP;
msg->frequency = _settings.frequency;
msg->power_level = _requested_power_level;
msg->mode = _settings.pit_mode ? vtx_s::MODE_PIT : vtx_s::MODE_NORMAL;
return true;
}
int Tramp::get_status()
{
const uint8_t buf[] = {COMMAND_GET_SETTINGS};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
_settings.frequency = (_rx_buf[2] | (_rx_buf[3] << 8));
_settings.requested_power_mW = (_rx_buf[4] | (_rx_buf[5] << 8));
_settings.control_mode = _rx_buf[6];
_settings.pit_mode = _rx_buf[7];
_settings.power_mW = (_rx_buf[8] | (_rx_buf[9] << 8));
return PX4_OK;
}
int Tramp::get_temperature()
{
const uint8_t buf[] = {COMMAND_GET_TEMPERATURE};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
_settings.temperature = int16_t(_rx_buf[6] | (_rx_buf[7] << 8));
return PX4_OK;
}
int Tramp::reset()
{
const uint8_t buf[] = {COMMAND_RESET};
const int rv = transmit(buf, sizeof(buf));
if (rv != 0) { return rv; }
_settings.min_frequency = int16_t(_rx_buf[2] | (_rx_buf[3] << 8));
_settings.max_frequency = int16_t(_rx_buf[4] | (_rx_buf[5] << 8));
_settings.max_power_mW = int16_t(_rx_buf[6] | (_rx_buf[7] << 8));
return PX4_OK;
}
int Tramp::set_power(int16_t power_level)
{
int16_t power_mW;
_requested_power_level = -1;
if (power_level < 0) {
power_mW = -power_level;
} else {
power_mW = vtxtable().power_value(power_level);
if (power_mW == 0) { return -EINVAL; }
_requested_power_level = power_level;
}
const uint8_t buf[] = {COMMAND_SET_POWER, uint8_t(power_mW), uint8_t(power_mW >> 8)};
int rv = transmit(buf, sizeof(buf));
if (rv) { return rv; }
px4_usleep(30_ms);
rv = get_status();
if (rv) { return rv; }
return (_settings.requested_power_mW == power_mW) ? PX4_OK : PX4_ERROR;
}
int Tramp::set_frequency(int16_t frequency_MHz)
{
if (frequency_MHz < 0) { return -EINVAL; } // Tramp does not support pit frequency setting
const uint8_t buf[] = {COMMAND_SET_FREQUENCY, uint8_t(frequency_MHz), uint8_t(frequency_MHz >> 8)};
int rv = transmit(buf, sizeof(buf));
if (rv) { return rv; }
px4_usleep(30_ms);
rv = get_status();
if (rv) { return rv; }
return (_settings.frequency == frequency_MHz) ? PX4_OK : PX4_ERROR;
}
int Tramp::set_pit_mode(bool onoff)
{
const uint8_t mode = onoff ? 0u : 1u;
const uint8_t buf[] = {COMMAND_SET_MODE, mode};
int rv = transmit(buf, sizeof(buf));
if (rv) { return rv; }
px4_usleep(30_ms);
rv = get_status();
if (rv) { return rv; }
return (_settings.pit_mode == onoff ? 1u : 0u) ? PX4_OK : PX4_ERROR;
}
int Tramp::transmit(const uint8_t *buf, size_t len)
{
if (len > 28) { return -1; }
memset(_tx_buf + 1, 0, sizeof(_tx_buf) - 1);
// copy the message data
memcpy(_tx_buf + 1, buf, len);
// compute the CRC
_tx_buf[offsetof(Frame, crc)] = crc8(_tx_buf);
// send command
if (_serial->write(_tx_buf, sizeof(Frame)) < ssize_t(sizeof(Frame))) {
return -errno;
}
_serial->flush();
return rx_msg();
}
int Tramp::rx_parser(uint8_t c)
{
enum {
SYNC = 0,
COMMAND = 1,
DATA = 2,
CRC = 3,
END = 4,
};
static constexpr uint8_t CRCPOS{offsetof(Frame, crc)};
switch (_read_state) {
case SYNC:
PX4_DEBUG("SYNC %x", c);
if (c == 0x0F) {
_rx_buf[0] = c;
_read_state = COMMAND;
return 1;
} else {
_read_state = SYNC;
return 1;
}
case COMMAND:
PX4_DEBUG("COMMAND %x", c);
switch (c) {
case COMMAND_RESET:
case COMMAND_GET_TEMPERATURE:
case COMMAND_GET_SETTINGS:
case COMMAND_SET_POWER:
case COMMAND_SET_FREQUENCY:
case COMMAND_SET_MODE:
_rx_buf[1] = c;
_read_state = DATA;
_read_data_length = 2;
return CRCPOS;
}
_read_state = SYNC;
return 1;
case DATA:
PX4_DEBUG("DATA %x", c);
_rx_buf[_read_data_length] = c;
if (++_read_data_length >= CRCPOS) {
_read_state = CRC;
}
return sizeof(Frame) - _read_data_length;
case CRC:
PX4_DEBUG("CRC %x", c);
_read_state = END;
_rx_buf[CRCPOS] = c;
return 1;
case END:
PX4_DEBUG("END %x", c);
_read_state = SYNC;
// small letter commands with empty payloads were sent by us
if ((_rx_buf[1] & 0x20) && !_rx_buf[2] && !_rx_buf[3]) { return 1; }
// large letter command do not get a response
// Check the CRC for the received data
return (_rx_buf[CRCPOS] == crc8(_rx_buf)) ? 0 : -CRC;
}
return -6000;
}
uint8_t Tramp::crc8(const uint8_t *data)
{
uint8_t crc{0};
for (uint_fast8_t ii{1}; ii < offsetof(Frame, crc); ii++) {
crc += data[ii];
}
return crc;
}
}
+112
View File
@@ -0,0 +1,112 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include "protocol.h"
namespace vtx
{
/**
* Implementation for working with Tramp protocol.
* @author Niklas Hauser <niklas@auterion.com>
*/
class Tramp final : public Protocol
{
public:
static constexpr int BAUDRATE{9600};
struct Settings {
// command 'v'
uint16_t frequency;
uint16_t requested_power_mW;
uint8_t control_mode;
uint8_t pit_mode;
uint16_t power_mW;
// command 's'
int16_t temperature;
// command 'r'
uint16_t min_frequency;
uint16_t max_frequency;
uint16_t max_power_mW;
};
const Settings &settings() const { return _settings; }
inline Tramp() : Protocol(BAUDRATE)
{
_tx_buf[0] = 0x0F;
}
int get_settings() override;
int reset() override;
int set_power(int16_t power_mW) override;
int set_frequency(int16_t frequency_MHz) override;
int set_pit_mode(bool onoff) override;
bool print_settings() override;
bool copy_to(vtx_s *msg) override;
// Additional methods not part of the Protocol interface
int get_status();
int get_temperature();
private:
int rx_parser(uint8_t c) override;
int transmit(const uint8_t *buf, size_t len);
static uint8_t crc8(const uint8_t *data);
enum {
COMMAND_RESET = 'r', // 0x72
COMMAND_GET_TEMPERATURE = 's', // 0x73
COMMAND_GET_SETTINGS = 'v', // 0x76
COMMAND_SET_POWER = 'P', // 0x50
COMMAND_SET_FREQUENCY = 'F', // 0x46
COMMAND_SET_MODE = 'I', // 0x49
};
struct Frame {
uint8_t start{0x0F};
uint8_t command;
uint8_t payload[12];
uint8_t crc;
uint8_t end{0x00};
} __attribute__((packed));
static_assert(sizeof(Frame) == 16, "Tramp frame size wrong");
Settings _settings{};
uint8_t _read_data_length{};
int8_t _requested_power_level{-1};
};
} // namespace vtx
+41
View File
@@ -0,0 +1,41 @@
############################################################################
#
# Copyright (c) 2025 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE drivers__vtxtable
MAIN vtxtable
COMPILE_FLAGS
SRCS
VtxTable.cpp
DEPENDS
crc
)
+28
View File
@@ -0,0 +1,28 @@
menuconfig DRIVERS_VTXTABLE
bool "VTX Table"
default n
---help---
Manages the configuration of VTX frequencies and power levels.
if DRIVERS_VTXTABLE
config VTXTABLE_CONFIG_FILE
string "VTX Configuration File Path"
default "/fs/microsd/vtx_config"
---help---
Allows saving/loading the VTX table and aux config
from a file system.
config VTXTABLE_USE_STORAGE
bool
default y if VTXTABLE_CONFIG_FILE != ""
default n if !(VTXTABLE_CONFIG_FILE != "")
config VTXTABLE_AUX_MAP
string "VTX Auxiliary Map"
default n
---help---
Enable the AUX map for mapping AUX channel ranges to VTX
table settings.
endif # DRIVERS_VTXTABLE
+369
View File
@@ -0,0 +1,369 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "VtxTable.hpp"
#include <px4_platform_common/log.h>
#include <px4_platform_common/module.h>
#ifdef CONFIG_VTXTABLE_USE_STORAGE
#include <px4_platform_common/workqueue.h>
#endif
using namespace time_literals;
static vtx::Config vtxtable_data;
vtx::Config &vtxtable()
{
return vtxtable_data;
}
#ifdef CONFIG_VTXTABLE_USE_STORAGE
int vtxtable_store(const char *filename)
{
if (filename == nullptr) { filename = CONFIG_VTXTABLE_CONFIG_FILE; }
const int rv = vtxtable().store(filename);
if (rv < 0) {
PX4_ERR("%s is not accessible!", filename);
return PX4_ERROR;
}
PX4_INFO("saved to %s", filename);
return PX4_OK;
}
int vtxtable_load(const char *filename)
{
if (filename == nullptr) { filename = CONFIG_VTXTABLE_CONFIG_FILE; }
const int rv = vtxtable().load(filename);
if (rv < 0) {
switch (rv) {
case -ENOENT:
PX4_ERR("%s not found!", filename);
break;
case -EPROTO:
PX4_ERR("VTX config serialization format is unsupported in %s!", filename);
break;
case -EBADMSG:
PX4_ERR("VTX config has a corrupt CRC in %s!", filename);
break;
case -EMSGSIZE:
PX4_ERR("VTX config in %s is incomplete!", filename);
break;
default:
PX4_ERR("Loading VTX config from %s failed!", filename);
break;
}
return PX4_ERROR;
}
PX4_INFO("loaded from %s", filename);
return PX4_OK;
}
static struct work_s storing_work;
#endif
static void vtxtable_autosave()
{
#ifdef CONFIG_VTXTABLE_USE_STORAGE
work_queue(LPWORK, &storing_work, [](void *) { vtxtable_store(nullptr); }, nullptr, USEC2TICK(1_s));
#endif
}
static int vtxtable_print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
Manages the VTX frequency, power level and RC mapping table for VTX configuration.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("vtxtable", "driver");
PRINT_MODULE_USAGE_COMMAND_DESCR("status", "Shows the current VTX table configuration.");
PRINT_MODULE_USAGE_COMMAND_DESCR("name", "Sets the VTX table name: <string>");
PRINT_MODULE_USAGE_COMMAND_DESCR("bands", "Sets the number of bands: <int>");
PRINT_MODULE_USAGE_COMMAND_DESCR("band", "Sets the band frequencies: <1-index> <name> <letter> <attribute> <frequencies...>");
PRINT_MODULE_USAGE_COMMAND_DESCR("channels", "Sets the number of channels: <int>");
PRINT_MODULE_USAGE_COMMAND_DESCR("powerlevels", "Sets number of power levels: <int>");
PRINT_MODULE_USAGE_COMMAND_DESCR("powervalues", "Sets the power level values: <int...>");
PRINT_MODULE_USAGE_COMMAND_DESCR("powerlabels", "Sets the power level labels: <3 chars...>");
#ifdef CONFIG_VTXTABLE_AUX_MAP
PRINT_MODULE_USAGE_COMMAND_DESCR("<int>", "Sets an entry in the mapping table: <0-index> <aux channel> <band> <channel> <power level> <start range> <end range>");
#endif
PRINT_MODULE_USAGE_COMMAND_DESCR("clear", "Clears the VTX table configuration.");
#ifdef CONFIG_VTXTABLE_USE_STORAGE
PRINT_MODULE_USAGE_COMMAND_DESCR("save", "Saves the VTX config to a file: <file>");
PRINT_MODULE_USAGE_COMMAND_DESCR("load", "Loads the VTX config from a file: <file>");
#endif
return 0;
}
void vtxtable_print_status()
{
if (vtxtable().bands() == 0) {
PX4_INFO("VTX table: <not configured>");
} else {
PX4_INFO("VTX table %ux%u: %s", vtxtable().bands(), vtxtable().channels(), vtxtable().name());
vtxtable_print_frequencies();
}
if (vtxtable().power_levels() == 0) {
PX4_INFO("Power levels: <not configured>");
} else {
PX4_INFO("Power levels:");
vtxtable_print_power_levels();
}
#ifdef CONFIG_VTXTABLE_AUX_MAP
if (vtxtable().map_size() == 0) {
PX4_INFO("RC mapping: <not configured>");
} else {
PX4_INFO("RC mapping:");
vtxtable_print_aux_map();
}
#endif
}
int vtxtable_custom_command(int argc, char *argv[])
{
if (!strcmp(argv[0], "status")) {
vtxtable_print_status();
return PX4_OK;
}
if (!strcmp(argv[0], "name")) {
if (argc < 2) {
return vtxtable_print_usage("not enough arguments");
}
vtxtable_autosave();
return vtxtable().set_name(argv[1]) ? PX4_OK : PX4_ERROR;
}
if (!strcmp(argv[0], "band")) {
if (argc < 5) {
return vtxtable_print_usage("not enough arguments");
}
const size_t band = atoi(argv[1]) - 1;
vtxtable().set_band_name(band, argv[2]);
vtxtable().set_band_letter(band, argv[3][0]);
vtxtable().set_band_attribute(band,
argv[4][0] == 'F' ? vtx::Config::BandAttribute::FACTORY : vtx::Config::BandAttribute::CUSTOM);
for (int i = 5; i < argc; i++) {
vtxtable().set_frequency(band, i - 5, atoi(argv[i]));
}
vtxtable_autosave();
return PX4_OK;
}
if (!strcmp(argv[0], "bands")) {
if (argc < 2) {
return vtxtable_print_usage("not enough arguments");
}
vtxtable_autosave();
return vtxtable().set_bands(atoi(argv[1])) ? PX4_OK : PX4_ERROR;
}
if (!strcmp(argv[0], "channels")) {
if (argc < 2) {
return vtxtable_print_usage("not enough arguments");
}
vtxtable_autosave();
return vtxtable().set_channels(atoi(argv[1])) ? PX4_OK : PX4_ERROR;
}
if (!strcmp(argv[0], "powervalues")) {
uint8_t levels{1};
for (; levels < argc; levels++) {
vtxtable().set_power_value(levels - 1, atoi(argv[levels]));
}
vtxtable_autosave();
return PX4_OK;
}
if (!strcmp(argv[0], "powerlabels")) {
uint8_t levels{1};
for (; levels < argc; levels++) {
vtxtable().set_power_label(levels - 1, argv[levels]);
}
vtxtable_autosave();
return PX4_OK;
}
if (!strcmp(argv[0], "powerlevels")) {
if (argc < 2) {
return vtxtable_print_usage("not enough arguments");
}
vtxtable_autosave();
return vtxtable().set_power_levels(atoi(argv[1])) ? PX4_OK : PX4_ERROR;
}
#ifdef CONFIG_VTXTABLE_AUX_MAP
char *endptr {};
strtol(argv[0], &endptr, 10);
if (endptr != argv[0]) {
if (argc < 7) {
return vtxtable_print_usage("not enough arguments");
}
vtxtable_autosave();
return vtxtable().set_map_entry(
atoi(argv[0]), atoi(argv[1]) + 4 - 1, atoi(argv[2]),
atoi(argv[3]), atoi(argv[4]), atoi(argv[5]), atoi(argv[6])) ? PX4_OK : PX4_ERROR;
}
#endif
if (!strcmp(argv[0], "clear")) {
vtxtable().clear();
return PX4_OK;
}
#ifdef CONFIG_VTXTABLE_USE_STORAGE
const char *const filename = (argc == 2) ? argv[1] : nullptr;
if (!strcmp(argv[0], "save")) {
return vtxtable_store(filename);
}
if (!strcmp(argv[0], "load")) {
return vtxtable_load(filename);
}
#endif
return vtxtable_print_usage("unknown command");
}
void vtxtable_print_frequencies()
{
for (uint8_t b = 0; b < vtxtable().bands(); b++) {
PX4_INFO_RAW("INFO [vtxtable] %c: %-*s=", vtxtable().band_letter(b), vtx::Config::NAME_LENGTH, vtxtable().band_name(b));
for (uint8_t c = 0; c < vtxtable().channels(); c++) {
PX4_INFO_RAW(" %4hu", vtxtable().frequency(b, c));
}
if (vtxtable().band_attribute(b) == vtx::Config::BandAttribute::CUSTOM) {
PX4_INFO_RAW(" CUSTOM\n");
} else {
PX4_INFO_RAW("\n");
}
}
}
void vtxtable_print_power_levels()
{
for (uint8_t p = 0; p < vtxtable().power_levels(); p++) {
PX4_INFO(" %u: %2hi = %s", p + 1, vtxtable().power_value(p), vtxtable().power_label(p));
}
}
#ifdef CONFIG_VTXTABLE_AUX_MAP
void vtxtable_print_aux_map()
{
for (uint8_t i = 0; i < vtx::Config::MAP_LENGTH; i++) {
uint8_t rc_channel{}, band{}, channel{};
int8_t power_level{};
uint16_t start_range{}, end_range{};
vtxtable().map_entry(i, &rc_channel, &band, &channel, &power_level, &start_range, &end_range);
if (!start_range && !end_range) { break; }
if (!band && !channel) {
if (power_level == -1) {
PX4_INFO(" %2u: Ch%-2u, %4hu - %4hu = pit mode",
i, rc_channel, start_range, end_range);
} else {
PX4_INFO(" %2u: Ch%-2u, %4hu - %4hu = %s",
i, rc_channel, start_range, end_range,
vtxtable().power_label(power_level - 1));
}
} else {
PX4_INFO(" %2u: Ch%-2u, %4hu - %4hu = %u + %u",
i, rc_channel, start_range, end_range,
band, channel);
}
}
}
#endif
extern "C" __EXPORT int vtxtable_main(int argc, char *argv[])
{
if (argc <= 1) {
return vtxtable_print_usage("missing command");
}
argc--; argv++;
return vtxtable_custom_command(argc, argv);
}
+53
View File
@@ -0,0 +1,53 @@
/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include "config.h"
/**
* @author Niklas Hauser <niklas@auterion.com>
*/
extern vtx::Config &vtxtable();
#ifdef CONFIG_VTXTABLE_USE_STORAGE
extern int vtxtable_store(const char *filename);
extern int vtxtable_load(const char *filename);
#endif
extern void vtxtable_print_frequencies();
extern void vtxtable_print_power_levels();
#ifdef CONFIG_VTXTABLE_AUX_MAP
extern void vtxtable_print_aux_map();
#endif
File diff suppressed because it is too large Load Diff
+1
View File
@@ -150,6 +150,7 @@ void LoggedTopics::add_default_topics()
add_topic("vehicle_rates_setpoint", 20);
add_topic("vehicle_roi", 1000);
add_topic("vehicle_status");
add_topic("vtx");
add_optional_topic("vtol_vehicle_status", 200);
add_topic("wind", 1000);
add_topic("fixed_wing_lateral_setpoint");