В тех случаях, когда встроенный в микроконтроллер ESP32 аналого-цифровой преобразователь (АЦП) не позволяет измерять аналоговые сигналы с нужной точностью и линейность, используются внешние специализированные микросхемы. Одним из лидеров в этой области является компания Analog Devices, которая предлагает очень широкий выбор аппаратных решений самых разных конфигураций практически под любую задачу. В рамках данной статьи мы познакомимся с одним из решений: 16-битный одноканальный АЦП последовательного приближения с псевдо-дифференциальным входом AD7683.
Содержание
- Общее описание
- Параметры и характеристики
- Схема подключения
- Описание схемы
- Программный код (скетч)
- Эксперименты
- Заключение
Общее описание
Преобразователь имеет 16-битное разрешение, то есть он способен различать до 65536 уровней сигнала.
Особенность данной микросхемы является высокая скорость регистрации: 100 тысяч выборок в секунду, что возможно благодаря применению архитектуры последовательного приближения (Succesive Approximation Register – SAR). В рамках этой технологии используется внутренний цифро-аналоговый преобразователь и компаратор, которые методом бинарного поиска «взвешивания» подбирают код за строго фиксированное число тактов (в нашем случае 16 тактов на каждый бит). Это несколько быстрее, чем сигма-дельта АЦП.
Вход преобразователя, в соответствии с даташитом, однополярный или псевдо-дифференциальный. Есть два входных пина: сигнальный (+IN) и земляной (-IN), и между ними измеряется разница напряжений.
Псевдо-дифференциальный вход – это архитектура построения входных цепей, которая позволяет измерять разницу напряжений между входными пинами +IN и -IN. Если очень коротко: однополярный вход, но с отдельным пином для земли сигнала.
На вход можно прикладывать только однополярный положительный сигнал, то есть напряжение на +IN должно быть выше, чем на –IN на величину от 0 вольт до величины опорного напряжения VREF. Этот вход нужен для того, чтобы компенсировать разницу потенциалов между землёй сигнальной и землёй питания АЦП, поскольку на самом пине -IN может быть потенциал, который не равен 0 относительно GND микросхемы, а в пределах ±100 мВ. То есть вместо того, чтобы регистрировать суммарное значение сигнала и шума земли, происходит вычитание синфазной составляющей (разницы потенциалов земель).
Широкий диапазон напряжений однополярного питания от 2,7 до 5,5 вольт позволяет без проблем подключается к 5- или 3,3-вольтовой логике (Arduino UNO или ESP32/STM32).
Опорное напряжение (VREF) задаётся внешним источником, и может варьироваться в диапазоне от 0,5 вольт до уровня питания VDD. Но рекомендуется по возможности не применять питание чипа как опорное VREF, а использовать специализированные решения (например, REF192 или LM4040).
Передача данных на микроконтроллер осуществляется по трёхпроводному SPI-интерфейсу с возможной максимальной частотой тактирования до 2,9 МГц. Но для стабильной работы для начала рекомендуется работать на 1 МГц (при этом максимальная частота дискретизации будет ограничена до примерно 55-58 кГц). Интерфейс чисто выходной, и это значит, что как только подаётся низкий логический уровень на пин CS, преобразователь тут же начинает свою работу и выдаёт через пин DOUT биты данных в такт сигналам синхронизации, которые генерируются микроконтроллером и подаются на пин DCLOCK. Благодаря низкому энергопотреблению (в активном режиме ток потребления составляет примерно 0,8 мА при питании 5 В), преобразователь хорошо подходит для батарейного питания в портативных устройствах.
Микросхема AD7683 выпускается в корпусах MSOP-8 или QFN-8 размером 3×3 мм. Ниже представлены цоколёвка чипа и таблица с описанием назначения выводов:


Параметры и характеристики

Интерфейс SPI организован по трёхпроводной схеме и включает в себя входы CS (выбор чипа и начало преобразования) и DCLOCK (тактовый сигнал синхронизации), а также выход DOUT для передачи данных.
Работа интерфейса соответствует режиму SPI_Mode3 (CPOL = 1, CPHA = 1): изменение состояния на линии DOUT происходит по спаду тактового сигнала, а считывание данных – по его фронту.
Процесс измерения запускается при низком логическом уровне на пине CS. В течение первых 5 тактовых импульсов микросхема выполняет внутреннее преобразование, при этом выход DOUT находится в высокоимпедансном состоянии (отключен и неактивен). На спаде пятого тактового импульса пин DOUT переходит в активное состояние и принудительно устанавливается в логический ноль.
Начиная с шестого, и вплоть до спада на двадцать втором тактовом импульсе, на выводе DOUT последовательно появляются биты результата преобразования (начиная со старшего разряда MSB). По окончании передачи, когда на пине CS устанавливается единица, выход DOUT вновь переходит в высокоимпедансное состояние, а микросхема переходит в режим пониженного энергопотребления.
Максимальная частота тактового сигнала DCLOCK составляет 2,9 МГц, а полное время цикла преобразования (включая выборку и оцифровку) равно 10 микросекунд (что как раз соответствует скорости 100 тысяч выборок в секунду).
Ниже представлена наглядная временная диаграмма работы SPI-интерфейса:

