Мій HAL знає, як працювати з GPIO, UART, I2C, Timer. І ой лихо — не знає, як працювати з ADC. Помітив я це аж зараз, готуючись до TDOA-мікрофонів, коли виявилось, що читати аналоговий сигнал у поточному HAL просто нічим.
Можливо, саме так мала б початись ця стаття. Але почнемо з іншого.
ADC — це та єдина периферія в цій серії, де баг неможливо відрізнити від просадки напруги, поки не перевіриш обидва варіанти. GPIO, UART, Timer живуть майже повністю в цифровому світі — вони прощають дрібні апаратні неточності, бо там усього два стани, 0 і 1, з великим запасом між ними. ADC не прощає і якщо в тебе просадка напруги, твої датчики будуть лагати, а код при цьому виглядатиме абсолютно правильним. Можливо не доречно та думаю саме тут час зазначити, те, що Важливо розуміти різницю між конденсатором, який тимчасово згладжує просадку напруги, і котушкою індуктивності, яка чинить опір зміні струму.
💡 Підвищити струм і підвищити напругу — це дві різні задачі з різною фізикою, і плутати їх не варто. Конденсатор чинить опір зміні напруги (тому й згладжує просадки) — той самий принцип у промисловому масштабі: коли електронасос запускається, пусковий струм двигуна може в 5-7 разів перевищувати номінальний (inrush current), і без буфера це просаджує напругу на всій лінії. Банк конденсаторів біля насосної станції бере цей короткочасний пік струму зі своїх запасів заряду, поки основне джерело “наздоганяє” — і напруга на шині не провалюється. Котушка чинить опір зміні струму — і саме ця властивість, доведена до крайності, дає ефект підвищення напруги: якщо різко перервати струм через котушку, магнітне поле колапсує й видає короткий викид напруги, який може в рази перевищувати вхідну. Це її пряме застосування V = L·(di/dt) — чим різкіше змінюється струм, тим вищий викид.
Класична саморобна схема на 3.7В→12В (BD140 + BC547 у петлі зворотного зв’язку, без жодної ШІМ-мікросхеми) — це буквально навмисна експлуатація цього ефекту: транзистори по черзі різко замикають і розмикають струм через котушку, кожен цикл генерує викид напруги, конденсатор 470мкФ згладжує ці викиди в більш-менш рівну лінію, а зенер-діод на 12В просто “зрізає” все, що вище — так і виходить стабільний вихід.
Популярна в ардуїно середовищі плата MT3608 працює за тим самим фізичним принципом (котушка + ключ + випрямляч + конденсатор), але з двома ключовими доповненнями: підстроювальний резистор ділить вихідну напругу й подає це значення назад на чіп, який на основі цього постійно підлаштовує тривалість імпульсів ШІМ, утримуючи вихід стабільним навіть при зміні навантаження — а замість звичайного діода стоїть швидкий діод Шотткі з малим падінням напруги. Саморобна схема просто вільно коливається на власній частоті без жодного зворотного зв’язку — тому й вихідна напруга помітно “пливе” зі зміною навантаження чи вхідної напруги, а MT3608 компенсує це в реальному часі.
Той самий принцип працює і в зовсім іншій задачі — генерації звуку. Схема на одному BC548 з 10мкГн котушкою й 3-вивідним п’єзиком — це той самий блокинг-генератор, тільки зворотний зв’язок тут іде не через другий транзистор, а через сам п’єзоелемент: третій пін п’єзика — окремий feedback-електрод. Коли транзистор різко закриває струм через котушку, виникає той самий викид напруги, він “б’є” по п’єзику через основний електрод — пластина клацає й видає звук на власній резонансній частоті, а її механічний рух через третій пін повертає невеликий сигнал назад на базу через резистор 150к, підтримуючи коливання. Одна й та сама фізика (V = L·di/dt, різкий обрив струму через котушку) — в одному випадку піднімає напругу до стабільних 12В, в іншому породжує звук.
Мені дуже хочеться— максимально розкрити ADC як тему саму по собі, для новачків, з нуля, на прикладах Arduino, якими простіше експериментувати без прошивки й дебагера. І лише в самому кінці ми повернемось до STM32 register-level hal_adc, як підготовку до фіналу серії.
◆ Аналоговий світ проти цифрового
Наш Всесвіт — аналоговий. Звук, світло, температура, тиск, напруга на акумуляторі змінюються плавно й мають нескінченну кількість значень.
Комп’ютерні процесори — цифрові. Вони живуть у світі жорсткої дискретності, де існують лише два стани: 0 (немає струму) і 1 (є струм).
Спробуй відповісти: яке значення має світло в кімнаті прямо зараз? Не “увімкнене чи вимкнене” — а наскільки світло. Чіткої відповіді “0 або 1” тут немає. Цифрові піни вміють розрізняти тільки два стани. Щоб плата “побачила” плавну зміну, потрібен спеціальний перекладач — аналого-цифровий перетворювач (ADC / АЦП, Analog-to-Digital Converter). Його завдання — перетворити аналогову напругу в двійковий числовий код.
Почнімо з самого початку.
◆ Компаратор — серце ADC
Неможливо миттєво перетворити напругу на складне число. Але можна запитати: «Ця напруга більша чи менша за наш еталон?» Для цього використовують компаратор (наприклад, мікросхему LM393).
У компаратора є два входи та один цифровий вихід:
- IN (плюс-вхід) — напруга, яку ми досліджуємо
- DAC (мінус-вхід) — еталонна (опорна) напруга, з якою порівнюємо
Закон компаратора:
- якщо
V_IN > V_DAC→ вихід = 1 (High) - якщо
V_IN < V_DAC→ вихід = 0 (Low)
Ключова думка: компаратор — це вже ADC! Але надпримітивний — усього на 1 біт. Він дає лише відповідь «так» або «ні». Щоб отримати точне багатобітне число, потрібна логіка, яка змусить компаратор працювати багато разів підряд, щоразу звужуючи здогадку.
◆ Звідки беруться числа (квантування)

