Skip to content

Commit e95bb7e

Browse files
committed
ArduCopter: implement MAV_CMD_DO_ORBIT in Guided mode
Implements MAV_CMD_DO_ORBIT (command 34) in ArduCopter Guided mode. Parameters: - param1: radius in meters (positive=CW, negative=CCW) - param2: tangential speed in m/s (0=default) - param3: yaw behaviour (ORBIT_YAW_BEHAVIOUR enum, all 5 values supported) - param4: number of turns (0=forever, NaN=preserve existing) - x/y: center latitude/longitude - z: altitude in meters Implementation: - New SubMode::Orbit in ModeGuided - circle_start(), orbit_run(), orbit_apply_yaw_behaviour() in mode_guided.cpp - handle_MAV_CMD_DO_ORBIT() in GCS_MAVLink_Copter.cpp - MAV_CMD_DO_ORBIT in command_long_stores_location() in GCS_Common.cpp - AC_COPTER_MODEGUIDED_ORBIT_ENABLED flash guard (default: MODE_GUIDED_ENABLED && HAL_PROGRAM_SIZE_LIMIT_KB>1024) - AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED in GCS_config.h, build_options.py, extract_features.py and test_build_options.py - Autotest DO_ORBIT in arducopter.py Fixes: - Double speed bug: removed duplicate update_ms() call in orbit_run() - Post-orbit hold: use hold_position() instead of pos_control_start() - Yaw consistency during approach, orbiting and after completion - Cross-platform compile: use copter.ahrs.get_yaw_rad() Addresses #15098
1 parent 3e40de5 commit e95bb7e

11 files changed

Lines changed: 370 additions & 2 deletions

File tree

ArduCopter/GCS_MAVLink_Copter.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ void GCS_MAVLINK_Copter::send_position_target_local_ned()
154154
POSITION_TARGET_TYPEMASK_YAW_IGNORE| POSITION_TARGET_TYPEMASK_YAW_RATE_IGNORE; // ignore everything except velocity & acceleration
155155
target_accel_ned_mss = copter.mode_guided.get_target_accel_NED_mss();
156156
break;
157+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
158+
case ModeGuided::SubMode::Orbit:
159+
if (copter.mode_guided.circle_moving_to_edge()) {
160+
// moving to edge - report WP position target
161+
type_mask = POSITION_TARGET_TYPEMASK_VX_IGNORE | POSITION_TARGET_TYPEMASK_VY_IGNORE | POSITION_TARGET_TYPEMASK_VZ_IGNORE |
162+
POSITION_TARGET_TYPEMASK_AX_IGNORE | POSITION_TARGET_TYPEMASK_AY_IGNORE | POSITION_TARGET_TYPEMASK_AZ_IGNORE |
163+
POSITION_TARGET_TYPEMASK_YAW_IGNORE| POSITION_TARGET_TYPEMASK_YAW_RATE_IGNORE;
164+
target_pos_ned_m = copter.mode_guided.get_target_pos_NED_m().tofloat();
165+
break;
166+
}
167+
// orbiting - no local position target to report
168+
return;
169+
#endif // AC_COPTER_MODEGUIDED_ORBIT_ENABLED
157170
}
158171

159172
mavlink_msg_position_target_local_ned_send(
@@ -552,8 +565,14 @@ MAV_RESULT GCS_MAVLINK_Copter::handle_command_int_packet(const mavlink_command_i
552565
return MAV_RESULT_ACCEPTED;
553566
}
554567
return MAV_RESULT_FAILED;
568+
555569
#endif
556570

571+
#if AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
572+
case MAV_CMD_DO_ORBIT:
573+
return handle_MAV_CMD_DO_ORBIT(packet);
574+
#endif // AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
575+
557576
default:
558577
return GCS_MAVLINK::handle_command_int_packet(packet, msg);
559578
}
@@ -837,6 +856,52 @@ MAV_RESULT GCS_MAVLINK_Copter::handle_command_pause_continue(const mavlink_comma
837856
return MAV_RESULT_DENIED;
838857
}
839858

