В предыдущей статье я подробно рассмотрел как работать с АЦП микроконтроллера ESP32. Протестируем какие данные дает 12-битный АЦП этого микроконтроллера и сравним с показаниями 16-ти битного АЦП ADS1115. При тестировании я буду использовать следующее оборудование:
- Модуль АЦП CJ-ADS1115 — приобретен на Aliexpress. Цена порядка 1.5 USD. Использую плату вишневого цвета.
- Генератор сигналов 4..20 мА — приобретен на Aliexpress. Подробное описание тестирования генератора — здесь.
- Высокоточный цифровой мультиметр UNI-T UT61E. Приобретен на Aliexpress у этого продавца за 50 USD.
- Цифровой мультиметр Victor VC890C+.
- ESP32 DevKit — приобретен на Aliexpress.
Измерительное сопротивление для АЦП ESP32 и ADS1115
Тестирование точности измерений выполняю в рамках одного из проектов, где используются датчики тока. Поэтому тестирование АЦП выполняется таким сложным образом, с использованием генератора тока 4.20 мА, вместо того, чтобы просто подать на вход эталонное напряжение.
Схемотехнику снятия данных с датчиков 4.20 мА я подробно рассмотрел в предыдущей статье. Собственно, основной элемент — это измерительное сопротивление на котором падает напряжение, измеряемое АЦП.
У АЦП ADS1115 есть несколько параметров напряжений для различных режимов работы аттенюатора
// ads.setGain(GAIN_TWOTHIRDS); +/- 6.144V 1 bit = 0.1875mV (default) //ads.setGain(GAIN_ONE); +/- 4.096V 1 bit = 0.125mV // ads.setGain(GAIN_TWO); +/- 2.048V 1 bit = 0.0625mV // ads.setGain(GAIN_FOUR); +/- 1.024V 1 bit = 0.03125mV // ads.setGain(GAIN_EIGHT); +/- 0.512V 1 bit = 0.015625mV // ads.setGain(GAIN_SIXTEEN); +/- 0.256V 1 bit = 0.0078125mV
Для АЦП ESP32 значения несколько другие:
0dB attenuaton (ADC_ATTEN_DB_0) between 100 and 950mV 2.5dB attenuation (ADC_ATTEN_DB_2_5) between 100 and 1250mV 6dB attenuation (ADC_ATTEN_DB_6) between 150 to 1750mV 11dB attenuation (ADC_ATTEN_DB_11) between 150 to 2450mV
Примерно одинаковые варианты ослабления входного напряжения GAIN_FOUR, +/- 1.024V для ADS1115 и 0dB attenuaton (ADC_ATTEN_DB_0) от 100 до 950 mV для АЦП ESP32.
В пределе при токе 20 mA напряжение должно быть 950 mV. Соотвественно, по закону Ома сопротивление R = 0,95/0,002 = 47,5 Ом. Ближайшее из стандартных сопротивление как раз 47 Ом.
Измерения АЦП ESP32 и ADS1115
Данные по напряжению на измерительном сопротивлении снимались параллельно АЦП ESP32, ADS1115 и высокоточным Unit-T UT61E.
Менее точный VC809C+ работал в качестве измерителя тока, формируемого генератором сигнала 4..20 mA.
| VC890С+, mA | UT61E, мV | ESP32, mV | ADS1115, mV | Ошибка ESP32 абс, mV | Ошибка ADS абс, mV | Ошибка ESP32, % | Ошибка ADS, % |
| 4,43 | 209,21 | 207 | 209,08 | 2,21 | 0,13 | 1,06% | 0,06% |
| 4,5 | 212,38 | 211 | 212,25 | 1,38 | 0,13 | 0,65% | 0,06% |
| 5 | 235,7 | 232 | 235,57 | 3,7 | 0,13 | 1,57% | 0,06% |
| 5,5 | 259,1 | 256 | 259,3 | 3,1 | -0,2 | 1,20% | -0,08% |
| 6 | 282,7 | 280 | 282,8 | 2,7 | -0,1 | 0,96% | -0,04% |
| 6,49 | 306 | 301 | 306,1 | 5 | -0,1 | 1,63% | -0,03% |
| 6,99 | 329,4 | 325 | 329,53 | 4,4 | -0,13 | 1,34% | -0,04% |
| 7,49 | 352,7 | 347 | 352,93 | 5,7 | -0,23 | 1,62% | -0,07% |
| 7,98 | 376 | 370 | 376,19 | 6 | -0,19 | 1,60% | -0,05% |
| 8,48 | 399,4 | 393 | 399,54 | 6,4 | -0,14 | 1,60% | -0,04% |
| 8,98 | 422,8 | 416 | 422,93 | 6,8 | -0,13 | 1,61% | -0,03% |
| 9,47 | 445,9 | 438 | 446,14 | 7,9 | -0,24 | 1,77% | -0,05% |
| 9,96 | 469,1 | 462 | 469,37 | 7,1 | -0,27 | 1,51% | -0,06% |
| 10,46 | 492,3 | 484 | 492,63 | 8,3 | -0,33 | 1,69% | -0,07% |
| 10,96 | 515,6 | 506 | 515,68 | 9,6 | -0,08 | 1,86% | -0,02% |
| 11,44 | 538,6 | 530 | 538,85 | 8,6 | -0,25 | 1,60% | -0,05% |
| 11,94 | 562 | 552 | 562,02 | 10 | -0,02 | 1,78% | 0,00% |
| 12,43 | 585,2 | 575 | 585,3 | 10,2 | -0,1 | 1,74% | -0,02% |
| 12,92 | 608,1 | 598 | 608,13 | 10,1 | -0,03 | 1,66% | 0,00% |
| 13,41 | 631,2 | 620 | 631,62 | 11,2 | -0,42 | 1,77% | -0,07% |
| 13,91 | 654,2 | 642 | 654,73 | 12,2 | -0,53 | 1,86% | -0,08% |
| 14,4 | 677,7 | 665 | 678,22 | 12,7 | -0,52 | 1,87% | -0,08% |
| 14,9 | 701,2 | 689 | 701,23 | 12,2 | -0,03 | 1,74% | 0,00% |
| 15,36 | 722,8 | 712 | 723,21 | 10,8 | -0,41 | 1,49% | -0,06% |
| 15,88 | 747 | 734 | 747,74 | 13 | -0,74 | 1,74% | -0,10% |
| 16,38 | 770,4 | 755 | 770,76 | 15,4 | -0,36 | 2,00% | -0,05% |
| 16,87 | 793,6 | 777 | 793,62 | 16,6 | -0,02 | 2,09% | 0,00% |
| 17,36 | 816,2 | 801 | 816,75 | 15,2 | -0,55 | 1,86% | -0,07% |
| 17,83 | 838,8 | 825 | 839,26 | 13,8 | -0,46 | 1,65% | -0,05% |
| 18,32 | 862 | 847 | 862,56 | 15 | -0,56 | 1,74% | -0,06% |
| 18,82 | 884,8 | 870 | 885,12 | 14,8 | -0,32 | 1,67% | -0,04% |
| 19,31 | 907,5 | 892 | 907,92 | 15,5 | -0,42 | 1,71% | -0,05% |
| 19,78 | 930,6 | 915 | 930,69 | 15,6 | -0,09 | 1,68% | -0,01% |
| 9,490606 | -0,2297 | 1,62% | -0,04% |

