Ардуїно як профі. Частина 4: Технологія DMA та бездротовий IoT-осцилограф

◆ Фінальний модуль

У третій частині ми навчились керувати ADC на STM32 Blue Pill напряму через регістри — і отримали колосальну швидкість. Але є межа: якщо хочемо читати сигнал на максимальній швидкості (до 1 000 000 вимірів/сек) і складати їх у масив, процесору все одно доведеться щоразу відволікатись — забрати число з ADC1->DR, покласти в пам’ять, повернутись до роботи. На частоті 1 МГц це мільйон переривань за секунду. Жоден процесор довго так не протягне.

Сьогодні розбираємо головну магію архітектури ARM — DMA, і об’єднуємо швидкість STM32 з бездротовими можливостями ESP32, щоб зібрати справжній Wi-Fi осцилограф, графік якого видно на екрані телефона.

◆ Що таке DMA і чому це магія

DMA (Direct Memory Access) — окремий апаратний “копроцесор” всередині чипа, єдина задача якого — переносити байти з одного місця в інше, без участі головного процесора.

Без DMA, навіть з регістрами: ADC виміряв напругу → підняв прапорець → процесор кинув поточні справи → скопіював число в масив → повернувся до роботи. На частоті 1 МГц процесор просто захлинеться в цих мікрозадачах.

З DMA: процесор каже “ось тобі масив на 1024 комірки, я пішов малювати графіку. Коли ADC закінчить вимір — сам перенеси дані туди. Заповниш увесь масив — розбуди мене”. Поки ADC і DMA працюють у парі, залізо оцифровує сигнал з нульовим навантаженням на центральний процесор.

◆ Вправа 4.1 — випускний проєкт “Бездротовий Wi-Fi осцилограф”

Об’єднуємо дві плати:

  1. STM32 Blue Pill — “супершвидке око”. Через тандем ADC+DMA збирає 1024 точки сигналу і “вистрілює” бінарним пакетом через апаратний UART в ESP32.
  2. ESP32 — “комунікатор”. Приймає пакет, піднімає Wi-Fi точку доступу, малює графік на веб-сторінці.

Схема з’єднання плат:

Потенціометр чи датчик живимо 3.3V від будь-якої з плат.

◆ Код для STM32 Blue Pill — ADC + DMA + UART

Реальна інженерна проблема, яку варто знати перш ніж називати цей код production-ready. DMA працює в циклічному режимі (DMA_CCR_CIRC) — тобто, щойно буфер заповнився, DMA одразу починає писати в нього знову, з початку, не чекаючи нікого. При 500000 baud передача 2048 байт (BUFFER_SIZE * 2) через Serial1.write() займає близько 41 мс. А ADC на швидкості під 1 МГц встигає заповнити ті самі 1024 комірки буфера заново десятки разів за той самий час. Це означає: поки Serial1.write() повільно вичитує масив байт за байтом, DMA паралельно й непомітно переписує ці самі байти новими даними — і на приймальному боці (ESP32) можна отримати “розірваний” кадр, зшитий з шматків різних моментів часу.

Це не гіпотетична дрібниця, а класична проблема паралельного доступу до пам’яті, яку в промислових системах вирішують подвійною буферизацією (double buffering / ping-pong buffer): поки один буфer передається по UART, DMA пише в другий, і вони міняються ролями по черзі. STM32 має для цього спеціальний режим переривань “half-transfer” — можна почати передавати першу половину буфера, поки DMA дописує другу. Реалізація такого double-buffering — гарне домашнє завдання підвищеної складності нижче, а не те, що варто показувати як “готове рішення” без застереження.

◆ Код для ESP32 — Wi-Fi веб-сервер і графік

ESP32 створює власну Wi-Fi точку доступу ESP32-Oscilloscope. Підключаєтесь до неї з телефона, відкриваєте в браузері 192.168.4.1 і бачите графік.

Дві свідомі спрощення, про які варто знати, а не видавати за “правильний” фінальний підхід:

По-перше, WiFi.softAP(ssid) без пароля створює повністю відкриту мережу — прийнятно для 30-хвилинного демо на закритому занятті, категорично неприйнятно для будь-чого поза навчальною лабораторією. Другий аргумент WiFi.softAP(ssid, password) вирішує це в одну зміну коду.

