STM32 з нуля без HAL. Частина 19: ADC

💡 Усі статті, обговорення, новини для початківців — в одному місці. Приєднуйтесь до Junior спільноти!

Мій 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 виконує формулу пропорції:

Результат ADC = round(V_IN × 1024 / V_REF)

Приклад: подали на Arduino 3.3В. Рахуємо: (3.3 × 1024) / 5.0 = 675.84. Округляємо й отримуємо число 675 (Число 675 в двоїчній системі дорівнює 1010100011). Це просто номер сходинки.

◆ Чому бінарний пошук, а не лінійний перебір

Як мікроконтролер дізнається, на якій саме сходинці перебуває напруга? «Сортування бульбашкою» тут не підходить — сортувати нема чого. Простір значень (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, В
000000.0000
000110.3125
001020.6250
001130.9375
010041.2500
010151.5625
011061.8750
011172.1875
100082.5000 (половина шкали)
100192.8125
1010103.1250
1011113.4375
1100123.7500
1101134.0625
1110144.3750
1111154.6875 (максимум ЦАП)

Якщо спробуєте зібрати стенд фізично — звірте ці «ідеальні» значення з реальними показами мультиметра. На практиці реальні резистори мають допуски, тому виміряні цифри трохи «гуляють» навколо теоретичних.

◆ Головний висновок: контролер «вірить» в опорну напругу

Контролер живе в умовному світі: він знає, що максимальний код (1111 для 4 біт) відповідає майже повній напрузі живлення. Але якщо ця напруга живлення раптом просяде — з 5.0В до, скажімо, 4.0В — матриця на коді 1000 видасть вже не 2.5В, а 2.0В. Контролер про це ніяк не дізнається. Він так само успішно завершить SAR-алгоритм і видасть код, який буде математично коректним, але фізично помилковим відносно реальних вольтів.

Дивлячись на цю схему, мимоволі задумуєшся: якщо наш Всесвіт — це квантова матриця, то де межа між кодом і реальністю? У звичайному ЦАП (DAC) ми маємо чітке квантування: є або 0, або 1, а резистори R-2R переводять цю дискретність у плавну аналогову напругу.
Але що відбувається у справжньому квантовому комп’ютері? Там кубіти існують у суперпозиції — стані «і 0, і 1 одночасно», наче нескінченна хвиля ймовірностей. Виходить, квантовий комп’ютер у своїй основі є куди більш «аналоговим» та плавним, ніж наш звичний цифровий світ. А класичний ЦАП — це лише груба спроба скопіювати величний механізм матеріалізації Всесвіту.

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

◆ Демонстрація: сонячна панель, яка «чує» світло

Сьогодні замість звичного фоторезистора спробуємо дещо цікавіше — маленьку сонячну панель. На відміну від фоторезистора (який просто змінює опір і потребує окремого живлення через дільник), сонячна панель сама генерує напругу від світла — той самий фізичний принцип, на якому працюють панелі на дахах будинків, тільки в мініатюрі.

Підключення:

Плюс панелі  → GPIO34 (аналоговий вхід ADC1 на ESP32) Мінус панелі → GND

Код:

#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) — і посвіти на панель ліхтариком телефона. Побачиш, як число живо реагує на світло в реальному часі.

◆ А тепер — почуємо світло

Додамо звук, який змінюється разом зі світлом — точнісінько як у справжньому теремін-інструменті:

#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 переміг у мікроконтролерах:

  1. Нульова конвеєрна затримка — критично, коли один фізичний ADC по черзі обслуговує кілька пінів через мультиплексор
  2. Помірне зростання складності з розрядністю — лінійно, а не експоненційно як у Flash
  3. Споживання енергії масштабується з частотою вимірів — SAR «спить» між замірами, ідеально для пристроїв на батарейках
  4. Просто лягає на стандартний CMOS-техпроцес — та сама технологія, з якої зроблений увесь інший цифровий чип
  5. «Достатньої» точності майже завжди вистачає — 10-12 біт покривають переважну більшість хобі- та промислових задач; коли потрібна вища точність (звук, ваги), на плату просто ставлять окремий зовнішній Σ-Δ чип

Практичний висновок — як обирати архітектуру під задачу:

  • Загальне вимірювання сенсорів → вбудований SAR цілком достатній
  • Висока точність повільного сигналу (вага, звук, точна температура) → зовнішній Σ-Δ чип
  • Швидкість понад можливості SAR (осцилограф, RF) → Flash для екстремальної швидкості, Pipeline — золота середина
  • Максимальна точність при мінімальній швидкості, стійкість до мережевого шуму → Dual-slope

◆ З компаратором vs без компаратора — два способи «вирішити» на основі світла

Пряме продовження теорії SAR: «компаратор — це вже ADC, тільки на 1 біт, він відповідає лише так/ні». Переробимо обидва варіанти і порівняємо, що ця фраза означає на практиці.

Задача одна й та сама для обох модулів: коли стає темно LED вмикається. Спосіб вирішення — принципово різний.

Модуль А — з апаратним компаратором (2N2222A, без жодного коду)

Це не мікросхема-компаратор на кшталт LM393 — а її найпростіший, «саморобний» аналог на одному транзисторі. Рішення «світло чи темрява» тут приймає сама схема, фізично, без мікроконтролера взагалі.

Компоненти: фоторезистор (LDR), транзистор 2N2222A (NPN), резистор 10 кОм (верхнє плече дільника), резистор 220 Ом (обмеження струму LED), LED.

Як це працює, крок за кроком:

  1. Резистор R1 і фоторезистор утворюють дільник напруги. Напруга в точці між ними подається прямо на базу транзистора.
  2. При яскравому світлі опір LDR низький → в точці дільника низька напруга → транзистор закритий → LED не світиться.
  3. У темряві опір LDR зростає → напруга в точці дільника зростає → щойно вона перевищує поріг відкриття транзистора (приблизно 0.6-0.7В між базою й емітером) → транзистор відкривається → струм тече через LED → LED світиться.

⚠ різниця з «справжнім» компаратором (LM393): транзисторна схема має м’який, поступовий перехід — LED починає ледь помітно засвічуватись задовго до «повного» порогу і яскравішає поступово, а не перемикається різко. Справжня мікросхема-компаратор має набагато різкіший перехід завдяки внутрішньому підсиленню операційного підсилювача.

Модуль Б — без компаратора, рішення через ADC і код

Той самий фоторезистор, той самий LED — але тепер рішення приймає не залізо, а програма, після того як ADC оцифрував сигнал.

#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).

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.1V
  • ADC_ATTEN_DB_12 (в старіших версіях ESP-IDF позначався DB_11) — діапазон приблизно 0-3.3V, типовий робочий режим

⚠ Точна назва константи атенюації змінювалась між версіями ESP-IDF (DB_11DB_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 між екземплярами чипа). Читаючи цей внутрішній канал, можна порахувати, яка напруга живлення насправді, і скоригувати всі інші виміри відповідно:

// Принцип корекції через 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 Uno10 біт10231.1VТак
Arduino Mega10 біт10231.1V та 2.56VТак
ATtiny1310 біт10231.1VНі (немає піна)
ESP3212 біт4095~1.1V (+ атенюатори)Ні
STM32 Blue Pill12 біт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 доступні з коробки:

  1. ADCCLK prescaler. ADC живиться не напряму від системної шини, а через дільник від PCLK2 (RCC_CFGR біти ADCPRE). Ліміт — 14 MHz. При 8 MHz HSI і дільнику /2 отримуємо 4 MHz, що в межах ліміту. Якщо пізніше піднімати частоту через PLL до 72 MHz — дільник треба міняти на /6 чи /8, інакше вихід за специфікацію дасть неточність.
  2. Калібрування. RM0008 прямо вимагає калібрування після кожного ввімкнення ADC — інакше значення будуть зміщені систематично. Це двокроковий процес: RSTCAL скидає калібрувальні регістри, потім CAL запускає саму процедуру, обидва — з очікуванням на самоскидання біта.
  3. Час стабілізації (Tstab). Між виходом з power-down (ADON) і початком калібрування потрібна коротка пауза. Якщо пропустити то перша конверсія піде зі сміттям.
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 це змінить.

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 синхронно), а тоді вже візьмусь за мікрофонну збірку.

