Skip to content

Commit 03821c4

Browse files
finger563SquaredPotato
authored andcommitted
feat(filters): Update filters example, and expose filters to x-plat library (esp-cpp#330)
* feat(filters): Expose lowpass filters to lib and update example * update example to test more permutations of filters * fix sa * add missing cpp files to cmake * readme: update
1 parent e184e50 commit 03821c4

22 files changed

+593
-156
lines changed

components/filters/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
idf_component_register(
22
INCLUDE_DIRS "include"
3-
REQUIRES format esp-dsp)
3+
SRC_DIRS "src"
4+
REQUIRES esp_timer format esp-dsp)

components/filters/example/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Filters Example
22

3-
This example shows how to use a `LowpassFilter` and a `ButterworthFilter` from
4-
the `filters` component to filter signals. This example simply operates on
5-
random perturbations of auto-generated data.
3+
This example shows how to use a `LowpassFilter`, `ButterworthFilter`, and
4+
`SimpleLowpassFilter` from the `filters` component to filter signals. This
5+
example simply operates on random perturbations of auto-generated data.
66

77
## How to use example
88

@@ -22,4 +22,4 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
2222

2323
## Example Output
2424

25-
![output](https://user-images.githubusercontent.com/213467/202814226-a0a41887-d5f0-4de8-a69b-df3e1a5ed316.png)
25+
![output](https://github.com/user-attachments/assets/091ca271-3923-448e-9d26-998fc4f97297)

components/filters/example/main/filters_example.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "butterworth_filter.hpp"
77
#include "lowpass_filter.hpp"
8+
#include "simple_lowpass_filter.hpp"
89
#include "task.hpp"
910

1011
using namespace std::chrono_literals;
@@ -29,21 +30,29 @@ extern "C" void app_main(void) {
2930
.normalized_cutoff_frequency = normalized_cutoff_frequency,
3031
.q_factor = 1.0f,
3132
});
32-
static constexpr size_t ORDER = 2;
33+
espp::SimpleLowpassFilter slpf({
34+
.time_constant = normalized_cutoff_frequency,
35+
});
36+
espp::ButterworthFilter<1, espp::BiquadFilterDf1> bwf_df1_o1(
37+
{.normalized_cutoff_frequency = normalized_cutoff_frequency});
38+
espp::ButterworthFilter<2, espp::BiquadFilterDf1> bwf_df1_o2(
39+
{.normalized_cutoff_frequency = normalized_cutoff_frequency});
3340
// NOTE: using the Df2 since it's hardware accelerated :)
34-
espp::ButterworthFilter<ORDER, espp::BiquadFilterDf2> butterworth(
41+
espp::ButterworthFilter<2, espp::BiquadFilterDf2> bwf_df2_o2(
42+
{.normalized_cutoff_frequency = normalized_cutoff_frequency});
43+
espp::ButterworthFilter<4, espp::BiquadFilterDf2> bwf_df2_o4(
3544
{.normalized_cutoff_frequency = normalized_cutoff_frequency});
36-
fmt::print("{}\n", butterworth);
3745
static auto start = std::chrono::high_resolution_clock::now();
38-
auto task_fn = [&lpf, &butterworth](std::mutex &m, std::condition_variable &cv) {
46+
auto task_fn = [&](std::mutex &m, std::condition_variable &cv) {
3947
auto now = std::chrono::high_resolution_clock::now();
4048
float seconds = std::chrono::duration<float>(now - start).count();
4149
// use time to create a stairstep function
4250
constexpr float noise_scale = 0.2f;
4351
float input = floor(seconds) + get_random() * noise_scale;
44-
float lpf_output = lpf.update(input);
45-
float bwf_output = butterworth.update(input);
46-
fmt::print("{:.03f}, {:.03f}, {:.03f}, {:.03f}\n", seconds, input, lpf_output, bwf_output);
52+
fmt::print("{:.03f}, {:.03f}, {:.03f}, {:.03f}, "
53+
"{:.03f}, {:.03f}, {:.03f}, {:.03f}\n",
54+
seconds, input, slpf.update(input), lpf.update(input), bwf_df1_o1.update(input),
55+
bwf_df1_o2.update(input), bwf_df2_o2.update(input), bwf_df2_o4.update(input));
4756
// NOTE: sleeping in this way allows the sleep to exit early when the
4857
// task is being stopped / destroyed
4958
{
@@ -56,7 +65,9 @@ extern "C" void app_main(void) {
5665
auto task = espp::Task({.name = "Lowpass Filter",
5766
.callback = task_fn,
5867
.log_level = espp::Logger::Verbosity::INFO});
59-
fmt::print("% time (s), input, lpf_output, bwf_output\n");
68+
fmt::print("% time (s), input, simple_lpf_output, lpf_output, "
69+
"bwf_df1_o1_output, bwf_df1_o2_output, bwf_df2_o2_output, "
70+
"bwf_df2_o4_output\n");
6071
task.start();
6172
//! [filter example]
6273
std::this_thread::sleep_for(num_seconds_to_run * 1s);

components/filters/include/biquad_filter.hpp

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#include <array>
44
#include <cmath>
55

6+
#if defined(ESP_PLATFORM)
67
#include "esp_dsp.h"
8+
#endif
79

810
#include "format.hpp"
911
#include "transfer_function.hpp"
@@ -122,7 +124,13 @@ class BiquadFilterDf2 {
122124
* @param length Number of samples, should be >= length of input & output memory.
123125
*/
124126
void update(const float *input, float *output, size_t length) {
127+
#if defined(ESP_PLATFORM)
125128
dsps_biquad_f32(input, output, length, coeffs_.data(), w_.data());
129+
#else
130+
for (size_t i = 0; i < length; i++) {
131+
output[i] = update(input[i]);
132+
}
133+
#endif
126134
}
127135

128136
/**
@@ -133,7 +141,13 @@ class BiquadFilterDf2 {
133141
*/
134142
float update(const float input) {
135143
float result;
144+
#if defined(ESP_PLATFORM)
136145
dsps_biquad_f32(&input, &result, 1, coeffs_.data(), w_.data());
146+
#else
147+
result = input * b_[0] + w_[0];
148+
w_[0] = input * b_[1] - result * a_[0] + w_[1];
149+
w_[1] = input * b_[2] - result * a_[1];
150+
#endif
137151
return result;
138152
}
139153

@@ -148,28 +162,4 @@ class BiquadFilterDf2 {
148162
};
149163
} // namespace espp
150164

151-
// for allowing easy serialization/printing of the
152-
// espp::BiquadFilterDf1
153-
template <> struct fmt::formatter<espp::BiquadFilterDf1> {
154-
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
155-
return ctx.begin();
156-
}
157-
158-
template <typename FormatContext>
159-
auto format(espp::BiquadFilterDf1 const &bqf, FormatContext &ctx) const {
160-
return fmt::format_to(ctx.out(), "DF1 - B: {}, A: {}", bqf.b_, bqf.a_);
161-
}
162-
};
163-
164-
// for allowing easy serialization/printing of the
165-
// espp::BiquadFilterDf2
166-
template <> struct fmt::formatter<espp::BiquadFilterDf2> {
167-
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
168-
return ctx.begin();
169-
}
170-
171-
template <typename FormatContext>
172-
auto format(espp::BiquadFilterDf2 const &bqf, FormatContext &ctx) const {
173-
return fmt::format_to(ctx.out(), "DF2 - B: {}, A: {}", bqf.b_, bqf.a_);
174-
}
175-
};
165+
#include "biquad_filter_formatters.hpp"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include "format.hpp"
4+
5+
// for allowing easy serialization/printing of the
6+
// espp::BiquadFilterDf1
7+
template <> struct fmt::formatter<espp::BiquadFilterDf1> {
8+
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
9+
return ctx.begin();
10+
}
11+
12+
template <typename FormatContext>
13+
auto format(espp::BiquadFilterDf1 const &bqf, FormatContext &ctx) const {
14+
return fmt::format_to(ctx.out(), "DF1 - B: {}, A: {}", bqf.b_, bqf.a_);
15+
}
16+
};
17+
18+
// for allowing easy serialization/printing of the
19+
// espp::BiquadFilterDf2
20+
template <> struct fmt::formatter<espp::BiquadFilterDf2> {
21+
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
22+
return ctx.begin();
23+
}
24+
25+
template <typename FormatContext>
26+
auto format(espp::BiquadFilterDf2 const &bqf, FormatContext &ctx) const {
27+
return fmt::format_to(ctx.out(), "DF2 - B: {}, A: {}", bqf.b_, bqf.a_);
28+
}
29+
};

components/filters/include/butterworth_filter.hpp

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cmath>
44

5+
#include "format.hpp"
56
#include "sos_filter.hpp"
67

78
namespace espp {
@@ -15,7 +16,7 @@ namespace espp {
1516
* @tparam ORDER The order of the filter.
1617
* @tparam Impl Which Biquad implementation form to use.
1718
*/
18-
template <size_t ORDER, class Impl = BiquadFilterDf2>
19+
template <size_t ORDER, class Impl = BiquadFilterDf1>
1920
class ButterworthFilter : public SosFilter<(ORDER + 1) / 2, Impl> {
2021
public:
2122
/**
@@ -72,23 +73,4 @@ class ButterworthFilter : public SosFilter<(ORDER + 1) / 2, Impl> {
7273
};
7374
} // namespace espp
7475

75-
// for allowing easy serialization/printing of the
76-
// espp::ButterworthFilter
77-
template <size_t ORDER, class Impl> struct fmt::formatter<espp::ButterworthFilter<ORDER, Impl>> {
78-
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
79-
return ctx.begin();
80-
}
81-
82-
template <typename FormatContext>
83-
auto format(espp::ButterworthFilter<ORDER, Impl> const &f, FormatContext &ctx) const {
84-
auto &&out = ctx.out();
85-
fmt::format_to(out, "Butterworth - [");
86-
if constexpr (ORDER > 0) {
87-
fmt::format_to(out, "[{}]", f.sections_[0]);
88-
}
89-
for (int i = 1; i < (ORDER + 1) / 2; i++) {
90-
fmt::format_to(out, ", [{}]", f.sections_[i]);
91-
}
92-
return fmt::format_to(out, "]");
93-
}
94-
};
76+
#include "butterworth_filter_formatters.hpp"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include "format.hpp"
4+
5+
// for allowing easy serialization/printing of the
6+
// espp::ButterworthFilter
7+
template <size_t ORDER, class Impl> struct fmt::formatter<espp::ButterworthFilter<ORDER, Impl>> {
8+
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
9+
return ctx.begin();
10+
}
11+
12+
template <typename FormatContext>
13+
auto format(espp::ButterworthFilter<ORDER, Impl> const &f, FormatContext &ctx) const {
14+
auto &&out = ctx.out();
15+
fmt::format_to(out, "Butterworth - [");
16+
if constexpr (ORDER > 0) {
17+
fmt::format_to(out, "[{}]", f.sections_[0]);
18+
}
19+
for (int i = 1; i < (ORDER + 1) / 2; i++) {
20+
fmt::format_to(out, ", [{}]", f.sections_[i]);
21+
}
22+
return fmt::format_to(out, "]");
23+
}
24+
};

components/filters/include/lowpass_filter.hpp

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
#include <stdlib.h>
44

5+
#if defined(ESP_PLATFORM)
56
#include "esp_dsp.h"
7+
#endif
8+
9+
#include "format.hpp"
610

711
namespace espp {
812
/**
@@ -21,11 +25,22 @@ class LowpassFilter {
2125
q_factor; /**< Quality (Q) factor of the filter. The higher the Q the better the filter. */
2226
};
2327

28+
/**
29+
* @brief Default constructor.
30+
*/
31+
LowpassFilter() = default;
32+
2433
/**
2534
* @brief Initialize the lowpass filter coefficients based on the config.
2635
* @param config Configuration struct.
2736
*/
28-
explicit LowpassFilter(const Config &config) { init(config); }
37+
explicit LowpassFilter(const Config &config);
38+
39+
/**
40+
* @brief Set the filter coefficients based on the config.
41+
* @param config Configuration struct.
42+
*/
43+
void configure(const Config &config);
2944

3045
/**
3146
* @brief Filter the input samples, updating internal state, and writing the
@@ -34,52 +49,41 @@ class LowpassFilter {
3449
* @param output Pointer to (floating point) array which will be filled with
3550
* the filtered input.
3651
* @param length Number of samples, should be >= length of input & output memory.
52+
* @note On ESP32, the input and output arrays must have
53+
* __attribute__((aligned(16))) to ensure proper alignment for the ESP32
54+
* DSP functions.
3755
*/
38-
void update(const float *input, float *output, size_t length) {
39-
dsps_biquad_f32(input, output, length, coeffs_, state_);
40-
}
56+
void update(const float *input, float *output, size_t length);
4157

4258
/**
4359
* @brief Filter the signal sampled by input, updating internal state, and
4460
* returning the filtered output.
4561
* @param input New sample of the input data.
4662
* @return Filtered output based on input and history.
4763
*/
48-
float update(const float input) {
49-
float output;
50-
dsps_biquad_f32(&input, &output, 1, coeffs_, state_);
51-
return output;
52-
}
64+
float update(const float input);
5365

5466
/**
5567
* @brief Filter the signal sampled by input, updating internal state, and
5668
* returning the filtered output.
5769
* @param input New sample of the input data.
5870
* @return Filtered output based on input and history.
5971
*/
60-
float operator()(float input) { return update(input); }
72+
float operator()(float input);
73+
74+
/**
75+
* @brief Reset the filter state to zero.
76+
*/
77+
void reset();
6178

6279
friend struct fmt::formatter<LowpassFilter>;
6380

6481
protected:
65-
void init(const Config &config) {
66-
dsps_biquad_gen_lpf_f32(coeffs_, config.normalized_cutoff_frequency, config.q_factor);
67-
}
82+
void init(const Config &config);
6883

69-
float coeffs_[5];
70-
float state_[2];
84+
float coeffs_[5] = {0, 0, 0, 0, 0};
85+
float state_[5] = {0, 0, 0, 0, 0};
7186
};
7287
} // namespace espp
7388

74-
// for allowing easy serialization/printing of the
75-
// espp::LowpassFilter
76-
template <> struct fmt::formatter<espp::LowpassFilter> {
77-
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
78-
return ctx.begin();
79-
}
80-
81-
template <typename FormatContext>
82-
auto format(espp::LowpassFilter const &f, FormatContext &ctx) const {
83-
return fmt::format_to(ctx.out(), "Lowpass - {}", f.coeffs_);
84-
}
85-
};
89+
#include "lowpass_filter_formatters.hpp"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include "format.hpp"
4+
5+
// for allowing easy serialization/printing of the
6+
// espp::LowpassFilter
7+
template <> struct fmt::formatter<espp::LowpassFilter> {
8+
template <typename ParseContext> constexpr auto parse(ParseContext &ctx) const {
9+
return ctx.begin();
10+
}
11+
12+
template <typename FormatContext>
13+
auto format(espp::LowpassFilter const &f, FormatContext &ctx) const {
14+
return fmt::format_to(ctx.out(), "Lowpass - {}", f.coeffs_);
15+
}
16+
};

0 commit comments

Comments
 (0)