



Para entender o que é um filtro e entender a necessidade dele, primeiro precisamos compreender o que é um ruído.
Na eletrônica e em outras áreas é muito importante medir as coisas, para tomar decisões baseadas nessas medidas.
Por exemplo, em nossas aplicações com Arduino é muito comum querermos medir a temperatura para que o microcontrolador possa acionar um ventilador, ar-condicionado, um freezer, etc. Ou até mesmo só medir para registrar, para fazer análises, como neste post.
Ai entra o problema do ruído. O ruído é um sinal aleatório parasita que entra no meio da nossa medição. O problema é que ele infiltra na medição, como uma erva daninha em uma plantação, e se passa por aquele sinal que a gente está medindo.

O segundo exemplo é medindo distância do Arduino a uma caixa com um sensor ultrassônico. A caixa está parada, e o sensor também, mas mesmo assim existe uma variação de 1 cm na medição.
E o último exemplo é medindo a temperatura com um sensor de temperatura do tipo NTC. Onde a temperatura está constante, e mesmo assim existe um pequeno ruído.
Como remover esse sinal impostor que pode atrapalhar bastante na medição de algo que queremos? Veremos aqui algumas estratégias.
Talvez os novatos em eletrônica talvez nunca tenha ouvido falar sobre esse termo, mas tenho certeza que os mais experientes que estão lendo esse post já ouviram muito falar sobre esse tipo de filtro.
Isso pode ser novidade para algumas pessoas, mas qualquer sinal que você possa imaginar pode ser decomposto em uma soma de senóides (daquelas mesmo que você aprendeu nas aulas do ensino fundamental e médio). Cada senóide com uma frequência determinada que compõe o sinal pode ser chamado de harmônicos (super recomendo ver esse video antes de prosseguir).
Um exemplo simples da soma de sinal é o sinal vermelho abaixo, que resulta da soma do sinal verde e do azul, onde cada harmônico ,


Analisar um sinal no domínio da frequência é interessante porque conseguimos rastrear o "DNA" do sinal, quais frequências compõe esse sinal, como mostra na imagem em azul.
Se alguém ficou curioso em como se chama essa técnica de poder ver um sinal vendo as frequências que compõe ele, o nome é Transformada de Fourier. A equação de Fourier transforma um sinal que está visto no tempo, em um sinal visto somente na frequência dele. O Arduino possui algumas bibliotecas que fazem essa conversão automaticamente, e uma delas é: ArduinoFFT. Caso queira um dia implementar um analisador de frequências, o caminho é esse.
Agora vem o pulo do gato em relação ao filtro. Vendo os GIFs na seção de cima, conseguimos notar que os ruídos possuem frequência muito maior que o do nosso sinal de interesse, então basta realizar alguma técnica onde seja possível remover as frequências que não temos interesse (que nesse caso são as frequências do ruído).
É por isso que o nome dessa técnica é "filtro passa-baixa", pois ele permite apenas passagem de frequências mais baixas no sinal final, retendo as frequências mais altas. Dessa forma o ruído fica retido.
Um filtro passa-baixa perfeito seria aquele onde a gente pudesse eliminar completamente o ruído e deixar apenas o sinal do sensor, como na imagem abaixo, onde o filtro corta perfeitamente toda a frequência mais alta.
Infelizmente isso não é possível, porque nenhum filtro consegue ser perfeito, porque o que conseguimos fazer é atenuar (reduzir o tamanho) das frequências mais altas e deixar intacto o sinal do sensor, como na imagem abaixo:


Filtros de ordens maiores conseguem filtrar muito bem ruídos que possuem frequência muito próxima a do sinal de interesse, mas são mais difíceis de implementar.
Sobre os filtros serem ativos ou passivos, são filtros analógicos que utilizam amplificadores operacionais. Não entraremos no assunto pois o tema de hoje são filtros digitais.
Até aqui espero que você tenha entendido o fundamento básico sobre sinais e filtro passa-baixa. Caso não tenha entendido, convido você a reler, porque vai ser muito importante para prosseguirmos. A grande vantagem de um filtro digital é que ele consegue ser tão eficaz quanto um filtro analógico que necessita de componentes externos, mas ele não necessita de componente externo algum, economizando o tamanho que ficaria a placa. Outra vantagem de filtros digitais é que ele consegue filtrar também sensores que funcionam através de medição puramente digital, como o sensor ultrassônico. A desvantagem de um filtro digital em comparação com um analógico, por sua vez, é que ele consome processamento do microcontrolador e em algumas implementações consome memória. Isso faz com que, em algumas aplicações, não seja possível utilizar esse tipo de filtro por gargalo do software. Mas para a maioria das aplicações um filtro digital será suficiente e funcionará muito bem. Os filtros digitais são divididos em dois tipos:

Um exemplo muito bom de uma saída extremamente ruidosa no Arduino é fazer a leitura de uma entrada analógica sem ligar nenhum circuito, somente o Arduino ligado na USB e mais nada. Isso faz com que a entrada analógica vire uma verdadeira antena de ruídos eletrostáticos de alta frequência. Para fazer esse exemplo, conecte seu Arduino na USB e carregue o código abaixo:
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(analogRead(0));
delay(1);
}
Abra o seu Plotter Serial indo em "Ferramentas > Plotter Serial" (usaremos ele bastante neste tutorial) .
Como podemos ver, se essa fosse uma saída de um sensor, seria bem complicada a leitura, por ter um ruído muito forte e com algumas frequências altas associadas. Mas como iremos aprender aqui, existe solução para sinais muito ruidosos.
Como dito anteriormente, ruídos geralmente são aleatórios. Logo, qualquer variável aleatória a gente pode resolver tirando a média, porque a média de K amostras de qualquer sinal aleatório (sem offset) tenderá a 0.
A primeira estratégia abordada será um filtro do tipo FIR, que neste caso será a média móvel. O filtro de média móvel para entender como funciona é bem simples, basta tirar a média das últimas N amostras (onde N é o número que a gente define no número de amostras do filtro). Mas caso uma solução elegante não seja usada, o esforço computacional pode ser grande, porque além de termos que registrar as últimas N amostras, também teremos que a cada amostragem somar esse número e tirar a média, e na próxima amostra somar tudo de novo e tirar média. Imagina caso a quantidade de amostras fossem mil, por exemplo. Imagina somar 1000 números a cada intervalo de amostras? Isso demandaria mais de 1000 ciclos de clock e seria um esforço muito grande. Uma estratégia bem melhor é usar um buffer circular e uma variável de soma total. Esse buffer circular é um vetor que termina na primeira posição (sim, a primeira posição está do lado da última). Neste essa estrutura será responsável por armazenar os valores, já que não temos de onde fugir de ter que armazenar os valores. A grande vantagem é que quando o filtro tiver cheio, não teremos gasto para ter que voltar para a primeira posição, ele volta automaticamente e reescreve. A grande sacada nessa solução mais elegante está na variável soma total. Os comandos nessa variável funciona da seguinte forma:
#define Qtd_Amostras 10 // ***Quantas Amostras o filtro terá para filtrar, mude para testar outros filtros***
#define Intervalo_Amostragem 1 // ***definindo o intervalo de amostragem em ms. Mude para testar novos filtros***
int Leitura_analogica = 0; // Variável global que salva o dado bruto lido da porta serial.
// A estratégia usada aqui é porque o comando analogRead possui um custo alto para o Arduino.
// Com isso salvamos na variável para essa leitura ser feita apenas uma vez a cada interação de loop.
unsigned long timer1=0; // A variável que irá contar o útimo
void setup() {
Serial.begin(9600); // Inicio da comunicação serial
Serial.println("CLEARDATA"); // Comando para o Serial Plotter
Serial.println("Sem_Filtro, Filtro_Media_Movel"); // Inicia os titulos dos eixos do Serial Plotter.
}
void loop() {
Leitura_analogica = analogRead(A0); // Leitura_analogica aqui é o valor bruto
Amostragem(); // Essa é a função que fará a amostragem no tempo que determinamos no intervalo de amostragem que definimos na segunda linha de código.
Serial.print(Leitura_analogica); // Imprime o dado bruto
Serial.print(",");
Serial.println(filtroMediaMovel(0)); // Imprime o dado filtrado
// A função recebe o valor 0 que é para saber que não é para alterar o seu valor, somente imprimir o que está lá.
}
void Amostragem(){ // Essa função verifica se o tempo de amostragem selecionado ocorreu
if(millis() - timer1>Intervalo_Amostragem){ // Caso o tempo de amostragem tenha ocorrido, ele envia 1 para a função de filtro de media movel
//Dessa forma a função sabe que é para atualizar o valor de saída para um novo valor filtrado
filtroMediaMovel(1);
timer1 = millis(); // atualiza para contar o tempo mais uma vez
}
}
float filtroMediaMovel(bool atualiza_saida){ // A função de media movel trabalha com variavel estática, que salva as variaveis sem perder
static int Leituras_anteriores[Qtd_Amostras]; // Esse é o vetor que servirá como buffer circular
static int Posicao = 0; // A posicao atual de leitura, que deverá ficar salva
static long Soma=0; // A soma total do buffer circular
static float Media = 0; // A media, que é a saída da função quando é chamada
static bool zera_vetor = 1; // A variavel para saber se é a primeira execução. Se for, ele zera todo o buffer circular.
if (zera_vetor){ // Zerando todo o buffer circular, para que as subtrações das sobrescrição não atrapalhe o filtro
for(int i=0; i<= Qtd_Amostras; i++){
Leituras_anteriores[i] = 0;
}
zera_vetor = 0;
// Serial.println("Não entra mais no laço");
}
if(atualiza_saida == 0) return((double)Media); // Se o parametro recebido na funcao for zero, ele retorna somente o valor de media calculado anteriormente
else{ // Caso seja 1, signfica que está no tempo de amostragem, e ai atualiza a variável média
Soma = Leitura_analogica - Leituras_anteriores[Posicao%Qtd_Amostras] + Soma;
Leituras_anteriores[Posicao%Qtd_Amostras] = Leitura_analogica;
Media = (float)Soma/(float)(Qtd_Amostras);
Posicao = (Posicao+1)%Qtd_Amostras;
return((double)Media);
}
}

