banner

Ir para o Forum

Você não sabia? Ajuste de ADC do ESP32



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:





3 comentários:

  1. Tem um tópico no forum para tirar dúvidas: http://forum.fernandok.com/viewtopic.php?f=4&t=5309&sid=8b37f6d6f0171b5f6244cf695b66520c

    ResponderExcluir
  2. excelente Fernando adorei e vou usar com alguns sensores com saida analogica que uso
    Obrigado mais uma vez

    ResponderExcluir

Tecnologia do Blogger.