Filter in C zum Vermeiden von Jittern bei digitalen Eingabeports bei Embedded-Programmen
Filter in C to prevent jitter problems on digital input ports in embedded applications
The header you see below contains a filter macro for C/C++, which allows to prevent input signal level detection problems if your input has some jitters. Latter can occur if a digital input of your controller is connected to a relay or if the controller input has no internal Schmitt-Trigger, or simply if the signal is noisy and has slowly rising/falling edges. In this case you might want to filter this digital input signal and wait until it is stable enough. On the other hand it is often not acceptable to spend much memory or CPU power for a filter function. The little piece of code below is a comprimise of these requirements. You normally need a 8-bit character sized variable for this macro, as the macro is inline it leaves the compiler a good opportunity to optimise, and it does not use any other variables than the one you use as buffer.
The way how it works and how to use it is annotated in the header file below.
Notes:
Before using this macro check if your controller hardware has already configurable input filters - some do.
Often used and applicable filter length values are in range between 2 and 5. If you have a high sample rate compared to the time that your application needs to react you use define 16 bit buffers as well. The macro does not care about the size, as long as the data type you choose is somehow integer numeric.
It is not wise to choose large filter lengths if you don't need to. This filter is in the very end a sliding-window low pass filter with output threshold. That means it delays the detection of edes by
length
sample cycles.
Die weiter unten stehende Headerdatei enthält ein Filter-Makro in C/C++, mit dem Signalschwankungen von digitalen I/Os ausgeglichen werden können. Solche Signalschwankungen treten z.B. auf wenn der Port an einem Relay angeschlossen ist (Prellen), wenn der Port keinen (guten) Schmitt-Trigger hat, oder einfach wenn das Signal verrauscht ist und keine steilen Flanken hat. In diesen Fällen macht es Sinn, das Eingabesignal zu filtern bzw. "auf ein stabiles Signal zu warten". Andererseits spendiert nimand gerne Speicher und Rechenleistung für aufwendige Filter. Die sieben Zeilen Quellcode in der Headerdatei stellen einen Kompromiss dar. Normalerweise wird eine 8-Bit-Variable benötigt, um über bis zu 6 Takte zu filtern. Als Makro ist die Funktion "inline" und arbeitet nur mit der angegebenen Puffervariablen (ohne weiteren Stackverbrauch). Der Compiler kann dies gut optimieren, so dass die meisten Operationen direkt mit einem Register und dem Konstantenspeicher arbeiten.
Wie es genau funktionniert und zu verwenden ist habe ich im Kommentarbereich der Headerdatei angegeben. Ein Beispielprogramm mit Makefile gibt's ebenfalls weiter unten (mit Ausgabe).
Anmerkungen:
Bevor dieses Makro verwendet wird bitte nochmals checken ob die Controllerhardware einen konfigurierbaren Filter hat - manche Controller/DSPs haben das.
Oft genutzte Standardwerte für die Filterlänge bewegen sich zeischen 2 und
- Wenn die Samplerate des Controllers viel höher ist als die benötigte Reaktionszeit des Programms, so kann auch eine 16- oder 32-bit Variable als Puffer verwendet werden. Dem Makro ist dies reichlich egal, so lange der Datentyp ganzzahlig ist.
Es macht oft keinen Sinn große Filterlängen zu definieren wenn man sie nicht braucht. Letztendlich ist dieser Bitfilter ein "Sliding-Window"-Tiefpass mit Schwellenwerterkennung. D.h. er verzögert die Erkennung von Signaländerungen für soviele Takte wie der Filter groß ist.
Header file containing the filter macro
Header-Datei mit dem Filter-Makro
/**
* @file bitfilter.h
* @author stfwi
* @date 2007-05-12
*
* ---
*
* 2013-06-03 Added extended documentation for publishing.
*
* ---
*
* Bit filter macro for use to prevent digital input jitter problems in embedded
* applications.
*
* Usage:
*
* bool_t is_io_set = bitfilter(buffer_variable, filter_length, new_io_bit);
*
* E.g. in a program where the io bit is memory mapped and defined in a
* structured variable gpio.port0.bit0 you like to filter over 4 samples and
* use an 8 bit filter buffer (unsigned char == uint8_t):
*
* uint8_t gpio0_0_buffer = 0;
* [...]
* if(bitfilter(gpio0_0_buffer, 4, gpio.port0.bit0)) {
* // Input is 1
* } else {
* // Input is 0
* }
*
* BUF is your buffer variable that the "function" needs.
* L is the length of the buffer. It must be at least 2 bits smaller than
* the size of yout buffer variable data type. E.g. if you use uint8, then
* the maximum length of the filter is 6. This is because the two MSBs are
* reserved to save the state of the filter output. NB is the new input value
* you like to add to the filter (e.g. read from io).
*
* How it works:
*
* The code below looks a bit complex, but in the very end this is the
* how it works. The buffer is used to save the last L bits, beginning form
* the LSB. Every time the macro is "called" the buffer will be shifted one
* bit left and the new bit will be set to the LSB. If all L bits are 1, then
* the output will change to 1. If all L buffer bits are 0 it will change to 0.
* Otherwise it will return the last state, which is written in bit L+1.
* Because of the left shift at the beginning, this result bit will be shifted
* to L+2 (this is why 2 bits are reserved). Everything is done in nested
* assignments, so that no other variables are needed to run this filter, and
* the compiler has plenty of possibilities to optimise this inline code
* fragment. E.g. for a 4 bit filter length this would be the buffer of a
* 8 bit variable:
*
* Unused bits
* /
* | Last return (shifted one up at the beginning)
* | /
* | | / Return value, saved for next cycle
* | || buffer (4 bits)
* | || /
* 00|LR|3210
*
* The macro line by line:
*
* 1. This is the start of the assignment of the result bit
* 2. We shift one up, set the new bit at position 1 and save the buffer back
* 3. We set the result bit if all buffer bits are set. Here 1<<L is bit 4
* (00010000), which is one higher the buffer bits [0-3] == 00001111.
* The "all bit check" is done using (1<<L)-1, which expands to 00001111.
* 4. !(BUF & ((1<<L)-1) is the boolean check if the buffer bits are all 0.
* 5. ((BUF & ((1<<L)<<1)) is the bit 6 00100000, which is the last result.
* if this is set we set bit 5 as well for the next time.
* 6. Line 5 was not true, so we reset the bit 5 as well.
* 7. We return TRUE if the bit 5 is now set, which is the actual result.
*
* @param BUF Your filter buffer variable
* @param L The length of the filter 1 to (sizeof(BUF)-1)>>2
* @param NB The new bit to add. Must be 0x0 or 0x1 (other bits ignored)
* @return BOOL
*/
#ifndef bitfilter
#define bitfilter(BUF, L, NB) ( \
((BUF=( /* 1. Framing assignment of result */\
((BUF=((BUF<<1)|(NB&0x01))) /* 2. Add new value */\
& (1<<L)-1) == ((1<<L)-1) ? (BUF|(1<<L)) :/* 3. All bits 1 -> set result bit */\
(!(BUF & ((1<<L)-1)) ? (BUF&~(1<<L)) /* 4. All bits 0 -> clear result */\
: ((BUF & ((1<<L)<<1)) ? (BUF|(1<<L)) : /* 5. Set result (last result == 1 */\
(BUF&~(1<<L))) )) /* 6. Clear result */\
) & (1<<L)) != 0 /* 7. Return bool */\
) \
#endif
Beispielprogramm
Example program
/**
* Example for bit filter macro.
*
* @file bitfilter.c
* @author stfwi
*/
#include "bitfilter.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Test main function
*
* @param int argc
* @param char **argv
* @return int
*/
int main(int argc, char** argv)
{
unsigned short buffer = 0;
int i, j, r, filter_length;
const char *input = NULL;
// Input and usage
if(argc != 3) {
fprintf(stderr, "Usage: bitfilter <filter length> <input sequence>\n");
fprintf(stderr, "\n");
fprintf(stderr, " filter: number from 2 to 14\n");
fprintf(stderr, " input : text containing only '1' and '0'\n");
fprintf(stderr, " e.g. '011010010001110011'\n");
fprintf(stderr, "\n");
fprintf(stderr, "Bit filter example for filtering of digital I/O levels.\n");
fprintf(stderr, "The program uses a 16 bit buffer variable. 2 bits are\n");
fprintf(stderr, "reserved, so the maximum filter length is 14.\n");
fprintf(stderr, "\n");
exit(1);
}
filter_length = atoi(argv[1]);
if(filter_length <= 0 || filter_length >= 14) {
fprintf(stderr, "Filter length must be 1 to 14 bits.\n");
exit(2);
}
input = argv[2];
if(!input) {
fprintf(stderr, "You did not set an input sequence of '1' and '0'.\n");
exit(3);
}
// Print header
printf(
"\n\n"
" new bit that was added\n"
"/\n"
"| filter return value\n"
"| /\n"
"| | buffer: last result bit\n"
"| | /\n"
"| | | buffer: result bit\n"
"| | |/\n"
"| | || buffer: buffered bits\n"
"| | || /\n"
"| | || |\n"
);
printf("N -> R ( LR|");
for(j=filter_length-1; j>=0; j--) printf("%1d", (int)j);
printf(" )\n--------------------------\n");
// Process input, filter and print the states
for(i=0; i < (int)strlen(input); i++) {
r = bitfilter(buffer, filter_length, input[i] == '1' ? 1 : 0);
printf("%c -> %d ( ", input[i], r);
for(j = filter_length+1; j>=0; j--) {
printf("%c", buffer & (1<<j) ? '1' : '0');
if(j == filter_length) printf("|");
}
printf(" )\n");
}
printf("\n");
return 0;
}
Makefile
CC=g++
CFLAGS=-c -Wall -O3
LDFLAGS=
all: bitfilter run
clean:
@rm -f *.o
bitfilter: main.o
@$(CC) $(LDFLAGS) main.o -o bitfilter
@rm -f *.o
main.o:
@$(CC) $(CFLAGS) main.c -o $@
run: bitfilter
./bitfilter 4 0000110111011110100100010000101010
Beispiel-Ausgabe
Example program output
stfwi$ make
./bitfilter 4 0000110111011110100100010000101010
new bit that was added
/
| filter return value
| /
| | buffer: last result bit
| | /
| | | buffer: result bit
| | |/
| | || buffer: buffered bits
| | || /
| | || |
N -> R ( LR|3210 )
--------------------------
0 -> 0 ( 00|0000 )
0 -> 0 ( 00|0000 )
0 -> 0 ( 00|0000 )
0 -> 0 ( 00|0000 )
1 -> 0 ( 00|0001 )
1 -> 0 ( 00|0011 )
0 -> 0 ( 00|0110 )
1 -> 0 ( 00|1101 )
1 -> 0 ( 00|1011 )
1 -> 0 ( 00|0111 )
0 -> 0 ( 00|1110 )
1 -> 0 ( 00|1101 )
1 -> 0 ( 00|1011 )
1 -> 0 ( 00|0111 )
1 -> 1 ( 01|1111 )
0 -> 1 ( 11|1110 )
1 -> 1 ( 11|1101 )
0 -> 1 ( 11|1010 )
0 -> 1 ( 11|0100 )
1 -> 1 ( 11|1001 )
0 -> 1 ( 11|0010 )
0 -> 1 ( 11|0100 )
0 -> 1 ( 11|1000 )
1 -> 1 ( 11|0001 )
0 -> 1 ( 11|0010 )
0 -> 1 ( 11|0100 )
0 -> 1 ( 11|1000 )
0 -> 0 ( 10|0000 )
1 -> 0 ( 00|0001 )
0 -> 0 ( 00|0010 )
1 -> 0 ( 00|0101 )
0 -> 0 ( 00|1010 )
1 -> 0 ( 00|0101 )
0 -> 0 ( 00|1010 )
Disassembly
AVR-GCC (r
, buffer
, i
are declared volatile
):
stfwi$ avr-gcc -c -g -Wa,-a,-ad -O3 main.c -o main.o
[...]
10:main.c **** r = bitfilter(buffer, 4, i);
49 0008 8091 0000 lds r24,buffer
50 000c 9091 0000 lds r25,i
51 0010 880F lsl r24
52 0012 9170 andi r25,lo8(1)
53 0014 892B or r24,r25
54 0016 8093 0000 sts buffer,r24
55 001a 8091 0000 lds r24,buffer
56 001e 8F70 andi r24,lo8(15)
57 0020 8F30 cpi r24,lo8(15)
58 0022 01F0 breq .L7
61 0024 8091 0000 lds r24,buffer
62 0028 90E0 ldi r25,lo8(0)
63 002a 8F70 andi r24,lo8(15)
64 002c 9070 andi r25,hi8(15)
65 002e 0097 sbiw r24,0
66 0030 01F4 brne .L4
70 0032 8091 0000 lds r24,buffer
71 0036 8F7E andi r24,lo8(-17)
75 0038 8093 0000 sts buffer,r24
76 003c 9091 0000 lds r25,buffer
77 0040 81E0 ldi r24,lo8(1)
78 0042 94FF sbrs r25,4
79 0044 80E0 ldi r24,lo8(0)
81 0046 8093 0000 sts r,r24
[...]
GCC (Intel, 64bit, all variables defined volatile
):
stfwi$ gcc -c -g -Wa,-a,-ad -O3 main.c -o main.o
[...]
10:main.c **** r = bitfilter(buffer, 4, i);
24 0014 0FB60500 movzbl buffer(%rip), %eax
25 001b 0FB61500 movzbl i(%rip), %edx
26 0022 01C0 addl %eax, %eax
27 0024 83E201 andl $1, %edx
28 0027 09D0 orl %edx, %eax
29 0029 88050000 movb %al, buffer(%rip)
30 002f 83E00F andl $15, %eax
31 0032 83F80F cmpl $15, %eax
32 0035 0FB60500 movzbl buffer(%rip), %eax
33 003c 7439 je .L6
35 003e A80F testb $15, %al
36 0040 0FB60500 movzbl buffer(%rip), %eax
37 0047 7523 jne .L4
40 0049 83E0EF andl $-17, %eax
43 004c A810 testb $16, %al
44 004e 88050000 movb %al, buffer(%rip)
45 0054 0F95C0 setne %al
46 0057 88050000 movb %al, r(%rip)
12:main.c **** return r;
[...]
AVR-GCC (r
, buffer
, i
not volatile
--> all done in registers):
stfwi$ avr-gcc -c -g -Wa,-a,-ad -O3 main.c -o main.o
[...]
11:main.c **** r = bitfilter(buffer, 4, i);
59 0012 2C2F mov r18,r28
60 0014 3D2F mov r19,r29
61 0016 220F lsl r18
62 0018 331F rol r19
63 001a 8170 andi r24,lo8(1)
64 001c 822B or r24,r18
65 001e 482F mov r20,r24
66 0020 50E0 ldi r21,lo8(0)
67 0022 242F mov r18,r20
68 0024 352F mov r19,r21
69 0026 2F70 andi r18,lo8(15)
70 0028 3070 andi r19,hi8(15)
71 002a 2F30 cpi r18,15
72 002c 3105 cpc r19,__zero_reg__
73 002e 01F0 breq .L9
76 0030 2115 cp r18,__zero_reg__
77 0032 3105 cpc r19,__zero_reg__
78 0034 01F0 breq .L5
81 0036 85FD sbrc r24,5
82 0038 00C0 rjmp .L9
86 003a 8F7E andi r24,lo8(-17)
90 003c C82F mov r28,r24
91 003e D0E0 ldi r29,lo8(0)
92 0040 81E0 ldi r24,lo8(1)
93 0042 2C2F mov r18,r28
94 0044 3D2F mov r19,r29
95 0046 24FF sbrs r18,4
96 0048 80E0 ldi r24,lo8(0)
98 004a 8093 0000 sts r,r24
[...]