859+
#if AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
860+
MAV_RESULT GCS_MAVLINK_Copter::handle_MAV_CMD_DO_ORBIT(const mavlink_command_int_t &packet)
861+
{
862+
// reject if not in Guided mode
863+
if (!copter.flightmode->in_guided_mode()) {
864+
return MAV_RESULT_FAILED;
865+
}
866+
867+
// param1: radius (m) - positive = CW, negative = CCW (MAVLink spec)
868+
if (isnan(packet.param1) || fabsf(packet.param1) < 0.1f) {
869+
return MAV_RESULT_DENIED;
870+
}
871+
const float radius_m = fabsf(packet.param1);
872+
const bool ccw = (packet.param1 < 0.0f);
873+
874+
// param2: tangential velocity (m/s) - optional, 0 or NaN = use default
875+
const float speed_ms = isnan(packet.param2) ? 0.0f : fabsf(packet.param2);
876+
877+
// param3: yaw behaviour (ORBIT_YAW_BEHAVIOUR enum), NaN or 0 = face center
878+
879+
// x/y: center coordinates
880+
if (packet.x == INT32_MAX || packet.y == INT32_MAX) {
881+
return MAV_RESULT_DENIED;
882+
}
883+
884+
// build center Location from packet
885+
if (!check_latlng(packet.x, packet.y)) {
886+
return MAV_RESULT_DENIED;
887+
}
888+
Location circle_center;
889+
if (!location_from_command_t(packet, circle_center)) {
890+
return MAV_RESULT_DENIED;
891+
}
892+
893+
// param4: Orbit around the centre point for this many radians. 0: Orbit forever
894+
// NaN means "do not change" - preserve existing turn count
895+
const bool update_turns = !isnan(packet.param4);
896+
const float turns = update_turns ? fabsf(packet.param4) : 0.0f;
897+
// param3: yaw behaviour (ORBIT_YAW_BEHAVIOUR enum), NaN or 0 = face center
898+
const ORBIT_YAW_BEHAVIOUR yaw_behaviour = (isnan(packet.param3) || packet.param3 < 0) ? ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TO_CIRCLE_CENTER : (ORBIT_YAW_BEHAVIOUR)(int)packet.param3;
899+
copter.mode_guided.orbit_start(circle_center, radius_m, ccw, speed_ms, update_turns, turns, yaw_behaviour);
900+
901+
return MAV_RESULT_ACCEPTED;
902+
}
903+
#endif // AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
904+
840905
// this is called on receipt of a MANUAL_CONTROL packet and is
841906
// expected to call manual_override to override RC input on desired
842907
// axes.

ArduCopter/GCS_MAVLink_Copter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <GCS_MAVLink/GCS.h>
44
#include <AP_Winch/AP_Winch_config.h>
55
#include "defines.h"
6+
#include "config.h"
67

78
#ifndef AC_MAVLINK_SOLO_BUTTON_COMMAND_HANDLING_ENABLED
89
#define AC_MAVLINK_SOLO_BUTTON_COMMAND_HANDLING_ENABLED 1
@@ -123,4 +124,8 @@ class GCS_MAVLINK_Copter : public GCS_MAVLINK
123124
MAV_RESULT handle_MAV_CMD_DO_WINCH(const mavlink_command_int_t &packet);
124125
#endif
125126

127+
#if AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
128+
MAV_RESULT handle_MAV_CMD_DO_ORBIT(const mavlink_command_int_t &packet);
129+
#endif
130+
126131
};

ArduCopter/config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@
178178
# define MODE_GUIDED_ENABLED 1
179179
#endif
180180

181+
#ifndef AC_COPTER_MODEGUIDED_ORBIT_ENABLED
182+
# define AC_COPTER_MODEGUIDED_ORBIT_ENABLED (MODE_GUIDED_ENABLED && HAL_PROGRAM_SIZE_LIMIT_KB>1024)
183+
#endif
184+
181185
//////////////////////////////////////////////////////////////////////////////
182186
// GuidedNoGPS mode - control vehicle's angles from GCS
183187
#ifndef MODE_GUIDED_NOGPS_ENABLED

