CTRE Phoenix 6 C++ 26.50.0-alpha-1
Loading...
Searching...
No Matches
LinearFilter.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) Cross The Road Electronics.  All rights reserved.
3 * License information can be found in CTRE_LICENSE.txt
4 * For support and suggestions contact support@ctr-electronics.com or file
5 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases
6 */
7#pragma once
8
9#include <algorithm>
10#include <cmath>
11#include <initializer_list>
12#include <span>
13#include <stdexcept>
14
16#include <wpi/units/time.hpp>
17
18namespace ctre {
19namespace phoenix6 {
20namespace swerve {
21
22/**
23 * This class implements a linear, digital filter. All types of FIR and IIR
24 * filters are supported. Static factory methods are provided to create commonly
25 * used types of filters.
26 *
27 * Filters are of the form:<br>
28 * y[n] = (b0 x[n] + b1 x[n-1] + … + bP x[n-P]) -
29 * (a0 y[n-1] + a2 y[n-2] + … + aQ y[n-Q])
30 *
31 * Where:<br>
32 * y[n] is the output at time "n"<br>
33 * x[n] is the input at time "n"<br>
34 * y[n-1] is the output from the LAST time step ("n-1")<br>
35 * x[n-1] is the input from the LAST time step ("n-1")<br>
36 * b0 … bP are the "feedforward" (FIR) gains<br>
37 * a0 … aQ are the "feedback" (IIR) gains<br>
38 * IMPORTANT! Note the "-" sign in front of the feedback term! This is a common
39 * convention in signal processing.
40 *
41 * What can linear filters do? Basically, they can filter, or diminish, the
42 * effects of undesirable input frequencies. High frequencies, or rapid changes,
43 * can be indicative of sensor noise or be otherwise undesirable. A "low pass"
44 * filter smooths out the signal, reducing the impact of these high frequency
45 * components. Likewise, a "high pass" filter gets rid of slow-moving signal
46 * components, letting you detect large changes more easily.
47 *
48 * Example FRC applications of filters:
49 * - Getting rid of noise from an analog sensor input
50 * - Smoothing out joystick input to prevent the wheels from slipping or the
51 * robot from tipping
52 * - Smoothing motor commands so that unnecessary strain isn't put on
53 * electrical or mechanical components
54 * - If you use clever gains, you can make a PID controller out of this class!
55 *
56 * For more on filters, we highly recommend the following articles:<br>
57 * https://en.wikipedia.org/wiki/Linear_filter<br>
58 * https://en.wikipedia.org/wiki/Iir_filter<br>
59 * https://en.wikipedia.org/wiki/Fir_filter<br>
60 *
61 * Note 1: Calculate() should be called by the user on a known, regular period.
62 * You can use a Notifier for this or do it "inline" with code in a
63 * periodic function.
64 *
65 * Note 2: For ALL filters, gains are necessarily a function of frequency. If
66 * you make a filter that works well for you at, say, 100Hz, you will most
67 * definitely need to adjust the gains if you then want to run it at 200Hz!
68 * Combining this with Note 1 - the impetus is on YOU as a developer to make
69 * sure Calculate() gets called at the desired, constant frequency!
70 *
71 * This is a copy of the WPILib implementation to fix
72 * https://github.com/wpilibsuite/allwpilib/pull/6628.
73 */
74template <class T>
76 public:
77 /**
78 * Create a linear FIR or IIR filter.
79 *
80 * @param ffGains The "feedforward" or FIR gains.
81 * @param fbGains The "feedback" or IIR gains.
82 */
83 LinearFilter(std::span<const double> ffGains, std::span<const double> fbGains)
84 : m_inputs(ffGains.size()),
85 m_outputs(fbGains.size()),
86 m_inputGains(ffGains.begin(), ffGains.end()),
87 m_outputGains(fbGains.begin(), fbGains.end()) {
88 for (size_t i = 0; i < ffGains.size(); ++i) {
89 m_inputs.emplace_front(0.0);
90 }
91 for (size_t i = 0; i < fbGains.size(); ++i) {
92 m_outputs.emplace_front(0.0);
93 }
94 }
95
96 /**
97 * Create a linear FIR or IIR filter.
98 *
99 * @param ffGains The "feedforward" or FIR gains.
100 * @param fbGains The "feedback" or IIR gains.
101 */
102 LinearFilter(std::initializer_list<double> ffGains,
103 std::initializer_list<double> fbGains)
104 : LinearFilter({ffGains.begin(), ffGains.end()},
105 {fbGains.begin(), fbGains.end()}) {}
106
107 // Static methods to create commonly used filters
108 /**
109 * Creates a one-pole IIR low-pass filter of the form:<br>
110 * y[n] = (1 - gain) x[n] + gain y[n-1]<br>
111 * where gain = e<sup>-dt / T</sup>, T is the time constant in seconds
112 *
113 * Note: T = 1 / (2 pi f) where f is the cutoff frequency in Hz, the frequency
114 * above which the input starts to attenuate.
115 *
116 * This filter is stable for time constants greater than zero.
117 *
118 * @param timeConstant The discrete-time time constant in seconds.
119 * @param period The period in seconds between samples taken by the
120 * user.
121 */
122 static LinearFilter<T> SinglePoleIIR(double timeConstant,
123 wpi::units::second_t period) {
124 double gain = std::exp(-period.value() / timeConstant);
125 return LinearFilter({1.0 - gain}, {-gain});
126 }
127
128 /**
129 * Creates a first-order high-pass filter of the form:<br>
130 * y[n] = gain x[n] + (-gain) x[n-1] + gain y[n-1]<br>
131 * where gain = e<sup>-dt / T</sup>, T is the time constant in seconds
132 *
133 * Note: T = 1 / (2 pi f) where f is the cutoff frequency in Hz, the frequency
134 * below which the input starts to attenuate.
135 *
136 * This filter is stable for time constants greater than zero.
137 *
138 * @param timeConstant The discrete-time time constant in seconds.
139 * @param period The period in seconds between samples taken by the
140 * user.
141 */
142 static LinearFilter<T> HighPass(double timeConstant, wpi::units::second_t period) {
143 double gain = std::exp(-period.value() / timeConstant);
144 return LinearFilter({gain, -gain}, {-gain});
145 }
146
147 /**
148 * Creates a K-tap FIR moving average filter of the form:<br>
149 * y[n] = 1/k (x[k] + x[k-1] + … + x[0])
150 *
151 * This filter is always stable.
152 *
153 * @param taps The number of samples to average over. Higher = smoother but
154 * slower
155 * @throws std::runtime_error if number of taps is less than 1.
156 */
158 if (taps <= 0) {
159 throw std::runtime_error("Number of taps must be greater than zero.");
160 }
161
162 std::vector<double> gains(taps, 1.0 / taps);
163 return LinearFilter(gains, {});
164 }
165
166 /**
167 * Reset the filter state.
168 */
169 void Reset() {
170 std::fill(m_inputs.begin(), m_inputs.end(), T{0.0});
171 std::fill(m_outputs.begin(), m_outputs.end(), T{0.0});
172 }
173
174 /**
175 * Resets the filter state, initializing internal buffers to the provided
176 * values.
177 *
178 * These are the expected lengths of the buffers, depending on what type of
179 * linear filter used:
180 *
181 * <table>
182 * <tr>
183 * <th>Type</th>
184 * <th>Input Buffer Size</th>
185 * <th>Output Buffer Size</th>
186 * </tr>
187 * <tr>
188 * <td>Unspecified</td>
189 * <td>size of {@code ffGains}</td>
190 * <td>size of {@code fbGains}</td>
191 * </tr>
192 * <tr>
193 * <td>Single Pole IIR</td>
194 * <td>1</td>
195 * <td>1</td>
196 * </tr>
197 * <tr>
198 * <td>High-Pass</td>
199 * <td>2</td>
200 * <td>1</td>
201 * </tr>
202 * <tr>
203 * <td>Moving Average</td>
204 * <td>{@code taps}</td>
205 * <td>0</td>
206 * </tr>
207 * <tr>
208 * <td>Finite Difference</td>
209 * <td>size of {@code stencil}</td>
210 * <td>0</td>
211 * </tr>
212 * <tr>
213 * <td>Backward Finite Difference</td>
214 * <td>{@code Samples}</td>
215 * <td>0</td>
216 * </tr>
217 * </table>
218 *
219 * @param inputBuffer Values to initialize input buffer.
220 * @param outputBuffer Values to initialize output buffer.
221 * @throws std::runtime_error if size of inputBuffer or outputBuffer does not
222 * match the size of ffGains and fbGains provided in the constructor.
223 */
224 void Reset(std::span<const T> inputBuffer, std::span<const T> outputBuffer) {
225 // Clear buffers
226 Reset();
227
228 if (inputBuffer.size() != m_inputGains.size() ||
229 outputBuffer.size() != m_outputGains.size()) {
230 throw std::runtime_error(
231 "Incorrect length of inputBuffer or outputBuffer");
232 }
233
234 for (size_t i = 0; i < inputBuffer.size(); ++i) {
235 m_inputs.push_front(inputBuffer[i]);
236 }
237 for (size_t i = 0; i < outputBuffer.size(); ++i) {
238 m_outputs.push_front(outputBuffer[i]);
239 }
240 }
241
242 /**
243 * Calculates the next value of the filter.
244 *
245 * @param input Current input value.
246 *
247 * @return The filtered value at this step
248 */
249 T Calculate(T input) {
250 T retVal{0.0};
251
252 // Rotate the inputs
253 if (m_inputGains.size() > 0) {
254 m_inputs.push_front(input);
255 }
256
257 // Calculate the new value
258 for (size_t i = 0; i < m_inputGains.size(); ++i) {
259 retVal += m_inputs[i] * m_inputGains[i];
260 }
261 for (size_t i = 0; i < m_outputGains.size(); ++i) {
262 retVal -= m_outputs[i] * m_outputGains[i];
263 }
264
265 // Rotate the outputs
266 if (m_outputGains.size() > 0) {
267 m_outputs.push_front(retVal);
268 }
269
270 return retVal;
271 }
272
273 /**
274 * Returns the last value calculated by the LinearFilter.
275 *
276 * @return The last value.
277 */
278 T LastValue() const { return m_outputs.front(); }
279
280 private:
281 circular_buffer<T> m_inputs;
282 circular_buffer<T> m_outputs;
283 std::vector<double> m_inputGains;
284 std::vector<double> m_outputGains;
285
286 /**
287 * Factorial of n.
288 *
289 * @param n Argument of which to take factorial.
290 */
291 static constexpr int Factorial(int n) {
292 if (n < 2) {
293 return 1;
294 } else {
295 return n * Factorial(n - 1);
296 }
297 }
298};
299
300}
301}
302}
LinearFilter(std::initializer_list< double > ffGains, std::initializer_list< double > fbGains)
Create a linear FIR or IIR filter.
Definition LinearFilter.hpp:102
static LinearFilter< T > HighPass(double timeConstant, wpi::units::second_t period)
Creates a first-order high-pass filter of the form: y[n] = gain x[n] + (-gain) x[n-1] + gain y[n-1] ...
Definition LinearFilter.hpp:142
void Reset(std::span< const T > inputBuffer, std::span< const T > outputBuffer)
Resets the filter state, initializing internal buffers to the provided values.
Definition LinearFilter.hpp:224
void Reset()
Reset the filter state.
Definition LinearFilter.hpp:169
T LastValue() const
Returns the last value calculated by the LinearFilter.
Definition LinearFilter.hpp:278
static LinearFilter< T > MovingAverage(int taps)
Creates a K-tap FIR moving average filter of the form: y[n] = 1/k (x[k] + x[k-1] + … + x[0]).
Definition LinearFilter.hpp:157
static LinearFilter< T > SinglePoleIIR(double timeConstant, wpi::units::second_t period)
Creates a one-pole IIR low-pass filter of the form: y[n] = (1 - gain) x[n] + gain y[n-1] where gain...
Definition LinearFilter.hpp:122
LinearFilter(std::span< const double > ffGains, std::span< const double > fbGains)
Create a linear FIR or IIR filter.
Definition LinearFilter.hpp:83
T Calculate(T input)
Calculates the next value of the filter.
Definition LinearFilter.hpp:249
This is a simple circular buffer so we don't need to "bucket brigade" copy old values.
Definition circular_buffer.hpp:24
Definition SwerveModule.hpp:28
Definition ExternalFeedbackConfigs.hpp:16
Definition motor_constants.h:14