Схема подключения AD7683 к ESP32
Для экспериментов мы применим систему, состоящую из следующих частей:
– измерительный мост Уитстона, в состав которого входит чувствительный элемент в виде терморезистора;
– инструментальный (дифференциальный) усилитель на базе чипа INA118;
– источник опорного напряжения шунтового типа на базе LM4040 с номинальным выходным уровнем 2,5 В;
– OLED-дисплей на базе контроллера SSD1306 с диагональю экрана 0,96” и разрешением 128×64 пикселей.
Вся система будет управляться отладочной платой NodeMCU-32S (38 pin) на базе модуля ESP-WROOM-32.
Ниже представлены таблица подключений и принципиальная схема экспериментальной измерительной системы:

Описание схемы
В качестве простого эксперимента была собрана система измерения температуры. В её основе – отладочная плата контроллера ESP32. Она будет подключаться к внешнему блоку питания через microUSB-разъём, поэтому для питания аналоговой части схемы мы будем брать 5 вольт с пина VIN на отладочной плате.
Чувствительный элемент – терморезистор B57861 с отрицательным (NTC) температурным коэффициентом сопротивления и с номинальным сопротивлением 10 кОм при температуре +25 °C.
Датчик включен в состав моста Уитстона, что значительно повышает его чувствительность. Более подробно про особенности такой схемы можно прочитать в статье про схемы включения терморезисторов.
Так как сигнал с выхода моста является дифференциальным, его необходимо преобразовать в однополярный. Для этого применяется дифференциальный усилитель на базе микросхемы INA118. Данный чип также выполняет роль согласования выходного сопротивления моста и входного сопротивления аналого-цифрового преобразователя. О важности данного схемотехнического решения можно прочитать в статье про буферный усилитель.
Для корректной работы буферного усилителя необходимо обеспечить двухполярное питание. Если питать однополярным, то на выходе будет смещение. Дополнительное отрицательное напряжение -5 вольт обеспечивается при помощи специализированной микросхемы ICL7660.
Формирование VREF обеспечивается микросхемой LM4040AIM3 с номиналом 2,5 вольта и точностью ±0,1 % (класс A). При питании 5 вольт используется балластный резистор на 2,4 кОм, чтобы вывести источник на номинальный режим работы.
Для практических тестов была использована печатная плата с собранной схемой измерительного моста с буферным усилителем и, собственно, с аналого-цифровым преобразователем, которая являлась частью экспериментального макета многоканальной системы сбора данных. На фотографии ниже представлен общий вид тестовой системы:

