Help with digital low pass filter

Good morning, i have the next problem, i need to make a low pass filter with a cut-off frecuency of 100 Hz but i don´t know nothing about how to program a low pass filter, i made some reasearch on internet but the information i found doesn´t help me too much.

I need some back up information (documents, tips, reference codes) in order to program the low pass filter

I attached an image of the circuit made in proteus, im using the ATMega 2560 (And yes, there is a R/2R DAC connected to the output).

Thanks for read.

a leaky integrator is a simple low pass filter

A += (s - A) * K K < 1

if K is 1/8 (0.125), A will be near a value of 1 after 24 (3 * 8) iterations, similar to 3 RC time constants.

the sample rate and K determine the time constant. I think you want 0.01 sec or 100 Hz

Also note that if the value of K is the inverse of a power of two, the multiplication (s - A)*K can be done with a shift operation. So you can do this with scaled (fixed point) integer arithmetic and be orders of magnitude faster than floating point math on the 2560.

I've used an online filter design utility that produces C code easily ported to Arduino here: https://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html

You've specified the cutoff frequency and would need the ADC sample rate as a design parameter. A 2nd order low pass filter so designed will run up to about 4000 samples per second on a 16 MHz 8 bit Arduino. This uses floating point, so it trades considerably more processor time than the suggestions above for flatter in band performance and higher out of band rejection than the integrator approach described in the first reply.

With a better overview of what you're trying to do, specifically what are the characteristics of the signal that is being filtered, we could offer more detailed guidance.

If you want some simple smoothing, a single-pole exponential moving average filter can be used.
It can be implemented very efficiently, and it smooths alright, but it has a nonlinear phase response, which means it distorts the signal.

You can find a full discussion of the exponential moving average filter and its characteristics here:
Exponential Moving Average
You can find implementation details and an Arduino example here:
EMA: C++ implementation
The EMA class is included in the Arduino Filters library:
EMA documentation

The implementations use inverses of powers of two for the filter coefficients (as mentioned by aarg). If you want finer control over the cut-off frequency, you could implement it using more general coefficients, using fixed-point integer divisions or floats but there are two things to keep in mind:

  1. It's often easier (and more efficient) to vary the sampling rate rather than the filter coefficients.
  2. The cut-off frequency of this filter is quite arbitrary anyway, since the roll-off is so slow (≃20dB/dec).

For more serious filtering, Butterworth filters are worth looking into. They have three main advantages: 1. the order can easily be increased for sharper roll-off, 2. the magnitude response in the pass-band is almost perfectly flat (by design) and 3. they have an almost linear phase response in the pass band (i.e. less distortion of the signal).
A flat pass band means that none of the frequencies below 100Hz will be amplified or attenuated.

The disadvantage is that they're more expensive to compute. You'll probably need floating point math, and the higher the order, the more calculations you need to carry out.

You can find a mathematical derivation of Butterworth filters here:
Butterworth filters
Butterworth filters are defined in the analog domain, so you have to discretize it in order to use it for digital filters:
Discretization of Butterworth filters
If you're not interested in all the mathematics, you can just use the implementation of the Arduino Filters library:
Butterworth filter: Arduino example

Simply enter the desired sampling rate and cut-off frequency, and the library will handle prewarping and filter design for you. By default, it uses a BiQuad implementation, so it should be numerically stable.
As mentioned by MrMark, an 8-bit AVR at 16 MHz like the ATmega2560 on an Arduino Mega will be able to handle a sampling rate of a little over 4 kHz for a second-order filter, 3.5 kHz for a fourth-order filter, 2.8 kHz for a sixth-order filter.

If the analog signal you want to filter has components with a frequency higher than half of the sampling rate, you'll need an anti-aliasing filter before your ADC (in hardware), see Nyquist-Shannon sampling theorem.

Pieter

MrMark:
I've used an online filter design utility that produces C code easily ported to Arduino here: https://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html

You've specified the cutoff frequency and would need the ADC sample rate as a design parameter. A 2nd order low pass filter so designed will run up to about 4000 samples per second on a 16 MHz 8 bit Arduino. This uses floating point, so it trades considerably more processor time than the suggestions above for flatter in band performance and higher out of band rejection than the integrator approach described in the first reply.

With a better overview of what you're trying to do, specifically what are the characteristics of the signal that is being filtered, we could offer more detailed guidance.

That is the biggest part of the problem, the only thing my professor told me is that the cut-off frequency has to be 100 Hz, with the signal generator (is in the image attached at the beginning of this post) connected to the input of the circuit and with an oscilloscope and the DAC R/2R connected to the output, i don´t get information about the real number of Bits that i have to use with the DAC R/2R so i made a R/2R of 8 bits.

The objective is that the output signal showed in the oscilloscope fade away as frequency varies (the frecuency variation is done manually).