Щоб перетворити напругу в число, ADC розбиває весь діапазон допустимої напруги на дрібні сходинки. Цей процес називається квантуванням, а номер сходинки — це і є наше цифрове число.

Скільки буде сходинок, залежить від розрядності (кількості бітів):
- 4-бітний ADC (наш навчальний стенд): 2⁴ = 16 значень (діапазон 0–15)
- 10-бітний ADC (вбудований в Arduino Uno): 2¹⁰ = 1024 значення (діапазон 0–1023)
Математика сходинок (опорна напруга V_REF = 5.0 В):
- на 4-бітному стенді кожна сходинка має ширину 5В / 16 = 0.3125 В
- на Arduino Uno (10 біт) кожна сходинка дорівнює 5В / 1024 ≈ 4.88 мВ
Коли викликаєш функцію analogRead(), Arduino виконує формулу пропорції:
|
1 2 |
Результат ADC = round(V_IN × 1024 / V_REF) |
Приклад: подали на Arduino 3.3В. Рахуємо: (3.3 × 1024) / 5.0 = 675.84. Округляємо й отримуємо число 675. Це просто номер сходинки.

◆ Чому бінарний пошук, а не лінійний перебір
Як мікроконтролер дізнається, на якій саме сходинці перебуває напруга? “Сортування бульбашкою” тут не підходить — сортувати нема чого. Простір значень (0, 1, 2… 15) уже ідеально відсортований самою математикою. Нам потрібно не сортувати масив, а знайти одну конкретну точку у вже впорядкованому просторі і це задача пошуку.
Шлях 1. Лінійний перебір (ADC лічильного типу). Контролер починає з нуля й по черзі перевіряє кожну сходинку: 0000, 0001, 0010… Якщо на вході максимальна напруга, для 4-бітного ADC доведеться зробити 15 кроків. Для 10-бітного ADC Arduino — 1023 кроки! Занадто повільно.
Шлях 2. Бінарний пошук (ADC послідовного наближення / SAR). Замість послідовного перебору застосовуємо алгоритм SAR (Successive Approximation Register) — той самий принцип, що й гра «вгадай число від 1 до 1000»: «більше за 500? Так. Більше за 750? Ні. Більше за 625?..». Ми ділимо простір пошуку навпіл на кожному кроці, зважуючи кожен біт окремо — від найстаршого (MSB) до наймолодшого (LSB). Кількість кроків завжди дорівнює розрядності ADC: для 4 біт — завжди 4 кроки, для 10 біт в Arduino — завжди 10 кроків, незалежно від того, яка напруга на вході. Це працює блискавично.
◆ Анатомія стенду та матриця R-2R
Щоб реалізувати бінарний пошук, контролеру потрібен інструмент, який видає еталонну напругу для компаратора на кожному кроці. Цей інструмент — ЦАП (Цифро-аналоговий перетворювач), зібраний на пасивній матриці резисторів R-2R.
Графічна архітектура стенду:

Ключове питання, яке варто задати собі самому: звідки контролер знає, яку напругу він видає через матрицю?
Він її не знає й не вимірює! Контролер керує лише цифровим кодом на своїх ніжках. Ніжка в HIGH — це строго напруга живлення (наприклад, 5.0В), в LOW — строго 0В. Матриця R-2R — пасивний математичний калькулятор: завдяки закону Ома вона перерозподіляє струми так, що кожен біт додає в підсумкову напругу свою фіксовану “вагу”:
- D3 (MSB) — половина шкали → 2.5В
- D2 — чверть шкали → 1.25В
- D1 — одна восьма → 0.625В
- D0 (LSB) — одна шістнадцята → 0.3125В
Код 1010 змусить матрицю видати: 2.5В + 0.625В = 3.125В.
Схема матриці R-2R (4 біти): резистори R = 10 кОм, тоді 2R = 20 кОм (або два по 10 кОм послідовно).

⚠ Світлодіоди індикації підключаються паралельно виходам D3-D0 обов’язково через власні струмообмежувальні резистори ~220 Ом на GND — інакше ризикуєш перевищити безпечний струм піна.
🔧 Спробуй сам, без паяльника
Перш ніж збирати це на макетці, є сенс покрутити схему руками в браузері — обидва інструменти безкоштовні й не потребують реєстрації для перегляду:
- Tinkercad — готова схема цього стенду — можна одразу натискати кнопку NEXT і дивитись, як заповнюється двійковий код
- Falstad Circuit Simulator — універсальний симулятор, де можна зібрати R-2R матрицю з нуля резистор за резистором і побачити напругу в реальному часі просто наведенням курсора на будь-яку точку схеми
◆ Покроковий розбір алгоритму наживо
V_IN = 3.3В, максимум системи — 5В. Натискаємо кнопку NEXT для кожного кроку.
Крок 1 — біт D3 (гіпотеза 1000): матриця видає 2.5В. Компаратор: 3.3В > 2.5В? Так. Залишаємо D3=1.
Крок 2 — біт D2 (гіпотеза 1100): матриця видає 2.5+1.25=3.75В. Компаратор: 3.3В > 3.75В? Ні. Скидаємо D2=0.
Крок 3 — біт D1 (гіпотеза 1010): матриця видає 2.5+0.625=3.125В. Компаратор: 3.3В > 3.125В? Так. Залишаємо D1=1.
Крок 4 — біт D0 (гіпотеза 1011): матриця видає 3.125+0.3125=3.4375В. Компаратор: 3.3В > 3.4375В? Ні. Скидаємо D0=0.
Результат: двійковий код 1010 = десяткове 10. За 4 кроки перетворили аналогові 3.3В у цифру 10. Саме так analogRead() в Arduino формує число за лічені мікросекунди — тільки повторює цей процес не 4, а 10 разів (для 10-бітного ADC).
Шпаргалка (4 біти, V_REF = 5.0В):
| Двійковий код | Десяткове | V_DAC, В |
|---|---|---|
| 0000 | 0 | 0.0000 |
| 0001 | 1 | 0.3125 |
| 0010 | 2 | 0.6250 |
| 0011 | 3 | 0.9375 |
| 0100 | 4 | 1.2500 |
| 0101 | 5 | 1.5625 |
| 0110 | 6 | 1.8750 |
| 0111 | 7 | 2.1875 |
| 1000 | 8 | 2.5000 (половина шкали) |
| 1001 | 9 | 2.8125 |
| 1010 | 10 | 3.1250 |
| 1011 | 11 | 3.4375 |
| 1100 | 12 | 3.7500 |
| 1101 | 13 | 4.0625 |
| 1110 | 14 | 4.3750 |
| 1111 | 15 | 4.6875 (максимум ЦАП) |
Якщо збереш стенд фізично — звір ці “ідеальні” значення з реальними показами мультиметра. На практиці реальні резистори мають допуски, тому виміряні цифри трохи “гуляють” навколо теоретичних.
◆ Головний висновок: контролер “вірить” в опорну напругу
Контролер живе в умовному світі: він знає, що максимальний код (1111 для 4 біт) відповідає майже повній напрузі живлення. Але якщо ця напруга живлення раптом просяде — з 5.0В до, скажімо, 4.0В — матриця на коді 1000 видасть вже не 2.5В, а 2.0В. Контролер про це ніяк не дізнається. Він так само успішно завершить SAR-алгоритм і видасть код, який буде математично коректним, але фізично помилковим відносно реальних вольтів.

Контролер керує лише цифровим кодом. Перетворення цього коду в точну фізичну напругу повністю лежить на совісті резисторів R-2R і стабільності живлення. Матриця це пасивний математичний калькулятор, що перераховує нулі й одиниці в мілівольти за допомогою закону Ома.
◆ Демонстрація: сонячна панель, яка “чує” світло
Сьогодні замість звичного фоторезистора спробуємо дещо цікавіше — маленьку сонячну панель. На відміну від фоторезистора (який просто змінює опір і потребує окремого живлення через дільник), сонячна панель сама генерує напругу від світла — той самий фізичний принцип, на якому працюють панелі на дахах будинків, тільки в мініатюрі.

Підключення:
|
1 2 3 |
Плюс панелі → GPIO34 (аналоговий вхід ADC1 на ESP32) Мінус панелі → GND |
Код:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#define PANEL_PIN 34 void setup() { Serial.begin(115200); } void loop() { int light = analogRead(PANEL_PIN); // число від 0 до 4095 Serial.println(light); delay(50); } |
Відкрий Serial Monitor чи ще краще Serial Plotter (Tools → Serial Plotter) — і посвіти на панель ліхтариком телефона. Побачиш, як число живо реагує на світло в реальному часі.
◆ А тепер — почуємо світло
Додамо звук, який змінюється разом зі світлом — точнісінько як у справжньому теремін-інструменті:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#define PANEL_PIN 34 #define BUZZER_PIN 25 void setup() { Serial.begin(115200); } void loop() { int light = analogRead(PANEL_PIN); int freq = map(light, 0, 4095, 200, 2000); // світло -> висота тону tone(BUZZER_PIN, freq); Serial.println(light); delay(20); } |
Плавно затуляй і відкривай панель рукою — звук “пливе” вгору-вниз услід за світлом. Це і є суть ADC: плата перетворює те, що ми бачимо оком, у число, а з числом уже можна робити що завгодно — хоч звук, хоч керування іншим пристроєм.
💡 Цікавий факт наостанок: існує старовинний ардуїнівський дослід — передати голос лазерним променем без жодного проводу. Мікрофон перетворює звук на коливання яскравості лазера, а сонячна панель на іншому кінці кімнати вловлює ці коливання світла назад у звук. Той самий принцип, що ми розглядали, тільки на відстані через кімнату.
◆ Родина ADC — чому саме SAR переміг у мікроконтролерах
Весь цей час ми говорили про ADC так, ніби це один тип пристрою. Насправді SAR (successive approximation) — лише одна з кількох конкуруючих архітектур, і вона перемогла нішу мікроконтролерів через конкретний набір інженерних компромісів. П’ять основних архітектур: Flash (паралельна), SAR, Pipeline (конвеєрна), Sigma-Delta (Σ-Δ) і Dual-slope (інтегруюча).
Flash ADC — найшвидша, найдорожча. Замість одного компаратора, що працює багато разів (як у SAR), Flash ADC ставить цілу батарею компараторів одночасно — для N-бітного результату потрібно 2ᴺ−1 компараторів, кожен порівнює вхідний сигнал зі своїм власним рівнем напруги. Результат готовий за один такт. Швидкість — найвища серед усіх архітектур (гігагерцові частоти дискретизації). Ціна — кількість компараторів подвоюється з кожним доданим бітом розрядності: 8-бітний Flash ADC — це вже 255 компараторів. Використовують в осцилографах, аналізі радіочастот.
SAR ADC — компроміс, який виграв мікроконтролери. Той самий бінарний пошук з R-2R стенду вище: один компаратор, один внутрішній ЦАП, результат формується біт за бітом за N кроків для N-бітної розрядності. Швидкість середня (сотні kSPS — кілька MSPS). Розрядність типово 8-16 біт, площа кристала зростає приблизно лінійно — на відміну від вибухового росту у Flash. Ключова перевага: нульова конвеєрна затримка, ідеально для мультиплексованого читання багатьох сенсорних пінів по черзі (саме так влаштований ADC1 на Uno, що обслуговує всі A0-A5 через один фізичний перетворювач).
Pipeline ADC — SAR “на конвеєрі”. Розбиває процес на кілька послідовних каскадів, кожен визначає кілька біт і передає “залишок” наступному — як конвеєр на заводі. Значно вища швидкість за SAR (десятки-сотні MSPS) при високій розрядності (12-16 біт), але з’являється конвеєрна затримка. Використовують у відеооцифруванні, швидких каналах зв’язку.
Sigma-Delta (Σ-Δ) — повільна, але надзвичайно точна. Замість одного точного виміру ADC семплить сигнал у сотні-тисячі разів швидше, ніж потрібно (oversampling), кожен раз грубо — а цифровий фільтр усереднює цей потік у один точний багатобітний результат. Розрядність найвища серед усіх (16-24 біти). Використовують у звуці, вагах і тензодатчиках (той самий легендарний HX711 — це 24-бітний Σ-Δ чип), вимірюванні електроенергії.


Dual-slope (інтегруючий) — найточніший ворог мережевого шуму. Замість порівняння з сходами напруг ADC заряджає конденсатор вхідним сигналом протягом фіксованого часу, а потім розряджає його відомим еталонним струмом, вимірюючи час розряду. Якщо час інтегрування підібрати кратним періоду мережевого шуму (наприклад, рівно 20мс для придушення 50Гц наведення), архітектура природно прибирає цей шум без жодного програмного фільтра. Дуже повільна. Використовують у класичних цифрових мультиметрах.
Порівняльна таблиця:
| Архітектура | Швидкість | Розрядність | Складність росте | Типове застосування |
|---|---|---|---|---|
| Flash | Найвища (GSPS) | Низька (6-8 біт) | Експоненційно з бітами | Осцилографи, RF |
| SAR | Середня (kSPS-MSPS) | Середня (8-16 біт) | Приблизно лінійно | Мікроконтролери, сенсори |
| Pipeline | Висока (MSPS-сотні MSPS) | Висока (12-16 біт) | Помірно, є затримка конвеєра | Відео, швидкі канали зв’язку |
| Sigma-Delta | Низька | Найвища (16-24 біт) | Складність у цифровому фільтрі | Аудіо, ваги, точні сенсори |
| Dual-slope | Найнижча | Висока, дуже стабільна | Проста аналогова частина | Мультиметри, лабораторні прилади |
Чому саме SAR переміг у мікроконтролерах:
- Нульова конвеєрна затримка — критично, коли один фізичний ADC по черзі обслуговує кілька пінів через мультиплексор
- Помірне зростання складності з розрядністю — лінійно, а не експоненційно як у Flash
- Споживання енергії масштабується з частотою вимірів — SAR “спить” між замірами, ідеально для пристроїв на батарейках
- Просто лягає на стандартний CMOS-техпроцес — та сама технологія, з якої зроблений увесь інший цифровий чип
- “Достатньої” точності майже завжди вистачає — 10-12 біт покривають переважну більшість хобі- та промислових задач; коли потрібна вища точність (звук, ваги), на плату просто ставлять окремий зовнішній Σ-Δ чип
Практичний висновок — як обирати архітектуру під задачу:
- Загальне вимірювання сенсорів → вбудований SAR цілком достатній
- Висока точність повільного сигналу (вага, звук, точна температура) → зовнішній Σ-Δ чип
- Швидкість понад можливості SAR (осцилограф, RF) → Flash для екстремальної швидкості, Pipeline — золота середина
- Максимальна точність при мінімальній швидкості, стійкість до мережевого шуму → Dual-slope
◆ З компаратором vs без компаратора — два способи “вирішити” на основі світла
Пряме продовження теорії SAR: “компаратор — це вже ADC, тільки на 1 біт, він відповідає лише так/ні”. Переробимо обидва варіанти і порівняємо, що ця фраза означає на практиці.
Задача одна й та сама для обох модулів: LED вмикається, коли стає темно. Спосіб вирішення — принципово різний.
Модуль А — з апаратним компаратором (2N2222A, без жодного коду)
Це не мікросхема-компаратор на кшталт LM393 — а її найпростіший, “саморобний” аналог на одному транзисторі. Рішення “світло чи темрява” тут приймає сама схема, фізично, без мікроконтролера взагалі.
Компоненти: фоторезистор (LDR), транзистор 2N2222A (NPN), резистор 10 кОм (верхнє плече дільника), резистор 220 Ом (обмеження струму LED), LED.

