LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/Rover - mode_circle.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 111 140 79.3 %
Date: 2026-09-09 11:02:08 Functions: 14 15 93.3 %

          Line data    Source code
       1             : #include "Rover.h"
       2             : 
       3             : #define AR_CIRCLE_ACCEL_DEFAULT         1.0 // default acceleration in m/s/s if not specified by user
       4             : #define AR_CIRCLE_RADIUS_MIN            0.1 // minimum radius in meters
       5             : 
       6             : const AP_Param::GroupInfo ModeCircle::var_info[] = {
       7             : 
       8             :     // @Param: _RADIUS
       9             :     // @DisplayName: Circle Radius
      10             :     // @Description: Vehicle will circle the center at this distance
      11             :     // @Units: m
      12             :     // @Range: 0 100
      13             :     // @Increment: 1
      14             :     // @User: Standard
      15             :     AP_GROUPINFO("_RADIUS", 1, ModeCircle, radius, 20),
      16             : 
      17             :     // @Param: _SPEED
      18             :     // @DisplayName: Circle Speed
      19             :     // @Description: Vehicle will move at this speed around the circle.  If set to zero WP_SPEED will be used
      20             :     // @Units: m/s
      21             :     // @Range: 0 10
      22             :     // @Increment: 0.1
      23             :     // @User: Standard
      24             :     AP_GROUPINFO("_SPEED", 2, ModeCircle, speed, 0),
      25             : 
      26             :     // @Param: _DIR
      27             :     // @DisplayName: Circle Direction
      28             :     // @Description: Circle Direction
      29             :     // @Values: 0:Clockwise, 1:Counter-Clockwise
      30             :     // @User: Standard
      31             :     AP_GROUPINFO("_DIR", 3, ModeCircle, direction, 0),
      32             : 
      33             :     AP_GROUPEND
      34             : };
      35             : 
      36         285 : ModeCircle::ModeCircle() : Mode()
      37             : {
      38         285 :     AP_Param::setup_object_defaults(this, var_info);
      39         285 : }
      40             : 
      41             : // get the distance at which the vehicle is considered to be on track along the circle
      42        3485 : float ModeCircle::get_reached_distance() const
      43             : {
      44        3485 :     if (config.radius >= 0.5) {
      45        3485 :         return 0.2;
      46             :     }
      47           0 :     return 0.1;
      48             : }
      49             : 
      50             : // initialise with specific center location, radius (in meters) and direction
      51             : // replaces use of _enter when initialised from within Auto mode
      52           2 : bool ModeCircle::set_center(const Location& center_loc, float radius_m, bool dir_ccw)
      53             : {
      54           2 :     Vector2f center_pos_cm;
      55           2 :     if (!center_loc.get_vector_xy_from_origin_NE_cm(center_pos_cm)) {
      56           0 :         return false;
      57             :     }
      58           2 :     if (!_enter()) {
      59           0 :         return false;
      60             :     }
      61             : 
      62             :     // convert center position from cm to m
      63           2 :     config.center_pos = center_pos_cm * 0.01;
      64             : 
      65             :     // set radius
      66           2 :     config.radius = MAX(fabsf(radius_m), AR_CIRCLE_RADIUS_MIN);
      67           2 :     check_config_radius();
      68             : 
      69             :     // set direction
      70           2 :     config.dir = dir_ccw ? Direction::CCW : Direction::CW;
      71             : 
      72             :     // set target yaw rad (target point on circle)
      73           2 :     init_target_yaw_rad();
      74             : 
      75             :     // record center as location (only used for reporting)
      76           2 :     config.center_loc = center_loc;
      77             : 
      78           2 :     return true;
      79             : }
      80             : 
      81             : // initialize circle mode from current position
      82           2 : bool ModeCircle::_enter()
      83             : {
      84             :     // capture starting point and yaw
      85           2 :     if (!AP::ahrs().get_relative_position_NE_origin_float(config.center_pos)) {
      86           0 :         return false;
      87             :     }
      88           2 :     config.radius = MAX(fabsf(radius), AR_CIRCLE_RADIUS_MIN);
      89           2 :     check_config_radius();
      90             : 
      91           2 :     config.dir = (direction == 1) ? Direction::CCW : Direction::CW;
      92           2 :     config.speed = is_positive(speed) ? speed : g2.wp_nav.get_default_speed();
      93           2 :     target.yaw_rad = AP::ahrs().get_yaw_rad();
      94           2 :     target.speed = 0;
      95             : 
      96             :     // record center as location (only used for reporting)
      97           2 :     config.center_loc = rover.current_loc;
      98             : 
      99             :     // check speed around circle does not lead to excessive lateral acceleration
     100           2 :     check_config_speed();
     101             : 
     102             :     // reset tracking_back 
     103           2 :     tracking_back = false;
     104             : 
     105             :     // calculate speed, accel and jerk limits
     106             :     // otherwise the vehicle uses wp_nav default speed limit
     107           2 :     float atc_accel_max = MIN(g2.attitude_control.get_accel_max(), g2.attitude_control.get_decel_max());
     108           2 :     if (!is_positive(atc_accel_max)) {
     109           0 :         atc_accel_max = AR_CIRCLE_ACCEL_DEFAULT;
     110             :     }
     111           2 :     const float accel_max = is_positive(g2.wp_nav.get_default_accel()) ? MIN(g2.wp_nav.get_default_accel(), atc_accel_max) : atc_accel_max;
     112           2 :     const float jerk_max = is_positive(g2.wp_nav.get_default_jerk()) ? g2.wp_nav.get_default_jerk() : accel_max;
     113             : 
     114             :     // initialise position controller
     115           2 :     g2.pos_control.set_limits(config.speed, accel_max, g2.attitude_control.get_turn_lat_accel_max(), jerk_max);
     116           2 :     g2.pos_control.init();
     117             : 
     118             :     // initialise angles covered and reached_edge state
     119           2 :     angle_total_rad = 0;
     120           2 :     reached_edge = false;
     121           2 :     dist_to_edge_m = 0;
     122             : 
     123           2 :     return true;
     124             : }
     125             : 
     126             : // initialise target_yaw_rad using the vehicle's position and yaw
     127             : // if there is no current position estimate target_yaw_rad is set to 0
     128           2 : void ModeCircle::init_target_yaw_rad()
     129             : {
     130             :     // if no position estimate use vehicle yaw
     131           2 :     Vector2f curr_pos_NE;
     132           2 :     if (!AP::ahrs().get_relative_position_NE_origin_float(curr_pos_NE)) {
     133           0 :         target.yaw_rad = AP::ahrs().get_yaw_rad();
     134           0 :         return;
     135             :     }
     136             : 
     137             :     // calc vector from circle center to vehicle
     138           2 :     Vector2f center_to_veh = (curr_pos_NE - config.center_pos);
     139           2 :     float dist_m = center_to_veh.length();
     140             : 
     141             :     // if current position is exactly at the center of the circle return vehicle yaw
     142           2 :     if (is_zero(dist_m)) {
     143           0 :         target.yaw_rad = AP::ahrs().get_yaw_rad();
     144             :     } else {
     145           2 :         target.yaw_rad = center_to_veh.angle();
     146             :     }
     147             : }
     148             : 
     149        1978 : void ModeCircle::update()
     150             : {
     151             :     // get current position
     152        1978 :     Vector2f curr_pos;
     153        1978 :     const bool position_ok = AP::ahrs().get_relative_position_NE_origin_float(curr_pos);
     154             : 
     155             :     // if no position estimate stop vehicle
     156        1978 :     if (!position_ok) {
     157           0 :         stop_vehicle();
     158           0 :         return;
     159             :     }
     160             : 
     161             :     // Update distance to destination and distance to edge
     162        1978 :     const Vector2f center_to_veh = curr_pos - config.center_pos;
     163        1978 :     _distance_to_destination = (target.pos.tofloat() - curr_pos).length();
     164        1978 :     dist_to_edge_m = fabsf(center_to_veh.length() - config.radius);
     165             : 
     166             :     // Update depending on stage
     167        1978 :     if (!reached_edge) {
     168         471 :         update_drive_to_radius();
     169        1507 :     } else if (dist_to_edge_m > 2 * MAX(g2.turn_radius, get_reached_distance()) && !tracking_back) {
     170             :         // if more than 2* turn_radius outside circle radius, slow vehicle by 20%
     171           0 :         config.speed = 0.8 * config.speed;
     172           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Circle: cannot keep up, slowing to %.1fm/s", config.speed);
     173           0 :         tracking_back = true;
     174        1507 :     } else if (dist_to_edge_m < MAX(g2.turn_radius, get_reached_distance()) && tracking_back) {
     175             :         // if within turn_radius, call the vehicle back on track
     176           0 :         tracking_back = false;
     177             :     } else {
     178        1507 :         update_circling();
     179             :     }
     180             : 
     181        1978 :     g2.pos_control.update(rover.G_Dt);
     182             : 
     183             :     // get desired speed and turn rate from pos_control
     184        1978 :     const float desired_speed = g2.pos_control.get_desired_speed();
     185        1978 :     const float desired_turn_rate = g2.pos_control.get_desired_turn_rate_rads();
     186             : 
     187             :     // run steering and throttle controllers
     188        1978 :     calc_steering_from_turn_rate(desired_turn_rate);
     189        1978 :     calc_throttle(desired_speed, true);
     190             : }
     191             : 
     192         471 : void ModeCircle::update_drive_to_radius()
     193             : {
     194             :     // check if vehicle has reached edge of circle
     195         471 :     const float dist_thresh_m = MAX(g2.turn_radius, get_reached_distance());
     196         471 :     reached_edge |= dist_to_edge_m <= dist_thresh_m;
     197             : 
     198             :     // calculate target point's position, velocity and acceleration
     199         471 :     target.pos = config.center_pos.topostype();
     200         471 :     target.pos.offset_bearing(degrees(target.yaw_rad), config.radius);
     201             : 
     202         471 :     g2.pos_control.input_pos_target(target.pos, rover.G_Dt);
     203         471 : }
     204             : 
     205             : // Update position controller targets while circling
     206        1507 : void ModeCircle::update_circling()
     207             : {
     208             : 
     209             :     // accelerate speed up to desired speed
     210        1507 :     const float speed_change_max = (g2.pos_control.get_accel_max() * 0.5 * rover.G_Dt);
     211        1507 :     const float accel_fb = constrain_float(config.speed - target.speed, -speed_change_max, speed_change_max);
     212        1507 :     target.speed += accel_fb;
     213             : 
     214             :     // calculate angular rate and update target angle, if the vehicle is not trying to track back
     215        1507 :     if (!tracking_back) {
     216        1507 :         const float circumference = 2.0 * M_PI * config.radius;
     217        1507 :         const float angular_rate_rad = (target.speed / circumference) * M_2PI * (config.dir == Direction::CW ? 1.0 : -1.0);
     218        1507 :         const float angle_dt = angular_rate_rad * rover.G_Dt;
     219        1507 :         target.yaw_rad = wrap_PI(target.yaw_rad + angle_dt);
     220        1507 :         angle_total_rad += angle_dt;
     221             :     } else {
     222           0 :         init_target_yaw_rad();
     223             :     }
     224             : 
     225             :     // calculate target point's position, velocity and acceleration
     226        1507 :     target.pos = config.center_pos.topostype();
     227        1507 :     target.pos.offset_bearing(degrees(target.yaw_rad), config.radius);
     228             : 
     229             :     // velocity is perpendicular to angle from the circle's center to the target point on the edge of the circle
     230        1507 :     target.vel = Vector2f(target.speed, 0);
     231        1507 :     target.vel.rotate(target.yaw_rad + radians(90));
     232             : 
     233             :     // acceleration is towards center of circle and is speed^2 / radius
     234        1507 :     target.accel = Vector2f(-sq(target.speed) / config.radius, accel_fb / rover.G_Dt);
     235        1507 :     target.accel.rotate(target.yaw_rad);
     236             : 
     237        1507 :     g2.pos_control.set_pos_vel_accel_target(target.pos, target.vel, target.accel);
     238             : 
     239        1507 : }
     240             : 
     241             : // return desired heading (in degrees) and cross track error (in meters) for reporting to ground station (NAV_CONTROLLER_OUTPUT message)
     242         828 : float ModeCircle::wp_bearing() const
     243             : {
     244         828 :     Vector2f curr_pos_NE;
     245         828 :     if (!AP::ahrs().get_relative_position_NE_origin_float(curr_pos_NE)) {
     246           0 :         return 0;
     247             :     }
     248             :     // calc vector from circle center to vehicle
     249         828 :     Vector2f veh_to_center = (target.pos.tofloat() - curr_pos_NE);
     250         828 :     if (veh_to_center.is_zero()) {
     251           0 :         return 0;
     252             :     }
     253         828 :     return degrees(veh_to_center.angle());
     254             : }
     255             : 
     256         789 : float ModeCircle::nav_bearing() const
     257             : {
     258             :     // get position error as a vector from the current position to the target position
     259         789 :     const Vector2p pos_error = g2.pos_control.get_pos_error();
     260         789 :     if (pos_error.is_zero()) {
     261          79 :         return 0;
     262             :     }
     263         710 :     return degrees(pos_error.angle());
     264             : }
     265             : 
     266         396 : float ModeCircle::get_desired_lat_accel() const
     267             : {
     268         396 :     return g2.pos_control.get_desired_lat_accel();
     269             : }
     270             : 
     271             : // set desired speed in m/s
     272           0 : bool ModeCircle::set_desired_speed(float speed_ms)
     273             : {
     274           0 :     if (is_positive(speed_ms)) {
     275           0 :         config.speed = speed_ms;
     276             : 
     277             :         // check desired speed does not lead to excessive lateral acceleration
     278           0 :         check_config_speed();
     279             : 
     280             :         // update position controller limits if required
     281           0 :         if (config.speed > g2.pos_control.get_speed_max()) {
     282           0 :             g2.pos_control.set_limits(config.speed, g2.pos_control.get_accel_max(), g2.pos_control.get_lat_accel_max(), g2.pos_control.get_jerk_max());
     283             :         }
     284           0 :         return true;
     285             :     }
     286             :  
     287           0 :     return false;
     288             : }
     289             : 
     290             : // return desired location
     291         393 : bool ModeCircle::get_desired_location(Location& destination) const
     292             : {
     293         393 :     destination = config.center_loc;
     294         393 :     destination.offset_bearing(degrees(target.yaw_rad), config.radius);
     295         393 :     return true;
     296             : }
     297             : 
     298             : // limit config speed so that lateral acceleration is within limits
     299             : // assumes that config.radius and attitude controller lat accel max have been set
     300             : // outputs warning to user if speed is reduced
     301           2 : void ModeCircle::check_config_speed()
     302             : {
     303             :     // calculate maximum speed based on radius and max lateral acceleration max
     304           2 :     const float speed_max = MAX(safe_sqrt(g2.attitude_control.get_turn_lat_accel_max() * config.radius), 0);
     305             : 
     306           2 :     if (config.speed > speed_max) {
     307           0 :         config.speed = speed_max;
     308           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Circle: max speed is %4.1f", (double)config.speed);
     309             :     }
     310           2 : }
     311             : 
     312             : // ensure config radius is no smaller then vehicle's TURN_RADIUS
     313             : // assumes that config.radius has been set
     314             : // radius is increased if necessary and warning is output to the user
     315           4 : void ModeCircle::check_config_radius()
     316             : {
     317             :     // ensure radius is at least as large as vehicle's turn radius
     318           4 :     if (config.radius < g2.turn_radius) {
     319           0 :         config.radius = g2.turn_radius;
     320           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Circle: radius increased to TURN_RADIUS (%4.1f)", (double)g2.turn_radius);
     321             :     }
     322           4 : }

Generated by: LCOV version 1.14