Основы · 17 мин чтения

Что такое АЦП (ADC)? Аналого-цифровое преобразование на Arduino

Что такое АЦП и как он работает? Аналоговый и цифровой сигнал, разрешение 10 и 12 бит, analogReference, формула перевода в вольты, ADC2 на ESP32 и снижение шума.

АЦП (аналого-цифровой преобразователь, ADC) — это схема, которая превращает непрерывно меняющееся напряжение на пине в число, с которым может работать микроконтроллер. Каждый вызов analogRead() на Arduino запускает этот преобразователь в фоне: 10-битный АЦП в Arduino Uno превращает напряжение от 0 до 5 V в число от 0 до 1023, а 12-битные АЦП в ESP32 и Raspberry Pi Pico превращают примерно 0–3,3 V в число от 0 до 4095. АЦП — ключ к чтению всего, что имеет аналоговый выход: потенциометров, фоторезисторов (LDR), датчиков влажности почвы, джойстиков и датчиков температуры.

В этом руководстве ты узнаешь, чем аналоговый сигнал отличается от цифрового, как АЦП шаг за шагом превращает напряжение в число и как разрешение и опорное напряжение влияют на измерения. На реальных числах разберём формулу перевода показаний в вольты, изменение диапазона измерения через analogReference, частоту дискретизации и практические способы снизить шум. Ещё поговорим об АЦП в ESP32, который нелинеен по краям диапазона, о конфликте ADC2 с Wi-Fi и о настройке ослабления (attenuation), о работе с АЦП Raspberry Pi Pico на MicroPython, а в конце кратко познакомимся с ЦАП, который делает обратную работу.

Чтение потенциометра через АЦП
Потенциометр — самый простой способ попробовать АЦП: напряжение на его среднем выводе меняется от 0 V до напряжения питания, когда поворачиваешь ручку.

Чем аналоговый сигнал отличается от цифрового?

Аналоговый сигнал — это величина, которая может принимать бесконечно много значений в пределах диапазона и непрерывно меняется во времени. Пока напряжение на среднем выводе потенциометра идёт от 0 V до 5 V, оно проходит через все промежуточные значения, например 1,2 V, 1,21 V и 1,215 V; звуковая волна от микрофона, зависящее от температуры напряжение термистора и медленно падающее напряжение батарейки тоже аналоговые. Цифровой сигнал, напротив, знает только определённые уровни. В мире логики Arduino их два: LOW (0) и HIGH (1). На Uno цифровой вход считает всё ниже примерно 1,5 V уровнем LOW, а всё выше 3,0 V — уровнем HIGH; область между ними не определена. Поэтому digitalRead() не отличит 3,7 V от 4,2 V на аккумуляторе и назовёт оба значения HIGH. Сила цифрового сигнала — в устойчивости к шуму: и 4,6 V, и 5,0 V означают один и тот же HIGH. Чтобы измерить, сохранить аналоговое значение или посчитать что-то с ним, его сначала нужно превратить в число, то есть в цифровое значение, и делает это АЦП.

Что такое АЦП и как он работает?

Работу АЦП можно описать в два шага. Первый — дискретизация (выборка): в момент измерения преобразователь копирует входное напряжение на маленький конденсатор (устройство выборки и хранения) и держит его неизменным, пока не закончится преобразование; в ATmega328P этот конденсатор около 14 pF. Второй — квантование: удерживаемое напряжение округляется до ближайшей ступени шкалы, которая делит опорное напряжение на равные ступени, и номер ступени возвращается как результат. АЦП в Arduino Uno, ESP32 и Pico — все последовательного приближения (SAR, Successive Approximation Register). АЦП последовательного приближения работает как игра «угадай число»: сначала пробует половину опорного напряжения, ставит старший бит в 1, если вход выше, и оставляет 0, если ниже, потом пробует половину оставшегося диапазона и повторяет это для каждого бита. Так 10-битный АЦП находит нужную ступень из 1024 вариантов всего за 10 сравнений. Для простоты в таблице ниже показано, как 4-битный АЦП с опорным напряжением 5 V измеряет входное напряжение 3,2 V:

ШагПроверяемый битНапряжение сравненияВход (3,2 V) выше?Биты результата
1Бит 3 (вес 8)8 / 16 × 5 = 2,500 VДа → 11000
2Бит 2 (вес 4)12 / 16 × 5 = 3,750 VНет → 01000
3Бит 1 (вес 2)10 / 16 × 5 = 3,125 VДа → 11010
4Бит 0 (вес 1)11 / 16 × 5 = 3,4375 VНет → 01010
РезультатВсе биты пройдены1010 = 10 → 10 × 5 / 16 = 3,125 VПогрешность 0,075 V (одна ступень — 0,3125 V)1010

Разница 0,075 V в результате — это погрешность квантования, и её не избежать ни одному АЦП: если реальное напряжение лежит между двумя ступенями, АЦП может назвать только одну из них. Погрешность остаётся меньше одной ступени (LSB), поэтому чем меньше ступень, тем точнее измерение. Конденсатор выборки и хранения должен заряжаться при каждом измерении, поэтому важно и сопротивление источника, который питает вход АЦП: даташит ATmega328P рекомендует выходное сопротивление около 10 kΩ или меньше, а делитель напряжения с более высоким сопротивлением не успевает зарядить конденсатор, и показания «плывут». Кроме типа SAR, который стоит в микроконтроллерах, бывают и другие АЦП. Сигма-дельта АЦП медленные, но очень точные: HX711 из проектов с весами даёт 24 бита, а модуль ADS1115 с подключением по I2C — 16 бит. Параллельные (flash) АЦП используют отдельный компаратор на каждую ступень; они чрезвычайно быстрые и встречаются в осциллографах.