Як це працює, крок за кроком:
- Резистор R1 і фоторезистор утворюють дільник напруги. Напруга в точці між ними подається прямо на базу транзистора.
- При яскравому світлі опір LDR низький → в точці дільника низька напруга → транзистор закритий → LED не світиться.
- У темряві опір LDR зростає → напруга в точці дільника зростає → щойно вона перевищує поріг відкриття транзистора (приблизно 0.6-0.7В між базою й емітером) → транзистор відкривається → струм тече через LED → LED світиться.
⚠ різниця з “справжнім” компаратором (LM393): транзисторна схема має м’який, поступовий перехід — LED починає ледь помітно засвічуватись задовго до “повного” порогу і яскравішає поступово, а не перемикається різко. Справжня мікросхема-компаратор має набагато різкіший перехід завдяки внутрішньому підсиленню операційного підсилювача.
Модуль Б — без компаратора, рішення через ADC і код
Той самий фоторезистор, той самий LED — але тепер рішення приймає не залізо, а програма, після того як ADC оцифрував сигнал.

|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#define LDR_PIN 34 #define LED_PIN 25 #define THRESHOLD 2000 // поріг, який можна міняти будь-коли — на відміну від Модуля А void setup() { pinMode(LED_PIN, OUTPUT); Serial.begin(115200); } void loop() { int light = analogRead(LDR_PIN); if (light > THRESHOLD) { digitalWrite(LED_PIN, HIGH); } else { digitalWrite(LED_PIN, LOW); } Serial.println(light); delay(20); } |
Тут ADC перетворює аналоговий сигнал у число (0-4095), і рішення “світло чи темрява” ухвалює звичайний оператор if у коді.
Додатково — Модуль В: той самий транзистор, але сенсор — палець
Той самий 2N2222A з Модуля А можна перетворити на дотиковий вимикач, просто прибравши LDR і замінивши його пальцем на базі. Це реальна, задокументована схема — найпростіший варіант “touch switch” на одному NPN-транзисторі.