ArduCopter/mode.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,11 @@ class ModeGuided : public Mode {
11531153
bool set_speed_up_ms(float speed_up_ms) override;
11541154
bool set_speed_down_ms(float speed_down_ms) override;
11551155

1156+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1157+
// true while moving to circle edge before orbiting
1158+
bool _circle_moving_to_edge = false;
1159+
#endif
1160+
11561161
// initialises position controller to implement take-off
11571162
// takeoff_alt_m is interpreted as alt-above-home (in m) or alt-above-terrain if a rangefinder is available
11581163
bool do_user_takeoff_start_m(float takeoff_alt_m) override;
@@ -1165,12 +1170,21 @@ class ModeGuided : public Mode {
11651170
VelAccel,
11661171
Accel,
11671172
Angle,
1173+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1174+
Orbit,
1175+
#endif
11681176
};
11691177

11701178
SubMode submode() const { return guided_mode; }
11711179

11721180
void angle_control_start();
11731181
void angle_control_run();
1182+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1183+
void orbit_start(const Location &circle_center, float radius_m, bool ccw, float speed_ms, bool update_turns, float turns, ORBIT_YAW_BEHAVIOUR yaw_behaviour = ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TO_CIRCLE_CENTER);
1184+
void orbit_run();
1185+
void orbit_apply_yaw_behaviour();
1186+
bool circle_moving_to_edge() const { return _circle_moving_to_edge; }
1187+
#endif // AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
11741188

11751189
// return guided mode timeout in milliseconds. Only used for velocity, acceleration, angle control, and angular rate control
11761190
uint32_t get_timeout_ms() const;
@@ -1228,6 +1242,13 @@ class ModeGuided : public Mode {
12281242
void pause_control_run();
12291243
void posvelaccel_control_run();
12301244
void set_yaw_state_rad(bool use_yaw, float yaw_rad, bool use_yaw_rate, float yaw_rate_rads, bool relative_angle);
1245+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1246+
float _orbit_rate_degs = 20.0f; // desired orbit rate in deg/s (signed for direction)
1247+
ORBIT_YAW_BEHAVIOUR _orbit_yaw_behaviour = ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TO_CIRCLE_CENTER;
1248+
float _orbit_turns = 0.0f;
1249+
float _orbit_angle_total_at_start = 0.0f;
1250+
bool _orbit_update_turns = true;
1251+
#endif
12311252

12321253
// controls which controller is run (pos or vel):
12331254
SubMode guided_mode = SubMode::TakeOff;

ArduCopter/mode_guided.cpp

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,18 @@ void ModeGuided::run()
106106
case SubMode::Angle:
107107
angle_control_run();
108108
break;
109+
110+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
111+
case SubMode::Orbit:
112+
if (_circle_moving_to_edge) {
113+
wp_control_run();
114+
} else {
115+
orbit_run();
116+
}
117+
break;
118+
#endif // AC_COPTER_MODEGUIDED_ORBIT_ENABLED
109119
}
110-
}
120+
}
111121

112122
// returns true if the Guided-mode-option is set (see GUID_OPTIONS)
113123
bool ModeGuided::option_is_enabled(Option option) const
@@ -147,6 +157,9 @@ bool ModeGuided::move_vehicle_on_ekf_reset() const
147157
case SubMode::WP:
148158
case SubMode::Pos:
149159
case SubMode::PosVelAccel:
160+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
161+
case SubMode::Orbit:
162+
#endif
150163
// these submodes have absolute position targets so we smoothly slew the target upon an ekf reset
151164
return true;
152165
}
@@ -241,6 +254,20 @@ void ModeGuided::wp_control_run()
241254
// run waypoint controller
242255
copter.failsafe_terrain_set_status(wp_nav->update_wpnav());
243256

257+
// if moving to circle edge, check if we've arrived
258+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
259+
if (guided_mode == SubMode::Orbit && _circle_moving_to_edge && wp_nav->reached_wp_destination()) {
260+
// initialise circle controller and switch to circle submode
261+
copter.circle_nav->init_NED_m(
262+
copter.circle_nav->get_center_NED_m(),
263+
copter.circle_nav->center_is_terrain_alt(),
264+
_orbit_rate_degs);
265+
_circle_moving_to_edge = false;
266+
orbit_apply_yaw_behaviour();
267+
guided_mode = SubMode::Orbit;
268+
}
269+
#endif // AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
270+
244271
// call z-axis position controller (wpnav should have already updated it's alt target)
245272
pos_control->D_update_controller();
246273