По результатам измерений видно, что 16-ти битный АЦП ADS1115 значительно лучше встроенного 12-ти битного АЦП ESP32:
- Средняя относительная ошибка измерения ESP32 — 1,62%
- Средняя относительная ошибка измерения ADS1115 — 0,04%. График практически полностью совпадает с показаниями высокоточного вольтметра UT61E.
- Относительная ошибка ADS1115 остается практически постоянной и минимальной на всем интервале измерений.
- Ошибка у АЦП ESP32 меняется в зависимости от величины измеряемого тока. При низких значениях тока, а, соответственно, напряжения, на входе — ошибка значительная.
Исходный код
#include <driver/adc.h>
#include "esp_adc_cal.h"
#include <Wire.h>
#include <Adafruit_ADS1015.h>
Adafruit_ADS1115 ads(0x4A);
const float multiplier = 0.03125; //0.1875F;
//float ads_voltage = 0.0;
//#define channel ADC1_CHANNEL_7
#define channel ADC1_CHANNEL_3 //SVN input
//#define pin ADC1_CHANNEL_7_GPIO_NUM
#define pin ADC1_CHANNEL_3_GPIO_NUM //SVN input
#define DEFAULT_VREF 1100
static esp_adc_cal_characteristics_t *adc_chars;
static const adc_atten_t atten = ADC_ATTEN_DB_0;
static const adc_unit_t unit = ADC_UNIT_1;
void check_efuse()
{
//Check TP is burned into eFuse
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
Serial.println("eFuse Two Point: Supported");
} else {
Serial.println("eFuse Two Point: NOT supported");
}
//Check Vref is burned into eFuse
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
Serial.println("eFuse Vref: Supported");
} else {
Serial.println("eFuse Vref: NOT supported");
}
}
String print_char_val_type(esp_adc_cal_value_t val_type)
{
switch (val_type)
{
case ESP_ADC_CAL_VAL_EFUSE_VREF:
return "ESP_ADC_CAL_VAL_EFUSE_VREF. Characterization based on reference voltage stored in eFuse.";
case ESP_ADC_CAL_VAL_EFUSE_TP:
return "ESP_ADC_CAL_VAL_EFUSE_TP. Characterization based on Two Point values stored in eFuse.";
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
return "ESP_ADC_CAL_VAL_DEFAULT_VREF. Characterization based on default reference voltage.";
};
return "Unknown";
}
String NumToAtten(int atten)
{
switch (atten)
{
case 0:
return "ADC_ATTEN_DB_0. No chages for the input voltage";
case 1:
return "ADC_ATTEN_DB_2_5. The input voltage will be reduce to about 1/1.34.";
case 2:
return "ADC_ATTEN_DB_6. The input voltage will be reduced to about 1/2";
case 3:
return "ADC_ATTEN_DB_11. The input voltage will be reduced to about 1/3.6";
}
return "Unknown attenuation.";
}
String NumToWidth(int width)
{
switch (width)
{
case 0:
return "ADC_WIDTH_BIT_9. ADC capture width is 9Bit";
case 1:
return "ADC_WIDTH_BIT_10. ADC capture width is 10Bit";
case 2:
return "ADC_WIDTH_BIT_11. ADC capture width is 11Bit";
case 3:
return "ADC_WIDTH_BIT_12. ADC capture width is 12Bit";
}
return "Unknown width.";
}
void setup() {
Serial.begin(9600);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(channel, ADC_ATTEN_DB_0);
check_efuse();
//Characterize ADC at particular atten
adc_chars = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
Serial.println("ADC number:\t" + String(adc_chars->adc_num));
Serial.println("ADC attenuation:\t" + NumToAtten(adc_chars->atten));
Serial.println("ADC bit width:\t" + NumToWidth(adc_chars->bit_width));
Serial.println("ADC coeff_a:\t" + String(adc_chars->coeff_a));
Serial.println("ADC coeff_b:\t" + String(adc_chars->coeff_b));
Serial.println("ADC VRef:\t" + String(adc_chars->vref));
//Check type of calibration value used to characterize ADC
Serial.println(print_char_val_type(val_type));
//ADS1115
// ads.setGain(GAIN_TWOTHIRDS); +/- 6.144V 1 bit = 0.1875mV (default)
//ads.setGain(GAIN_ONE); // +/- 4.096V 1 bit = 0.125mV
// ads.setGain(GAIN_TWO); +/- 2.048V 1 bit = 0.0625mV
ads.setGain(GAIN_FOUR); //+/- 1.024V 1 bit = 0.03125mV
// ads.setGain(GAIN_EIGHT); +/- 0.512V 1 bit = 0.015625mV
// ads.setGain(GAIN_SIXTEEN); +/- 0.256V 1 bit = 0.0078125mV
ads.begin();
}
//char buff[10];
int lastMillis = 0;
int lastMeasurementsMillis = 0;
float AverageESP = 0;
float AverageADS = 0;
int MeasurementsToAverage = 0;
void loop() {
int currentMillis = millis();
if (currentMillis - lastMeasurementsMillis > 50)
{
float adcr = adc1_get_raw(channel);
int16_t adc0 = ads.readADC_SingleEnded(0); //16 bits ADC read of input A0
AverageESP += adcr;
AverageADS += adc0;
MeasurementsToAverage++;
lastMeasurementsMillis = currentMillis;
}
if (currentMillis - lastMillis > 1000)
{
int adcr = adc1_get_raw(channel);
//ADS1115 read
int16_t adc0 = ads.readADC_SingleEnded(0); //16 bits ADC read of input A0
float ads_voltage = (adc0 * multiplier); ///1000
//--------
AverageESP /= MeasurementsToAverage;
AverageADS /= MeasurementsToAverage;
AverageADS *= multiplier;
AverageESP = esp_adc_cal_raw_to_voltage(AverageESP, adc_chars);
float voltage = esp_adc_cal_raw_to_voltage(adcr, adc_chars);
Serial.println("Read ADC pin [" + String(pin) + "]: " + String(adcr) + "\tVoltage: " + String(voltage) + " [" + String(ads_voltage) + "]" + "\tAverage: " + String(AverageESP) + " [" + String(AverageADS) + "]");
lastMillis = currentMillis;
AverageESP = 0;
AverageADS = 0;
MeasurementsToAverage = 0;
}
}