Разрешение АЦП: что значат 10 и 12 бит?

Разрешение показывает, на сколько ступеней АЦП делит свой диапазон измерения. N-битный АЦП даёт 2^N ступеней: 10 бит — это 1024 ступени (0–1023), 12 бит — 4096 ступеней (0–4095), а 16 бит — 65 536 ступеней. Напряжение одной ступени называют LSB (младший значащий разряд), и его находят по формуле LSB = Vref / 2^N. На Arduino Uno это 5 V / 1024 = 4,88 mV, а на ESP32 и Pico — 3,3 V / 4096 = 0,81 mV. Значит, Uno не видит изменений меньше 4,88 mV; с LM35, который выдаёт 10 mV/°C, это шаг около 0,5 °C. Но разрешение и точность — не одно и то же. Шум АЦП, нелинейность и погрешность опорного напряжения делают часть битов «на бумаге» бесполезными. Мерой этого служит эффективное число разрядов (ENOB): АЦП RP2040 в Raspberry Pi Pico выдаёт 12-битный результат, но по даташиту эффективное число разрядов — 8,7. Сравним АЦП трёх плат бок о бок:

ХарактеристикаArduino Uno R3ESP32 (WROOM-32)Raspberry Pi Pico
Разрешение АЦП10 бит (0–1023)12 бит (0–4095)12 бит (в MicroPython масштабируется до 0–65535)
Аналоговые входы6 (A0–A5; на Nano A0–A7)18 каналов: 8 на ADC1, 10 на ADC23 пина (GP26–GP28) + VSYS/3 (GP29) + датчик температуры
Диапазон измерения по умолчанию0–5 VОколо 0–3,1 V (ослабление 11 dB)0–3,3 V (ADC_VREF)
Одна ступень (LSB)4,88 mVОколо 0,8 mV0,81 mV
Варианты опорного напряженияDEFAULT (5 V), INTERNAL (1,1 V), EXTERNAL (AREF)Ослабление: 0, 2,5, 6 и 11 dBВнешнее опорное напряжение на пине ADC_VREF
Частота дискретизацииОколо 10 000 измерений/с20 kHz – 2 MHz в непрерывном режиме (DMA)500 000 измерений/с аппаратно
Заметка о точностиАбсолютная погрешность ±2 LSBНелинеен по краям, нужна калибровкаENOB 8,7 бита, смещение около 30 mV
Аналоговый выход (ЦАП)Нет (используется ШИМ)2 канала, 8 бит (GPIO 25 и 26)Нет (используется ШИМ)

Формула перевода показаний АЦП в напряжение

Число, которое возвращает АЦП, само по себе ничего не значит; чтобы превратить его в напряжение, нужны опорное напряжение и число ступеней: Напряжение = значение АЦП × Vref / 2^N. На Arduino Uno это V = значение × 5,0 / 1024, так что если analogRead() вернул 512, на пине 512 × 5 / 1024 = 2,50 V. В обратную сторону, какому числу соответствует заданное напряжение, находят по формуле значение = V × 1024 / Vref: 3,3 V на Uno читается примерно как 675. Во многих примерах встречается деление на 1023 вместо 1024. Даташит ATmega328P даёт формулу ADC = Vin × 1024 / Vref; наибольшее значение 1023 соответствует напряжению на одну ступень ниже Vref. Деление на 1023 даёт ровно 5,00 V на верхнем значении, но сдвигает все значения примерно на 0,1%. Эта разница меньше собственной погрешности АЦП ±2 LSB, так что работают оба варианта; важно лишь придерживаться одного в пределах проекта. Настоящий крупный источник погрешности — сам Vref: на пине 5V у Uno с питанием от USB часто бывает что угодно от 4,7 до 5,1 V, и если подставить в формулу 5,0, погрешность доходит до 5%. Поэтому для точных измерений измерь пин 5V мультиметром и впиши в код реальное значение. Единственный аналоговый вход ESP8266 (A0) тоже 10-битный; сам чип измеряет 0–1 V, а встроенный делитель на платах вроде NodeMCU и Wemos D1 Mini расширяет диапазон примерно до 0–3,2 V.

Плата и настройкаДиапазон показанийПеревод в вольтыПример
Arduino Uno / Nano (DEFAULT, 5 V)0–1023V = значение × 5,0 / 1024768 → 3,75 V
Uno, analogReference(INTERNAL)0–1023V = значение × 1,1 / 1024512 → 0,55 V
ESP32 (11 dB)0–4095С калибровкой: analogReadMilliVolts() / 1000; грубо: значение × 3,3 / 40951650 mV → 1,65 V
Raspberry Pi Pico (read_u16)0–65535V = значение × 3,3 / 6553532768 → 1,65 V
ESP8266 NodeMCU / Wemos D1 Mini (A0)0–1023V = значение × 3,2 / 1024512 → 1,60 V

Подключение потенциометра: Arduino, ESP32 и Pico

Вывод потенциометраArduino UnoESP32Raspberry Pi Pico
Крайний вывод 15V3V33V3(OUT) (пин 36)
Средний вывод (сигнал)A0GPIO 34 (ADC1_CH6)GP26 / ADC0 (пин 31)
Крайний вывод 2GNDGNDAGND (пин 33) или GND
Конденсатор от шума (по желанию)100 nF между A0 и GND100 nF между GPIO 34 и GND100 nF между GP26 и AGND
Аналоговые входы ESP32 и Raspberry Pi Pico принимают не больше 3,3 V; если подключить крайний вывод потенциометра к 5 V или подсоединить датчик с выходом 5 V напрямую, пин может выйти из строя навсегда. И на Uno никогда не подавай на вход напряжение выше напряжения питания (5 V) или ниже 0 V; более высокие напряжения понижай делителем напряжения. Аналоговый пин, к которому ничего не подключено, читает случайные значения; это не поломка, просто пин «висит в воздухе».

Пошагово: первое измерение АЦП на Arduino

  1. Собери схему: подключи крайние выводы потенциометра на 10 kΩ к 5V и GND, а средний вывод — к A0.
  2. Измерь пин 5V: измерь мультиметром напряжение между 5V и GND и впиши его в константу VREF в коде (например, 4.93).
  3. Загрузи код: открой скетч ниже в Arduino IDE, выбери плату и порт и загрузи его.
  4. Открой монитор порта: выставь скорость 9600 бод и смотри, как значение АЦП меняется от 0 до 1023, когда поворачиваешь ручку (подробности в руководстве по монитору порта).
  5. Проверь: измерь мультиметром напряжение между A0 и GND и сравни с напряжением на экране; разница должна быть в пределах 10–20 mV.
C++ (Arduino IDE)
1// Potansiyometreyi ADC ile okuma / Reading a potentiometer with the ADC
2// Kenar bacaklar 5V ve GND'ye, orta bacak A0'a / Outer legs to 5V and GND, wiper to A0
3const int POT_PIN = A0;
4const float VREF = 5.0;      // 5V pininde ölçtüğün gerilim / voltage you measured on the 5V pin
5
6void setup() {
7  Serial.begin(9600);
8}
9
10void loop() {
11  int raw = analogRead(POT_PIN);             // 0-1023 arası sayı / number between 0-1023
12  float volts = raw * VREF / 1024.0;         // sayıyı volta çevir / convert the number to volts
13  int percent = map(raw, 0, 1023, 0, 100);   // yüzdeye ölçekle / scale to percent
14
15  Serial.print(F("ADC: "));
16  Serial.print(raw);
17  Serial.print(F("  Gerilim / Voltage: "));
18  Serial.print(volts, 3);
19  Serial.print(F(" V  Yuzde / Percent: "));
20  Serial.println(percent);
21  delay(200);
22}

analogRead() выполняет измерение примерно за 100 µs и возвращает целое число от 0 до 1023. Код сначала переводит это число в вольты по формуле, а потом в проценты с помощью map(). map() работает с целочисленной арифметикой и отбрасывает дробную часть; если нужен результат с дробью, посчитай вручную через float. Это ратиометрическое измерение: потенциометр и АЦП питаются от одних и тех же 5 V, поэтому даже если питание просядет до 4,9 V, положение ручки, а значит и значение АЦП, не изменится; изменится только рассчитанное напряжение. То же самое верно для датчиков, которые читаются через делитель, например фоторезисторов (LDR) и NTC-термисторов. А вот при измерении напряжения батарейки или датчика с фиксированным выходом в mV, как у LM35, реальное значение Vref напрямую попадает в результат.

Опорное напряжение и analogReference

Опорное напряжение (Vref) — это верх шкалы АЦП: если входное напряжение равно Vref или выше, результат упирается в максимум (1023). Если понизить Vref, диапазон измерения сужается, зато каждая ступень становится меньше, и слабые сигналы измеряются точнее. На Arduino Uno опорное напряжение выбирают функцией analogReference(). С вариантом INTERNAL Vref становится 1,1 V, а одна ступень уменьшается до 1,07 mV; LM35 (10 mV/°C) можно читать с шагом около 0,1 °C, но взамен всё выше 1,1 V, то есть выше 110 °C, измерить уже нельзя. Само внутреннее опорное напряжение 1,1 V может отличаться от чипа к чипу в пределах 1,0–1,2 V, поэтому в точных проектах нужна разовая калибровка мультиметром. Первые несколько показаний после смены опорного напряжения могут быть неточными, об этом говорит и документация Arduino, так что отбрасывать пару показаний после переключения — хорошая привычка. К тому же настройка опорного напряжения действует сразу на все аналоговые пины платы. Варианты зависят от платы:

ВариантОпорное напряжениеПлатыКогда использовать
DEFAULT5 V (3,3 V на платах с питанием 3,3 V)Uno, Nano, Mega, LeonardoДатчики, ратиометрические к питанию: потенциометры, фоторезисторы, джойстики
INTERNAL1,1 V (ATmega328P); 2,56 V на LeonardoUno, Nano, Leonardo (на Mega нет)Датчики с низким напряжением, например LM35
INTERNAL1V1 / INTERNAL2V561,1 V / 2,56 VТолько MegaСлабые сигналы на Mega
EXTERNALНапряжение на пине AREF (0–5 V)Uno, Nano, MegaСвой прецизионный источник опорного напряжения (например, 2,5 V или 4,096 V)
AR_INTERNALОколо 1,43 V (внутреннее опорное напряжение чипа)Uno R4 Minima и WiFiСлабые сигналы на Uno R4
Если к пину AREF подключён внешний источник опорного напряжения, нужно вызвать analogReference(EXTERNAL) до вызова analogRead(). Иначе опорное напряжение, которое формируется внутри чипа, замкнётся на напряжение пина AREF, и микроконтроллер может пострадать. Если нужно переключаться между вариантами, подключай внешнее напряжение к AREF через резистор 5 kΩ; он образует делитель с внутренним сопротивлением пина около 32 kΩ, поэтому учитывай, что напряжение на AREF немного просядет (примерно 2,2 V вместо 2,5 V). Никогда не подавай на AREF меньше 0 V или больше 5 V.