Реальний механізм: тіло людини має власну електричну ємність відносно землі (body capacitance). Коли торкаєшся бази, ця ємність утворює шлях для крихітного струму — і завдяки високому коефіцієнту підсилення транзистора (β/hFE, часто в сотні разів) навіть мікроамперний струм бази перетворюється на цілком помітний струм через LED.
Джерело: RG Electrics — Simple Touch Switch Circuit using BC547 Transistor — та сама схема, тільки на BC547 замість 2N2222A.
Роль конденсатора: плавне згасання LED після того, як прибираєш палець — заряд поступово “стікає” через конденсатор, а не зникає миттєво.
⚠ Уточнення щодо наведення мережі 50 Гц це не основний механізм, а побічний, небажаний ефект. У промислових версіях цієї схеми (джерело: ElecCircuit — 8 simple touch switch circuit projects) навпаки спеціально додають маленькі конденсатори (0.01-0.22 мкФ), щоб прибрати наведення з повітря, яке проходить через палець під час дотику. Основний сигнал — саме ємність тіла, а не 50 Гц фон.
Пряме порівняння:
| Модуль А (транзистор) | Модуль Б (ADC + код) | |
|---|---|---|
| Потрібен мікроконтролер? | Ні, взагалі | Так |
| Швидкість реакції | Миттєва, фізична | Обмежена швидкістю analogRead() і циклу loop() |
| Гнучкість порогу | Тільки заміною резистора | Змінюється в коді за секунду, навіть на льоту |
| Різкість перемикання | М’яка, поступова | Чітка, як задано в коді |
| Можна додати складнішу логіку | Ні | Так, будь-яка складність |
| Енергоспоживання в очікуванні | Мінімальне (пасивна схема) | Мікроконтролер повинен постійно працювати |
| Вартість | Копійки, 4 компоненти | Потрібна ціла плата з мікроконтролером |
◆ Опорна напруга на п’яти популярних платформах
Класика: Arduino Uno та Mega
10-бітний ADC (0-1023). Опорна напруга обирається через analogReference().
Uno: DEFAULT (напруга живлення, зазвичай 5V), INTERNAL (вбудоване стабільне джерело 1.1V), EXTERNAL (зовнішній сигнал на пін AREF, 0-5V).
Mega 2560: окрім DEFAULT і EXTERNAL, має два внутрішніх джерела — INTERNAL1V1 (1.1V) і INTERNAL2V56 (2.56V).
|
1 2 3 4 |
void setup() { analogReference(INTERNAL); // Uno: 1.1V. Для Mega — INTERNAL1V1 або INTERNAL2V56 } |
⚠ Якщо використовуєш
EXTERNAL, обов’язково викликай налаштування до першогоanalogRead(). Ще важливіший нюанс: подача зовнішньої напруги на AREF одночасно з активним внутрішнім референсом — це фактично коротке замикання джерел всередині чипа, і навіть із захисним резистором це створює задокументовану похибку через дільник напруги з внутрішнім ~32 кОм опором AREF-піна.
Малюк ATtiny13
8 ніжок, з них 6 програмованих (PB0-PB5). У ATtiny13 фізично немає виведеного піна AREF за офіційним даташитом: жоден з 6 I/O-пінів не має альтернативної функції AREF. Доступні тільки DEFAULT (напруга живлення) чи INTERNAL (вбудовані 1.1V).
Особливість: якщо ввімкнено режим 1.1V, будь-яка напруга на вході вища за цей рівень сприйматиметься як максимум (1023). Для вимірювання вищих напруг обов’язково потрібен зовнішній резистивний дільник.
Сучасний ESP32
Архітектура кардинально інша. ADC 12-бітний (0-4095), а внутрішня опорна напруга фіксована на рівні приблизно 1.1V (за специфікацією Espressif — реально гуляє в діапазоні 1000-1200 мВ між різними екземплярами чипа).
Щоб вимірювати сигнали аж до 3.3V, замість зміни самого референсу використовується атенюатор (послаблювач сигналу) через analogSetAttenuation():
ADC_ATTEN_DB_0— діапазон 0-1.1VADC_ATTEN_DB_12(в старіших версіях ESP-IDF позначавсяDB_11) — діапазон приблизно 0-3.3V, типовий робочий режим
⚠ Точна назва константи атенюації змінювалась між версіями ESP-IDF (
DB_11→DB_12) — перед використанням у власному проєкті варто звірити з актуальною документацією версії ядра.
Мінус ESP32: помітна нелінійність ADC, особливо біля 0V і 3.3V. Плюс — кожен чип має заводське калібрування, тому функція analogReadMilliVolts() автоматично враховує це калібрування й повертає результат одразу в мілівольтах, без ручних розрахунків.
Професійний STM32 Blue Pill (F103C8T6)
Тут принципово інша філософія: лінія опорної напруги апаратно з’єднана з живленням 3.3V і програмно змінити її неможливо (на відміну від AVR чи ESP32). ADC 12-бітний (0-4095).
Проблема: якщо живлення від USB нестабільне (просідає, скажімо, до 3.2V), виміри стають хибними — той самий принцип “контролер вірить в опорну напругу”, який ми розбирали на R-2R стенді.
Рішення: всередині STM32F103 є прихований еталонний канал VREFINT, з типовим значенням близько 1.20V (за даташитом реальна розбіжність 1.16-1.24V між екземплярами чипа). Читаючи цей внутрішній канал, можна порахувати, яка напруга живлення насправді, і скоригувати всі інші виміри відповідно:
|
1 2 3 4 |
// Принцип корекції через VREFINT (спрощено, за офіційним прикладом stm32duino) uint16_t vrefint_raw = analogRead(AVREF); // читаємо внутрішній канал uint32_t actual_vref_mV = (1200UL * 4095) / vrefint_raw; // рахуємо реальну Vref |
⚠ На відміну від ESP32, де
analogReadMilliVolts()— єдина стабільна вбудована функція в офіційному ядрі, у STM32duino ситуація трохи менш однорідна: офіційні приклади (репозиторій stm32duino/STM32Examples) показують саме такий підхід через зчитування внутрішнього каналу VREFINT і власний розрахунок.
Зведена таблиця:
| Платформа | Розрядність ADC | Макс. значення | Внутрішні опорні рівні | Зовнішній AREF |
|---|---|---|---|---|
| Arduino Uno | 10 біт | 1023 | 1.1V | Так |
| Arduino Mega | 10 біт | 1023 | 1.1V та 2.56V | Так |
| ATtiny13 | 10 біт | 1023 | 1.1V | Ні (немає піна) |
| ESP32 | 12 біт | 4095 | ~1.1V (+ атенюатори) | Ні |
| STM32 Blue Pill | 12 біт | 4095 | Фіксована 3.3V (+ внутр. канал VREFINT ~1.20V) | Ні |
◆ Register-level: власний hal_adc для STM32
Тепер, коли ADC розібраний на рівні фізики й арифметики, — фінальний крок цього тижня: підготовка для TDOA-мікрофонів.
Що таке ADC1 на STM32F103: 12-бітний, до 14 MHz clock, регулярна група (regular group) каналів. У Blue Pill доступні канали 0-9 — це PA0-PA7 і PB0-PB1.
Три речі, які HAL типу Cube ховає, а тут доводиться зробити:
- ADCCLK prescaler. ADC живиться не напряму від системної шини, а через дільник від PCLK2 (
RCC_CFGRбіти ADCPRE). Ліміт — 14 MHz. При 8 MHz HSI і дільнику /2 отримуємо 4 MHz, що в межах ліміту. Якщо пізніше піднімати частоту через PLL до 72 MHz — дільник треба міняти на /6 чи /8, інакше вихід за специфікацію дасть неточність. - Калібрування. RM0008 прямо вимагає калібрування після кожного ввімкнення ADC — інакше значення будуть зміщені систематично. Це двокроковий процес:
RSTCALскидає калібрувальні регістри, потімCALзапускає саму процедуру, обидва — з очікуванням на самоскидання біта. - Час стабілізації (Tstab). Між виходом з power-down (
ADON) і початком калібрування потрібна коротка пауза. Якщо пропустити то перша конверсія піде зі сміттям.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void adc_init(void) { RCC_APB2ENR |= RCC_ADC1EN; RCC_CFGR &= ~(0x3 << 14); // ADCPRE = /2 ADC1_CR2 = 0; ADC1_CR2 |= CR2_ADON; adc_stab_delay(); ADC1_CR2 |= CR2_RSTCAL; while (ADC1_CR2 & CR2_RSTCAL); ADC1_CR2 |= CR2_CAL; while (ADC1_CR2 & CR2_CAL); ADC1_CR2 |= CR2_EXTSEL_SWSTART; ADC1_SQR1 &= ~(0xF << 20); // довжина послідовності = 1 } |
EXTSEL = SWSTART (111) означає, що конверсія стартує програмно записом біта SWSTART, а не апаратним тригером від таймера — для одиночних вимірів цього достатньо, dual-simultaneous режим під TDOA це змінить.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
uint16_t adc_read(uint8_t channel) { ADC1_SMPR2 &= ~(0x7u << (channel * 3)); ADC1_SMPR2 |= (0x6u << (channel * 3)); // 55.5 циклів ADC1_SQR3 = channel & 0x1F; ADC1_CR2 |= CR2_SWSTART; while (!(ADC1_SR & SR_EOC)); return (uint16_t)(ADC1_DR & 0xFFF); } |
Час вибірки (SMPR) — компроміс: більше циклів = точніше, але повільніше. 55.5 циклів — середина, для потенціометра чи повільного датчика температури вистачає з запасом.
◆ Що далі
Вийшли на фінішну пряму. Думаю цієї бази вже буде достатньо, щоб зробити наступний крок перед самим TDOA — dual simultaneous mode (ADC1+ADC2 синхронно), а тоді вже візьмусь за мікрофонну збірку.




