1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Copyright 2023 Enphase Energy, Inc.
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

/*!
# `fixed-filters`

Biquad filters for fixed-point numbers.

`fixed-filters` is a `#![no_std]` library for creating [biquad filters](https://en.wikipedia.org/wiki/Digital_biquad_filter).  These are simple 2nd order IIR filters which can be configured to implement a variety of filter behaviors (i.e. lowpass, bandpass, highpass, notch, proportional-integral, etc).  It is designed specifically to work with 32-bit fixed-point numbers based on the [fixed](https://crates.io/crates/fixed) crate.

**Alpha:** This crate requires the alpha release of 2.0.0 of the [fixed](https://crates.io/crates/fixed) crate that makes use of const generics instead of the [*typenum* crate](https://crates.io/crate/typenum). This version requires the nightly compiler with the [`generic_const_exprs` feature] enabled.

# How to use

The main data structure for this crate is [Biquad<X,Y>](crate::biquad::Biquad) where `X` and `Y` are `i32` constant generics which represent the number of fractional bits of the input and output signals respectively.  While the run functions are all designed use fixed-point arithmetic, the construction APIs accept floating point variables to simplify use.  The most generic way of creating a filter is to construct with `new()` and pass the filter coefficients.

```rust
#![feature(generic_const_exprs)]

use fixed_filters::Biquad;
use fixed::FixedI32;

// poles
let a0 = 1.;
let a1 = -1.97985135;
let a2 = 0.980052318;

// zeros
let b0 = 0.000050241;
let b1 = 0.000100482;
let b2 = 0.000050241;

// make a filter with 20 fractional bits on input and 16 fractional bits on output
let mut filter = Biquad::<20, 16>::new(a0, a1, a2, b0, b1, b2);

let signal = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut y = FixedI32::<16>::ZERO;
for s in signal {
    let x = FixedI32::<20>::from_num(s); // convert to fixed-point number
    y = filter.update(x);
}
```

While passing the pole and zero coefficients is the most universal way of creating a filter, `Biquad` includes a number of associated functions for creating standard filter forms with the user specifying just the sampling period, the center frequency, and the q factor.

```rust
use fixed_filters::Biquad;

// create a butterworth lowpass filter
let ts = 1./10e3;
let f0 = 100.;
let q = core::f32::consts::FRAC_1_SQRT_2;
let mut filter = Biquad::<16, 16>::lowpass(ts, f0, q);
```

The library includes associted functions for creating the following filter forms:

1.  [lowpass](crate::biquad::Biquad::lowpass)
2.  [bandpass](crate::biquad::Biquad::bandpass)
3.  [highpass](crate::biquad::Biquad::highpass)
4.  [notch](crate::biquad::Biquad::notch)
5.  [single_pole_lowpass](crate::biquad::Biquad::single_pole_lowpass)
6.  [proportional-integral behavior](crate::biquad::Biquad::pi)

By default, the filters do not limit their output and can in fact over-flow if you are not careful.  You can add limits to an existing filter using the `set_min`, `set_max`, or `set_limit` functions, or you can setup limits at construction using a builder pattern with the `with_min`, `with_max`, or `with_limit` functions.

```rust
use fixed_filters::Biquad;

// create a proportional-integral controller with a limit ot +-20
let kp = 0.1;
let ki = 0.01;
let mut filter = Biquad::<16, 16>::pi(kp, ki).with_limit(20.0);
```

# Benchmarking

The performance of this crate was benchmarked against other available `#![no_std]` biquad filter implementations by running a filter at 10kHz in a simple [RTIC](https://rtic.rs/1/book/en/) application on an [STM32F411RE Nucelo](https://www.st.com/en/evaluation-tools/nucleo-f411re.html) board.  The RTIC monotonic API was used to quantify the number of CPU cycles used for each filters' update method.

| Crate                         | Cycles  |
| :---------------------------- |:-------:|
| fixed-filters                 | 80      |
| biquad::DirectForm1           | 80      |
| biquad::DirectForm2Transposed | 72      |
| idsp::iir_int                 | 90      |

From these results, it can be seen that the crates all provide similar performance in terms of execution efficiency with [biquad](https://crates.io/crates/biquad/0.3.0) DirectForm2Transposed having a slight edge in microcontrollers with an FPU.  This crate however does not support internal limiting of the output signal which can be critical in certain applications such as implementing anti-windup and PI controllers.
*/
#![cfg_attr(not(test), no_std)]

pub mod biquad;

pub use biquad::Biquad;