👍ПодобаєтьсяСподобалось18
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Закидати автора лайном і обригати весь його контент за таке неподобство. Чо руцями писати вже слабо і схеми нормальні робити?

Що ти мелиш? Напиши своє чи слабо? Чи тільки коменти вмієш писати?

Зупинись випадковий перехожий. Якщо ти читаєш цей коментар і ти радіаматор, який хоче більше контенту про embedded українською, постав пальчик. Твоя реакція це хоч і маленький, але вклад в спільноту отих аматорів самоучок, бєшених чудіків

Два лайка, спільнота embedded не дуже хоче більше

контенту про embedded українською

ШІ рулєз!

А до псевдокоду запитань не було ніколи, а от до псевдосхемотехніки запалало

🤗😂😀 Новий термін! Якось писав в тредс розумні пости і нуль переглядів, нуль коментарів. Знайома порадила написати з помилками назву. За добу було 200+ коментарів. Звісно це не привід порівнювати статті технічні де помилка схемотехніки коштує, як мінімум спалених деталей. Та нехай кине в мене каміння той хто вчився на правильних схемах. Постав лайк перехожий, якщо ти палив радіодеталі звичайною стататикою

— є варіант перетворювач напруга -> частота
— слід вказати, що АЦПів може бути 1,2, і т.д., хоча входів набагато більше, так як на вхід АЦП може мультиплексуватися кілька фізичних ліній. Як наслідок, щоб поміряти декілька ліній на одному АЦП спочатку потрібно завершити цикл вимірювання, перемкнутись на іншу лінію, почекати який час і почати вимірювання, і т.д., або є потреба міряти синхронно то починати вимірювання на різних АЦП одночасно

