STM32 з нуля без HAL. Частина 20: Два мікрофони, один регістр

Щодня бачу рекламу embedded-курсів, де обіцяють за декілька місяців зробити з тебе спеца й ще й допомогти знайти роботу. Фронтендери й бекендери масово перекваліфіковуються в embedded — бо ж це deep tech чи military tech, а значить і “пісеньки”, і бронь.

Хочу тебе розчарувати. Курси з тебе спеца не зроблять. Люди вчаться цьому роками, а профі стають одиниці — і не тому, що ти тупий чи не піддаєшся навчанню. Просто тому, що це треба любити. Тому що це важко. І тому що від тебе будуть вимагати не тільки код, але й проектування і дизайн плат, тобто усе і це треба буде на завтра, а інформації мало, і, на відміну від фронтенду чи бекенду, тут справді треба знати точні науки. Фізика — царина embedded. Якщо мрієш з цим працювати — почни з підручника елементарної фізики. Якщо це тебе не зламає — берись за STM32.

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

Зараз читаю “Ім’я рози” Умберто Еко. Кажуть, якщо здолаєш перші сто сторінок, де ченці сперечаються про теологію та єресі, — далі піде як по маслу, справжній детектив у стилі Конан Дойля, тільки в монастирі XIV століття. Цей тиждень був рівно таким самим. Перші кроки — карти регістрів, офсети, теорія dual-режиму — це “теологічна” частина, нудна, але без неї нікуди. А потім, коли перший тест видав дивний результат, це перетворилось на детектив: підозрювані, хибні сліди, свідок, який сам себе спростував, і фінальне зізнання винного, яке виявилось геть не тим, кого підозрювали спочатку.

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

◆ Навіщо взагалі два мікрофони одночасно

Уяви, що ти стоїш посеред кімнати із зав’язаними очима, і хтось ляскає в долоні. Ти можеш приблизно вказати напрямок — звідки прийшов звук — тому що твої вуха почули ляскіт не одночасно. Різниця в частки мілісекунди — і мозок з неї вираховує напрямок.

Це і є ідея TDOA (Time Difference Of Arrival — різниця часу прибуття), і саме заради неї весь цей тиждень пішов на боротьбу з одним регістром. Але щоб виміряти цю крихітну різницю в часі, треба спершу навчитись чути обома “вухами” одночасно — точно в один і той самий момент. Якщо мікроконтролер спочатку послухає перший мікрофон, а через мить другий (замість одночасно) — це те саме, що вимірювати різницю в прибутті звуку, попередньо самому додавши власну затримку. Вимір стане безглуздим.

STM32F103 вміє слухати два входи в один момент — апаратно, без заморочок у коді. Називається це dual simultaneous ADC mode. У теорії — кілька рядків коду. На практиці трохи довше і складніше.

◆ Як це працює — простими словами

Уяви два фотоапарати, які мають клацнути затвор в один момент, не по черзі. У STM32 є два “фотоапарати” (їх називають ADC1 і ADC2) — кожен слухає свій пін (PA0 і PA1). Є спеціальний перемикач у налаштуваннях (DUALMOD), який каже: “коли я скажу СТАРТ одному — обидва спрацюють миттю”. Результат — одне 32-бітне число, де в молодшій половині лежить відповідь від першого “фотоапарата”, а в старшій половині — від другого.

От тільки в мене друга половина завжди була 0.

◆ Перша спроба — виглядає правильно, але працює наполовину

Підключив сонячну панель до PA0 — світиш ліхтариком, число реагує. mic2 — завжди нуль, навіть шумів немає.

◆ Крок 1: спершу перевір, чи взагалі щось зламане

Найважливіше правило будь-якого пошуку несправності — не гадати одразу, а звузити коло підозрюваних. Спочатку перевірив: а чи взагалі PA1 фізично живий, поза “спареним” режимом? Викликав старий, простий одноканальний режим саме на цьому піні:

Працює бездоганно. Значить, пін живий, проводулька підключена правильно. Проблема саме в “спареному” режимі.

◆ Крок 2: перевір, чи це взагалі не підробка

Пошук видає, що Buе Pill (плата, на якій усе це запускається) продається мільйонами копій з Китаю — і частина з них не справжні мікросхеми ST, а дешеві клони, які іноді “економлять” на рідковживаних фічах. Перш ніж підозрювати свій код, варто виключити просту версію: “може, це просто не той чіп”.

Одна команда:

0x0410 — це офіційний, справжній номер мікросхеми ST. Значить копати треба в коді та в документації.

◆ Крок 3: подивись, що насправді відбувається всередині

Так, прошу плату розповісти прямо, що коїться всередині:

Бачу три речі одразу:

  • перемикач “спарений режим” (cr1) увімкнений правильно
  • другий “фотоапарат” (ADC2) сам каже “я готовий” (adc2_sr)
  • і в нього є правильна відповідь у власному регістрі (adc2_dr_raw=0x5c7)

Тобто другий “фотоапарат” клацнув і має фото. Просто це фото не потрапляє в спільний альбом (ADC1_DR).

⚠ Пастка №1: невірна адреса

Йдемо рядок за рядком і ось баг.

Мда, це складніше і нудніше ніж я собі то уявляв.