Программный код (скетч)
Проект создавался в среде программирования Arduino IDE 2. На всякий случай подробная инструкция по настройке представлена в статье про диктофон на ESP32 с цифровым микрофоном INMP411.
Для OLED-дисплея на базе контроллера SSD1306 воспользуемся библиотекой Adafruit_SSD1306. Дополнительно можно подключить Adafruit_GFX для отображения графики.
Функции для работы с AD7683 написаны самостоятельно. Ниже представлен подробно прокомментированный полный листинг кода:
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Количество отсчётов для усреднения
#define AVG_COUNT 100
// Опорное напряжение АЦП (вольты)
#define VREF_ADC 2.491 // Измерьте точное значение на Вашем экземпляре
// Скорость SPI-интерфейса
// 1 МГц - оптимально для стабильной работы
#define SPI_SPEED 1000000 // 1 МГц
#define PIN_CS_AD7683 5
#define PIN_SCK 18
#define PIN_MISO 19
#define PIN_MOSI 23 // этого пина на АЦП нет, но его объявляем для корректной настройки SPI
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Объект для работы с SPI на аппаратном порту HSPI (второй SPI-интерфейс ESP32)
SPIClass spi_ad(HSPI);
// Функция чтения данных с АЦП
inline uint16_t readAD7683() {
uint8_t byte0, byte1, byte2;
// Начинаем SPI-транзакцию
spi_ad.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE3));
digitalWrite(PIN_CS_AD7683, LOW);
// небольшая временная задержка для стабилизации сигнала пина CS
delayMicroseconds(1);
// Получение данных 24 бита, разделённых на 3 байта
// transfer - это встроенный метод класса SPIClass из стандартной библиотеки Arduino (SPI.h)
byte0 = spi_ad.transfer(0xFF);
byte1 = spi_ad.transfer(0xFF);
byte2 = spi_ad.transfer(0xFF);
digitalWrite(PIN_CS_AD7683, HIGH);
// Завершаем транзакцию
spi_ad.endTransaction();
return ((uint16_t)(byte0 & 0x03) << 14) | ((uint16_t)byte1 << 6) | ((uint16_t)(byte2 & 0xFC) >> 2);
}
// Функция измерения напряжения
float measureVoltage(int count) {
if (count < 1) count = 1;
uint32_t sum = 0;
for (int i = 0; i < count; i++) {
uint16_t value = readAD7683();
sum += value;
}
float adc_avg = (float)sum / count;
return (adc_avg * VREF_ADC) / 65535.0;
}
// Функция вывода информации на OLED-дисплей
void updateDisplay(float voltage) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Voltage:");
display.setTextSize(2);
display.setCursor(0, 20);
display.print(voltage, 4);
display.println(" V");
// Показываем количество выборок
display.setTextSize(1);
display.setCursor(0, 50);
display.print("AVG = %d", AVG_COUNT);
display.display();
}
// Первичная инициализация контроллера
void setup() {
// Инициализация OLED-дисплея
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) {
delay(100);
}
}
// Настройка пина CS - активация микросхемы АЦП и начало преобразования
pinMode(PIN_CS_AD7683, OUTPUT);
digitalWrite(PIN_CS_AD7683, HIGH);
// Настройка SPI
spi_ad.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_CS_AD7683);
spi_ad.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE3));
spi_ad.endTransaction();
// Инициализация UART
Serial.begin(115200);
// Готовность
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Ready!");
display.display();
delay(1000);
}
// Главный бесконечный цикл loop
void loop() {
// Измеряем с усреднением
float voltage = measureVoltage(AVG_COUNT);
// Обновляем дисплей
updateDisplay(voltage);
// Обновляем показания в мониторе порта раз в 200 миллисекунд
static uint32_t last_serial = 0;
if (millis() - last_serial > 200) {
last_serial = millis();
Serial.println(voltage, 4);
}
}
Несмотря на то, что в качестве источника сигнала применён мост Уитстона с терморезистором, мы акцентируем внимание только на регистрации напряжения.
Представленный код реализует минимальный функционал: чтение данных с микросхемы преобразователя, аккумулирование измеренных значений с последующим вычислением среднего арифметического. Это позволяет повысить точность.
Попробуем провести эксперименты с разным количеством отсчётов (переменная AVG_COUNT) для усреднения. Для сравнения с показаниями на OLED-дисплее применим мультиметр с высоким входным сопротивлением (чтобы снизить воздействие на входные цепи микросхемы преобразователя), регистрирующий разность потенциалов непосредственно на входе АЦП.
В коде применено простое скользящее накопление с последующим делением на число отсчётов (например, AVG_COUNT = 6000).
Это рабочий способ борьбы с шумами. Он действительно снижает разброс показаний, но плата за это – прямое пропорциональное падение эффективной частоты обновления данных.
Поскольку АЦП выдаёт 100 000 отсчётов в секунду, накопление 6000 семплов занимает примерно 60 миллисекунд. Соответственно, результирующая частота выдачи одного усреднённого значения составит приблизительно 17 Гц. То есть вместо 100 тысяч мы получаем 17 измерений в секунду. Для измерения температуры такое решение допустимо, но для любых быстропротекающих процессов – неприемлемо. Ведь главное преимущество SAR-архитектуры – это высокая скорость. Гораздо более грамотным решением было бы применение фильтра типа «скользящая средняя» (moving average), где происходит обновление на каждом семпле без блокировки, или же использование экспоненциального фильтра, который характерен минимальной задержкой при хорошем подавлении высокочастотных шумов.
Эксперименты
Ниже представлены фотографии с результатами измерений при разном количестве отсчётов:
– 50

– 100

– 200

– 500

– 1000

– 6000

Заключение
В рамках данной статьи мы познакомились с одноканальным 16-разрядным АЦП последовательного приближения AD7683. Данный чип подходит для случаев, когда в Вашем проекте требуется отдельный измерительный канал повышенной точности с высокой скоростью преобразования.