Не розглянуто джерела похибок:
— квантування
— внутрішні шуми
— нелінійність АЦП
— дрейф (напр. темпрературний)
— вплив вхідного опору АЦП
— і т.п. і т.д. (наводки, напр. WiFi, паразитичні зворотні струми, пульсації живлення ...)

Мало приділено уваги зменшенню похибок і уникення промахів.
Але для базового розуміння «студентам» більш як достатньо

А ви точно впевнені у коректності схеми на першому малюнку? Це не може працювати взагалі ніяк, вхід замкнений накоротко через індуктивність, далі вже нічого працювати не може. Та і наступна частина схеми теж непрацездатна. І де ви бачили BD140 у TO220? Ця картинка нейрослоп?

Картинка ілюстративна, взята з відкритих джерел

Якщо не дивитися ту схему, із закороченим входом, та не робочим виходом (там просто випадковим чином поєднані елементи) — то все інше (текст) — цілком добре, мені — сподобалося (я ще не дуже знайомий з архітектурою STM32)

це послання свідкам секти «ШІ всіх звільнить»

Звільнить. Ще рік-два і все

Дякую за коментар! Неймовірно радий, що вам це було корисно, а значить більше 20 годин свого життя я витратив недаремно

Ось нейропомої chatGPT намалював, а схема яка використана це мегапопулярна грамнаци
Цікаво побачити ваш експертний аналіз як профі, а не згенерований нейрослопний коментар

А передбачаючи генеровані коментарі ось, що написав клод, про вищезазначену схему: Так, це набагато грамотніша й, в принципі, робоча схема — на відміну від попередньої картинки, тут виправлено головну проблему.

Що зроблено правильно:
— L1 — дросель із двома обмотками на спільному осерді (силова + зворотного зв’язку) — це і є класичний Joule Thief, який реально самозбуджується. Головний недолік попередньої схеми виправлено.
— D1 — діод Шоткі SS34 замість стабілітрона в силовому ланцюгу — правильний вибір для передачі енергії в C2/навантаження з мінімальними втратами.
— C2 на 25V для 12V виходу — коректний запас за напругою (в першій схемі цього не було).
— Формула порогу регулювання: U = 0.65×(1+R2/R3) = 0.65×(1+100/5.6) ≈ 12.2V — розрахунок вірний, це стандартна формула для транзисторного шунт-регулятора на VBE.
— Логіка регулювання (Q2 зупиняє генерацію при досягненні порогу, ZD1 — резервний захист від перенапруги) — робоча, гістерезисна (bang-bang) схема регулювання, часто використовується саме в таких дискретних перетворювачах.

На що варто зважати на практиці (робочість під питанням саме тут):

1. Запас по насиченню L1. При 200мА на виході й 12V/3.7V ≈ реальний середній струм через обмотку 1 буде значно вищий за 200мА (орієнтовно 0.7—1А середній, пікові значення можуть підходити до заявлених 1.5А I_sat). На повному навантаженні це впритул до межі — краще закласти дросель з запасом (2A+) або не розраховувати на стабільні 200мА, а орієнтуватись на 100-150мА як реальну робочу точку.

2. Якість намотування обмоток критична. Біфілярна намотка (як вказано в примітках) — це не побажання, а вимога. Погана зв’язаність обмоток = нестабільна генерація або її відсутність. На макетці це найчастіша причина, чому такі схеми «не заводяться».

