Что такое АЦП (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 до 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 | Да → 1 | 1000 |
| 2 | Бит 2 (вес 4) | 12 / 16 × 5 = 3,750 V | Нет → 0 | 1000 |
| 3 | Бит 1 (вес 2) | 10 / 16 × 5 = 3,125 V | Да → 1 | 1010 |
| 4 | Бит 0 (вес 1) | 11 / 16 × 5 = 3,4375 V | Нет → 0 | 1010 |
| Результат | Все биты пройдены | 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 R3 | ESP32 (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 на ADC2 | 3 пина (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 mV | 0,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–1023 | V = значение × 5,0 / 1024 | 768 → 3,75 V |
| Uno, analogReference(INTERNAL) | 0–1023 | V = значение × 1,1 / 1024 | 512 → 0,55 V |
| ESP32 (11 dB) | 0–4095 | С калибровкой: analogReadMilliVolts() / 1000; грубо: значение × 3,3 / 4095 | 1650 mV → 1,65 V |
| Raspberry Pi Pico (read_u16) | 0–65535 | V = значение × 3,3 / 65535 | 32768 → 1,65 V |
| ESP8266 NodeMCU / Wemos D1 Mini (A0) | 0–1023 | V = значение × 3,2 / 1024 | 512 → 1,60 V |
Подключение потенциометра: Arduino, ESP32 и Pico
| Вывод потенциометра | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Крайний вывод 1 | 5V | 3V3 | 3V3(OUT) (пин 36) |
| Средний вывод (сигнал) | A0 | GPIO 34 (ADC1_CH6) | GP26 / ADC0 (пин 31) |
| Крайний вывод 2 | GND | GND | AGND (пин 33) или GND |
| Конденсатор от шума (по желанию) | 100 nF между A0 и GND | 100 nF между GPIO 34 и GND | 100 nF между GP26 и AGND |
Пошагово: первое измерение АЦП на Arduino
- Собери схему: подключи крайние выводы потенциометра на 10 kΩ к 5V и GND, а средний вывод — к A0.
- Измерь пин 5V: измерь мультиметром напряжение между 5V и GND и впиши его в константу VREF в коде (например, 4.93).
- Загрузи код: открой скетч ниже в Arduino IDE, выбери плату и порт и загрузи его.
- Открой монитор порта: выставь скорость 9600 бод и смотри, как значение АЦП меняется от 0 до 1023, когда поворачиваешь ручку (подробности в руководстве по монитору порта).
- Проверь: измерь мультиметром напряжение между A0 и GND и сравни с напряжением на экране; разница должна быть в пределах 10–20 mV.
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, так что отбрасывать пару показаний после переключения — хорошая привычка. К тому же настройка опорного напряжения действует сразу на все аналоговые пины платы. Варианты зависят от платы:
| Вариант | Опорное напряжение | Платы | Когда использовать |
|---|---|---|---|
| DEFAULT | 5 V (3,3 V на платах с питанием 3,3 V) | Uno, Nano, Mega, Leonardo | Датчики, ратиометрические к питанию: потенциометры, фоторезисторы, джойстики |
| INTERNAL | 1,1 V (ATmega328P); 2,56 V на Leonardo | Uno, Nano, Leonardo (на Mega нет) | Датчики с низким напряжением, например LM35 |
| INTERNAL1V1 / INTERNAL2V56 | 1,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 |
Частота дискретизации: сколько измерений в секунду делает АЦП?
Частота дискретизации — это сколько измерений АЦП может сделать за одну секунду. На 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 dB | ADC_0db | 100–950 mV | Слабые сигналы, самое точное измерение |
| 2,5 dB | ADC_2_5db | 100–1250 mV | Выходы датчиков около 1 V |
| 6 dB | ADC_6db | 150–1750 mV | Сигналы среднего уровня |
| 11 dB (по умолчанию) | ADC_11db | 150–2450 mV (показания идут примерно до 3,1 V) | Потенциометры и датчики 0–3,3 V |
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 в коде ниже.
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) даёт плавную кривую, почти не расходуя память.
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. С ЦАП можно генерировать звук, выдавать синусоиду или треугольный сигнал и даже подать выход ЦАП на вход АЦП, чтобы проверить АЦП известными напряжениями:
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, используй конденсаторы и медианный фильтр |
| Показания испортились после подачи напряжения на AREF | AREF использовался без вызова analogReference(EXTERNAL) | Сначала выбери EXTERNAL или подключай AREF через резистор 5 kΩ |
| После выбора INTERNAL потенциометр доходит до 1023 на полпути | Опорное напряжение 1,1 V действует на все аналоговые пины | Перед чтением ратиометрических датчиков вернись к DEFAULT и отбрось несколько показаний |
| На Pico показание больше 0, даже когда вход на GND | Смещение АЦП и шум ADC_VREF | Подключи один канал к AGND, измерь смещение и вычитай его; подай HIGH на GP23 |
АЦП — это мост между физическим миром и твоим кодом: когда известны его разрешение, опорное напряжение и частота дискретизации, становится понятно, что означает каждое число на экране и насколько ему можно доверять. На 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
Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

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

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

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

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

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

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

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