Що ж відбулось: ADC весь цей час слухав сигнал занадто швидко — 1.5 такту замість повних 239.5. Уяви, що фотоапарат мав час на витримку 1/2000 секунди замість нормальної 1/60 — на темному фоні знімок вийде майже чорний, “розмитий шумом”. Це, до речі, пояснює дивні стрибки чисел ще з минулого тижня коли я налаштовував на сонячній панелі і тестив в темряві.

Виправив адресу — але mic2 усе одно нуль.

◆ Обхідний шлях

Раз другий “фотоапарат” сам по собі справний — найпростіше рішення: не чекати, поки він покладе фото у спільний альбом, а піти й забрати фото:

Це запрацювало. mic2 ожив. Обидва “фотоапарати” клацають одночасно, просто результат забирається різними шляхами. Думаю для TDOA це вже цілком робочий варіант, адже синхронність не постраждала.

Але залишалось незрозуміло — чому спільний альбом (ADC1_DR) не отримує друге фото?

◆ Хибний слід: підозра на DMA

У документації є фраза про те, що “спарений 32-бітний результат” згадується саме в контексті DMA — спеціального механізму, який може сам, без участі процесора, переносити дані з одного місця в інше, ніби автоматичний конвеєр. Виникла ідея: може, спільний альбом заповнюється лише тоді, коли забирає саме цей автоматичний конвеєр, а не коли процесор сам туди заглядає?

Побудував повноцінний DMA-конвеєр ( hal_dma.c) і зробив порівняльний тест — три способи забрати дані одночасно:

На перший погляд — підтвердження: у варіантах A і B старша половина ненульова, у C — теж ненульова, вердикт “гейтиться вимикачем DMA”. Здавалось, питання закрите.

⚠ Пастка №2: сам собі повірив на слово

Дивлюсь уважно на C: 0x0888. Там є число відмінне від нуля. Але чи воно динамічне, тобто воно дійсно оновлюється щоразу? Чи воно просто забуте з минулого разу, коли вимикач DMA був увімкнений?

Це як подивитись на годинник один раз і побачити стрілку на цифрі 5. Це доказ, що годинник іде? Ні — треба подивитись двічі, з паузою, і побачити, чи стрілка зрушила з місця.

Зробив саме так — дві заміри підряд, замість одного:

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

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

⚠ Пастка №3: перевір навіть те, у чому вже впевнений

Залишалось одне сумнівне питання: може, насправді це виправлена адреса (Пастка №1) сама все полагодила, а вимикач DMA тут ні до чого — просто збіглось у часі?

Єдиний спосіб дізнатись напевно — прибрати вимикач DMA, залишивши виправлену адресу скриньки і прогнати знову:

Миттєво й однозначно: mic2 знову нуль. Виправлена адреса сама по собі нічого не вирішувала. Єдина причина, чому все запрацювало — той самий вимикач DMA.

Це найважливіший урок усього тижня: навіть коли здається, що вже все зрозуміло і причина знайдена — варто перевірити ще раз, окремо, саме той фактор, у якому найбільше впевнений. Легко повірити у власний висновок, він же до того ж  і логічний, а по факту перевірив не те що треба.

◆ Фінальний, протестований код

Живий тест на панелі:

Обидва канали, одним рухом, синхронно.

◆ Підсумок тижня

Що знайдено Що це насправді означало
Адреса SMPR2 вела не туди (Пастка №1) Реальний баг
Плата оригінальна (chipid=0x0410) Спростовано підозру
“DMA гейтить пакування” (перший тест) Виглядало підтвердженим, але тест сам був недостатньо строгий
Застигле число замість живого (Пастка №2) Урок: перевіряй в динаміці, а не  “є число чи нема”
SMPR2 не мав жодного стосунку (Пастка №3) Урок: перевір навіть те, у чому найбільше впевнений
ADC1_CR2.DMA-вимикач Справжня, єдина й підтверджена причина

💡 На замітку ардуїнщикам. Усе, що описано вище, неможливо повторити на класичному AVR (Uno, Nano, Mega) так само просто. У ATmega328p/2560 фізично один-єдиний ADC — всі “аналогові піни” A0-A5 (чи A0-A15 на Mega) це не окремі перетворювачі, а один спільний ADC за мультиплексором, який перемикається між ними по черзі. Апаратної команди “слухай два входи в один момент” там просто не існує в камні, скільки регістри не крути.

Якщо колись знадобиться те саме на AVR то можан спробувати такі варіанти, але це не точно: прийняти фіксовану затримку між читаннями (~13 мкс на 16МГц) і відняти її математично; поставити зовнішні sample-and-hold мікросхеми (LF398), які “заморожують” напругу синхронно, а AVR вже спокійно оцифровує обидва значення по черзі; або взяти окрему мікросхему з вбудованим simultaneous ADC (AD7899-2, LTC1867) по SPI. Усі три — це апаратний обхідний шлях там, де STM32 доступно одним регістром.

Наступний крок — навчити плату слухати мікрофони не по команді від процесора, а самостійно, на фіксованій частоті, через апаратний таймер. Скоро дізнаємось чи вийшло це в мене чи ні.

Опубліковано в STM32

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

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