/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
simple model of a servo. Model is:
- time delay for transport protocol delay
- slew limit
- 2-pole butterworth
*/
#include "ServoModel.h"
#include "SITL.h"
// SITL servo model parameters
const AP_Param::GroupInfo SITL::SIM::ServoParams::var_info[] = {
// @Param: SPEED
// @DisplayName: servo speed
// @Description: servo speed (time for 60 degree deflection). If DELAY and FILTER are not set then this is converted to a 1p lowpass filter. If DELAY or FILTER are set then this is treated as a rate of change limit
// @Units: s
AP_GROUPINFO("SPEED", 1, ServoParams, servo_speed, 0.14),
// @Param: DELAY
// @DisplayName: servo delay
// @Description: servo delay
// @Units: s
AP_GROUPINFO("DELAY", 2, ServoParams, servo_delay, 0.0),
// @Param: FILTER
// @DisplayName: servo filter
// @Description: servo filter
// @Units: Hz
AP_GROUPINFO("FILTER", 3, ServoParams, servo_filter, 0),
AP_GROUPEND
};
/*
simpler filter used when SIM_SERVO_FILTER and SIM_SERVO_DELAY are not set
this filter is a 1p low pass based on SIM_SERVO_SPEED
*/
float ServoModel::apply_simple_filter(float v, float dt)
{
const auto *sitl = AP::sitl();
if (!is_positive(sitl->servo.servo_speed)) {
return v;
}
const float cutoff = 1.0f / (2 * M_PI * sitl->servo.servo_speed);
filter1p.set_cutoff_frequency(cutoff);
return filter1p.apply(v, dt);
}
/*
apply a filter for a servo model consistting of a delay, speed and 2p filter
*/
float ServoModel::apply_filter(float v, float dt)
{
const auto *sitl = AP::sitl();
if (!sitl) {
return v;
}
if (!is_positive(sitl->servo.servo_delay) &&
!is_positive(sitl->servo.servo_filter)) {
// fallback to a simpler 1p filter model
return apply_simple_filter(v, dt);
}
// apply delay
if (sitl->servo.servo_delay > 0) {
uint32_t delay_len = MAX(1,sitl->servo.servo_delay * sitl->loop_rate_hz);
if (!delay) {
delay = new ObjectBuffer();
}
if (delay->get_size() != delay_len) {
delay->set_size(delay_len);
}
while (delay->space() > 0) {
delay->push(v);
}
IGNORE_RETURN(delay->pop(v));
}
// apply slew limit
if (sitl->servo.servo_speed > 0) {
// assume SIM_SERVO_SPEED is time for 60 degrees
float time_per_degree = sitl->servo.servo_speed / 60.0;
float proportion_per_second = 1.0 / (angle_deg * time_per_degree);
delta_max = proportion_per_second * dt;
v = constrain_float(v, last_v-delta_max, last_v+delta_max);
v = constrain_float(v, -1, 1);
last_v = v;
}
// apply filter
if (sitl->servo.servo_filter > 0) {
filter.set_cutoff_frequency(sitl->loop_rate_hz, sitl->servo.servo_filter);
v = filter.apply(v);
}
return v;
}
float ServoModel::filter_range(uint16_t pwm, float dt)
{
const float v = (pwm - pwm_min)/float(pwm_max - pwm_min);
return apply_filter(v, dt);
}
float ServoModel::filter_angle(uint16_t pwm, float dt)
{
const float v = (pwm - 0.5*(pwm_max+pwm_min))/(0.5*float(pwm_max - pwm_min));
return apply_filter(v, dt);
}
void ServoModel::set_pwm_range(uint16_t _pwm_min, uint16_t _pwm_max)
{
pwm_min = _pwm_min;
pwm_max = _pwm_max;
}
void ServoModel::set_deflection(float _angle_deg)
{
angle_deg = fabsf(_angle_deg);
}