CTRE Phoenix 6 C++ 26.0.0-beta-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 "units/time.h"
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 (note: the roboRIO's FPGA
50 * can do this faster in hardware)
51 * - Smoothing out joystick input to prevent the wheels from slipping or the
52 * robot from tipping
53 * - Smoothing motor commands so that unnecessary strain isn't put on
54 * electrical or mechanical components
55 * - If you use clever gains, you can make a PID controller out of this class!
56 *
57 * For more on filters, we highly recommend the following articles:<br>
58 * https://en.wikipedia.org/wiki/Linear_filter<br>
59 * https://en.wikipedia.org/wiki/Iir_filter<br>
60 * https://en.wikipedia.org/wiki/Fir_filter<br>
61 *
62 * Note 1: Calculate() should be called by the user on a known, regular period.
63 * You can use a Notifier for this or do it "inline" with code in a
64 * periodic function.
65 *
66 * Note 2: For ALL filters, gains are necessarily a function of frequency. If
67 * you make a filter that works well for you at, say, 100Hz, you will most
68 * definitely need to adjust the gains if you then want to run it at 200Hz!
69 * Combining this with Note 1 - the impetus is on YOU as a developer to make
70 * sure Calculate() gets called at the desired, constant frequency!
71 *
72 * This is a copy of the WPILib implementation to fix
73 * https://github.com/wpilibsuite/allwpilib/pull/6628.
74 */
75template <class T>
77 public:
78 /**
79 * Create a linear FIR or IIR filter.
80 *
81 * @param ffGains The "feedforward" or FIR gains.
82 * @param fbGains The "feedback" or IIR gains.
83 */
84 LinearFilter(std::span<const double> ffGains, std::span<const double> fbGains)
85 : m_inputs(ffGains.size()),
86 m_outputs(fbGains.size()),
87 m_inputGains(ffGains.begin(), ffGains.end()),
88 m_outputGains(fbGains.begin(), fbGains.end()) {
89 for (size_t i = 0; i < ffGains.size(); ++i) {
90 m_inputs.emplace_front(0.0);
91 }
92 for (size_t i = 0; i < fbGains.size(); ++i) {
93 m_outputs.emplace_front(0.0);
94 }
95 }
96
97 /**
98 * Create a linear FIR or IIR filter.
99 *
100 * @param ffGains The "feedforward" or FIR gains.
101 * @param fbGains The "feedback" or IIR gains.
102 */
103 LinearFilter(std::initializer_list<double> ffGains,
104 std::initializer_list<double> fbGains)
105 : LinearFilter({ffGains.begin(), ffGains.end()},
106 {fbGains.begin(), fbGains.end()}) {}
107
108 // Static methods to create commonly used filters
109 /**
110 * Creates a one-pole IIR low-pass filter of the form:<br>
111 * y[n] = (1 - gain) x[n] + gain y[n-1]<br>
112 * where gain = e<sup>-dt / T</sup>, T is the time constant in seconds
113 *
114 * Note: T = 1 / (2 pi f) where f is the cutoff frequency in Hz, the frequency
115 * above which the input starts to attenuate.
116 *
117 * This filter is stable for time constants greater than zero.
118 *
119 * @param timeConstant The discrete-time time constant in seconds.
120 * @param period The period in seconds between samples taken by the
121 * user.
122 */
123 static LinearFilter<T> SinglePoleIIR(double timeConstant,
124 units::second_t period) {
125 double gain = std::exp(-period.value() / timeConstant);
126 return LinearFilter({1.0 - gain}, {-gain});
127 }
128
129 /**
130 * Creates a first-order high-pass filter of the form:<br>
131 * y[n] = gain x[n] + (-gain) x[n-1] + gain y[n-1]<br>
132 * where gain = e<sup>-dt / T</sup>, T is the time constant in seconds
133 *
134 * Note: T = 1 / (2 pi f) where f is the cutoff frequency in Hz, the frequency
135 * below which the input starts to attenuate.
136 *
137 * This filter is stable for time constants greater than zero.
138 *
139 * @param timeConstant The discrete-time time constant in seconds.
140 * @param period The period in seconds between samples taken by the
141 * user.
142 */
143 static LinearFilter<T> HighPass(double timeConstant, units::second_t period) {
144 double gain = std::exp(-period.value() / timeConstant);
145 return LinearFilter({gain, -gain}, {-gain});
146 }
147
148 /**
149 * Creates a K-tap FIR moving average filter of the form:<br>
150 * y[n] = 1/k (x[k] + x[k-1] + … + x[0])
151 *
152 * This filter is always stable.
153 *
154 * @param taps The number of samples to average over. Higher = smoother but
155 * slower
156 * @throws std::runtime_error if number of taps is less than 1.
157 */
159 if (taps <= 0) {
160 throw std::runtime_error("Number of taps must be greater than zero.");
161 }
162
163 std::vector<double> gains(taps, 1.0 / taps);
164 return LinearFilter(gains, {});
165 }
166
167 /**
168 * Reset the filter state.
169 */
170 void Reset() {
171 std::fill(m_inputs.begin(), m_inputs.end(), T{0.0});
172 std::fill(m_outputs.begin(), m_outputs.end(), T{0.0});
173 }
174
175 /**
176 * Resets the filter state, initializing internal buffers to the provided
177 * values.
178 *
179 * These are the expected lengths of the buffers, depending on what type of
180 * linear filter used:
181 *
182 * <table>
183 * <tr>
184 * <th>Type</th>
185 * <th>Input Buffer Size</th>
186 * <th>Output Buffer Size</th>
187 * </tr>
188 * <tr>
189 * <td>Unspecified</td>
190 * <td>size of {@code ffGains}</td>
191 * <td>size of {@code fbGains}</td>
192 * </tr>
193 * <tr>
194 * <td>Single Pole IIR</td>
195 * <td>1</td>
196 * <td>1</td>
197 * </tr>
198 * <tr>
199 * <td>High-Pass</td>
200 * <td>2</td>
201 * <td>1</td>
202 * </tr>
203 * <tr>
204 * <td>Moving Average</td>
205 * <td>{@code taps}</td>
206 * <td>0</td>
207 * </tr>
208 * <tr>
209 * <td>Finite Difference</td>
210 * <td>size of {@code stencil}</td>
211 * <td>0</td>
212 * </tr>
213 * <tr>
214 * <td>Backward Finite Difference</td>
215 * <td>{@code Samples}</td>
216 * <td>0</td>
217 * </tr>
218 * </table>
219 *
220 * @param inputBuffer Values to initialize input buffer.
221 * @param outputBuffer Values to initialize output buffer.
222 * @throws std::runtime_error if size of inputBuffer or outputBuffer does not
223 * match the size of ffGains and fbGains provided in the constructor.
224 */
225 void Reset(std::span<const T> inputBuffer, std::span<const T> outputBuffer) {
226 // Clear buffers
227 Reset();
228
229 if (inputBuffer.size() != m_inputGains.size() ||
230 outputBuffer.size() != m_outputGains.size()) {
231 throw std::runtime_error(
232 "Incorrect length of inputBuffer or outputBuffer");
233 }
234
235 for (size_t i = 0; i < inputBuffer.size(); ++i) {
236 m_inputs.push_front(inputBuffer[i]);
237 }
238 for (size_t i = 0; i < outputBuffer.size(); ++i) {
239 m_outputs.push_front(outputBuffer[i]);
240 }
241 }
242
243 /**
244 * Calculates the next value of the filter.
245 *
246 * @param input Current input value.
247 *
248 * @return The filtered value at this step
249 */
250 T Calculate(T input) {
251 T retVal{0.0};
252
253 // Rotate the inputs
254 if (m_inputGains.size() > 0) {
255 m_inputs.push_front(input);
256 }
257
258 // Calculate the new value
259 for (size_t i = 0; i < m_inputGains.size(); ++i) {
260 retVal += m_inputs[i] * m_inputGains[i];
261 }
262 for (size_t i = 0; i < m_outputGains.size(); ++i) {
263 retVal -= m_outputs[i] * m_outputGains[i];
264 }
265
266 // Rotate the outputs
267 if (m_outputGains.size() > 0) {
268 m_outputs.push_front(retVal);
269 }
270
271 return retVal;
272 }
273
274 /**
275 * Returns the last value calculated by the LinearFilter.
276 *
277 * @return The last value.
278 */
279 T LastValue() const { return m_outputs.front(); }
280
281 private:
282 circular_buffer<T> m_inputs;
283 circular_buffer<T> m_outputs;
284 std::vector<double> m_inputGains;
285 std::vector<double> m_outputGains;
286
287 /**
288 * Factorial of n.
289 *
290 * @param n Argument of which to take factorial.
291 */
292 static constexpr int Factorial(int n) {
293 if (n < 2) {
294 return 1;
295 } else {
296 return n * Factorial(n - 1);
297 }
298 }
299};
300
301}
302}
303}
This class implements a linear, digital filter.
Definition LinearFilter.hpp:76
LinearFilter(std::initializer_list< double > ffGains, std::initializer_list< double > fbGains)
Create a linear FIR or IIR filter.
Definition LinearFilter.hpp:103
static LinearFilter< T > SinglePoleIIR(double timeConstant, 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:123
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:225
void Reset()
Reset the filter state.
Definition LinearFilter.hpp:170
T LastValue() const
Returns the last value calculated by the LinearFilter.
Definition LinearFilter.hpp:279
static LinearFilter< T > HighPass(double timeConstant, 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:143
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:158
LinearFilter(std::span< const double > ffGains, std::span< const double > fbGains)
Create a linear FIR or IIR filter.
Definition LinearFilter.hpp:84
T Calculate(T input)
Calculates the next value of the filter.
Definition LinearFilter.hpp:250
This is a simple circular buffer so we don't need to "bucket brigade" copy old values.
Definition circular_buffer.hpp:24
Definition motor_constants.h:14