Nesse comparativo podemos ver como os parâmetros mudam completamente a qualidade de um filtro digital.
Abaixo veremos um pouco mais sobre outros tripos de filtro. Um outro tipo de filtro que fácil de entender é um que podemos desenvolver usando somente a lógica. Na verdade esse é um filtro digital de primeira ordem, mas chamarei ele aqui de filtro lógico, para que possamos entender o quão simples é a lógica por trás do modelo matemático deste filtro. A saída dele é idêntica ao do filtro matemático que será mostrado posteriormente. A lógica para implementar esse filtro será:
#define Qtd_Amostras 10 // ***Quantas Amostras o filtro terá para filtrar, mude para testar outros filtros***
#define Intervalo_Amostragem 1 // ***definindo o intervalo de amostragem em ms. Mude para testar novos filtros***
int Leitura_analogica = 0; // Variável global que salva o dado bruto lido da porta serial.
// A estratégia usada aqui é porque o comando analogRead possui um custo alto para o Arduino.
// Com isso salvamos na variável para essa leitura ser feita apenas uma vez a cada interação de loop.
unsigned long timer1=0; // A variável que irá contar o útimo
void setup() {
Serial.begin(9600); // Inicio da comunicação serial
Serial.println("CLEARDATA"); // Comando para o Serial Plotter
Serial.println("Sem_Filtro, Filtro_Logico"); // Inicia os titulos dos eixos do Serial Plotter.
}
void loop() {
Leitura_analogica = analogRead(A0); // Leitura_analogica aqui é o valor bruto
Amostragem(); // Essa é a função que fará a amostragem no tempo que determinamos no intervalo de amostragem que definimos na segunda linha de código.
Serial.print(Leitura_analogica); // Imprime o dado bruto
Serial.print(",");
Serial.println(filtroLogico(0)); // Imprime o dado filtrado
// A função recebe o valor 0 que é para saber que não é para alterar o seu valor, somente imprimir o que está lá.
}
void Amostragem(){ // Essa função verifica se o tempo de amostragem selecionado ocorreu
if(millis() - timer1>Intervalo_Amostragem){ // Caso o tempo de amostragem tenha ocorrido, ele envia 1 para a função de filtro de media movel
//Dessa forma a função sabe que é para atualizar o valor de saída para um novo valor filtrado
filtroLogico(1);
timer1 = millis(); // atualiza para contar o tempo mais uma vez
}
}
float filtroLogico(bool atualiza_saida){ // Igual nos outros exemplos, ele usa variavel estática.
int diferenca; // Variavel que salvará a diferenca entre o valor do filtro e a saída
static float Saida_Filtro = 0; // A variavel
if(atualiza_saida == 0) return((double)Saida_Filtro);
else{
if(Leitura_analogica<Saida_Filtro){
diferenca = abs(Leitura_analogica - Saida_Filtro);
Saida_Filtro -= (float)diferenca/(float)Qtd_Amostras;
}
else if(Leitura_analogica>Saida_Filtro){
diferenca = abs(Leitura_analogica - Saida_Filtro);
Saida_Filtro += (float)diferenca/(float)Qtd_Amostras;
}
}
}
Filtro com 10 amostras (como dito anteriormente, o correto é dizer peso 10) e intervalo de amostragem de 1ms,
Filtro com 100 amostras e intervalo de amostragem de 1ms,
Se alguém já implementou um filtro analógico, deve percebeu que a curva do filtro é exatamente como a curva de um filtro RC. Porque matematicamente os dois filtros são iguais.
Abaixo veremos o modelo matemático deste filtro lógico e sua implementação. O chamaremos de filtro recursivo.
O filtro recursivo é uma implementação matemática de um filtro de primeira ordem analógico, mas passando ele para discreto. A matemática para chegar no resultado deste filtro pode ser um pouco complicada para quem não domina alguns assuntos matemáticos relacionados às equações diferenciais como transformada de Laplace e transformada Z. Se for o seu caso, pule até o fim da demonstração matemática do filtro.
Sabendo que essa é a função de transferência, podemos chegar em uma equação diferencial, fazendo a operação de inversa de Laplace.
A constante τ nessa equação significa a constante que define a velocidade do filtro, que no filtro RC é o produto da resistência vezes a capacitância.
A partir da equação acima será usado um recurso para discretizar que é fazer a equação de diferenças. Sabemos que dy/dt pode ser aproximado por Δy/Δt, onde Δ significa uma variação. Logo surgirá algumas distorções relacionados a essa aproximação. A multiplicação dessa distorção vezes o tau (τ ), chamaremos de W.
Com isso, toda vez que for visto W, é só saber que é um valor muito próximo a τ , e tem a ver com a taxa de amostragem e quantidade de amostras.
Como agora a equação já está em tempo discreto, ao invés de a notação ser y(t), agora é y[k], que é a notação de tempo discreto. Onde [K] significa a ultima amostra, e [K - 1] é a amostra anterior.
Com isso agora é só fazer alguns algebrismos e se acha que:
Aqui já dá para implementar esse filtro em um microcontrolador, mas não dá para enxergar direito o filtro lógico. Para ser possível enxergar bem o filtro lógico, chamaremos a constante que acompanha x de alfa, e a que acompanha y[k-1] de beta.
Com isso ficou fácil observar uma coisa, que se somar alfa mais beta o resultado é 1. Como isso é verdade, podemos substituir Beta por 1 - Alfa, como é visto na equação abaixo:
Aqui já chegamos a um resultado interessante. Fazendo um pouco de algebrismo conseguimos ver a seguinte equação abaixo:
Agora ficou extremamente interessante, já se pode notar que a saída do filtro é igual à saída anterior somando a diferença da saída do filtro com a amostra atual, multiplicada por um peso alfa.
Se lembrarmos, alfa foi substituído. Podemos voltar lá e substituir de volta pelo valor original:
E aqui está a equação final, o filtro lógico! Como é linda a matemática, não é mesmo?
Essa equação está dizendo exatamente o filtro lógico, que a saída do filtro atual é a saída anterior somando a diferença entre a amostra atual e o filtro e dividindo por um peso que chamamos de "quantidade de amostras". Nessa equação podemos ver que na verdade essa "quantidade de amostras" não era bem a quantidade de amostras, como foi discutido, e sim uma constante (1 + W), onde esse W depende de uma série de fatores, como o tempo de amostragem e a quantidade de amostras de fato.
Iremos prosseguir chamando esse (1 + W) de quantidade de amostras, somente para fins didáticos.
#define Qtd_Amostras 100 // ***Quantas Amostras o filtro terá para filtrar, mude para testar outros filtros***
#define Intervalo_Amostragem 1 // ***definindo o intervalo de amostragem em ms. Mude para testar novos filtros***
int Leitura_analogica = 0; // Variável global que salva o dado bruto lido da porta serial.
// A estratégia usada aqui é porque o comando analogRead possui um custo alto para o Arduino.
// Com isso salvamos na variável para essa leitura ser feita apenas uma vez a cada interação de loop.
unsigned long timer1=0; // A variável que irá contar o útimo
void setup() {
Serial.begin(9600); // Inicio da comunicação serial
Serial.println("CLEARDATA"); // Comando para o Serial Plotter
Serial.println("Sem_Filtro, Filtro_Recursivo"); // Inicia os titulos dos eixos do Serial Plotter.
}
void loop() {
Leitura_analogica = analogRead(A0); // Leitura_analogica aqui é o valor bruto
Amostragem(); // Essa é a função que fará a amostragem no tempo que determinamos no intervalo de amostragem que definimos na segunda linha de código.
Serial.print(Leitura_analogica); // Imprime o dado bruto
Serial.print(",");
Serial.println(filtroRecursivo(0)); // Imprime o dado filtrado
// A função recebe o valor 0 que é para saber que não é para alterar o seu valor, somente imprimir o que está lá.
}
void Amostragem(){ // Essa função verifica se o tempo de amostragem selecionado ocorreu
if(millis() - timer1>Intervalo_Amostragem){ // Caso o tempo de amostragem tenha ocorrido, ele envia 1 para a função de filtro de media movel
//Dessa forma a função sabe que é para atualizar o valor de saída para um novo valor filtrado
filtroRecursivo(1);
timer1 = millis(); // atualiza para contar o tempo mais uma vez
}
}
float filtroRecursivo(bool atualiza_saida){ // Implementação matematica do filtro de 1a ordem analogico.
static float Saida_Filtro = 0;
if(atualiza_saida == 0) return((double)Saida_Filtro);
else{
Saida_Filtro += (float)(Leitura_analogica - Saida_Filtro)/(float)Qtd_Amostras;
}
}
Filtro com 100 amostras e intervalo de amostragem de 1ms,
No vídeo abaixo você vai poder conferir a comparação do desempenho de todos os filtros que desenvolvemos e abordamos:
Neste post de hoje podemos aprender muito sobre filtros digitais, a diferença entre um filtro FIR e um IIR, e como implementar estes filtros e as vantagens que eles possuem em relação ao outro. Existem diversas bibliotecas de filtros para Arduino, onde você pode encontrar biblioteca de média móvel, mediana móvel, filtro exponencial, etc. Propositalmente não apresentei nenhuma biblioteca neste post, porque estávamos interessados em saber como funciona a teoria do filtro, e como faz para implementar. Não utilizar uma biblioteca te deixa livre para implementar um filtro em absolutamente qualquer microcontrolador que não seja uma placa Arduino, enquanto uma biblioteca te deixa refém. Desde já peço desculpas se o post ficou muito grande. Filtros é um assunto bem extenso e gostaria que um iniciante pudesse ler e sair com muitos conhecimentos novos, apesar de talvez não absorver tudo por ter detalhes técnicos um pouco complexo. Caso você tenha interesse em mergulhar no assunto e aprender toda a parte teórica por trás, recomendo o livro Sinais e Sistemas do Oppenheim. Ele aborda de forma muito interessante toda a teoria de sinais contínuos e discretos, transformadas, amostragem e filtro. Pode ser um pouco complexo para entender de cara, por isso, recomendo que anteriormente você faça uma base aprendendo um pouco sobre equações diferenciais. Espero que você tenha tido um excelente aprendizado até aqui, e caso você tenha implementado um filtro desse em seu projeto que envolve um sensor, e tenha resolvido o seu problema, faça um videozinho e marque a gente no instagram @eletrogate. Vai ser um enorme prazer para nós ver que seu projeto deu uma melhorada. Em nosso instagram além de dicas, curiosidades e tutoriais, rola eventuais sorteios de kits 😱. Não fique de fora, siga a gente e fique por dentro de tudo. Muito obrigado por sua leitura e atenção até aqui. Caso tenha alguma dúvida ou sugestão, utilize o campo dos comentários que irei responder o mais rápido possível. Forte abraço e até a próxima! Conheça a Metodologia Eletrogate e ofereça aulas de robótica em sua escola!
|
Você já teve vários problemas com ruído na medição do seu sensor e gostaria de corrigir isso? Clique aqui e aprenda mais.testeststestsetsetset
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200