Частота дискретизации: сколько измерений в секунду делает АЦП?

Частота дискретизации — это сколько измерений АЦП может сделать за одну секунду. На Arduino Uno тактовая частота АЦП получается делением 16 MHz на 128 и равна 125 kHz, а одно преобразование занимает 13 тактов; поэтому analogRead() длится около 100 µs, и за секунду можно сделать максимум примерно 10 000 измерений. Для медленно меняющихся величин вроде температуры, освещённости или напряжения батарейки этого более чем достаточно; часто хватает даже 10 измерений в секунду. Для быстрых сигналов вступает в силу правило Найквиста (теорема Котельникова): чтобы поймать сигнал без потерь, дискретизировать нужно как минимум вдвое чаще его наивысшей частоты, а на практике предпочитают в 5–10 раз. Чтобы увидеть сетевой гул 50 Hz, нужно не меньше 100 измерений/с, а чтобы записать речь до 4 kHz — не меньше 8000 измерений/с. Если на Uno нужны более быстрые измерения, можно уменьшить предделитель АЦП: строка ADCSRA = (ADCSRA & 0xF8) | 0x04; в setup() ставит предделитель 16, тактовая частота АЦП поднимается до 1 MHz, и analogRead() ускоряется примерно до 16 µs ценой некоторой потери точности. На ESP32 отдельные вызовы analogRead() позволяют делать десятки тысяч измерений в секунду, а в непрерывном режиме с DMA (analogContinuous() в Arduino) можно выбрать частоту от 20 kHz до 2 MHz; этот режим работает только с пинами ADC1. АЦП Raspberry Pi Pico работает от собственного тактового сигнала 48 MHz, выполняет каждое преобразование за 96 тактов, то есть за 2 µs, и достигает 500 000 измерений в секунду; цикл на MicroPython за такой скоростью не угонится, для полной скорости нужны C SDK и DMA.

АЦП в ESP32: нелинейность, конфликт ADC2 с Wi-Fi и ослабление

На бумаге 12-битный АЦП ESP32 измеряет в четыре раза точнее, чем Uno, но на практике есть три момента, за которыми нужно следить. Первый — нелинейность: около 0 V АЦП ESP32 читает 0 вплоть до примерно 100–150 mV, а на верхнем краю упирается в 4095 ещё до того, как вход дойдёт до 3,3 V; кривая между ними тоже не идеальная прямая. Второй — разброс опорного напряжения: по задумке внутреннее опорное напряжение ESP32 равно 1100 mV, но, по данным Espressif, оно отличается от чипа к чипу в пределах 1000–1200 mV, а это погрешность примерно ±9% у некалиброванных показаний. Поэтому Espressif ещё на производстве записывает калибровочные данные в eFuse чипов. Функция Arduino analogReadMilliVolts() использует эти данные и переводит «сырое» значение сразу в откалиброванные милливольты, и в большинстве случаев это гораздо точнее, чем считать analogRead() × 3,3 / 4095. Третий — ослабление (attenuation): ядро АЦП способно измерять только примерно до 1 V, поэтому более высокие напряжения уменьшаются регулируемым аттенюатором на входе. В Arduino по умолчанию стоит 11 dB, что позволяет читать примерно до 3,1 V, но точный диапазон, который Espressif рекомендует для этой настройки, — 150–2450 mV. Для слабых сигналов уменьшение ослабления повышает и точность, и разрешение:

ОслаблениеКонстанта ArduinoТочный диапазонПрименение
0 dBADC_0db100–950 mVСлабые сигналы, самое точное измерение
2,5 dBADC_2_5db100–1250 mVВыходы датчиков около 1 V
6 dBADC_6db150–1750 mVСигналы среднего уровня
11 dB (по умолчанию)ADC_11db150–2450 mV (показания идут примерно до 3,1 V)Потенциометры и датчики 0–3,3 V
На ESP32 ADC2 использует ещё и драйвер Wi-Fi. После запуска Wi-Fi читать с пинов ADC2 (GPIO 0, 2, 4, 12, 13, 14, 15, 25, 26, 27) нельзя; ESP-IDF в этом случае возвращает ошибку таймаута (ESP_ERR_TIMEOUT), а в актуальных версиях ядра Arduino analogRead() возвращает 0. В проектах с Wi-Fi всегда подключай аналоговые датчики к пинам ADC1: GPIO 32, 33, 34, 35, 36 (VP) и 39 (VN). Из них 34–39 работают только как входы и не имеют встроенных подтягивающих резисторов, поэтому идеально подходят для аналоговых измерений. GPIO 0, 2, 12 и 15 к тому же пины конфигурации при старте (strapping), так что для аналоговых датчиков они вообще не годятся.
C++ (Arduino IDE)
1// ESP32 ADC: ham değer ve kalibre milivolt / ESP32 ADC: raw value and calibrated millivolts
2// Potansiyometre: 3V3, GND ve orta bacak GPIO 34 / Pot: 3V3, GND and wiper to GPIO 34
3const int POT_PIN = 34;      // ADC1_CH6, Wi-Fi açıkken de çalışır / ADC1_CH6, works with Wi-Fi on
4
5void setup() {
6  Serial.begin(115200);
7  analogReadResolution(12);                     // 0-4095 (varsayılan / default)
8  analogSetPinAttenuation(POT_PIN, ADC_11db);   // yaklaşık 3,1 V'a kadar / up to about 3.1 V
9}
10
11void loop() {
12  uint32_t rawSum = 0;
13  uint32_t mvSum = 0;
14  for (int i = 0; i < 16; i++) {                // 16 ölçümün ortalaması / average of 16 readings
15    rawSum += analogRead(POT_PIN);              // ham ADC değeri / raw ADC value
16    mvSum += analogReadMilliVolts(POT_PIN);     // eFuse kalibrasyonlu mV / eFuse-calibrated mV
17  }
18  float raw = rawSum / 16.0;
19  float naiveV = raw * 3.3 / 4095.0;            // basit formül / naive formula
20  float calibratedV = mvSum / 16.0 / 1000.0;    // kalibre edilmiş / calibrated
21
22  Serial.printf("Ham / Raw: %4.0f  Formul / Formula: %.3f V  Kalibre / Calibrated: %.3f V\n",
23                raw, naiveV, calibratedV);
24  delay(500);
25}

