To read a digital or analog input reliably on an Arduino PLC, filter it in software. The Industrial Shields Filter library has two classes: DigitalFilter<T> accepts a new input state only after it has stayed stable for T milliseconds (debounce), and AnalogFilter<N, T> returns the average of the last N samples, taking one sample every T milliseconds.
This post explains why an input needs a filter, how each class works and how to use it with two examples for the M-Duino PLC: a digital filter on input I0.8 and an analog filter on input I0.10.
Why filter a digital or analog input?
When a digital input changes from HIGH to LOW, or from LOW to HIGH, the signal does not change cleanly. Mechanical contacts such as push buttons, relays and limit switches bounce, and the PLC can read several changes in a few milliseconds. If the program counts pulses or reacts to each change, these bounces are read as wrong values and cause errors.
Analog inputs have a similar problem: electrical noise and small variations of the sensor make the reading move from one value to the next. An average of several samples gives a more stable value.
| Class | What it does | Parameters |
|---|---|---|
DigitalFilter<T> | Debounce. The output changes only when the input keeps the new state for T ms. | T: filtering time in ms |
AnalogFilter<N, T> | Moving average of the last N samples. | N: number of samples. T: sample period in ms (0 takes samples as fast as possible) |
Requirements
- An Arduino PLC from Industrial Shields. The examples use inputs I0.8 and I0.10, available on the M-Duino 21, the M-Duino 42 and the M-Duino 58. On an Ardbox Analog or another model, change the pins to inputs of your PLC.
- The Industrial Shields boards installed in the Arduino IDE: how to install the Industrial Shields boards.
- The Filter library on GitHub. Download it as a ZIP file and add it from the Arduino IDE menu Sketch > Include Library > Add .ZIP Library.
The Filter library was part of the Tools40 library. Since version 1.0.0 (January 2024) it is a separate library, under the LGPL license, and it works on all architectures.
Example 1: digital filter on input I0.8
The template parameter of DigitalFilter is the filtering time in milliseconds. In this example it is 500 ms.
#include <Filter.h>
// Digital filter (debounce)
// Filtering time: 500 ms. A new state is accepted only
// after it has stayed stable for 500 ms
DigitalFilter<500> filter;
void setup() {
// Init Serial port
Serial.begin(9600L);
pinMode(I0_8, INPUT);
}
void loop() {
// Read pin value
int value = digitalRead(I0_8);
// Filter it
int filtered = filter.update(value);
// Print the filtered value
Serial.println(filtered);
}
How it works: while the input keeps the same value as the filtered output, the library restarts its timer. When the input changes, the output keeps the old value. The new value is accepted only if the input stays in it for 500 ms. Bounces and pulses shorter than 500 ms are ignored, and a real change appears at the output 500 ms after the last bounce. This works the same way from HIGH to LOW and from LOW to HIGH.
Choose the filtering time for your signal: a shorter time reacts faster, and a longer time rejects longer disturbances.
Example 2: analog filter on input I0.10
For an analog input, define the number of samples and the sample period in milliseconds:
#include <Filter.h>
// Analog filter (moving average)
// Number of samples: 10
// Sample period: 5 ms (with 0, samples are taken as fast as possible)
AnalogFilter<10, 5> filter;
void setup() {
// Init Serial port
Serial.begin(9600L);
pinMode(I0_10, INPUT);
}
void loop() {
// Read pin value
int value = analogRead(I0_10);
// Filter it
int filtered = filter.update(value);
// Print the filtered value
Serial.println(filtered);
}
With AnalogFilter<10, 5> the filter keeps the last 10 samples, one every 5 ms, and update() returns their average. The average covers the last 50 ms of the signal. More samples or a longer period give a smoother value that follows changes more slowly.
The filter starts with all samples at 0. To start from another value, pass it to the constructor, for example AnalogFilter<10, 5> filter(512);.
Call update() in every loop and avoid delay() in the program: both filters use millis() to measure time. Create one filter object for each input you want to filter.
Both examples compile for the M-Duino 21+ and the M-Duino 58+ with the Industrial Shields AVR boards package 1.2.1.
FAQ about input filters on an Arduino PLC
Does the digital filter add a delay?
Yes. A real change reaches the output after the filtering time, 500 ms in the example. Use a shorter time if the program must react faster.
Can I filter several inputs?
Yes. Each filter object keeps its own state, so create one object for each input.
Is Filter the same as the Tools40 library?
The filter classes come from Tools40. They are now in the separate Filter library, with the same DigitalFilter and AnalogFilter classes.