Files
allwpilib/wpilibc/src/main/native/cpp/PIDBase.cpp
Peter Johnson 471f375a38 Simplify Sendable interface (#1864)
This removes the name and subsystem from individual objects, and instead
puts this data into a new singleton class, SendableRegistry.  Much of
LiveWindow has been refactored into SendableRegistry.

In C++, a new CRTP helper class, SendableHelper, has been added to provide
move and destruction functionality.

Shims for GetName, SetName, GetSubsystem, and SetSubsystem have been added
to Command and Subsystem (both old and new), and also to SendableHelper to
prevent code breakage.

This deprecates SendableBase in preparation for future removal.
2019-09-14 15:22:54 -05:00

354 lines
8.7 KiB
C++

/*----------------------------------------------------------------------------*/
/* Copyright (c) 2008-2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "frc/PIDBase.h"
#include <algorithm>
#include <cmath>
#include <hal/HAL.h>
#include "frc/PIDOutput.h"
#include "frc/smartdashboard/SendableBuilder.h"
#include "frc/smartdashboard/SendableRegistry.h"
using namespace frc;
template <class T>
constexpr const T& clamp(const T& value, const T& low, const T& high) {
return std::max(low, std::min(value, high));
}
PIDBase::PIDBase(double Kp, double Ki, double Kd, PIDSource& source,
PIDOutput& output)
: PIDBase(Kp, Ki, Kd, 0.0, source, output) {}
PIDBase::PIDBase(double Kp, double Ki, double Kd, double Kf, PIDSource& source,
PIDOutput& output) {
m_P = Kp;
m_I = Ki;
m_D = Kd;
m_F = Kf;
m_pidInput = &source;
m_filter = LinearFilter::MovingAverage(1);
m_pidOutput = &output;
m_setpointTimer.Start();
static int instances = 0;
instances++;
HAL_Report(HALUsageReporting::kResourceType_PIDController, instances);
SendableRegistry::GetInstance().Add(this, "PIDController", instances);
}
double PIDBase::Get() const {
std::scoped_lock lock(m_thisMutex);
return m_result;
}
void PIDBase::SetContinuous(bool continuous) {
std::scoped_lock lock(m_thisMutex);
m_continuous = continuous;
}
void PIDBase::SetInputRange(double minimumInput, double maximumInput) {
{
std::scoped_lock lock(m_thisMutex);
m_minimumInput = minimumInput;
m_maximumInput = maximumInput;
m_inputRange = maximumInput - minimumInput;
}
SetSetpoint(m_setpoint);
}
void PIDBase::SetOutputRange(double minimumOutput, double maximumOutput) {
std::scoped_lock lock(m_thisMutex);
m_minimumOutput = minimumOutput;
m_maximumOutput = maximumOutput;
}
void PIDBase::SetPID(double p, double i, double d) {
{
std::scoped_lock lock(m_thisMutex);
m_P = p;
m_I = i;
m_D = d;
}
}
void PIDBase::SetPID(double p, double i, double d, double f) {
std::scoped_lock lock(m_thisMutex);
m_P = p;
m_I = i;
m_D = d;
m_F = f;
}
void PIDBase::SetP(double p) {
std::scoped_lock lock(m_thisMutex);
m_P = p;
}
void PIDBase::SetI(double i) {
std::scoped_lock lock(m_thisMutex);
m_I = i;
}
void PIDBase::SetD(double d) {
std::scoped_lock lock(m_thisMutex);
m_D = d;
}
void PIDBase::SetF(double f) {
std::scoped_lock lock(m_thisMutex);
m_F = f;
}
double PIDBase::GetP() const {
std::scoped_lock lock(m_thisMutex);
return m_P;
}
double PIDBase::GetI() const {
std::scoped_lock lock(m_thisMutex);
return m_I;
}
double PIDBase::GetD() const {
std::scoped_lock lock(m_thisMutex);
return m_D;
}
double PIDBase::GetF() const {
std::scoped_lock lock(m_thisMutex);
return m_F;
}
void PIDBase::SetSetpoint(double setpoint) {
{
std::scoped_lock lock(m_thisMutex);
if (m_maximumInput > m_minimumInput) {
if (setpoint > m_maximumInput)
m_setpoint = m_maximumInput;
else if (setpoint < m_minimumInput)
m_setpoint = m_minimumInput;
else
m_setpoint = setpoint;
} else {
m_setpoint = setpoint;
}
}
}
double PIDBase::GetSetpoint() const {
std::scoped_lock lock(m_thisMutex);
return m_setpoint;
}
double PIDBase::GetDeltaSetpoint() const {
std::scoped_lock lock(m_thisMutex);
return (m_setpoint - m_prevSetpoint) / m_setpointTimer.Get();
}
double PIDBase::GetError() const {
double setpoint = GetSetpoint();
{
std::scoped_lock lock(m_thisMutex);
return GetContinuousError(setpoint - m_pidInput->PIDGet());
}
}
double PIDBase::GetAvgError() const { return GetError(); }
void PIDBase::SetPIDSourceType(PIDSourceType pidSource) {
m_pidInput->SetPIDSourceType(pidSource);
}
PIDSourceType PIDBase::GetPIDSourceType() const {
return m_pidInput->GetPIDSourceType();
}
void PIDBase::SetTolerance(double percent) {
std::scoped_lock lock(m_thisMutex);
m_toleranceType = kPercentTolerance;
m_tolerance = percent;
}
void PIDBase::SetAbsoluteTolerance(double absTolerance) {
std::scoped_lock lock(m_thisMutex);
m_toleranceType = kAbsoluteTolerance;
m_tolerance = absTolerance;
}
void PIDBase::SetPercentTolerance(double percent) {
std::scoped_lock lock(m_thisMutex);
m_toleranceType = kPercentTolerance;
m_tolerance = percent;
}
void PIDBase::SetToleranceBuffer(int bufLength) {
std::scoped_lock lock(m_thisMutex);
m_filter = LinearFilter::MovingAverage(bufLength);
}
bool PIDBase::OnTarget() const {
double error = GetError();
std::scoped_lock lock(m_thisMutex);
switch (m_toleranceType) {
case kPercentTolerance:
return std::fabs(error) < m_tolerance / 100 * m_inputRange;
break;
case kAbsoluteTolerance:
return std::fabs(error) < m_tolerance;
break;
case kNoTolerance:
// TODO: this case needs an error
return false;
}
return false;
}
void PIDBase::Reset() {
std::scoped_lock lock(m_thisMutex);
m_prevError = 0;
m_totalError = 0;
m_result = 0;
}
void PIDBase::PIDWrite(double output) { SetSetpoint(output); }
void PIDBase::InitSendable(SendableBuilder& builder) {
builder.SetSmartDashboardType("PIDController");
builder.SetSafeState([=]() { Reset(); });
builder.AddDoubleProperty("p", [=]() { return GetP(); },
[=](double value) { SetP(value); });
builder.AddDoubleProperty("i", [=]() { return GetI(); },
[=](double value) { SetI(value); });
builder.AddDoubleProperty("d", [=]() { return GetD(); },
[=](double value) { SetD(value); });
builder.AddDoubleProperty("f", [=]() { return GetF(); },
[=](double value) { SetF(value); });
builder.AddDoubleProperty("setpoint", [=]() { return GetSetpoint(); },
[=](double value) { SetSetpoint(value); });
}
void PIDBase::Calculate() {
if (m_pidInput == nullptr || m_pidOutput == nullptr) return;
bool enabled;
{
std::scoped_lock lock(m_thisMutex);
enabled = m_enabled;
}
if (enabled) {
double input;
// Storage for function inputs
PIDSourceType pidSourceType;
double P;
double I;
double D;
double feedForward = CalculateFeedForward();
double minimumOutput;
double maximumOutput;
// Storage for function input-outputs
double prevError;
double error;
double totalError;
{
std::scoped_lock lock(m_thisMutex);
input = m_filter.Calculate(m_pidInput->PIDGet());
pidSourceType = m_pidInput->GetPIDSourceType();
P = m_P;
I = m_I;
D = m_D;
minimumOutput = m_minimumOutput;
maximumOutput = m_maximumOutput;
prevError = m_prevError;
error = GetContinuousError(m_setpoint - input);
totalError = m_totalError;
}
// Storage for function outputs
double result;
if (pidSourceType == PIDSourceType::kRate) {
if (P != 0) {
totalError =
clamp(totalError + error, minimumOutput / P, maximumOutput / P);
}
result = D * error + P * totalError + feedForward;
} else {
if (I != 0) {
totalError =
clamp(totalError + error, minimumOutput / I, maximumOutput / I);
}
result =
P * error + I * totalError + D * (error - prevError) + feedForward;
}
result = clamp(result, minimumOutput, maximumOutput);
{
// Ensures m_enabled check and PIDWrite() call occur atomically
std::scoped_lock pidWriteLock(m_pidWriteMutex);
std::unique_lock mainLock(m_thisMutex);
if (m_enabled) {
// Don't block other PIDBase operations on PIDWrite()
mainLock.unlock();
m_pidOutput->PIDWrite(result);
}
}
std::scoped_lock lock(m_thisMutex);
m_prevError = m_error;
m_error = error;
m_totalError = totalError;
m_result = result;
}
}
double PIDBase::CalculateFeedForward() {
if (m_pidInput->GetPIDSourceType() == PIDSourceType::kRate) {
return m_F * GetSetpoint();
} else {
double temp = m_F * GetDeltaSetpoint();
m_prevSetpoint = m_setpoint;
m_setpointTimer.Reset();
return temp;
}
}
double PIDBase::GetContinuousError(double error) const {
if (m_continuous && m_inputRange != 0) {
error = std::fmod(error, m_inputRange);
if (std::fabs(error) > m_inputRange / 2) {
if (error > 0) {
return error - m_inputRange;
} else {
return error + m_inputRange;
}
}
}
return error;
}