ADS1115: подключение к Arduino, 16-битный АЦП и усиление PGA
Термопара или токовый шунт обычно и проламывают доверие к встроенному АЦП Uno: вместо плавной кривой плоттер рисует лестницу, потому что сетка из 1024 ступеней нарезана по 4,88 мВ, а весь милливольтовый сигнал умещается на ней в две-три клетки. Первая реакция — усилить снаружи операционником. Она честная и уводит в тупик: вместе с сигналом умножается шум, и лестница возвращается, только теперь дрожит. ADS1115 решает это иначе: 16-битный дельта-сигма преобразователь с программируемым усилителем PGA сидит на шине I2C, съедает два провода и даёт ступень от 187,5 мкВ вплоть до 7,8 мкВ — в шестьсот раз мельче, чем у A0.
Ниже — подключение четырьмя проводами, два рабочих скетча на библиотеке Adafruit_ADS1X15: одиночное чтение с readADC_SingleEnded, перевод кода в вольты через computeVolts и дифференциальная пара для шунта. Плюс таблица gain от ±6,144 до ±0,256 В и раскладка адресов 0x48-0x4B по ножке ADDR — на них уживаются до четырёх чипов сразу. Скорость честная: потолок 860 отсчётов в секунду, и это плата не только за разрядность — медленные режимы давят шум, быстрые дрожат последним разрядом. Если модуль на макетке молчит, первым инструментом остаётся сканер I2C-адресов, а не угадывание по форумам.
Зачем внешний АЦП, когда у Uno есть A0-A5
Штатный преобразователь ATmega328 десятибитный: analogRead() возвращает 0-1023, одно чтение занимает около 110 микросекунд, и для потенциометра, фоторезистора или джойстика этого хватает с запасом. Проблема начинается там, где вся полезная амплитуда сигнала меньше одной клетки сетки или занимает две-три: термопара выдаёт десятки микровольт на градус, токовый шунт — десятки милливольт на ампер, и на этом масштабе 4,88 мВ между соседними числами превращают измерение в лотерею. Второй сценарий — 3,3-вольтовые датчики на пятёрке: половина шкалы уходит вверх, туда, где сигнала нет.
| Параметр | Uno A0-A5 | ADS1115 |
|---|---|---|
| Разрядность | 10 бит: 1024 ступени | 16 бит: от −32768 до 32767 |
| Шаг сетки при питании 5 В | 4,88 мВ | 187,5 мкВ на пределе ±6,144 В, 7,8 мкВ на ±0,256 В |
| Скорость | около 110 мкс на чтение | 8-860 отсчётов в секунду, задаются программно |
| Каналы | 6 однополярных | 4 однополярных или 2 дифференциальные пары |
| Усиление | нет | PGA: шесть пределов от ±6,144 В до ±0,256 В |
| Интерфейс | ножки платы напрямую | I2C: два провода, адрес 0x48-0x4B |
Плата за разрядность — скорость и способ общения. Даже на пиковых 860 отсчётах в секунду I2C-обмен добавляет время на запись конфига и чтение результата, так что на плотном цикле Uno внешний чип не ускорит, а замедлит измерение. Скорость тут же меняет характер показаний: на 860 отсч/с дрожит младший разряд, на 8 отсч/с цифры застывают, потому что преобразователь дольше копит и точнее давит шум — для печки и шунта это подарок, для вибрации приговор. Если задача наоборот упирается в темп, а не в точность, у самой Uno есть резервы: чтение АЦП через регистры поднимает скорость на порядок без внешних чипов. И ещё одна тихая деталь, о которой не пишут в описаниях: в однополярном режиме, когда меряете вход против земли, старший знаковый разряд простаивает — фактически доступны 15 бит, все 16 достаются только дифференциальному включению.
Схема подключения: 4 провода I2C
На модуле четыре вывода: VDD на 5 В, GND на землю, SDA на A4 и SCL на A5 — стандартный расклад шины, механика которой разобрана в уроке про два провода I2C. Подтяжки на модуль обычно напаяны заводом, допаивать ничего не нужно. Чип питается от 2,0 до 5,5 В, так что и Uno, и 3,3-вольтовые платы работают напрямую. Ножка ADDR на большинстве модулей подтянута к земле заводом, поэтому заводской адрес почти всегда 0x48; перевесить её на питание, SDA или SCL — осознанное действие, а не случайность. Первое чтение заканчивайте проверкой: сканер шины должен показать 0x48, иначе дальше можно не гадать.
#include <Adafruit_ADS1X15.h> // VDD->5V, GND->GND, SDA->A4, SCL->A5. ADDR никуда не подключён -> 0x48Adafruit_ADS1115 ads; void setup() { Serial.begin(9600); if (!ads.begin()) { // чип не ответил на 0x48 Serial.println("ADS1115 ne nayden: provoda ili adres"); while (true) {} } ads.setGain(GAIN_ONE); // полный масштаб ±4,096 В ads.setDataRate(RATE_ADS1115_250SPS);} void loop() { int16_t raw = ads.readADC_SingleEnded(0); // канал AIN0 против земли float volts = ads.computeVolts(raw); // код -> вольты с учётом gain Serial.print("raw="); Serial.print(raw); Serial.print(" U="); Serial.print(volts, 4); Serial.println(" V"); delay(200);}computeVolts() — недооценённая функция библиотеки: она сама учитывает выбранный gain, и вся арифметика со сдвигами и весом ступени остаётся внутри. Меняете GAIN_ONE на GAIN_SIXTEEN — пересчитывать вручную ничего не надо. setDataRate() задаёт темп из диапазона 8-860 отсч/с; если строку убрать, библиотека поставит скромные 128, и для большинства датчиков этого достаточно. Один преобразователь внутри чипа обслуживает все четыре входа через мультиплексор, поэтому одновременного чтения каналов не бывает — чип щёлкает ими поочерёдно за доли миллисекунды.
PGA и усиление: выбираем диапазон
Gain — это не «умножить число в N раз», а выбор полного масштаба, на который натягивается 16-битная сетка. Пределов шесть, и вес ступени считается одинаково: масштаб, делённый на 32768. Правило выбора банальное, но его постоянно игнорируют: масштаб берут чуть выше максимального напряжения сигнала, а не «побольше на всякий случай». Поставили предел с запасом в десять раз — и девяносто процентов сетки простаивают, точность падает до одной десятой возможной.
| setGain() | Полный масштаб | Вес ступени | Под что годится |
|---|---|---|---|
| GAIN_TWOTHIRDS | ±6,144 В | 187,5 мкВ | сигналы до 5 В, режим по умолчанию |
| GAIN_ONE | ±4,096 В | 125 мкВ | датчики с выходом до 4 В |
| GAIN_TWO | ±2,048 В | 62,5 мкВ | 3,3-вольтовые сенсоры |
| GAIN_FOUR | ±1,024 В | 31,25 мкВ | делители, NTC в узком окне температур |
| GAIN_EIGHT | ±0,512 В | 15,6 мкВ | мостовые схемы без усилителя |
| GAIN_SIXTEEN | ±0,256 В | 7,8 мкВ | термопары, шунты, милливольтовые сигналы |
Дифференциальный режим: меряем разницу, а не землю
Пара AIN0-AIN1 (или AIN2-AIN3) сравнивает два входа между собой, минуя общую землю: AIN0 выше — плюс, ниже — минус, весь диапазон от −32768 до 32767. Главная выгода — общий шум вычитается сам: если на оба провода наводится одинаково, дифференциальная пара его молча убирает. Именно так меряют ток на шунте, посадив концы резистора на два входа чипа, — и именно там становятся доступными все 16 бит, которых однополярному режиму не достаётся.
#include <Adafruit_ADS1X15.h> Adafruit_ADS1115 ads;const float SHUNT_OM = 0.010; // шунт 10 мОм в разрыве питания void setup() { Serial.begin(9600); ads.setGain(GAIN_SIXTEEN); // весь масштаб ±0,256 В на малый сигнал if (!ads.begin()) { Serial.println("ADS1115 ne nayden"); while (true) {} }} void loop() { int16_t diff = ads.readADC_Differential_0_1(); // AIN0 минус AIN1, со знаком float volts = ads.computeVolts(diff); float amper = volts / SHUNT_OM; // I = U/R Serial.print("U="); Serial.print(volts * 1000, 3); Serial.print(" mV I="); Serial.print(amper, 3); Serial.println(" A"); delay(250);}Адрес и несколько чипов на шине
Четыре адреса выбираются одной ножкой ADDR: на земле 0x48, на питании 0x49, на линии SDA 0x4A, на линии SCL 0x4B. Сажаете два чипа на разные позиции — и шина живёт без конфликтов, механику которых мы разбирали в уроке про коллизии адресов. Адрес передаётся в begin(): ads.begin(0x49) для чипа с ADDR на питании. Четыре чипа — это 16 однополярных каналов или 8 дифференциальных пар на двух проводах; когда и этого мало, дорогу уступают мультиплексору TCA9548A, который делает восемь отдельных шин. После каждой смены перемычек ADDR прогоняйте сканер I2C: оба чипа должны показаться на своих позициях, молчание любого — сигнал искать обрыв, а не лезть в код.
- ADDR на земле — жду 0x48 в сканере; перекинул перемычку — жду другой адрес
- setGain() выбран чуть выше максимума сигнала, а не «с запасом на десять раз»
- вход не выходит за пределы питания: ниже земли или выше VDD+0,3 В чип клинит
- скорость под задачу: 860 отсч/с для быстрого, 128 и ниже — ради тишины в последних разрядах