STM32 з нуля без HAL: Місяць 4, Тиждень 4: Ultrasonic HC-SR04, один рядок −517 і драйвер з нуля. Частина 14

Місяць 4, Тиждень 4. Ultrasonic HC-SR04 як /dev/hcsr04. Але насправді ця стаття не про датчик, а про болі, які виникають коли впираєшся в обмеження досвіду, а тут ще й ця еволюцію Linux Driver Model — і твої приклади з книжок, та інтернету це вже legacy, які не працюють на ядрі 2026 року.

◆ План, який здавався простим

Задача виглядала як класична пригода на вечір: узяти пляшку пива ультразвуковий далекомір HC-SR04, під’єднати два GPIO, зробити символьний пристрій /dev/hcsr04, і щоб cat виводив диистанцію в сантиметрах.

Сам алгоритм вимірювання — тривіальний:

  1. сформувати 10 мкс імпульс на Trigger;
  2. дочекатися фронту Echo;
  3. виміряти скільки Echo тримається у HIGH;
  4. поділити тривалість (мкс) на 58;
  5. повернути сантиметри.

Формула distance_cm = duration_us / 58 — це швидкість звуку (~343 м/с) з поправкою на те, що сигнал іде туди й назад. Класика, ардуінщика HC-SR04, що робиться 5 хвилин. Я взяв рівно той підхід, що демонструють сотні туторіалів — legacy GPIO API:

І тут почались ні не гулі, а справжні болі. та давайте йти послідовно і спочатку підключимо залізо. Класичний HC-SR04: echo видає 5V, а Pi може витрамати 3.3V. Я не пробував та пишуть, що якщо подати echo напряму на GPIO то згорить пін. Тому на echo тре обовʼязковий дільник напруги. Trigger же на Pi видає 3.3V, сенсору цього досить, має бути все нормально.

◆ Перший запуск: компілюється чисто, але одразу падає

Збірка проходить без єдиного warning’а:

insmod<!--TgQPHd||[]--> (від англ. insert module) — це стандартна команда Linux, яка використовується для ручного завантаження окремого модуля безпосередньо в ядро операційної системи. На відміну від більш розумної команди modprobe<!--TgQPHd||[]-->insmod<!--TgQPHd||[]--> потребує точного шляху до файлу модуля (із розширенням .ko<!--TgQPHd||[]-->) та не завантажує інші модулі, від яких він залежить.

А insmod одразу викидає такі коні:

Утиліта dmesg (від англ. diagnostic message) у Linux — це потужний інструмент для перегляду та керування кільцевим буфером повідомлень ядра. Вона дозволяє відстежувати процеси завантаження системи, ініціалізації драйверів та виявляти апаратні або системні помилки.

І в dmesg:

Ніяких пояснень. Просто «failed» і загадкове число 517. Цікаво що про це число скаже Соломія мій бот нумеролог.

У нумерології число 517 є потужним символом особистісної свободи, духовного пробудження та позитивних життєвих змін. Воно поєднує енергію трьох цифр:
  • 5 — відповідає за зміни, пригоди та адаптивність.
  • 1 — символізує нові починання, лідерство та створення власної реальності.
  • 7 — уособлює духовність, мудрість та глибокий самоаналіз.

Сума цих цифр (5 + 1 + 7 = 13, а 1 + 3 = 4) зводить число 517 до вібрації четвірки, яка додає енергію стабільності, працездатності та побудови міцного фундаменту. Якщо ви часто бачите 517, це знак від Всесвіту (або ваших ангелів-охоронців), що ваші нещодавні рішення були правильними, і вас чекає період духовного зростання. Ангели постійно показуватимуть вам число 517, коли захочуть привернути вашу увагу. Вони дуже цікавляться вашим життям. Тому не турбуйтеся про речі, які ви не можете контролювати. Нехай з ними розуміються ваші ангели. Зосередьтеся на тому, в чому ви найкраще знаєтеся. Ангели допоможуть вам процвітати незалежно від поточної ситуації.

Отакої, якось вже не дуже мені весело, невже це знак покинути і йти займатись тим на чому я найкраще знаюсь? Піду заварю чаю.

◆ Перша (хибна) гіпотеза: пін зайнятий

Найлогічніше припущення — GPIO вже кимось захоплений. Перевіряємо:

Лінії вільні. Жодного consumer. Жодного конфлікту. Далі я зробив те, за що мені соромно: почав перебирати піни. GPIO4/5 — failed. GPIO16/17 — failed. GPIO23/24 — failed. Витратив години, міняючи номери в коді, ловлячи то -517, то -16, і не розуміючи чому навіть явно вільні відповідно доgpioinfo піни не беруться.

✏️ Видно ангели дуже серйозно мною зацікавились. Мабуть кажуть мені, друже не займайся херньою йди зроби щось корисне, можеш в кімнаті поприбиратись

Як з’ясувалось пізніше, видно ресурс чисел в ангелів закінчився), проблема була не в тому, які піни. Проблема була в тому, що я читав -517 як «зайнято», а воно ж то означає зовсім інше.

◆ Що насправді означає -517

Помилка Unknown error 517 (або —517 у системних логах) у Linux — це код EPROBE_DEFER<!--TgQPHd||[]-->. Вона означає, що драйвер пристрою або модуль ядра намагається завантажитись, але потрібний йому базовий компонент (наприклад, шина або контролер) ще не ініціалізовано системою. Ядро автоматично перенесе завантаження на пізніше, коли залежність буде готова.

Розшифровка коду:

Це не «зайнято». Це ядро буквально каже:

«Я ще не готове віддати тобі цей ресурс. Повернись пізніше.»

І ось де захована пастка. Legacy-драйвер вантажиться через module_init(), а module_init() викликається рівно один раз:

«Пізніше» вже ніколи не настане, краще вже не буде, бо, як зясувалось немає механізму, який би повторив спробу. Драйвер, побудований на голому module_init + gpio_request, на сучасному ядрі приречений — і жоден вибір піна цього не виправить. ⚠ Ключове усвідомлення: тре змінювати не код, тре змінювати себе, бо Ангели вже прям кричать про це).

◆ Як Linux хоче, щоб це робилось у 2026

Колись драйвер міг просто запопросити GPIO по номеру. А от сучасний Linux вимагає іншу модель — з відкладеним, керованим ядром стартом:

Драйвер більше не повинен знати, що таке «GPIO23». Він просить лінію на ім’я — trigecho — а ядро само вирішує, коли вона готова. Якщо не готова зараз — ядро само викличе probe() пізніше. Той самий EPROBE_DEFER, але тепер він працює на нас, а не проти.

◆ Переписуємо: platform_driver + Device Tree + gpiod

Сумно. Мені завжди трохи сумно, коли тре щось видаляти. Прибираємо gpio_request, переходимо на gpiod, пишемо власний Device Tree overlay, і оформлюємо модуль як platform_driver.

Device Tree overlay

Тепер піни живуть не в C-коді, а в описі заліза:

compatible = "alex,hcsr04" — це «замок», до якого драйвер підбере «ключ». trig-gpios / echo-gpios — імена, які драйвер шукатиме через gpiod.

Драйвер: probe замість module_init

Зверни увагу: немає gpio_request, немає номерів пінів, немає ручного module_initdevm_gpiod_get(&pdev->dev, "trig", ...) бере лінію trig, опис якої ядро витягло з overlay. devm_ означає, що ядро само звільнить ресурс при вивантаженні — менше ручного cleanup, менше шансів залишити висіти GPIO.

Makefile: збираємо і модуль, і overlay

⚠ Прапорець -@ у dtc критичний — він додає symbols в overlay, без нього посилання &gpio не зарезолвиться. Легко пропустити й потім не розуміти, чому overlay не вантажиться.

◆ Складання і встановлення

Обидва файли на місці. Кладемо overlay і прописуємо його в конфіг завантажувача:

⚠ Шлях /boot/firmware/overlays/ — це Raspberry Pi OS Bookworm. На старіших версіях це /boot/overlays/. Перевір свій:

Як видно мій результат → /boot/firmware/ чітко підтверджує, що на малинці встановлена нова версія операційної системи (Raspberry Pi OS Bookworm або новіша).

Розділ boot змонтовано саме в /boot/firmware/, тому якщо знадобиться вручну правити конфігураційний файл (наприклад, увімкнути якийсь екран чи апаратний модуль), шукатииму файл config.txt саме там: sudo nano /boot/firmware/config.txt

◆ Момент істини

Після перезавантаження перевіряємо, що overlay створив пристрій, і вантажимо модуль:

Ось воно. probe/dev/hcsr04 ready. Жодного -517.

Важливо тут те, що тепер ядро само викликає мій probe() — тоді, коли Device Tree буде готовий віддати GPIO. Модель драйвера тепер архітектурно правильною для сучасного ядра. І перший вимір:

Ще раз — 18. Ще — 17. Датчик міряє. Працює.

◆ Опису анального програмного болю у всій красі

Софт запрацював, але поки я доводив систему до стабільних замірів, назбирався цілий букет гемору пасток. Кожна виглядала як «GPIO не доступно» або «не працює» і все це мало свою окрему причину. Ось ці причини.

Ґуля № 1: залишений gpiomon тримає лінію → -16 EBUSY

У нумерології число 16 символізує духовний розвиток, трансформацію та пошук балансу. Оскільки сума цифр (1+6=7) дорівнює 7, воно поєднує лідерство одиниці, турботу шістки та глибоку мудрість сімки. В ангельській нумерології число 16 (і його подвоєння 16:16 на годиннику) — це потужне послання від Всесвіту, яке закликає вас переглянути свої пріоритети, відпустити минуле і знайти баланс між матеріальним та духовним

Під час діагностики я запустив gpiomon у фоні (&), щоб подивитись фронти Echo. Потім вивантажив модуль, перезавантажив — і:

-16 = EBUSY. Але probe цього разу викликався (бачимо в dmesg)! Ресурс реально зайнятий — моїм же фоновим gpiomon, який досі тримав GPIO24. Зверни увагу: помилка інша — -16 EBUSY, а не -517 EPROBE_DEFER. Обидві виглядають як «не можу взяти GPIO», але причини протилежні: -517 = «ще не готове, спробую пізніше», -16 = «зайнято прямо зараз».

Комбінація 16 і 517 — це сильний ангельський знак, який символізує злам старого життя та сприятливі зміни. Всесвіт закликає вас відпустити застарілі страхи або рутини і довіритися новим можливостям, які прийдуть завдяки вашому оптимізму та правильному вибору.

Лікуємо — вбиваючи фонові процеси:

Ґуля № 2: Permission denied — та сама, що з /dev/mydev

Модуль завантажений, /dev/hcsr04 існує, а cat без sudo:

device_create створює вузол з правами 0600 — тільки root. Точно як у статті Місяць 4, Тиждень 3: пишемо /dev/mydev — character device driver. Частина 13. Рішення те саме — udev rule:

Після цього crw-rw-rw- і cat без sudo працює.

Ґуля № 3: читаємо throttled=0x50000 як бітову маску

У логах раз по раз блимав Undervoltage detected!. Тре брати блок живлення від 2А. Замінив блок живлення, перевіряю

Це бітова маска. 0x50000 = біти 16 і 18. Обидва в старшій половині (біти 16+) — а це означає «траплялось у минулому», не «активне зараз»:

Біт Значення Що означає
0 (0x1) undervoltage зараз активна проблема
1 (0x2) throttling зараз активна проблема
16 (0x10000) undervoltage траплявся історія
18 (0x40000) throttling траплявся історія

0x50000 = біти 16+18, тобто вже залиишки. Жодного біта 0–3 → зараз живлення чисте. Undervoltage був на старому блоці живлення, до того як я його поміняв.

Ґуля № 4: стрибки значень і timeout falling — Linux не real-time

Запускаю цикл замірів:

Здебільшого рівні числа — 3736118120115. Але подекуди зриви: то timeout falling, то дичина 666719. І це не баг коду. Це фундамент. Моя polling-версія крутить cpu_relax() у циклі й міряє час через ktime_get(). Планувальник Linux може перервати цей цикл будь-коли — і тоді виміряний інтервал розтягується, даючи фальшиво велике значення. Linux не гарантує таймінг на рівні мікросекунд. Ось той самий контраст, що тягнеться крізь усю серію: на bare-metal STM32 з апаратним input capture ці ж кілька мікросекунд ловились би таймером точно, без участі планувальника. На Linux — ні. Не тому що Linux гірший, а тому що в нього інша задача: він керує сотнею процесів, а не одним датчиком.

◆ Цікава деталь: після reboot пристрій «зникає»

Ще один момент, який спершу лякає. Після перезавантаження Pi:

Здається, драйвер «зламався». Насправді ні. Device Tree overlay лише повідомляє ядру про існування пристрою. Сам модуль треба або завантажити вручну (insmod), або встановити в систему, щоб він підхоплювався автоматично:

Тоді після кожного завантаження overlay створить пристрій, а modules-load підтягне драйвер, і /dev/hcsr04 з’явиться сам.

◆ Висновок

Найцінніший результат цієї статті, як на мене це ангельська нумерологія, хоча може і розуміння того, що більшість навчальних прикладів застарілі, теж цінно, але ж одна помилка. як ми з’ясували -517 виявилась не багом, а ангельською підказкою:

«Не борись із Driver Model. Працюй разом із нею.»

Попереду Місяць 5 — FreeRTOS на STM32. Далі буде…

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

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