PieterP:
If you're not interested in all the mathematics, you can just use the implementation of the Arduino Filters library:
Butterworth filter: Arduino example

Pieter

Thanks a lot for the information, i gonna try with the arduino filter library.

Sorry if the next question sounds really "noob" but, how can i show the filtered signal in an oscilloscope? (i attached an image of the circuit at the beginning of the post) looks like i have to edit the void loop in order to get it working with the oscilloscope, isn´t it?

Sorry for the tons of questions, i´m really new with arduino.

Yes, you'll have to edit the loop function.
If your R/2R DAC is wired up correctly, to the 8 pins of one of the ports, you can simply add the line DDRDX = 0xFF to the setup. In your loop, you can output the data using [nobbc]PORTX = constrain(std::round(filter(analogRead(A0)) / 8), 0, 255)[/nobbc], where X is the port you're using for the DAC.

Edit: it looks like your DAC pins are all over the place. Use a single port, e.g. port K, and connect all 8 pins of the DAC to the 8 pins of this port (i.e. A8-15), where the least significant bit of the DAC (C0 in your diagram) connects to the least significant bit of the port (PK0, i.e. A8).

Ok Pieter, i gonna try it, thanks for the information.

PieterP:
Yes, you'll have to edit the loop function.
If your R/2R DAC is wired up correctly, to the 8 pins of one of the ports, you can simply add the line DDRDX = 0xFF to the setup. In your loop, you can output the data using [nobbc]PORTX = constrain(std::round(filter(analogRead(A0)) / 8), 0, 255)[/nobbc], where X is the port you're using for the DAC.

Edit: it looks like your DAC pins are all over the place. Use a single port, e.g. port K, and connect all 8 pins of the DAC to the 8 pins of this port (i.e. A8-15), where the least significant bit of the DAC (C0 in your diagram) connects to the least significant bit of the port (PK0, i.e. A8).

Hi, i already tried with your recommendations, i put the R/2R DAC in the port C (A8-A15) and i modified the setup and the loop, the program compiled, but when i tried to run it on Proteus i got a ton of errors, all of them with the same problem, saying "PC=0x028C. [AVR AD CONVERTER] Reference value = 0. [U1]", it sometimes changes to "0x028A" or "0x0286".

Apparently i made some mistakes in the code.

This is the code right now, i think the problem is with the void loop, i gonna keep trying in order to make it functional, however if someone sees what´s the mistake with the code please tell me.

#include <Filters.h>

#include <AH/Timing/MillisMicrosTimer.hpp>
#include <Filters/Butterworth.hpp>

void setup() {
Serial.begin(115200);
DDRC = 0xFF; //The DAC R/2R is connected in the port C, that is the reason of the port C being an output port
}

// Sampling frequency
const double f_s = 200; // Hz
// Cut-off frequency (-3 dB)
const double f_c = 120; // Hz //I received news in the morning, the cut-off frecuency is now 120 Hz, not 100 Hz
// Normalized cut-off frequency
const double f_n = 2 * f_c / f_s;

// Sample timer
Timer timer = std::round(1e6 / f_s);

// Sixth-order Butterworth filter
auto filter = butter<6>(f_n);

void loop() {
if (timer)
Serial.println(filter(analogRead(A0)));
PORTC = constrain(std::round(filter(analogRead(A0)) / 8 ), 0, 255);
}

Your indentation suggests you think both the print and the output are conditional.
They are not.
Also, why two (slow) analogReads?

You cannot have a digital filter with a sampling rate of 200 Hz and a cut-off frequency of 120 Hz. It's mathematically impossible, the maximum frequency you can represent with a sampling rate of 200 Hz is 100 Hz. See the Nyquist-Shannon sampling theorem I mentioned in reply #4.
Use a much higher sampling rate, try 2000 Hz, for example.

If you look at the documentation of the butter function, you'll see that the normalized cut-off frequency should be a number from 0 to 1. In your program, it's 2×120/200 = 1.2.

If you go higher, make sure that your code is fast enough. Measure how long it takes to process 2000 samples, it should be almost exactly 1 second. You could also toggle a pin each time you update the filter, and connect that pin to a frequency counter or oscilloscope. To toggle a pin really efficiently, you can write a one to the corresponding bit in the PINX register: PINX |= 1 << b, where X is the port, and b is the bit (between 0 and 7).

You can only update the filter once on each iteration. filter(analogRead(A0)) actually updates the state of the filter. If you update it at the wrong rate, or twice per sampling period, the results are completely meaningless. Digital filters rely on a steady sampling rate.

Like AWOL said, your if statement is incorrect, the second line is not part of the body of the if statement. Add braces around it if you want them both to be inside of the body, indentation is meaningless to the compiler.
I'd suggest removing the Serial.println line altogether, it'll only slow down your code.