Код показывает два результата рядом: «сырое» значение, переведённое по простой формуле, и значение, откалиброванное analogReadMilliVolts(). Если поворачивать потенциометр к одному из краёв, разница заметно растёт, а в самом низу, где формула показывает 0.000 V, мультиметр всё ещё показывает около 0,1 V. analogSetPinAttenuation() меняет ослабление для одного пина, а analogSetAttenuation() — для всех пинов. ESP32 заметно шумнее Uno, особенно когда Wi-Fi передаёт данные; сама Espressif советует добавить на вход конденсатор 100 nF и усреднять несколько отсчётов, что и делает усреднение 16 измерений в коде. В более новых чипах вроде ESP32-S3 и ESP32-C3 применяется более продвинутая калибровка с аппроксимацией кривой, но номера пинов и правила для ADC2 зависят от модели, поэтому обязательно сверяйся с распиновкой своей платы (утилита распиновки). Как собрать делитель для измерения напряжений выше 3,3 V, рассказываем в руководстве о делителе напряжения.

АЦП на Raspberry Pi Pico (MicroPython)

У Raspberry Pi Pico три аналоговых входа, доступных снаружи: GP26 (ADC0), GP27 (ADC1) и GP28 (ADC2). GP29 (ADC3) измеряет треть напряжения питания VSYS на плате, а ADC4 подключён к внутреннему датчику температуры чипа. Функция read_u16() в MicroPython масштабирует 12-битный результат до диапазона 0–65535, чтобы один и тот же код работал на разных платах, но реальное разрешение по-прежнему 4096 ступеней. У RP2040 нет отдельного источника опорного напряжения: опорой служит собственное питание АЦП. На Pico это питание (ADC_VREF, пин 35) берётся с выхода импульсного стабилизатора 3,3 V (SMPS) и фильтруется через резистор около 200 Ω. Даташит платы от Raspberry Pi прямо называет три следствия: точность 3,3 V ограничена, питание немного шумит, а ток, который потребляет АЦП, создаёт смещение около 30 mV. Решения описаны в том же документе: подать HIGH на GP23, чтобы перевести стабилизатор в режим PWM с меньшими пульсациями, подключить неиспользуемый канал АЦП к AGND и вычитать его показание как смещение, а для гораздо лучшей точности поставить между ADC_VREF и AGND источник опорного напряжения 3,0 V вроде LM4040 (тогда диапазон измерения становится 0–3,0 V). Ещё известно, что у АЦП RP2040 есть небольшие скачки (ошибки DNL, дифференциальной нелинейности) на значениях 512, 1536, 2560 и 3584; усреднение нескольких измерений уменьшает их влияние. На Pico W пин GP23 отдан беспроводному чипу, а режимом стабилизатора управляют через WL_GPIO1, поэтому на Pico W удали строки с GP23 в коде ниже.

MicroPython
1from machine import ADC, Pin
2import time
3
4pot = ADC(26)                 # GP26 = ADC0 (pin 31)
5chip_temp = ADC(4)            # dahili sıcaklık sensörü / internal temperature sensor
6VREF = 3.3                    # ADC_VREF; multimetreyle ölçüp düzelt / measure and correct with a multimeter
7
8# Yalnız Pico ve Pico 2 (W değil): SMPS PWM moduna, daha az gürültü / Plain Pico and Pico 2 only (not W): SMPS to PWM mode, less noise
9smps_mode = Pin(23, Pin.OUT)
10smps_mode.value(1)
11
12
13def read_average(adc, samples=16):
14    total = 0
15    for _ in range(samples):
16        total += adc.read_u16()           # 0-65535 (12 bit ölçeklenmiş / 12-bit scaled)
17    return total / samples
18
19
20while True:
21    raw = read_average(pot)
22    volts = raw * VREF / 65535            # sayıyı volta çevir / convert the number to volts
23    t_volts = read_average(chip_temp) * VREF / 65535
24    temp_c = 27 - (t_volts - 0.706) / 0.001721    # RP2040 veri sayfası formülü / RP2040 datasheet formula
25    print("ADC: {:5d}  {:.3f} V  Yonga / Chip: {:.1f} C".format(int(raw), volts, temp_c))
26    time.sleep(0.5)

Как снизить шум АЦП

