Pessoal, hoje vou falar de um
assunto mais técnico, mas que acho que todo mundo que trabalha com o ESP32
deveria saber, que é a questão do ajuste de leitura do conversor ADC (conversor
analógico–digital). E, por que eu acho isso importante? Porque, quando você
está fazendo “medida”, principalmente com um instrumento que tem saída
analógica, você precisa ter absoluta certeza que a leitura está sendo executada
e de forma correta.
Neste vídeo de hoje, portanto,
vamos realizar medidas usando o ADC do ESP32, observar
as discrepâncias da conversão e aplicar um método de ajuste/calibração do ADC.
O que é um conversor AD?
Um conversor AD é um circuito capaz de traduzir uma grandeza analógica (contínua) em valores digitais (discretos). O que isso quer significa? Significa que enquanto valores digitais só podem assumir valores discretos formados pela combinação de zeros e uns, uma grandeza analógica pode assumir qualquer valor dentro de uma faixa. Por exemplo, se medíssemos a tensão de uma pilha AA ideal, poderíamos encontrar qualquer valor entre 0V e 1,5V, pois essa é grandeza analógica. Já o estado de saída de uma lâmpada ideal, deve assumir somente dois estados, apagada ou acesa, essa é uma grandeza discreta. Como os microcontroladores funcionam usando essa lógica discreta, precisamos de um circuito capaz de traduzir uma grandeza analógica em digital (ou discreta).
Recursos usados
·
Uma placa Lolin32 Lite v1.0.0
·
Um osciloscópio Tektronix TDS1001C para captura.
·
Um cabo USB para o ESP32.
·
Um osciloscópio Hantek DSO4102C como gerador de
sinal
ADC do ESP32
Segundo dados da Espressif, os
chips ESP32 podem apresentar uma diferença de +/- 6% de um chip para outro nos
resultados medidos.
Além disso, a conversão NÃO
possui uma resposta linear por toda faixa disponível para leitura.
A Espressif disponibiliza um
método para calibração e sugere que os usuários implementem outros métodos caso
julguem necessário, afim de alcançar a precisão desejada.
Vamos realizar uma aquisição
de dados e, a partir deles, mostrar as respostas do ADC e um exemplo de
aplicação de um processo matemático para ajuste da leitura.
Existem várias formas, mais
simples ou mais complexas para realizar estas correções. Cabe a cada um avaliar
a mais adequada ao projeto.
A demonstrada aqui terá um
objetivo ilustrativo, tentando abordar pontos interessantes que podem ser
observados durante ajustes.
Circuito utilizado
Utilizei um osciloscópio com
gerador de sinal que vai até 25 MHz, o Hantek DSO4102C. Geramos uma onda que
foi lida pelo A/D do ESP e pelo osciloscópio. Os dados colhidos foram gravados
em csv e em uma planilha, que vou deixar ao fim do artigo para download.
Sinal utilizado
Escolhemos um sinal
trapezoidal de baixa frequência para que tenhamos acesso às rampas que percorrem
toda a faixa de interesse da conversão e que permita um número grande de
amostras nestas rampas.
Dados obtidos pelo osciloscópio
Imagem da captura realizada
pelo osciloscópio. Os dados foram armazenados em um arquivo csv. Note a leve
curvatura nas rampas de subida e de descida do sinal.
Dados obtidos pelo osciloscópio (arquivo csv no Excel)
Temos aqui as amostragens.
Dados obtidos pelo ADC
Alterando a taxa de
transferência da serial, podemos visualizar os dados capturados pela ADC.
Observe a deformação do sinal trapezoidal.
Dados observados no plotter serial da IDE Arduino
Dados obtidos pelo ADC – Excel
Usando uma taxa maior e o
terminal serial, podemos capturar os valores e aplicá-los no Excel para nossas
comparações.
Comparação das rampas de subida
Comparamos as duas rampas de
subida das duas capturas.
Note a curvatura que ocorre nas duas rampas.
Note também que para a mesma
rampa temos muito mais amostras do ESP32 que do osciloscópio.
Equiparando o número de amostras
Como o ESP32 forneceu um
número de amostras maior que o osciloscópio, precisamos equiparar estes valores,
pois eles servirão de índice para comparar as duas curvas.
Para isso vamos fazer uma
comparação direta.
Temos 305 amostras para a
rampa do osciloscópio e 2365 amostras para a rampa do ADC.
Como as rampas são do mesmo
intervalo, podemos dizer que temos aproximadamente 7,75 amostras do ADC para
cada do osciloscópio.
Multiplicando o índice de cada
amostra do osciloscópio temos a mesma curva, mas com índices equivalentes ao do
ADC e os dados redistribuídos.
Para preencher os dados
faltantes para as novas posições, vamos aplicar uma curva que se adeque
estatisticamente aos dados conhecidos.
Preenchendo as lacunas – Linha de Tendência
Selecionando os dados
conhecidos (pontinhos azuis), através de um clique e em seguida clicando com o
botão direito, selecionamos: “Adicionar linha de Tendência...”
Na janela que surge,
selecionamos o tipo Polinomial, de ordem 2 já será o bastante.
Marcamos também as opções
“Exibir Equação no gráfico” e “Exibir valor de R-quadrado no gráfico”.
Clicamos em “Fechar”.
Preenchendo as lacunas – Curva polinomial de grau 2
O Excel nos dá duas novas
informações, a equação de segundo grau que melhor se adequa aos dados e o
R-quadrado, que quantiza essa adequação.
Basta lembrar que quanto mais
próximo de 1, mais adequada a equação.
Não vamos nos aprofundar na
matemática envolvida, vamos utilizar somente como uma ferramenta.
Preenchendo as lacunas – Avaliando a função
Vamos preencher as lacunas da
amostragem com os dados gerados pela equação.
E assim, compará-las ponto a ponto.
y = -9E-08x2 +
0,0014x + 0,1505
R² = 0,9995
Tensão no
osciloscópio = -9E-08*indice² + 0,0014*indice + 0,1505
Convertendo a tensão do osciloscópio em um valor equivalente para comparar com o ADC
Vamos aproveitar este momento
também para transformar o valor da tensão do osciloscópio em um valor do ADC
equivalente.
Como o maior valor obtido no
ADC do ESP32 foi de 4095 e equivale à leitura de 2,958V para o mesmo índice,
podemos dizer que:
Ou seja, cada volt nas medidas do osciloscópio equivalem a aproximadamente 1384,4 unidades do AD. Logo, podemos multiplicar todas as medidas do osciloscópio por este valor.
Comparado as duas rampas obtidas
Visualizando as diferenças
obtidas nas duas leituras.
Comportamento da diferença da leitura do ADC (ERRO)
A curva abaixo mostra como se
comporta a diferença na leitura do ADC em função da medida. Esta coleção de
dados permitirá encontrarmos uma função de correção.
Para encontrar esta curva, simplesmente plotamos a diferença encontrada em cada medida em função de cada posição possível do AD (0 a 4095).
Comportamento da diferença da leitura do ADC - Encontrando uma função de correção
Podemos determinar no próprio
Excel uma função de correção, adicionando uma Linha de Tendência, agora de
maior grau, até que se adeque o suficiente aos nossos dados.
Usando outro software
Outro software interessante
para a determinação das curvas é o PolySolve,
que pode ser usado diretamente no link: https://arachnoid.com/polysolve/ ou
baixado como um aplicativo Java.
Ele permite a aplicação de
regressões polinomiais de maiores graus e entrega a função formatada, além de
outras funcionalidades.
Para usá-lo, basta introduzir
os dados na primeira caixa de texto. Os dados devem seguir a ordem X,Y
separados por uma virgula, ou tabulação. Atenção na utilização correta do ponto
como separador decimal.
Um gráfico irá aparecer na
caixa seguinte, se os dados introduzidos estiverem corretamente formatados.
Veja como ficou nossa curva de
erro do ADC.
Essa janela apresentará o
resultado da regressão, incluindo os dados de adequação da função, que por sua
vez pode ter sua saída formatada de diversas formas: como uma função C/C++, uma
lista de coeficientes, uma função escrita em Java...
Obs.: Atenção aos separadores
decimais
Constantes e Setup()
Aponto aqui o GPIO usado para captura
analógica. Inicializo a porta serial, bem como o pino determinado para captura
analógica.
const int pin_leitura = 36; //GPIO usado para captura analógica void setup() { Serial.begin(1000000); //Iniciciando a porta serial somente para debug pinMode(pin_leitura, INPUT); //Pino utilizado para captura analógica }
Loop() e a função de correção
Realizamos a captura da tensão
ajustada, imprimimos os valores com ou sem as devidas correções.
void loop() { int valor_analogico = analogRead(pin_leitura); //realiza a captura da tensão ajustada //Serial.print(valor_analogico + f(valor_analogico)); //imprime os valores para debug (COM CORREÇÃO) Serial.print(valor_analogico); //imprimime os valores para debug (SEM CORREÇÃO) Serial.print(","); Serial.print(4095);//cria uma linha para marcar o valor máximo de 4095 Serial.print(","); Serial.println(0); //cria uma linha para marcar o valor mínimo de 0 }
Observe na linha 12 que temos a opção de imprimir os dados com a adição da função de diferença f(valor_analogico).
Usando a função de correção do PolySolve
Aqui jogamos a função do
PolySolve dentro do Arduino IDE.
/* Mode: normal Polynomial degree 6, 2365 x,y data pairs Correlation coefficient (r^2) = 9,907187626418e-01 Standard error = 1,353761109831e+01 Output form: C/C++ function: Copyright © 2012, P. Lutus -- http://www.arachnoid.com. All Rights Reserved. */ double f(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x + 1.276218788985e-04 * pow(x, 2) + -3.470360275448e-07 * pow(x, 3) + 2.082790802069e-10 * pow(x, 4) + -5.306931174991e-14 * pow(x, 5) + 4.787659214703e-18 * pow(x, 6); }
Observe a mudança da vírgula por ponto como separador decimal.
Captura com correção – Plotter Serial
Captura com correção – Excel
Custo computacional
Para realizar os cálculos dos polinômios, é necessário que o processador se ocupe desta tarefa. Isto pode provocar atrasos na execução, dependendo do código fonte e do poder computacional disponível.
Aqui vemos uma tabela de
resultado de um teste usando polinômios de vários graus. Observe a diferença
entre os tempos quando a função pow() foi utilizada e quando não foi.
Código de teste – Setup() e início do Loop()
Aqui temos o código utilizado
em nosso teste.
void setup() { Serial.begin(1000000); //Iniciando a porta serial somente para debug } void loop() { float valor_analogico = 500.0; //um valor arbtrario float quantidade = 10000.0; //quantidade de chamadas float contador = 0.0; //contador de chamadas
Código de teste – Loop() e o processamento
Usei a função micros() para
adquirir o valor em microssegundos.
//============= inicia o processo float agora = micros(); //marca o instante inicial while (contador < quantidade) { //v(valor_analogico); //função vazia //r(valor_analogico); //função com retorno //f0(valor_analogico); //grau 0 //f1(valor_analogico); //grau 1 //f2(valor_analogico); //grau 2 //f3(valor_analogico); //grau 3 //f4(valor_analogico); //grau 4 //f5(valor_analogico); //grau 5 //f6(valor_analogico); //grau 6 //f13_semPow(valor_analogico); //grau 13º SEM a função POW //f13_comPow(valor_analogico); //grau 13º COM a função POW contador++; } agora = (micros() - agora) / quantidade; //determina o intervalo que se passou para cada iteração //============= finaliza o processo
Código de teste – Loop() – resultados
Imprimimos o valor retornado
da função de grau 13 com e sem POW para comparação, bem como o intervalo de
processamento.
//imprime o valor retornado da função de grau 13 com e sem POW para comparação Serial.print(f13_semPow(valor_analogico)); //grau 13º SEM a função POW Serial.print(" - "); Serial.print(f13_comPow(valor_analogico)); //grau 13º COM a função POW Serial.print(" - "); //imprime o intervalo do processamento Serial.println(agora, 6); }
Código de teste – Funções usadas
Função vazia, somente com retorno, de grau 0 e 1.
//FUNÇÃO VAZIA double v(double x) { } //FUNÇÃO SOMENTE COM RETORNO double r(double x) { return x; } //FUNÇÃO DE GRAU 0 double f0(double x) { return 2.202196968876e+02; } //FUNÇÃO DE GRAU 1 double f1(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x; }
Função de grau 2, 3 e 4.
//FUNÇÃO DE GRAU 2 double f2(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x + 1.276218788985e-04 * pow(x, 2); } //FUNÇÃO DE GRAU 3 double f3(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x + 1.276218788985e-04 * pow(x, 2) + -3.470360275448e-07 * pow(x, 3); } //FUNÇÃO DE GRAU 4 double f4(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x + 1.276218788985e-04 * pow(x, 2) + -3.470360275448e-07 * pow(x, 3) + 2.082790802069e-10 * pow(x, 4); }
Função de grau 5 e 6.
//FUNÇÃO DE GRAU 5 double f5(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x + 1.276218788985e-04 * pow(x, 2) + -3.470360275448e-07 * pow(x, 3) + 2.082790802069e-10 * pow(x, 4) + -5.306931174991e-14 * pow(x, 5); } //FUNÇÃO DE GRAU 6 double f6(double x) { return 2.202196968876e+02 + 3.561383996027e-01 * x + 1.276218788985e-04 * pow(x, 2) + -3.470360275448e-07 * pow(x, 3) + 2.082790802069e-10 * pow(x, 4) + -5.306931174991e-14 * pow(x, 5) + 4.787659214703e-18 * pow(x, 6); }
Função de grau 13 usando o POW.
//FUNÇÃO DE GRAU 13 USANDO O POW double f13_comPow(double x) { return 2, 161282383460e+02 + 3, 944594843419e-01 * x + 5, 395439724295e-04 * pow(x, 2) + -3, 968558178426e-06 * pow(x, 3) + 1, 047910519933e-08 * pow(x, 4) + -1, 479271312313e-11 * pow(x, 5) + 1, 220894795714e-14 * pow(x, 6) + -6, 136200785076e-18 * pow(x, 7) + 1, 910015248179e-21 * pow(x, 8) + -3, 566607830903e-25 * pow(x, 9) + 5, 000280815521e-30 * pow(x, 10) + 3, 434515045670e-32 * pow(x, 11) + -1, 407635444704e-35 * pow(x, 12) + 9,871816383223e-40 * pow(x,13); }
Função de grau 13 sem usar o POW.
//FUNÇÃO DE GRAU SEM USAR O POW double f13_semPow(double x) { return 2, 161282383460e+02 + 3, 944594843419e-01 * x + 5, 395439724295e-04 * x * x + -3, 968558178426e-06 * x * x * x + 1, 047910519933e-08 * x * x * x * x + -1, 479271312313e-11 * x * x * x * x * x + 1, 220894795714e-14 * x * x * x * x * x * x + -6, 136200785076e-18 * x * x * x * x * x * x * x + 1, 910015248179e-21 * x * x * x * x * x * x * x * x + -3, 566607830903e-25 * x * x * x * x * x * x * x * x * x + 5, 000280815521e-30 * x * x * x * x * x * x * x * x * x * x + 3, 434515045670e-32 * x * x * x * x * x * x * x * x * x * x * x + -1, 407635444704e-35 * x * x * x * x * x * x * x * x * x * x * x * x + 9, 871816383223e-40 * x * x * x * x * x * x * x * x * x * x * x * x * x; }
Faça o download dos arquivos:
4 Comentários
Muito bom!
ResponderExcluirTem um tópico no forum para tirar dúvidas: http://forum.fernandok.com/viewtopic.php?f=4&t=5309&sid=8b37f6d6f0171b5f6244cf695b66520c
ResponderExcluirexcelente Fernando adorei e vou usar com alguns sensores com saida analogica que uso
ResponderExcluirObrigado mais uma vez
Ola Professor
ResponderExcluirtem algum exemplo de osciloscopio com esp 32 e display QDtech TFTM1802 1.8 spi 128X160?