Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/libraries/AC_WPNav/AC_Circle.cpp
Views: 1798
#include <AP_HAL/AP_HAL.h>1#include <AP_Math/AP_Math.h>2#include <AP_Terrain/AP_Terrain.h>3#include "AC_Circle.h"45#include <AP_Logger/AP_Logger.h>67extern const AP_HAL::HAL& hal;89const AP_Param::GroupInfo AC_Circle::var_info[] = {10// @Param: RADIUS11// @DisplayName: Circle Radius12// @Description: Defines the radius of the circle the vehicle will fly when in Circle flight mode13// @Units: cm14// @Range: 0 20000015// @Increment: 10016// @User: Standard17AP_GROUPINFO("RADIUS", 0, AC_Circle, _radius_parm, AC_CIRCLE_RADIUS_DEFAULT),1819// @Param: RATE20// @DisplayName: Circle rate21// @Description: Circle mode's turn rate in deg/sec. Positive to turn clockwise, negative for counter clockwise. Circle rate must be less than ATC_SLEW_YAW parameter.22// @Units: deg/s23// @Range: -90 9024// @Increment: 125// @User: Standard26AP_GROUPINFO("RATE", 1, AC_Circle, _rate_parm, AC_CIRCLE_RATE_DEFAULT),2728// @Param: OPTIONS29// @DisplayName: Circle options30// @Description: 0:Enable or disable using the pitch/roll stick control circle mode's radius and rate31// @Bitmask: 0:manual control, 1:face direction of travel, 2:Start at center rather than on perimeter, 3:Make Mount ROI the center of the circle32// @User: Standard33AP_GROUPINFO("OPTIONS", 2, AC_Circle, _options, 1),3435AP_GROUPEND36};3738// Default constructor.39// Note that the Vector/Matrix constructors already implicitly zero40// their values.41//42AC_Circle::AC_Circle(const AP_InertialNav& inav, const AP_AHRS_View& ahrs, AC_PosControl& pos_control) :43_inav(inav),44_ahrs(ahrs),45_pos_control(pos_control)46{47AP_Param::setup_object_defaults(this, var_info);4849// init flags50_flags.panorama = false;51_rate = _rate_parm;52}5354/// init - initialise circle controller setting center specifically55/// set terrain_alt to true if center.z should be interpreted as an alt-above-terrain. Rate should be +ve in deg/sec for cw turn56/// caller should set the position controller's x,y and z speeds and accelerations before calling this57void AC_Circle::init(const Vector3p& center, bool terrain_alt, float rate_deg_per_sec)58{59_center = center;60_terrain_alt = terrain_alt;61_rate = rate_deg_per_sec;6263// initialise position controller (sets target roll angle, pitch angle and I terms based on vehicle current lean angles)64_pos_control.init_xy_controller_stopping_point();65_pos_control.init_z_controller_stopping_point();6667// calculate velocities68calc_velocities(true);6970// set start angle from position71init_start_angle(false);72}7374/// init - initialise circle controller setting center using stopping point and projecting out based on the copter's heading75/// caller should set the position controller's x,y and z speeds and accelerations before calling this76void AC_Circle::init()77{78// initialize radius and rate from params79_radius = _radius_parm;80_last_radius_param = _radius_parm;81_rate = _rate_parm;8283// initialise position controller (sets target roll angle, pitch angle and I terms based on vehicle current lean angles)84_pos_control.init_xy_controller_stopping_point();85_pos_control.init_z_controller_stopping_point();8687// get stopping point88const Vector3p& stopping_point = _pos_control.get_pos_desired_cm();8990// set circle center to circle_radius ahead of stopping point91_center = stopping_point;92if ((_options.get() & CircleOptions::INIT_AT_CENTER) == 0) {93_center.x += _radius * _ahrs.cos_yaw();94_center.y += _radius * _ahrs.sin_yaw();95}96_terrain_alt = false;9798// calculate velocities99calc_velocities(true);100101// set starting angle from vehicle heading102init_start_angle(true);103}104105/// set circle center to a Location106void AC_Circle::set_center(const Location& center)107{108if (center.get_alt_frame() == Location::AltFrame::ABOVE_TERRAIN) {109// convert Location with terrain altitude110Vector2f center_xy;111int32_t terr_alt_cm;112if (center.get_vector_xy_from_origin_NE(center_xy) && center.get_alt_cm(Location::AltFrame::ABOVE_TERRAIN, terr_alt_cm)) {113set_center(Vector3f(center_xy.x, center_xy.y, terr_alt_cm), true);114} else {115// failed to convert location so set to current position and log error116set_center(_inav.get_position_neu_cm(), false);117LOGGER_WRITE_ERROR(LogErrorSubsystem::NAVIGATION, LogErrorCode::FAILED_CIRCLE_INIT);118}119} else {120// convert Location with alt-above-home, alt-above-origin or absolute alt121Vector3f circle_center_neu;122if (!center.get_vector_from_origin_NEU(circle_center_neu)) {123// default to current position and log error124circle_center_neu = _inav.get_position_neu_cm();125LOGGER_WRITE_ERROR(LogErrorSubsystem::NAVIGATION, LogErrorCode::FAILED_CIRCLE_INIT);126}127set_center(circle_center_neu, false);128}129}130131/// set_circle_rate - set circle rate in degrees per second132void AC_Circle::set_rate(float deg_per_sec)133{134if (!is_equal(deg_per_sec, _rate)) {135_rate = deg_per_sec;136}137}138139/// set_circle_rate - set circle rate in degrees per second140void AC_Circle::set_radius_cm(float radius_cm)141{142_radius = constrain_float(radius_cm, 0, AC_CIRCLE_RADIUS_MAX);143}144145/// returns true if update has been run recently146/// used by vehicle code to determine if get_yaw() is valid147bool AC_Circle::is_active() const148{149return (AP_HAL::millis() - _last_update_ms < 200);150}151152/// update - update circle controller153bool AC_Circle::update(float climb_rate_cms)154{155calc_velocities(false);156157// calculate dt158const float dt = _pos_control.get_dt();159160// ramp angular velocity to maximum161if (_angular_vel < _angular_vel_max) {162_angular_vel += fabsf(_angular_accel) * dt;163_angular_vel = MIN(_angular_vel, _angular_vel_max);164}165if (_angular_vel > _angular_vel_max) {166_angular_vel -= fabsf(_angular_accel) * dt;167_angular_vel = MAX(_angular_vel, _angular_vel_max);168}169170// update the target angle and total angle travelled171float angle_change = _angular_vel * dt;172_angle += angle_change;173_angle = wrap_PI(_angle);174_angle_total += angle_change;175176// calculate terrain adjustments177float terr_offset = 0.0f;178if (_terrain_alt && !get_terrain_offset(terr_offset)) {179return false;180}181182// calculate z-axis target183float target_z_cm;184if (_terrain_alt) {185target_z_cm = _center.z + terr_offset;186} else {187target_z_cm = _pos_control.get_pos_desired_z_cm();188}189190// if the circle_radius is zero we are doing panorama so no need to update loiter target191Vector3p target {192_center.x,193_center.y,194target_z_cm195};196if (!is_zero(_radius)) {197// calculate target position198target.x += _radius * cosf(-_angle);199target.y += - _radius * sinf(-_angle);200201// heading is from vehicle to center of circle202_yaw = get_bearing_cd(_pos_control.get_pos_desired_cm().xy().tofloat(), _center.tofloat().xy());203204if ((_options.get() & CircleOptions::FACE_DIRECTION_OF_TRAVEL) != 0) {205_yaw += is_positive(_rate)?-9000.0f:9000.0f;206_yaw = wrap_360_cd(_yaw);207}208209} else {210// heading is same as _angle but converted to centi-degrees211_yaw = _angle * DEGX100;212}213214// update position controller target215Vector2f zero;216_pos_control.input_pos_vel_accel_xy(target.xy(), zero, zero);217if (_terrain_alt) {218float zero2 = 0;219float target_zf = target.z;220_pos_control.input_pos_vel_accel_z(target_zf, zero2, 0);221} else {222_pos_control.set_pos_target_z_from_climb_rate_cm(climb_rate_cms);223}224225// update position controller226_pos_control.update_xy_controller();227228// set update time229_last_update_ms = AP_HAL::millis();230231return true;232}233234// get_closest_point_on_circle - returns closest point on the circle235// circle's center should already have been set236// closest point on the circle will be placed in result, dist_cm will be updated with the 3D distance to the center237// result's altitude (i.e. z) will be set to the circle_center's altitude238// if vehicle is at the center of the circle, the edge directly behind vehicle will be returned239void AC_Circle::get_closest_point_on_circle(Vector3f& result, float& dist_cm) const240{241// get current position242Vector3p stopping_point;243_pos_control.get_stopping_point_xy_cm(stopping_point.xy());244_pos_control.get_stopping_point_z_cm(stopping_point.z);245246// calc vector from stopping point to circle center247Vector3f vec = (stopping_point - _center).tofloat();248dist_cm = vec.length();249250// return center if radius is zero251if (!is_positive(_radius)) {252result = _center.tofloat();253return;254}255256// if current location is exactly at the center of the circle return edge directly behind vehicle257if (is_zero(dist_cm)) {258result.x = _center.x - _radius * _ahrs.cos_yaw();259result.y = _center.y - _radius * _ahrs.sin_yaw();260result.z = _center.z;261return;262}263264// calculate closest point on edge of circle265result.x = _center.x + vec.x / dist_cm * _radius;266result.y = _center.y + vec.y / dist_cm * _radius;267result.z = _center.z;268}269270// calc_velocities - calculate angular velocity max and acceleration based on radius and rate271// this should be called whenever the radius or rate are changed272// initialises the yaw and current position around the circle273void AC_Circle::calc_velocities(bool init_velocity)274{275// if we are doing a panorama set the circle_angle to the current heading276if (_radius <= 0) {277_angular_vel_max = ToRad(_rate);278_angular_accel = MAX(fabsf(_angular_vel_max),ToRad(AC_CIRCLE_ANGULAR_ACCEL_MIN)); // reach maximum yaw velocity in 1 second279}else{280// calculate max velocity based on waypoint speed ensuring we do not use more than half our max acceleration for accelerating towards the center of the circle281float velocity_max = MIN(_pos_control.get_max_speed_xy_cms(), safe_sqrt(0.5f*_pos_control.get_max_accel_xy_cmss()*_radius));282283// angular_velocity in radians per second284_angular_vel_max = velocity_max/_radius;285_angular_vel_max = constrain_float(ToRad(_rate),-_angular_vel_max,_angular_vel_max);286287// angular_velocity in radians per second288_angular_accel = MAX(_pos_control.get_max_accel_xy_cmss()/_radius, ToRad(AC_CIRCLE_ANGULAR_ACCEL_MIN));289}290291// initialise angular velocity292if (init_velocity) {293_angular_vel = 0;294}295}296297// init_start_angle - sets the starting angle around the circle and initialises the angle_total298// if use_heading is true the vehicle's heading will be used to init the angle causing minimum yaw movement299// if use_heading is false the vehicle's position from the center will be used to initialise the angle300void AC_Circle::init_start_angle(bool use_heading)301{302// initialise angle total303_angle_total = 0;304305// if the radius is zero we are doing panorama so init angle to the current heading306if (_radius <= 0) {307_angle = _ahrs.yaw;308return;309}310311// if use_heading is true312if (use_heading) {313_angle = wrap_PI(_ahrs.yaw-M_PI);314} else {315// if we are exactly at the center of the circle, init angle to directly behind vehicle (so vehicle will backup but not change heading)316// curr_pos_desired is the position before we add offsets and terrain317const Vector3f &curr_pos_desired= _pos_control.get_pos_desired_cm().tofloat();318if (is_equal(curr_pos_desired.x,float(_center.x)) && is_equal(curr_pos_desired.y,float(_center.y))) {319_angle = wrap_PI(_ahrs.yaw-M_PI);320} else {321// get bearing from circle center to vehicle in radians322float bearing_rad = atan2f(curr_pos_desired.y-_center.y, curr_pos_desired.x-_center.x);323_angle = wrap_PI(bearing_rad);324}325}326}327328// get expected source of terrain data329AC_Circle::TerrainSource AC_Circle::get_terrain_source() const330{331// use range finder if connected332if (_rangefinder_available) {333return AC_Circle::TerrainSource::TERRAIN_FROM_RANGEFINDER;334}335#if AP_TERRAIN_AVAILABLE336const AP_Terrain *terrain = AP_Terrain::get_singleton();337if ((terrain != nullptr) && terrain->enabled()) {338return AC_Circle::TerrainSource::TERRAIN_FROM_TERRAINDATABASE;339} else {340return AC_Circle::TerrainSource::TERRAIN_UNAVAILABLE;341}342#else343return AC_Circle::TerrainSource::TERRAIN_UNAVAILABLE;344#endif345}346347// get terrain's altitude (in cm above the ekf origin) at the current position (+ve means terrain below vehicle is above ekf origin's altitude)348bool AC_Circle::get_terrain_offset(float& offset_cm)349{350// calculate offset based on source (rangefinder or terrain database)351switch (get_terrain_source()) {352case AC_Circle::TerrainSource::TERRAIN_UNAVAILABLE:353return false;354case AC_Circle::TerrainSource::TERRAIN_FROM_RANGEFINDER:355if (_rangefinder_healthy) {356offset_cm = _rangefinder_terrain_offset_cm;357return true;358}359return false;360case AC_Circle::TerrainSource::TERRAIN_FROM_TERRAINDATABASE:361#if AP_TERRAIN_AVAILABLE362float terr_alt = 0.0f;363AP_Terrain *terrain = AP_Terrain::get_singleton();364if (terrain != nullptr && terrain->height_above_terrain(terr_alt, true)) {365offset_cm = _inav.get_position_z_up_cm() - (terr_alt * 100.0);366return true;367}368#endif369return false;370}371372// we should never get here but just in case373return false;374}375376void AC_Circle::check_param_change()377{378if (!is_equal(_last_radius_param,_radius_parm.get())) {379_radius = _radius_parm;380_last_radius_param = _radius_parm;381}382}383384385