@@ -449,6 +476,21 @@ bool ModeGuided::get_wp(Location& destination) const
449476
case SubMode::Pos:
450477
destination = Location::from_ekf_offset_NED_m(guided_pos_target_ned_m, guided_is_terrain_alt ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
451478
return true;
479+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
480+
case SubMode::Orbit:
481+
if (_circle_moving_to_edge) {
482+
return wp_nav->get_oa_wp_destination(destination);
483+
}
484+
// already orbiting - return circle center
485+
{
486+
Location circle_center;
487+
if (!AP::ahrs().get_location_from_origin_offset_NED(circle_center, copter.circle_nav->get_center_NED_m())) {
488+
return false;
489+
}
490+
destination = circle_center;
491+
return true;
492+
}
493+
#endif // AP_MAVLINK_MAV_CMD_DO_ORBIT_ENABLED
452494
case SubMode::Angle:
453495
case SubMode::TakeOff:
454496
case SubMode::Accel:
@@ -1147,6 +1189,12 @@ float ModeGuided::wp_distance_m() const
11471189
return get_horizontal_distance(pos_control->get_pos_estimate_NED_m().xy().tofloat(), guided_pos_target_ned_m.xy().tofloat());
11481190
case SubMode::PosVelAccel:
11491191
return pos_control->get_pos_error_NE_m();
1192+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1193+
case SubMode::Orbit:
1194+
return _circle_moving_to_edge ?
1195+
wp_nav->get_wp_distance_to_destination_m() :
1196+
copter.circle_nav->get_distance_to_target_m();
1197+
#endif
11501198
default:
11511199
return 0.0f;
11521200
}
@@ -1161,6 +1209,12 @@ float ModeGuided::wp_bearing_deg() const
11611209
return degrees(get_bearing_rad(pos_control->get_pos_estimate_NED_m().xy().tofloat(), guided_pos_target_ned_m.xy().tofloat()));
11621210
case SubMode::PosVelAccel:
11631211
return degrees(pos_control->get_bearing_to_target_rad());
1212+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1213+
case SubMode::Orbit:
1214+
return _circle_moving_to_edge ?
1215+
degrees(wp_nav->get_wp_bearing_to_destination_rad()) :
1216+
degrees(copter.circle_nav->get_bearing_to_target_rad());
1217+
#endif
11641218
case SubMode::TakeOff:
11651219
case SubMode::Accel:
11661220
case SubMode::VelAccel:
@@ -1183,6 +1237,11 @@ float ModeGuided::crosstrack_error_m() const
11831237
case SubMode::VelAccel:
11841238
case SubMode::PosVelAccel:
11851239
return pos_control->crosstrack_error_m();
1240+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1241+
case SubMode::Orbit:
1242+
return _circle_moving_to_edge ?
1243+
wp_nav->crosstrack_error_m() : 0;
1244+
#endif
11861245
case SubMode::Angle:
11871246
// no track to have a crosstrack to
11881247
return 0;
@@ -1211,4 +1270,110 @@ bool ModeGuided::resume()
12111270
return true;
12121271
}
12131272

