mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-06-01 02:55:07 +08:00
feat(crsf_rc): add CRSF receiver bind command (#26790)
* feat(crsf_rc): add CRSF receiver bind command Add ability to initiate CRSF receiver binding from QGroundControl or the NSH console. When MAV_CMD_START_RX_PAIR is received with RC_TYPE_CRSF, the driver sends the CRSF bind command frame over UART. Binding is rejected when armed or on singlewire configurations. Also adds RC_TYPE and RC_SUB_TYPE constants to VehicleCommand.msg and replaces magic numbers in DsmRc and RCInput drivers. Based on PX4/PX4-Autopilot#23294. * style(crsf_rc): use C++ style comment * style(crsf_rc): zero-init vcmd, remove noisy comments, drop unused enum value * fix(rc): check write return value in BindCRSF, guard Spektrum bind against invalid sub-type * fix(rc): warn and deny invalid Spektrum bind sub-type Previously, an unrecognized param2 sub-type would silently leave dsm_bind_pulses at 0 and return the generic UNSUPPORTED ACK. Add an explicit else-branch that logs a PX4_WARN and returns DENIED so users get clear feedback in QGC.
This commit is contained in:
@@ -191,6 +191,13 @@ uint8 ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TANGENT_TO_CIRCLE = 3
|
|||||||
uint8 ORBIT_YAW_BEHAVIOUR_RC_CONTROLLED = 4
|
uint8 ORBIT_YAW_BEHAVIOUR_RC_CONTROLLED = 4
|
||||||
uint8 ORBIT_YAW_BEHAVIOUR_UNCHANGED = 5
|
uint8 ORBIT_YAW_BEHAVIOUR_UNCHANGED = 5
|
||||||
|
|
||||||
|
# Used as param1&2 in CMD_START_RX_PAIR.
|
||||||
|
uint8 RC_TYPE_SPEKTRUM = 0
|
||||||
|
uint8 RC_TYPE_CRSF = 1
|
||||||
|
uint8 RC_SUB_TYPE_SPEKTRUM_DSM2 = 0
|
||||||
|
uint8 RC_SUB_TYPE_SPEKTRUM_DSMX = 1
|
||||||
|
uint8 RC_SUB_TYPE_SPEKTRUM_DSMX8 = 2
|
||||||
|
|
||||||
# Used as param1 in ARM_DISARM command.
|
# Used as param1 in ARM_DISARM command.
|
||||||
int8 ARMING_ACTION_DISARM = 0
|
int8 ARMING_ACTION_DISARM = 0
|
||||||
int8 ARMING_ACTION_ARM = 1
|
int8 ARMING_ACTION_ARM = 1
|
||||||
|
|||||||
@@ -37,11 +37,6 @@
|
|||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include <uORB/topics/battery_status.h>
|
|
||||||
#include <uORB/topics/vehicle_attitude.h>
|
|
||||||
#include <uORB/topics/sensor_gps.h>
|
|
||||||
#include <uORB/topics/vehicle_status.h>
|
|
||||||
|
|
||||||
using namespace time_literals;
|
using namespace time_literals;
|
||||||
|
|
||||||
ModuleBase::Descriptor CrsfRc::desc{task_spawn, custom_command, print_usage};
|
ModuleBase::Descriptor CrsfRc::desc{task_spawn, custom_command, print_usage};
|
||||||
@@ -192,6 +187,43 @@ void CrsfRc::Run()
|
|||||||
const hrt_abstime time_now_us = hrt_absolute_time();
|
const hrt_abstime time_now_us = hrt_absolute_time();
|
||||||
perf_count_interval(_cycle_interval_perf, time_now_us);
|
perf_count_interval(_cycle_interval_perf, time_now_us);
|
||||||
|
|
||||||
|
if (_vehicle_status_sub.updated()) {
|
||||||
|
vehicle_status_s vehicle_status;
|
||||||
|
|
||||||
|
if (_vehicle_status_sub.copy(&vehicle_status)) {
|
||||||
|
_armed = (vehicle_status.arming_state == vehicle_status_s::ARMING_STATE_ARMED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle_command_s vcmd{};
|
||||||
|
|
||||||
|
if (_vehicle_cmd_sub.update(&vcmd)) {
|
||||||
|
if (vcmd.command == vehicle_command_s::VEHICLE_CMD_START_RX_PAIR) {
|
||||||
|
uint8_t cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_UNSUPPORTED;
|
||||||
|
|
||||||
|
if (!_is_singlewire && !_armed) {
|
||||||
|
if ((int)vcmd.param1 == vehicle_command_s::RC_TYPE_CRSF) {
|
||||||
|
if (BindCRSF()) {
|
||||||
|
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_TEMPORARILY_REJECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish acknowledgement
|
||||||
|
vehicle_command_ack_s command_ack{};
|
||||||
|
command_ack.command = vcmd.command;
|
||||||
|
command_ack.result = cmd_ret;
|
||||||
|
command_ack.target_system = vcmd.source_system;
|
||||||
|
command_ack.target_component = vcmd.source_component;
|
||||||
|
command_ack.timestamp = hrt_absolute_time();
|
||||||
|
uORB::Publication<vehicle_command_ack_s> vehicle_command_ack_pub{ORB_ID(vehicle_command_ack)};
|
||||||
|
vehicle_command_ack_pub.publish(command_ack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read all available data from the serial RC input UART
|
// Read all available data from the serial RC input UART
|
||||||
int new_bytes = _uart->readAtLeast(&_rcs_buf[0], RC_MAX_BUFFER_SIZE, 1, 100);
|
int new_bytes = _uart->readAtLeast(&_rcs_buf[0], RC_MAX_BUFFER_SIZE, 1, 100);
|
||||||
|
|
||||||
@@ -518,6 +550,23 @@ bool CrsfRc::SendTelemetryFlightMode(const char *flight_mode)
|
|||||||
return _uart->write((void *) buf, (size_t) offset);
|
return _uart->write((void *) buf, (size_t) offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CrsfRc::BindCRSF()
|
||||||
|
{
|
||||||
|
uint8_t bind_frame[] = {
|
||||||
|
0xC8, // sync
|
||||||
|
0x07, // frame length
|
||||||
|
(uint8_t)crsf_frame_type_t::command,
|
||||||
|
(uint8_t)crsf_address_t::crsf_receiver,
|
||||||
|
(uint8_t)crsf_address_t::flight_controller,
|
||||||
|
(uint8_t)crsf_sub_command_t::subcmd_rx,
|
||||||
|
(uint8_t)crsf_sub_command_t::subcmd_rx_bind,
|
||||||
|
0x9E, // command CRC8
|
||||||
|
0xE8, // packet CRC8
|
||||||
|
};
|
||||||
|
|
||||||
|
return _uart->write((void *)bind_frame, sizeof(bind_frame)) == sizeof(bind_frame);
|
||||||
|
}
|
||||||
|
|
||||||
int CrsfRc::print_status()
|
int CrsfRc::print_status()
|
||||||
{
|
{
|
||||||
if (_device[0] != '\0') {
|
if (_device[0] != '\0') {
|
||||||
@@ -547,6 +596,16 @@ int CrsfRc::print_status()
|
|||||||
|
|
||||||
int CrsfRc::custom_command(int argc, char *argv[])
|
int CrsfRc::custom_command(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
if (!strcmp(argv[0], "bind")) {
|
||||||
|
uORB::Publication<vehicle_command_s> vehicle_command_pub{ORB_ID(vehicle_command)};
|
||||||
|
vehicle_command_s vcmd{};
|
||||||
|
vcmd.command = vehicle_command_s::VEHICLE_CMD_START_RX_PAIR;
|
||||||
|
vcmd.param1 = vehicle_command_s::RC_TYPE_CRSF;
|
||||||
|
vcmd.timestamp = hrt_absolute_time();
|
||||||
|
vehicle_command_pub.publish(vcmd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_RC_CRSF_INJECT
|
#ifdef CONFIG_RC_CRSF_INJECT
|
||||||
|
|
||||||
if (!strcmp(argv[0], "start")) {
|
if (!strcmp(argv[0], "start")) {
|
||||||
@@ -604,6 +663,7 @@ This module parses the CRSF RC uplink protocol and generates CRSF downlink telem
|
|||||||
PRINT_MODULE_USAGE_SUBCATEGORY("radio_control");
|
PRINT_MODULE_USAGE_SUBCATEGORY("radio_control");
|
||||||
PRINT_MODULE_USAGE_COMMAND("start");
|
PRINT_MODULE_USAGE_COMMAND("start");
|
||||||
PRINT_MODULE_USAGE_PARAM_STRING('d', "/dev/ttyS3", "<file:dev>", "RC device", true);
|
PRINT_MODULE_USAGE_PARAM_STRING('d', "/dev/ttyS3", "<file:dev>", "RC device", true);
|
||||||
|
PRINT_MODULE_USAGE_COMMAND_DESCR("bind", "Send a CRSF bind command (not available on singlewire)");
|
||||||
#ifdef CONFIG_RC_CRSF_INJECT
|
#ifdef CONFIG_RC_CRSF_INJECT
|
||||||
PRINT_MODULE_USAGE_COMMAND_DESCR("inject", "Inject frame data bytes (for testing)");
|
PRINT_MODULE_USAGE_COMMAND_DESCR("inject", "Inject frame data bytes (for testing)");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
#include <uORB/topics/vehicle_attitude.h>
|
#include <uORB/topics/vehicle_attitude.h>
|
||||||
#include <uORB/topics/sensor_gps.h>
|
#include <uORB/topics/sensor_gps.h>
|
||||||
#include <uORB/topics/vehicle_status.h>
|
#include <uORB/topics/vehicle_status.h>
|
||||||
|
#include <uORB/topics/vehicle_command.h>
|
||||||
|
#include <uORB/topics/vehicle_command_ack.h>
|
||||||
|
|
||||||
using namespace device;
|
using namespace device;
|
||||||
|
|
||||||
@@ -92,10 +94,13 @@ private:
|
|||||||
|
|
||||||
bool SendTelemetryFlightMode(const char *flight_mode);
|
bool SendTelemetryFlightMode(const char *flight_mode);
|
||||||
|
|
||||||
|
bool BindCRSF();
|
||||||
|
|
||||||
Serial *_uart = nullptr; ///< UART interface to RC
|
Serial *_uart = nullptr; ///< UART interface to RC
|
||||||
|
|
||||||
char _device[20] {}; ///< device / serial port path
|
char _device[20] {}; ///< device / serial port path
|
||||||
bool _is_singlewire{false};
|
bool _is_singlewire{false};
|
||||||
|
bool _armed{false};
|
||||||
|
|
||||||
static constexpr size_t RC_MAX_BUFFER_SIZE{64};
|
static constexpr size_t RC_MAX_BUFFER_SIZE{64};
|
||||||
uint8_t _rcs_buf[RC_MAX_BUFFER_SIZE] {};
|
uint8_t _rcs_buf[RC_MAX_BUFFER_SIZE] {};
|
||||||
@@ -114,6 +119,7 @@ private:
|
|||||||
uORB::Subscription _vehicle_attitude_sub{ORB_ID(vehicle_attitude)};
|
uORB::Subscription _vehicle_attitude_sub{ORB_ID(vehicle_attitude)};
|
||||||
uORB::Subscription _vehicle_gps_position_sub{ORB_ID(vehicle_gps_position)};
|
uORB::Subscription _vehicle_gps_position_sub{ORB_ID(vehicle_gps_position)};
|
||||||
uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)};
|
uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)};
|
||||||
|
uORB::Subscription _vehicle_cmd_sub{ORB_ID(vehicle_command)};
|
||||||
|
|
||||||
enum class crsf_frame_type_t : uint8_t {
|
enum class crsf_frame_type_t : uint8_t {
|
||||||
gps = 0x02,
|
gps = 0x02,
|
||||||
@@ -140,6 +146,17 @@ private:
|
|||||||
attitude = 6,
|
attitude = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class crsf_address_t : uint8_t {
|
||||||
|
flight_controller = 0xC8,
|
||||||
|
crsf_receiver = 0xEC,
|
||||||
|
crsf_transmitter = 0xEE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class crsf_sub_command_t : uint8_t {
|
||||||
|
subcmd_rx = 0x10,
|
||||||
|
subcmd_rx_bind = 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
void WriteFrameHeader(uint8_t *buf, int &offset, const crsf_frame_type_t type, const uint8_t payload_size);
|
void WriteFrameHeader(uint8_t *buf, int &offset, const crsf_frame_type_t type, const uint8_t payload_size);
|
||||||
void WriteFrameCrc(uint8_t *buf, int &offset, const int buf_size);
|
void WriteFrameCrc(uint8_t *buf, int &offset, const int buf_size);
|
||||||
|
|
||||||
|
|||||||
@@ -166,25 +166,29 @@ void DsmRc::Run()
|
|||||||
#if defined(SPEKTRUM_POWER)
|
#if defined(SPEKTRUM_POWER)
|
||||||
|
|
||||||
if (!_rc_scan_locked && !_armed) {
|
if (!_rc_scan_locked && !_armed) {
|
||||||
if ((int)vcmd.param1 == 0) {
|
if ((int)vcmd.param1 == vehicle_command_s::RC_TYPE_SPEKTRUM) {
|
||||||
// DSM binding command
|
// DSM binding command
|
||||||
int dsm_bind_mode = (int)vcmd.param2;
|
int dsm_bind_mode = (int)vcmd.param2;
|
||||||
|
|
||||||
int dsm_bind_pulses = 0;
|
int dsm_bind_pulses = 0;
|
||||||
|
|
||||||
if (dsm_bind_mode == 0) {
|
if (dsm_bind_mode == vehicle_command_s::RC_SUB_TYPE_SPEKTRUM_DSM2) {
|
||||||
dsm_bind_pulses = DSM2_BIND_PULSES;
|
dsm_bind_pulses = DSM2_BIND_PULSES;
|
||||||
|
|
||||||
} else if (dsm_bind_mode == 1) {
|
} else if (dsm_bind_mode == vehicle_command_s::RC_SUB_TYPE_SPEKTRUM_DSMX) {
|
||||||
dsm_bind_pulses = DSMX_BIND_PULSES;
|
dsm_bind_pulses = DSMX_BIND_PULSES;
|
||||||
|
|
||||||
} else {
|
} else if (dsm_bind_mode == vehicle_command_s::RC_SUB_TYPE_SPEKTRUM_DSMX8) {
|
||||||
dsm_bind_pulses = DSMX8_BIND_PULSES;
|
dsm_bind_pulses = DSMX8_BIND_PULSES;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
PX4_WARN("invalid Spektrum bind sub-type: %d", dsm_bind_mode);
|
||||||
|
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
bind_spektrum(dsm_bind_pulses);
|
if (dsm_bind_pulses > 0 && bind_spektrum(dsm_bind_pulses)) {
|
||||||
|
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
|
||||||
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -388,25 +388,29 @@ void RCInput::Run()
|
|||||||
#if defined(SPEKTRUM_POWER)
|
#if defined(SPEKTRUM_POWER)
|
||||||
|
|
||||||
if (!_rc_scan_locked && !_armed) {
|
if (!_rc_scan_locked && !_armed) {
|
||||||
if ((int)vcmd.param1 == 0) {
|
if ((int)vcmd.param1 == vehicle_command_s::RC_TYPE_SPEKTRUM) {
|
||||||
// DSM binding command
|
// DSM binding command
|
||||||
int dsm_bind_mode = (int)vcmd.param2;
|
int dsm_bind_mode = (int)vcmd.param2;
|
||||||
|
|
||||||
int dsm_bind_pulses = 0;
|
int dsm_bind_pulses = 0;
|
||||||
|
|
||||||
if (dsm_bind_mode == 0) {
|
if (dsm_bind_mode == vehicle_command_s::RC_SUB_TYPE_SPEKTRUM_DSM2) {
|
||||||
dsm_bind_pulses = DSM2_BIND_PULSES;
|
dsm_bind_pulses = DSM2_BIND_PULSES;
|
||||||
|
|
||||||
} else if (dsm_bind_mode == 1) {
|
} else if (dsm_bind_mode == vehicle_command_s::RC_SUB_TYPE_SPEKTRUM_DSMX) {
|
||||||
dsm_bind_pulses = DSMX_BIND_PULSES;
|
dsm_bind_pulses = DSMX_BIND_PULSES;
|
||||||
|
|
||||||
} else {
|
} else if (dsm_bind_mode == vehicle_command_s::RC_SUB_TYPE_SPEKTRUM_DSMX8) {
|
||||||
dsm_bind_pulses = DSMX8_BIND_PULSES;
|
dsm_bind_pulses = DSMX8_BIND_PULSES;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
PX4_WARN("invalid Spektrum bind sub-type: %d", dsm_bind_mode);
|
||||||
|
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
bind_spektrum(dsm_bind_pulses);
|
if (dsm_bind_pulses > 0 && bind_spektrum(dsm_bind_pulses)) {
|
||||||
|
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
|
||||||
cmd_ret = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user