3. Регулювання буде грубим (не лінійним). Гістерезисна схема з Q2 дає пульсації виходу помітно більші, ніж лінійний стабілізатор — реально очікуй сотні мВ — ~1V пульсацій на виході, а не гладкі 12.00V. Для живлення чутливої електроніки цього може бути замало без додаткового LDO після C2.

4. ККД 75-85% — оптимістична оцінка. BD140 має відносно високу Vce(sat) при токах у сотні мА, тож реальний ККД швидше в районі 65-75% на практиці, особливо ближче до максимального навантаження.

Висновок: топологія коректна й реально працездатна (це вже не «картинка з інтернету», а грамотно зібраний Joule Thief з регулюванням) — але це не готовий до продакшену дизайн, а макетна схема з реальним ризиком по насиченню дроселя на повному струмі й помітними пульсаціями виходу. Якщо мета — просто підняти 3.7V до ~12V для нескладного навантаження (не 200мА постійно) — збереться і запрацює. Якщо потрібна стабільність під повним навантаженням — я б рекомендував або взяти дросель із суттєвим запасом по струму, або просто піти шляхом готової мікросхеми (MT3608/XL6009) для передбачуваного результату.

Цікаво, де чатгпт відкопав взагалі ідею робити блокінг-генератор у 2026 році. Схема і пояснення виглядають як бездумний переказ з дуже критичними помилками.

От просто для старту, вхідна напруга подається на базу pnp-транзистор через обмотку трансформатора. Її полярність позитивна щодо і колектора, і емітера — струм нікуди не потече, інших шляхів його протікання до решти схеми немає, нічого не відбувається. Аналізувати далі немає сенсу.

Трохи переробивши, вже буде щось що може працювати (але все одно без тестів краще таке не робити): imgur.com/a/r1FMB9H

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

Вау-ефект в моменті досягнено, зате бажання рекомендувати цикл статей знайомим початківцям — знижено, бо раптом і в інших місцях навалено нейрослопу.

Автор, після цього допису впаде у відчай і відключить монетизацію контента.

Деякі мудрі викладачі бувало навмисно робили помилки, щоб поспостерігати, які студенти тупо копіпастять і зубрять, а які думає головом а не дупом. Хто пасивний, а хто «не взирає на авторитети».

І третє, який хитродупий зробить навчальний посібник, а там несподівано засідка. От сміху то буде.

Ой, та прям там рекомендувати. Як той казав та походи по базару статей же на цю тему повно. Можна сюди кинути хтось перейде а тут купа коментарів не читайте це лайно ось норм контент. Будь ласка пару посилань вау контенту

Та наче стаття про ADC, чи ви тільки на схемах знаєтесь?

Нащо таке постити в учбовій темі, якщо воно повна алхімічна нісенітниця. При чому те, що намальовано ще і не збігається з тим, що нейронка написала. Шунтує вона базу, ага. І взагалі щось середнє між boost та buck, все не туди. Навіть готове вона не змогла скопіпастити, ну тільки дільник на напругу, а не на струм приліпити.

А де написано, що це учбова тема? Наче автор постійно пише, що це для нього пригода і він описує свій досвід. Ніде не вказує, що він кандидат технічних наук чи викладач політеха, і в описі не написано що він embedded sinior

sinior

школоло, мав би шапку перед таким людьми ламати

Ніде не вказує, що він кандидат технічних наук чи викладач політеха,

тобі шашечки чи їхати?

А де написано, що це учбова тема?

Ну а про що тоді тема? Про живописність та композицію нагенерованих картинок? :)

в описі не написано що він embedded sinior

ембедед аналоговою/силовою електронікою і не займається- більшість його роботи всередині SoC

Може автор новий Горовиць чи Гілл

Ентоні Горовиця та Реджинальда Гілла на жаль не читав, але жарт зацінив

Скільки потрібно програмістів щоб замітити перегорівшу лампу? © (тм)

Мені подобається варіант з сисадмінами.

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

Можливо то мені реактивні токи голову надули? Читав вакансії, всюди вимагають вміння читати даташити, схеми підключення, електричні схеми та технічну документацію. Розуміння основ електроніки та здатність самостійно локалізувати hardware/software проблеми.
А якщо почитати сповіді тих хто там працює, то там тре все від проектуваня прототипів до створення плат в Kikad.

тільки платити ні 3х ні 2х за 3в1 ніхто тут не збираються, так що можуть вимають далі, базар великий

ми автора вже простили за картинку чи ще ригаємо на нього? Наче годна тема про ADC, нам так професора не розжовували як тут розклали

Підписатись на коментарі