1214-
#endif
1273+
#if AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1274+
// orbit_start - initialise guided controller to fly a circle around a specified location
1275+
void ModeGuided::orbit_start(const Location &circle_center, float radius_m, bool ccw, float speed_ms, bool update_turns, float turns, ORBIT_YAW_BEHAVIOUR yaw_behaviour)
1276+
{
1277+
// set circle center, radius and direction
1278+
copter.circle_nav->set_center(circle_center);
1279+
// configure position controller speeds and accelerations
1280+
pos_control->NE_set_max_speed_accel_m(wp_nav->get_default_speed_NE_ms(), wp_nav->get_wp_acceleration_mss());
1281+
pos_control->NE_set_correction_speed_accel_m(wp_nav->get_default_speed_NE_ms(), wp_nav->get_wp_acceleration_mss());
1282+
pos_control->D_set_max_speed_accel_m(wp_nav->get_default_speed_down_ms(), wp_nav->get_default_speed_up_ms(), wp_nav->get_accel_D_mss());
1283+
pos_control->D_set_correction_speed_accel_m(wp_nav->get_default_speed_down_ms(), wp_nav->get_default_speed_up_ms(), wp_nav->get_accel_D_mss());
1284+
copter.circle_nav->set_radius_m(radius_m);
1285+
1286+
// convert tangential speed to angular rate, or use default rate with correct direction
1287+
if (is_positive(speed_ms) && is_positive(radius_m)) {
1288+
_orbit_rate_degs = degrees(speed_ms / radius_m);
1289+
} else {
1290+
_orbit_rate_degs = fabsf(copter.circle_nav->get_rate_degs());
1291+
}
1292+
if (ccw) {
1293+
_orbit_rate_degs = -_orbit_rate_degs;
1294+
}
1295+
copter.circle_nav->set_rate_degs(_orbit_rate_degs);
1296+
// store requested number of turns (0 = orbit forever)
1297+
if (update_turns) {
1298+
_orbit_turns = turns;
1299+
_orbit_angle_total_at_start = copter.circle_nav->get_angle_total_rad();
1300+
}
1301+
_orbit_yaw_behaviour = yaw_behaviour;
1302+
1303+
// check distance to edge of circle
1304+
Vector3p circle_edge_ned_m;
1305+
float dist_to_edge_m;
1306+
copter.circle_nav->get_closest_point_on_circle_NED_m(circle_edge_ned_m, dist_to_edge_m);
1307+
1308+
// if more than 3m away, fly to edge first
1309+
if (dist_to_edge_m > 3.0f) {
1310+
Location circle_edge = Location::from_ekf_offset_NED_m(circle_edge_ned_m, Location::AltFrame::ABOVE_ORIGIN);
1311+
circle_edge.copy_alt_from(circle_center);
1312+
if (!wp_nav->set_wp_destination_loc(circle_edge)) {
1313+
// terrain data missing
1314+
copter.failsafe_terrain_on_event();
1315+
return;
1316+
}
1317+
// during approach look in direction of travel (default WP yaw behaviour)
1318+
auto_yaw.set_mode_to_default(false);
1319+
guided_mode = SubMode::Orbit;
1320+
_circle_moving_to_edge = true;
1321+
} else {
1322+
// already at edge - start circling immediately
1323+
copter.circle_nav->init_NED_m(
1324+
copter.circle_nav->get_center_NED_m(),
1325+
copter.circle_nav->center_is_terrain_alt(),
1326+
_orbit_rate_degs);
1327+
// set yaw behaviour
1328+
orbit_apply_yaw_behaviour();
1329+
guided_mode = SubMode::Orbit;
1330+
}
1331+
}
1332+
1333+
// orbit_apply_yaw_behaviour - set yaw mode based on ORBIT_YAW_BEHAVIOUR parameter
1334+
void ModeGuided::orbit_apply_yaw_behaviour()
1335+
{
1336+
switch ((ORBIT_YAW_BEHAVIOUR)_orbit_yaw_behaviour) {
1337+
case ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TO_CIRCLE_CENTER:
1338+
default:
1339+
auto_yaw.set_mode(AutoYaw::Mode::CIRCLE);
1340+
break;
1341+
case ORBIT_YAW_BEHAVIOUR_HOLD_INITIAL_HEADING:
1342+
auto_yaw.set_fixed_yaw_rad(copter.ahrs.get_yaw_rad(), 0.0f, 0, false);
1343+
break;
1344+
case ORBIT_YAW_BEHAVIOUR_UNCONTROLLED:
1345+
auto_yaw.set_mode(AutoYaw::Mode::HOLD);
1346+
break;
1347+
case ORBIT_YAW_BEHAVIOUR_HOLD_FRONT_TANGENT_TO_CIRCLE:
1348+
auto_yaw.set_mode(AutoYaw::Mode::LOOK_AHEAD);
1349+
break;
1350+
case ORBIT_YAW_BEHAVIOUR_RC_CONTROLLED:
1351+
auto_yaw.set_mode(AutoYaw::Mode::PILOT_RATE);
1352+
break;
1353+
case ORBIT_YAW_BEHAVIOUR_UNCHANGED:
1354+
// do not change yaw mode
1355+
break;
1356+
}
1357+
}
1358+
1359+
// orbit_run - run circle controller
1360+
void ModeGuided::orbit_run()
1361+
{
1362+
// call circle controller
1363+
copter.failsafe_terrain_set_status(copter.circle_nav->update_ms(0.0f));
1364+
// check if we've completed the requested number of turns
1365+
if (is_positive(_orbit_turns)) {
1366+
const float turns_completed = fabsf(copter.circle_nav->get_angle_total_rad() / float(M_2PI));
1367+
if (turns_completed >= _orbit_turns) {
1368+
// orbit complete - hold position and maintain yaw behaviour
1369+
hold_position();
1370+
orbit_apply_yaw_behaviour();
1371+
return;
1372+
}
1373+
}
1374+
// call attitude, position and yaw controllers
1375+
pos_control->D_update_controller();
1376+
attitude_control->input_thrust_vector_heading(pos_control->get_thrust_vector(), auto_yaw.get_heading());
1377+
}
1378+
#endif // AC_COPTER_MODEGUIDED_ORBIT_ENABLED
1379+
#endif // MODE_GUIDED_ENABLED

0 commit comments

Comments
 (0)