По-друге, підхід “перезавантажувати всю сторінку кожні 100мс через meta http-equiv='refresh'” і будувати рядок з тисячі конкатенацій String при кожному запиті — робочий, простий для розуміння, але грубий спосіб. Професійний варіант — WebSockets: одне постійне з’єднання, сервер штовхає тільки нові дані (не всю HTML-сторінку заново), браузер малює на Canvas без перезавантаження. Для навчальної демонстрації принципу поточний варіант цілком виправданий — він показує суть (STM32 вимірює, ESP32 показує) без додаткової складності протоколу. Перехід на WebSockets — природний наступний крок, коли захочете зробити з цього щось більше за демо.

◆ Що ми фактично отримали

  • Сигнал вимірюється на швидкості ARM-архітектури через тандем ADC+DMA — без втрати жодного такту процесора на копіювання даних вручну.
  • Дані передаються не повільним текстом (Serial.println()), а компактним бінарним потоком — вдвічі байт на семпл замість символьного представлення числа.
  • ESP32 бере на себе всю мережеву роботу (Wi-Fi стек, HTTP-сервер), лишаючи STM32 повністю зосередженим на швидкому вимірюванні.

Підключіть телефон до мережі ESP32-Oscilloscope, відкрийте 192.168.4.1 в браузері — перед вами власний бездротовий осцилограф.

◆ Типові помилки

  • Забути налаштувати RCC_CFGR_ADCPRE — ADC працюватиме поза специфікацією даташита, результати можуть бути недостовірними навіть якщо код компілюється й “начебто працює”.
  • Скопіювати назви CMSIS-констант з чужого прикладу не перевіривши — як бачили в статтях 3 і 4, навіть згенерований код регулярно містить друкарські помилки в іменах регістрів.
  • Читати циклічний DMA-буфер напряму під час повільної передачі, вважаючи дані “замороженими” — насправді DMA продовжує писати паралельно.
  • Лишити відкриту Wi-Fi мережу поза межами навчального стенду.
  • Заплутатись, який UART на якій платі — Serial1 на STM32 (апаратний, пін PA9) і Serial2 на ESP32 (програмно призначені піни 16/17) — це різні об’єкти на різних платах, легко переплутати в перших спробах.

◆ Домашнє завдання

  1. Зберіть обидві плати, перевірте передачу даних через UART між ними осцилографом чи логічним аналізатором, якщо є.
  2. Додайте пароль до Wi-Fi точки доступу ESP32.
  3. Підвищена складність: реалізуйте double-buffering на STM32 — два масиви замість одного, перемикання між ними по перериванню half-transfer/transfer-complete, щоб гарантовано передавати тільки “застиглі”, не перезаписувані дані.
  4. Якщо відчуваєте впевненість — спробуйте замінити meta refresh + String-конкатенацію на WebSocket-з’єднання для плавнішого й ефективнішого оновлення графіка.

◆ Запитання для самоперевірки

  1. Чим DMA принципово відрізняється від просто “швидкого” зчитування регістра в циклі?
  2. Чому передача бінарних даних (Serial1.write з uint8_t*) ефективніша за Serial.println() з числами?
  3. У чому конкретно полягає race condition між циклічним DMA-буфером і повільною UART-передачею, і як double-buffering її вирішує?
  4. Чому в цьому коді критично важливо явно налаштувати RCC_CFGR_ADCPRE, а не покладатись на значення за замовчуванням?
  5. Які конкретні компроміси зроблено заради простоти в ESP32-коді (мережа, спосіб оновлення сторінки), і як би ви їх усунули для production-версії?

◆ Підсумок серії — від новачка до профі

За чотири частини ви пройшли шлях від “чому не Blink” до власноруч зібраного бездротового осцилографа:

  • Базову теорію аналогового світу, квантування й опорної напруги.
  • Різницю розрядностей 10 біт (Uno) проти 12 біт (ESP32/STM32) — і чому “більше біт” не означає “точніше”.
  • Асинхронне програмування через millis() і апаратне прискорення через prescaler-регістри.
  • Математичну цифрову фільтрацію (Moving Average) для очищення сигналу від шуму.
  • Низькорівневе програмування регістрової периферії ARM Cortex-M3 — без жодної Arduino-обгортки.
  • Технологію прямого доступу до пам’яті (DMA) і базову архітектуру розподіленої IoT-системи.

Курс по ADC на цьому завершено. Куди рухатись далі — ШІМ і керування моторами, протоколи I2C/SPI/UART для розмови між пристроями, чи переривання й таймери для реакції на події за наносекунди — тема для наступної серії.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *