Ардуїно як профі. Частина 3: Прощавай, analogRead()! STM32 Blue Pill та магія регістрів

◆ Де ми зупинились

У перших двох частинах ми вичавили з плат Arduino максимум: розібрались із розрядністю ADC, прибрали delay(), розігнали ADC через prescaler і навчились фільтрувати шум програмно. Але якщо потрібно оцифровувати справді швидкі сигнали — звук з мікрофона, радіоімпульси для майбутнього осцилографа — 8-бітні плати й стандартні функції Arduino стають головним гальмом.

Сьогодні переходимо на 32-бітний ARM Cortex-M3 — STM32 Blue Pill. Навчимось збирати безпечний захисний дільник напруги і повністю відмовимось від analogRead() на користь прямого керування регістрами процесора чистим C.

◆ Чому analogRead() повільний на STM32

Коли викликаєте analogRead(PA0) через Arduino-обгортку (STM32duino) — під капотом щоразу запускається важкий цикл: ADC вмикається заново, налаштовується пін, обирається канал, запускається вимір — і, найголовніше, процесор блокується, чекаючи завершення конверсії. Це та сама проблема, з якою ми боролись в Uno в другій частині, тільки тепер ціна вища — STM32 фізично здатен на значно більше, а обгортка цей потенціал ховає.

В архітектурі ARM уся периферія керується через регістри — комірки пам’яті, фізично з’єднані з логікою чипа. Замість того, щоб просити абстрактну функцію виконати замір, можна напряму перемикати потрібні біти.

Ключова ідея цієї статті — режим безперервного перетворення (Continuous Conversion Mode):

  1. Вмикаємо ADC один раз при старті.
  2. Кажемо: “вимірюй напругу на PA0 без зупину”.
  3. ADC циклічно робить заміри сам, у фоні. Процесору більше не треба чекати — у будь-яку мить можна просто зазирнути в регістр даних ADC1->DR і забрати найсвіжіше значення.

◆ Залізна безпека — як не спалити STM32

Перш ніж підключати хоч один дріт до Blue Pill, запам’ятайте головне правило: аналогові піни STM32 працюють тільки в діапазоні 0-3.3V, і вони не толерантні до 5V. Подача напруги від Arduino (5V) чи будь-якого 5-вольтового джерела миттєво пошкодить пін чи весь мікроконтролер.

Щоб безпечно виміряти сигнал з амплітудою вище 3.3V, потрібен резистивний дільник напруги — два резистори:

Формула:

При R1=1.8 кОм, R2=3.3 кОм і Vin=5V (максимум з Arduino):

⚠ Формально це вкладається в безпечний діапазон STM32, але запас всього ~70 мВ — надто тонкий для комфорту, особливо якщо джерело сигналу нестабільне чи Vin випадково трохи перевищить 5V (наприклад, USB-живлення “гуляє” в межах 4.75-5.25V за специфікацією). На практиці варто цілити не в “формально влазить під 3.3V”, а в 3.0V з запасом — саме тому в домашньому завданні нижче для автомобільного акумулятора (12V) прямо стоїть підказка “не більше 3.0V на виході”, а не 3.3V впритул.

Важливо: якщо сигнал береться напряму з потенціометра, живленого від 3.3V-піна самої Blue Pill — дільник взагалі не потрібен, напруга вже безпечна за визначенням.

◆ Вправа 3.1 — прямий доступ до заліза ADC

Схема підключення:

  1. Зберіть дільник напруги за схемою вище, якщо подаєте сигнал до 5V. Якщо живите потенціометр від 3.3V самої Blue Pill — дільник не потрібен.
  2. Вихід сигналу (0-3.3V) — на пін PA0.
  3. Об’єднайте всі GND.

Код (переконайтесь, що в Arduino IDE обрано плату STM32F1xx / STM32duino):

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

По-перше, крок 7 покладається на класичний трюк “подвійного ADON” — коли EXTTRIG/EXTSEL лишаються не налаштованими (за замовчуванням 0), другий запис у біт ADON сам по собі запускає перетворення, а рядок SWSTART в такому разі просто ігнорується апаратно (нешкідливо, але й без ефекту). Це поширений і робочий патерн у прикладах для STM32F103, але поведінка периферії — тонка річ; перш ніж показувати цей код студентам як канонічний, варто підтвердити на реальній платі, що ADC1->DR дійсно оновлюється безперервно, а не застряг на одному значенні.

По-друге, SMP0 виставлений на мінімальний час вибірки (1.5 циклу) заради максимальної швидкості — але, як і з prescaler на Uno в другій статті, швидкість тут не безкоштовна. Коротший час вибірки означає, що внутрішній конденсатор sample-and-hold менше встигає зарядитись від джерела сигналу — і чим вищий вихідний опір джерела (а дільник напруги з резисторами кОм-діапазону саме таким і є), тим сильніша похибка. Для демо на потенціометрі це непомітно; для точних вимірів через дільник — варто спробувати збільшити час вибірки (наприклад, SMP0 на кілька тактів більше) і порівняти стабільність показань.

◆ Бонус: апаратне калібрування — те, чого немає в AVR

Зверни увагу на крок 6 — ADC_CR2_CAL. Це не просто “гарний тон”, а вбудована апаратна функція STM32F1: чип сам вимірює й компенсує внутрішні похибки перетворювача при кожному калібруванні. Це принципово інша філософія за той “секретний вольтметр” з першої статті серії, де ми калібрували AVR вручну, мультиметром, підбираючи константу під конкретний екземпляр чипа. STM32 частину цієї роботи бере на себе сам, апаратно, за 10 мікросекунд — ще один наочний приклад того, наскільки далі просунулась периферія в 32-бітних ARM-чипах порівняно з 8-бітним AVR.

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

  • Підключити 5V сигнал напряму до PA0 без дільника — миттєве пошкодження піна чи всього чипа.
  • Розрахувати дільник “впритул” до 3.3V без запасу — на межі допуску, а не з комфортним відступом.
  • Забути об’єднати GND між джерелом сигналу і Blue Pill — без спільної землі дільник напруги працює непередбачувано.
  • Скопіювати назви регістрів з чужого прикладу не перевіривши — як побачили вище, навіть згенерований код може містити помилки в іменах CMSIS-констант, які просто не скомпілюються.
  • Плутати “резистор обмеження струму” (один резистор, захищає від струму, але не знижує напругу) з “дільником напруги” (два резистори, справді знижує напругу) — це різні речі з різним призначенням.

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

  1. Скопіюйте код у свою Arduino IDE, перевірте компіляцію і роботу на реальній платі.
  2. Змініть RCC_CFGR_ADCPRE_DIV6 на DIV8 чи DIV4, порахуйте нову тактову частоту ADC і звірте з максимумом 14 МГц із даташита RM0008.
  3. Розрахуйте резистори дільника для виміру автомобільного акумулятора на 12V (підказка: на виході має бути не більше 3.0V — навмисно з запасом, а не впритул до 3.3V).
  4. Якщо є осцилограф чи логічний аналізатор — перевірте, чи справді ADC1->DR оновлюється безперервно в режимі, описаному вище.

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

  1. Чим “режим безперервного перетворення” концептуально відрізняється від звичайного analogRead()?
  2. Чому резистор обмеження струму і дільник напруги — не одне й те саме, і чим небезпечна ця плутанина?
  3. Навіщо STM32 потребує апаратного калібрування ADC, а AVR у попередніх статтях калібрували вручну?
  4. Що станеться, якщо виставити занадто короткий час вибірки (SMP0) при високому вихідному опорі джерела сигналу?
  5. Чому запас “3.23V формально під 3.3V” вважається тонким, і яке значення варто цілити натомість?

◆ Далі в серії

У фінальній частині розбираємо найпотужнішу інженерну технологію цього курсу — DMA (Direct Memory Access), яка дозволить ADC записувати тисячі точок сигналу в пам’ять взагалі без участі процесора. І зберемо все разом — бездротовий Wi-Fi осцилограф на базі ESP32, з яким ця серія і починалась.

Коли залізо стабільно показує числа на екрані — рухаємось до фіналу.

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

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