Даже при измерении постоянного напряжения значение АЦП нормально «гуляет» на одну-две ступени; свой вклад вносят квантование, пульсации питания, наводки на длинные провода, а также моторы или передача Wi-Fi поблизости. На Uno колебания ±1–2 ступени считаются нормой; гораздо больший разброс обычно указывает на проблему в схеме. Бороться с шумом можно и аппаратно, и программно:

  • Добавь конденсатор: керамический конденсатор 100 nF между аналоговым пином и GND отфильтровывает быстрые помехи и при высокоомных источниках даёт конденсатору выборки и хранения готовый заряд.
  • Делай провода короткими: если кабель датчика длинный, скрути сигнальный провод с проводом GND; провода моторов и реле прокладывай подальше от аналоговых линий.
  • Сделай правильную землю: не пускай обратный ток мощных нагрузок вроде моторов, реле и светодиодных лент через провод GND датчика; соединяй земли в одной точке (заземление звездой).
  • Используй чистое опорное напряжение: питание от USB шумное; для измерения абсолютного напряжения внутреннее опорное напряжение или внешняя микросхема источника опорного напряжения дают более стабильный результат.
  • Отбрасывай первое показание после смены канала: мультиплексор на входе АЦП может переносить заряд с предыдущего канала; когда читаешь несколько пинов по очереди, выбрасывай по одному показанию на каждом пине.
  • Усредняй: среднее из N измерений уменьшает случайный шум примерно в √N раз; 16 измерений снижают шум до четверти.
  • Передискретизация (oversampling): если сложить 4^n измерений и сдвинуть результат вправо на n бит, получится n дополнительных бит разрешения; сдвиг суммы 16 измерений на 2 бита превращает 10 бит Uno в 12. Чтобы это работало, в сигнале должен быть естественный шум хотя бы в одну ступень.
  • Медианный и экспоненциальный фильтры: медианный фильтр убирает резкие выбросы от моторов, а экспоненциальное скользящее среднее (EMA) даёт плавную кривую, почти не расходуя память.
C++ (Arduino IDE)
1// Ham, ortalama ve EMA filtreli ADC değerleri / Raw, averaged and EMA-filtered ADC values
2// Serial Plotter ile izle / Watch with the Serial Plotter
3const int SENSOR_PIN = A0;
4const float ALPHA = 0.1;     // EMA katsayısı (0-1) / EMA coefficient (0-1)
5float ema = 0;
6
7void setup() {
8  Serial.begin(115200);
9  analogRead(SENSOR_PIN);              // ilk okumayı at / discard the first reading
10  ema = analogRead(SENSOR_PIN);        // filtreyi başlat / seed the filter
11}
12
13void loop() {
14  int raw = analogRead(SENSOR_PIN);    // ham değer / raw value
15
16  long sum = 0;
17  for (int i = 0; i < 16; i++) {       // 16 ölçüm topla / sum 16 readings
18    sum += analogRead(SENSOR_PIN);
19  }
20  float average = sum / 16.0;          // ortalama (0-1023) / average (0-1023)
21
22  ema += ALPHA * (raw - ema);          // üstel hareketli ortalama / exponential moving average
23
24  Serial.print(F("ham:"));
25  Serial.print(raw);
26  Serial.print(F(" ortalama:"));
27  Serial.print(average);
28  Serial.print(F(" ema:"));
29  Serial.println(ema);
30  delay(20);
31}

Если загрузить код и открыть в Arduino IDE «Плоттер по последовательному соединению» (Serial Plotter), видны три линии: «сырое» значение дрожит, среднее из 16 измерений заметно спокойнее, а EMA — самая плавная кривая, но реагирует на изменения чуть позже. Коэффициент ALPHA выбирают от 0 до 1: 0.1 означает, что каждое новое измерение вносит в результат 10%, то есть влияют примерно последние 10 измерений; если увеличить его, реакция станет быстрее, но шум вырастет. Выбор фильтра зависит от сигнала: медленно меняющейся температуре подходит сильный EMA, а джойстику, которому нужна быстрая реакция, — короткое усреднение. Скользящее среднее, медианный фильтр и гистерезис на реальных датчиках сравниваем в руководстве по фильтрации данных датчиков.

Коротко о ЦАП: из цифры в аналог

ЦАП (цифро-аналоговый преобразователь, DAC) делает обратное АЦП: берёт число и выдаёт пропорциональное ему напряжение. Запиши 0 в 8-битный ЦАП — получишь 0 V, запиши 255 — почти напряжение питания, запиши 128 — примерно половину. Простейшие ЦАП строят на резистивной лестнице R-2R. Проясним заодно частую путаницу: analogWrite() на Arduino Uno — это не ЦАП, он выдаёт ШИМ; на самом деле пин быстро переключается между 0 V и 5 V. Для яркости светодиода и скорости мотора этого достаточно, но если действительно нужно постоянное напряжение, на выход ШИМ придётся добавить RC-фильтр нижних частот; постоянную времени фильтра можно рассчитать в утилите постоянной времени RC. Что касается плат с настоящим ЦАП: у ESP32 два 8-битных ЦАП на GPIO 25 и GPIO 26, с ними работают через dacWrite(пин, значение), а у ESP32-S3 и ESP32-C3 ЦАП нет. У Arduino Uno R4 есть 12-битный ЦАП на пине A0. Для Uno R3, Nano и Pico проще всего взять 12-битный модуль MCP4725 с подключением по I2C. С ЦАП можно генерировать звук, выдавать синусоиду или треугольный сигнал и даже подать выход ЦАП на вход АЦП, чтобы проверить АЦП известными напряжениями:

C++ (Arduino IDE)
1// ESP32 DAC ile sinüs dalgası / Sine wave with the ESP32 DAC
2// Çıkış GPIO 25'te, osiloskopla izle / Output on GPIO 25, watch it with an oscilloscope
3const int DAC_PIN = 25;      // DAC1 = GPIO 25 (DAC2 = GPIO 26)
4const int STEPS = 64;        // bir periyottaki nokta sayısı / points per period
5uint8_t sineTable[STEPS];
6
7void setup() {
8  for (int i = 0; i < STEPS; i++) {
9    sineTable[i] = 128 + 127 * sin(2 * PI * i / STEPS);  // 1-255 arası tablo / table between 1-255
10  }
11}
12
13void loop() {
14  for (int i = 0; i < STEPS; i++) {
15    dacWrite(DAC_PIN, sineTable[i]);   // 0 → 0 V, 255 → yaklaşık 3,3 V / 0 → 0 V, 255 → about 3.3 V
16    delayMicroseconds(150);            // yaklaşık 100 Hz / roughly 100 Hz
17  }
18}

Идеи проектов с АЦП

  • Цифровой вольтметр: измеряй 0–25 V через делитель напряжения и выводи результат на LCD; шаги описаны в руководстве вольтметр на Arduino.
  • Автоматический ночник: читай напряжение с фоторезистора (LDR) и включай светодиоды, когда темнеет.
  • Умный цветочный горшок: используй аналоговый выход датчика влажности почвы, чтобы включать насос, когда земля пересыхает.
  • Робот с управлением от джойстика: две оси джойстика — это два отдельных аналоговых входа; преврати их значения в скорость и направление моторов.
  • Измеритель тока: читай пропорциональное току напряжение с ACS712, чтобы видеть, сколько тока потребляет устройство.
  • Индикатор уровня звука: быстро опрашивай аналоговый выход датчика звука KY-038 и зажигай светодиодную шкалу по пиковому значению.
  • Индикатор заряда: измеряй напряжение в проекте с питанием от батареи и показывай оставшийся заряд в процентах на дисплее.

Частые ошибки и как их исправить

СимптомВероятная причинаРешение
Значение случайно скачет, хотя ничего не подключеноПин «висит в воздухе» (floating)Подключи датчик; не читай неиспользуемые пины или подтяни их к GND
Значение застывает на 1023 (на ESP32 на 4095)Вход выше опорного напряжения или выбран INTERNALНастрой опорное напряжение или ослабление, понижай высокие напряжения делителем
На ESP32 analogRead() возвращает 0 после включения Wi-FiДатчик подключён к пину ADC2Используй пины ADC1, GPIO 32–39
ESP32 показывает 0 при малых напряжениях и 4095 при большихАЦП нелинеен по краямИспользуй analogReadMilliVolts() и держи сигнал в пределах 150–2450 mV
Рассчитанное напряжение отличается от мультиметра на 3–5%Vref не равен 5,00 V (питание от USB)Измерь пин 5V и впиши значение в VREF или добавь калибровочный коэффициент
При поочерёдном чтении двух пинов значения влияют друг на другаМультиплексор и высокое сопротивление источникаОтбрасывай первое показание после переключения, держи сопротивление источника ниже 10 kΩ, добавь 100 nF
Значения скачут, когда работает мотор или релеШум на общей линии GND, просадка питанияДай нагрузкам отдельный обратный провод GND, используй конденсаторы и медианный фильтр
Показания испортились после подачи напряжения на AREFAREF использовался без вызова analogReference(EXTERNAL)Сначала выбери EXTERNAL или подключай AREF через резистор 5 kΩ
После выбора INTERNAL потенциометр доходит до 1023 на полпутиОпорное напряжение 1,1 V действует на все аналоговые пиныПеред чтением ратиометрических датчиков вернись к DEFAULT и отбрось несколько показаний
На Pico показание больше 0, даже когда вход на GNDСмещение АЦП и шум ADC_VREFПодключи один канал к AGND, измерь смещение и вычитай его; подай HIGH на GP23
Самый быстрый способ проверить схему с АЦП — измерить известное напряжение. Сначала подключи аналоговый вход к GND и убедись, что значение близко к 0. Затем возьми пальчиковую батарейку AA, напряжение которой заранее измерено мультиметром, и подключи её минус к GND, а плюс — к аналоговому пину: при примерно 1,5 V на Uno должно получиться около 307, на ESP32 с analogReadMilliVolts() — около 1500 mV, а на Pico с read_u16() — около 29 800. Если результат совпадает, код и подключение в порядке; если нет, проблема, скорее всего, в опорном напряжении или в общей земле GND.

АЦП — это мост между физическим миром и твоим кодом: когда известны его разрешение, опорное напряжение и частота дискретизации, становится понятно, что означает каждое число на экране и насколько ему можно доверять. На Uno правильно измерь опорное напряжение, на ESP32 используй пины ADC1 и analogReadMilliVolts(), на Pico учитывай смещение и шум, а потом успокой результат усреднением и фильтрами. В приложении BlueGrays есть проекты на основе аналоговых измерений, например измерение освещённости с фоторезистором, цифровой вольтметр с LCD-дисплеем, измерение температуры с NTC и управление сервоприводом и яркостью светодиода потенциометром, у каждого — пошаговые схемы подключения и код.

Частые вопросы

Что такое АЦП и для чего он нужен?

АЦП (аналого-цифровой преобразователь, ADC) — это схема, которая превращает напряжение на пине в число, которое может обработать микроконтроллер. С его помощью читают детали с аналоговым выходом: потенциометры, фоторезисторы, датчики температуры и влажности. На Arduino этот преобразователь запускает команда analogRead().

Чем аналоговый сигнал отличается от цифрового?

Аналоговый сигнал может принимать любое значение в пределах диапазона и меняется непрерывно, а цифровой состоит только из определённых уровней, на Arduino это LOW и HIGH. digitalRead() сообщает лишь, 0 на пине или 1, а analogRead() числом показывает, какое там напряжение.

Почему analogRead на Arduino возвращает значения от 0 до 1023?

АЦП в Arduino Uno 10-битный и даёт 2^10 = 1024 разные ступени, пронумерованные от 0 до 1023. Значение 0 соответствует 0 V, а 1023 — напряжению на одну ступень ниже опорного (обычно 5 V). 12-битные АЦП в ESP32 и Pico возвращают значения от 0 до 4095.

Как перевести значение АЦП в напряжение?

Используй формулу: напряжение = значение АЦП × опорное напряжение / число ступеней. На Arduino Uno это V = значение × 5,0 / 1024; на ESP32 analogReadMilliVolts() сразу даёт откалиброванные милливольты. На Pico для значения read_u16() из MicroPython используй V = значение × 3,3 / 65535.

Работают ли пины ADC2 на ESP32 вместе с Wi-Fi?

Нет. На ESP32 блок ADC2 использует ещё и драйвер Wi-Fi, поэтому при включённом Wi-Fi читать с пинов ADC2 (GPIO 0, 2, 4, 12–15, 25–27) нельзя. В проектах с Wi-Fi подключай аналоговые датчики к пинам ADC1 (GPIO 32–39).

Что такое ЦАП и чем он отличается от АЦП?

ЦАП (цифро-аналоговый преобразователь, DAC) делает обратное АЦП: превращает число в напряжение. АЦП нужен, чтобы измерять напряжение от датчика, а ЦАП — чтобы выдавать напряжение, например звук или сигнал. У Arduino Uno ЦАП нет, и analogWrite() выдаёт ШИМ; у ESP32 два 8-битных ЦАП на GPIO 25 и 26, а у Uno R4 12-битный ЦАП на A0.

Все проекты
Измерение освещённости с LDR
#11Новичок

Измерение освещённости с LDR

Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

4.6(81)
139
Цифровой вольтметр с LCD-дисплеем
#56Средний
Premium

Цифровой вольтметр с LCD-дисплеем

Узнай, какое напряжение даёт батарейка или блок питания, с помощью измерителя, собранного своими руками. Делитель из двух резисторов понижает высокое напряжение до уровня, который может прочитать плат

4.5(27)
36
Измерение освещённости модулем LDR на ESP32
#113Новичок

Измерение освещённости модулем LDR на ESP32

Наблюдение в мониторе порта в реальном времени за сырым значением ADC модуля LDR и относительным процентом освещённости, вычисленным по опорным значениям темноты и света. Этот процент — не измерение в люксах. Учит логике калибровки модуля. Идеально выполнить его перед проектом 10, потому что он нужен для определения порога.

4.6(36)
55
Регулировка яркости светодиода потенциометром на ESP32 (аналог → PWM)
#115Средний
Premium

Регулировка яркости светодиода потенциометром на ESP32 (аналог → PWM)

В этом проекте яркость светодиода плавно регулируется потенциометром. Пин АЦП ESP32 читает напряжение 0–3,3V с потенциометра, затем это значение превращается в сигнал PWM (широтно-импульсная модуляция) и подаётся на светодиод. В результате при повороте потенциометра светодиод постепенно становится ярче или тусклее. В Arduino Core 3.0+ можно сразу использовать analogWrite(); на ESP32-S2/S3 разрешение АЦП фиксировано — 12 бит.

4.5(19)
32
Измерение температуры с NTC и LCD-дисплеем 2x16
#44Новичок
Premium

Измерение температуры с NTC и LCD-дисплеем 2x16

Измерь температуру вокруг с помощью датчика, похожего на крошечную бусинку. Эта деталь, NTC, при нагреве легче пропускает электричество; Arduino читает это изменение, по формуле переводит его в градус

4.5(33)
44
Управление сервоприводом потенциометром
#36Новичок
Premium

Управление сервоприводом потенциометром

Когда ты поворачиваешь ручку потенциометра, рычаг сервопривода поворачивается вместе с ней: одно крайнее положение ручки — ноль градусов, другое — пол-оборота. Arduino всё время читает положение ручки

4.6(37)
52
Управление светодиодами джойстиком
#59Средний
Premium

Управление светодиодами джойстиком

Наклони рычажок джойстика до упора в любую сторону, и загорится светодиод этого направления: вправо, влево, вверх и вниз — четыре разных цвета. Отпусти рычажок, он вернётся в центр, и все светодиоды п

4.7(50)
67
Смеситель цветов на ESP32 (3 потенциометра + RGB-светодиод + OLED)
#138Продвинутый
Premium

Смеситель цветов на ESP32 (3 потенциометра + RGB-светодиод + OLED)

Умная лампа для смешивания цветов на ESP32: вращая три потенциометра, можно получить любой цвет! Когда крутишь потенциометры красного, зелёного и синего, RGB-светодиод с общим катодом меняет цвет в реальном времени. OLED-дисплей SSD1306 показывает значения каждого цвета в диапазоне 0–255, полосы заполнения и веб-код цвета HEX (например, #FF8040). Мини-студия цвета, которая учит теории цвета RGB, чтению АЦП, регулировке яркости через PWM и дизайну экрана I2C.

4.7(6)
10