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

◆ План, який здавався простим
Задача виглядала як класична пригода на вечір: узяти пляшку пива ультразвуковий далекомір HC-SR04, під’єднати два GPIO, зробити символьний пристрій /dev/hcsr04, і щоб cat виводив диистанцію в сантиметрах.
|
1 2 3 4 5 6 7 8 9 10 |
Userspace │ <span class="hljs-built_in">cat</span> /dev/hcsr04 ▼ <span class="hljs-built_in">read</span>() │ ▼ Trigger GPIO ──► HC-SR04 ──► Echo GPIO │ ▼ Відстань у сантиметрах |
Сам алгоритм вимірювання — тривіальний:
- сформувати 10 мкс імпульс на Trigger;
- дочекатися фронту Echo;
- виміряти скільки Echo тримається у HIGH;
- поділити тривалість (мкс) на 58;
- повернути сантиметри.
Формула distance_cm = duration_us / 58 — це швидкість звуку (~343 м/с) з поправкою на те, що сигнал іде туди й назад. Класика, ардуінщика HC-SR04, що робиться 5 хвилин. Я взяв рівно той підхід, що демонструють сотні туторіалів — legacy GPIO API:
|
1 2 3 4 5 |
gpio_request(); gpio_direction_output(); gpio_direction_input(); gpio_get_value(); gpio_set_value(); |
І тут почались ні не гулі, а справжні болі. та давайте йти послідовно і спочатку підключимо залізо. Класичний HC-SR04: echo видає 5V, а Pi може витрамати 3.3V. Я не пробував та пишуть, що якщо подати echo напряму на GPIO то згорить пін. Тому на echo тре обовʼязковий дільник напруги. Trigger же на Pi видає 3.3V, сенсору цього досить, має бути все нормально.



◆ Перший запуск: компілюється чисто, але одразу падає
Збірка проходить без єдиного warning’а:
|
1 2 |
<span class="hljs-built_in">make</span> sudo insmod hcsr04.ko |
insmod<!--TgQPHd||[]-->(від англ. insert module) — це стандартна команда Linux, яка використовується для ручного завантаження окремого модуля безпосередньо в ядро операційної системи. На відміну від більш розумної командиmodprobe<!--TgQPHd||[]-->,insmod<!--TgQPHd||[]-->потребує точного шляху до файлу модуля (із розширенням.ko<!--TgQPHd||[]-->) та не завантажує інші модулі, від яких він залежить.
А insmod одразу викидає такі коні:
|
1 |
insmod: ERROR: could <span class="hljs-keyword">not</span> <span class="hljs-keyword">insert</span> <span class="hljs-keyword">module</span> hcsr04.ko: <span class="hljs-literal">Unknown</span> error <span class="hljs-number">517</span> |
Утиліта dmesg (від англ. diagnostic message) у Linux — це потужний інструмент для перегляду та керування кільцевим буфером повідомлень ядра. Вона дозволяє відстежувати процеси завантаження системи, ініціалізації драйверів та виявляти апаратні або системні помилки.
І в dmesg:
|
1 2 |
hcsr04: loading <span class="hljs-keyword">out</span><span class="hljs-operator">-</span><span class="hljs-keyword">of</span><span class="hljs-operator">-</span>tree <span class="hljs-keyword">module</span> taints kernel. hcsr04: gpio_request(<span class="hljs-number">23</span>) failed |
Ніяких пояснень. Просто «failed» і загадкове число 517. Цікаво що про це число скаже Соломія мій бот нумеролог.
У нумерології число 517 є потужним символом особистісної свободи, духовного пробудження та позитивних життєвих змін. Воно поєднує енергію трьох цифр:
- 5 — відповідає за зміни, пригоди та адаптивність.
- 1 — символізує нові починання, лідерство та створення власної реальності.
- 7 — уособлює духовність, мудрість та глибокий самоаналіз.
Сума цих цифр (5 + 1 + 7 = 13, а 1 + 3 = 4) зводить число 517 до вібрації четвірки, яка додає енергію стабільності, працездатності та побудови міцного фундаменту. Якщо ви часто бачите 517, це знак від Всесвіту (або ваших ангелів-охоронців), що ваші нещодавні рішення були правильними, і вас чекає період духовного зростання. Ангели постійно показуватимуть вам число 517, коли захочуть привернути вашу увагу. Вони дуже цікавляться вашим життям. Тому не турбуйтеся про речі, які ви не можете контролювати. Нехай з ними розуміються ваші ангели. Зосередьтеся на тому, в чому ви найкраще знаєтеся. Ангели допоможуть вам процвітати незалежно від поточної ситуації.
Отакої, якось вже не дуже мені весело, невже це знак покинути і йти займатись тим на чому я найкраще знаюсь? Піду заварю чаю.
◆ Перша (хибна) гіпотеза: пін зайнятий
Найлогічніше припущення — GPIO вже кимось захоплений. Перевіряємо:
|
1 2 3 4 5 6 7 8 9 |
gpioinfo | grep <span class="hljs-string">"input"</span> | grep -v <span class="hljs-string">"consumer"</span> | head -<span class="hljs-number">10</span> line <span class="hljs-number">0</span>: <span class="hljs-string">"ID_SDA"</span> <span class="hljs-built_in">input</span> line <span class="hljs-number">1</span>: <span class="hljs-string">"ID_SCL"</span> <span class="hljs-built_in">input</span> line <span class="hljs-number">2</span>: <span class="hljs-string">"GPIO2"</span> <span class="hljs-built_in">input</span> line <span class="hljs-number">3</span>: <span class="hljs-string">"GPIO3"</span> <span class="hljs-built_in">input</span> line <span class="hljs-number">4</span>: <span class="hljs-string">"GPIO4"</span> <span class="hljs-built_in">input</span> line <span class="hljs-number">5</span>: <span class="hljs-string">"GPIO5"</span> <span class="hljs-built_in">input</span> ... |
Лінії вільні. Жодного consumer. Жодного конфлікту. Далі я зробив те, за що мені соромно: почав перебирати піни. GPIO4/5 — failed. GPIO16/17 — failed. GPIO23/24 — failed. Витратив години, міняючи номери в коді, ловлячи то -517, то -16, і не розуміючи чому навіть явно вільні відповідно доgpioinfo піни не беруться.
✏️ Видно ангели дуже серйозно мною зацікавились. Мабуть кажуть мені, друже не займайся херньою йди зроби щось корисне, можеш в кімнаті поприбиратись
Як з’ясувалось пізніше, видно ресурс чисел в ангелів закінчився), проблема була не в тому, які піни. Проблема була в тому, що я читав -517 як «зайнято», а воно ж то означає зовсім інше.
◆ Що насправді означає -517
Помилка Unknown error 517 (або —517 у системних логах) у Linux — це код
EPROBE_DEFER<!--TgQPHd||[]-->. Вона означає, що драйвер пристрою або модуль ядра намагається завантажитись, але потрібний йому базовий компонент (наприклад, шина або контролер) ще не ініціалізовано системою. Ядро автоматично перенесе завантаження на пізніше, коли залежність буде готова.
Розшифровка коду:
|
1 |
<span class="hljs-attr">-517</span> = EPROBE_DEFER |
Це не «зайнято». Це ядро буквально каже:
«Я ще не готове віддати тобі цей ресурс. Повернись пізніше.»
І ось де захована пастка. Legacy-драйвер вантажиться через module_init(), а module_init() викликається рівно один раз:
|
1 2 3 4 5 6 7 8 |
<span class="hljs-function"><span class="hljs-title">module_init</span><span class="hljs-params">()</span> │ ▼ <span class="hljs-title">gpio_request</span><span class="hljs-params">()</span> ──► EPROBE_DEFER <span class="hljs-params">(-<span class="hljs-number">517</span>)</span> │ ▼ <span class="hljs-title">insmod</span> завершується з помилкою </span> |
«Пізніше» вже ніколи не настане, краще вже не буде, бо, як зясувалось немає механізму, який би повторив спробу. Драйвер, побудований на голому module_init + gpio_request, на сучасному ядрі приречений — і жоден вибір піна цього не виправить. ⚠ Ключове усвідомлення: тре змінювати не код, тре змінювати себе, бо Ангели вже прям кричать про це).
◆ Як Linux хоче, щоб це робилось у 2026
Колись драйвер міг просто запопросити GPIO по номеру. А от сучасний Linux вимагає іншу модель — з відкладеним, керованим ядром стартом:
|
1 2 3 4 5 6 7 8 9 10 |
Device<span class="hljs-type"> Tree</span> ← описує, який пристрій існує і на яких пінах │ ▼<span class="hljs-type"> Platform</span> Device ← ядро створює пристрій з опису<span class="hljs-literal"> DT</span> │ ▼<span class="hljs-type"> Platform</span> Driver ← probe() викликається КОЛИ ресурс готовий │ ▼<span class="hljs-literal"> GPIO</span> Descriptor ← devm_gpiod_get(), без номерів |
Драйвер більше не повинен знати, що таке «GPIO23». Він просить лінію на ім’я — trig, echo — а ядро само вирішує, коли вона готова. Якщо не готова зараз — ядро само викличе probe() пізніше. Той самий EPROBE_DEFER, але тепер він працює на нас, а не проти.
◆ Переписуємо: platform_driver + Device Tree + gpiod
Сумно. Мені завжди трохи сумно, коли тре щось видаляти. Прибираємо gpio_request, переходимо на gpiod, пишемо власний Device Tree overlay, і оформлюємо модуль як platform_driver.
Device Tree overlay
Тепер піни живуть не в C-коді, а в описі заліза:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="hljs-comment">// hcsr04-overlay.dts</span> /dts-v1/; <span class="hljs-regexp">/plugin/</span>; / { compatible = <span class="hljs-string">"brcm,bcm2835"</span>; fragment@<span class="hljs-number">0</span> { target-path = <span class="hljs-string">"/"</span>; __overlay__ { <span class="hljs-attr">hcsr04</span>: hcsr04 { compatible = <span class="hljs-string">"alex,hcsr04"</span>; trig-gpios = <&gpio <span class="hljs-number">23</span> <span class="hljs-number">0</span>>; echo-gpios = <&gpio <span class="hljs-number">24</span> <span class="hljs-number">0</span>>; status = <span class="hljs-string">"okay"</span>; }; }; }; }; |
compatible = "alex,hcsr04" — це «замок», до якого драйвер підбере «ключ». trig-gpios / echo-gpios — імена, які драйвер шукатиме через gpiod.
Драйвер: probe замість module_init
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<span class="hljs-keyword">static</span> int <span class="hljs-title function_ invoke__">hcsr04_probe</span>(<span class="hljs-keyword">struct</span> <span class="hljs-title class_">platform_device</span> *pdev) { <span class="hljs-keyword">struct</span> <span class="hljs-title class_">hcsr04_dev</span> *data; data = <span class="hljs-title function_ invoke__">devm_kzalloc</span>(&pdev<span class="hljs-punctuation">-></span>dev, <span class="hljs-title function_ invoke__">sizeof</span>(*data), GFP_KERNEL); <span class="hljs-title function_ invoke__">if</span> (!data) <span class="hljs-keyword">return</span> -ENOMEM; <span class="hljs-comment">/* беремо лінії за ІМЕНЕМ, не за номером */</span> data<span class="hljs-punctuation">-></span>trig = <span class="hljs-title function_ invoke__">devm_gpiod_get</span>(&pdev<span class="hljs-punctuation">-></span>dev, <span class="hljs-string">"trig"</span>, GPIOD_OUT_LOW); <span class="hljs-title function_ invoke__">if</span> (<span class="hljs-title function_ invoke__">IS_ERR</span>(data<span class="hljs-punctuation">-></span>trig)) <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">PTR_ERR</span>(data<span class="hljs-punctuation">-></span>trig); data<span class="hljs-punctuation">-></span>echo = <span class="hljs-title function_ invoke__">devm_gpiod_get</span>(&pdev<span class="hljs-punctuation">-></span>dev, <span class="hljs-string">"echo"</span>, GPIOD_IN); <span class="hljs-title function_ invoke__">if</span> (<span class="hljs-title function_ invoke__">IS_ERR</span>(data<span class="hljs-punctuation">-></span>echo)) <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">PTR_ERR</span>(data<span class="hljs-punctuation">-></span>echo); <span class="hljs-comment">/* ... character device як у статті 13 ... */</span> } <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">of_device_id</span> hcsr04_of_match[] = { { .compatible = <span class="hljs-string">"alex,hcsr04"</span> }, <span class="hljs-comment">/* той самий "замок" */</span> { } }; <span class="hljs-title function_ invoke__">MODULE_DEVICE_TABLE</span>(of, hcsr04_of_match); <span class="hljs-keyword">static</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">platform_driver</span> hcsr04_driver = { .probe = hcsr04_probe, .remove = hcsr04_remove, .driver = { .name = DRIVER_NAME, .of_match_table = hcsr04_of_match, }, }; <span class="hljs-title function_ invoke__">module_platform_driver</span>(hcsr04_driver); |
Зверни увагу: немає gpio_request, немає номерів пінів, немає ручного module_init. devm_gpiod_get(&pdev->dev, "trig", ...) бере лінію trig, опис якої ядро витягло з overlay. devm_ означає, що ядро само звільнить ресурс при вивантаженні — менше ручного cleanup, менше шансів залишити висіти GPIO.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
<span class="hljs-comment">// hcsr04.c</span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/module.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/platform_device.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/of.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/gpio/consumer.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/fs.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/cdev.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/device.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/uaccess.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/mutex.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/ktime.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/delay.h></span></span> <span class="hljs-meta">#<span class="hljs-keyword">define</span> DRIVER_NAME <span class="hljs-string">"hcsr04"</span></span> <span class="hljs-meta">#<span class="hljs-keyword">define</span> BUFFER_SIZE 32</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">hcsr04_dev</span> { <span class="hljs-keyword">struct</span> <span class="hljs-title class_">gpio_desc</span> *trig; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">gpio_desc</span> *echo; <span class="hljs-type">dev_t</span> dev_num; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">cdev</span> cdev; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">class</span> *<span class="hljs-keyword">class</span>; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">device</span> *device; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">mutex</span> lock; <span class="hljs-type">char</span> buf[BUFFER_SIZE]; <span class="hljs-type">size_t</span> len; }; <span class="hljs-type">static</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">hcsr04_dev</span> *hcsr04; <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">ssize_t</span> <span class="hljs-title">hcsr04_measure</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> hcsr04_dev *data)</span> </span>{ <span class="hljs-type">int</span> timeout; u64 start_us, end_us, duration_us; <span class="hljs-type">long</span> distance_cm; <span class="hljs-built_in">gpiod_set_value</span>(data->trig, <span class="hljs-number">0</span>); <span class="hljs-built_in">udelay</span>(<span class="hljs-number">2</span>); <span class="hljs-built_in">gpiod_set_value</span>(data->trig, <span class="hljs-number">1</span>); <span class="hljs-built_in">udelay</span>(<span class="hljs-number">10</span>); <span class="hljs-built_in">gpiod_set_value</span>(data->trig, <span class="hljs-number">0</span>); timeout = <span class="hljs-number">100000</span>; <span class="hljs-keyword">while</span> (!<span class="hljs-built_in">gpiod_get_value</span>(data->echo) && timeout-- > <span class="hljs-number">0</span>) <span class="hljs-built_in">cpu_relax</span>(); <span class="hljs-keyword">if</span> (timeout <= <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-built_in">snprintf</span>(data->buf, BUFFER_SIZE, <span class="hljs-string">"timeout rising\n"</span>); start_us = <span class="hljs-built_in">ktime_to_us</span>(<span class="hljs-built_in">ktime_get</span>()); timeout = <span class="hljs-number">100000</span>; <span class="hljs-keyword">while</span> (<span class="hljs-built_in">gpiod_get_value</span>(data->echo) && timeout-- > <span class="hljs-number">0</span>) <span class="hljs-built_in">cpu_relax</span>(); <span class="hljs-keyword">if</span> (timeout <= <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-built_in">snprintf</span>(data->buf, BUFFER_SIZE, <span class="hljs-string">"timeout falling\n"</span>); end_us = <span class="hljs-built_in">ktime_to_us</span>(<span class="hljs-built_in">ktime_get</span>()); duration_us = end_us - start_us; distance_cm = duration_us / <span class="hljs-number">58</span>; <span class="hljs-keyword">if</span> (distance_cm < <span class="hljs-number">2</span> || distance_cm > <span class="hljs-number">400</span>) <span class="hljs-keyword">return</span> <span class="hljs-built_in">snprintf</span>(data->buf, BUFFER_SIZE, <span class="hljs-string">"error %ld\n"</span>, distance_cm); <span class="hljs-keyword">return</span> <span class="hljs-built_in">snprintf</span>(data->buf, BUFFER_SIZE, <span class="hljs-string">"%ld\n"</span>, distance_cm); } <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">ssize_t</span> <span class="hljs-title">hcsr04_read</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> file *filp, <span class="hljs-type">char</span> __user *ubuf, <span class="hljs-type">size_t</span> count, <span class="hljs-type">loff_t</span> *ppos)</span> </span>{ <span class="hljs-type">ssize_t</span> ret; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">hcsr04_dev</span> *data = filp->private_data; <span class="hljs-built_in">mutex_lock</span>(&data->lock); ret = <span class="hljs-built_in">hcsr04_measure</span>(data); <span class="hljs-keyword">if</span> (ret < <span class="hljs-number">0</span>) { <span class="hljs-built_in">mutex_unlock</span>(&data->lock); <span class="hljs-keyword">return</span> ret; } data->len = ret; <span class="hljs-keyword">if</span> (*ppos >= data->len) { <span class="hljs-built_in">mutex_unlock</span>(&data->lock); <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; } count = <span class="hljs-built_in">min</span>(count, data->len - (<span class="hljs-type">size_t</span>)*ppos); <span class="hljs-keyword">if</span> (<span class="hljs-built_in">copy_to_user</span>(ubuf, data->buf + *ppos, count)) { <span class="hljs-built_in">mutex_unlock</span>(&data->lock); <span class="hljs-keyword">return</span> -EFAULT; } *ppos += count; <span class="hljs-built_in">mutex_unlock</span>(&data->lock); <span class="hljs-keyword">return</span> count; } <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title">hcsr04_open</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> inode *inode, <span class="hljs-keyword">struct</span> file *filp)</span> </span>{ filp->private_data = hcsr04; <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; } <span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">file_operations</span> hcsr04_fops = { .owner = THIS_MODULE, .open = hcsr04_open, .read = hcsr04_read, }; <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title">hcsr04_probe</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> platform_device *pdev)</span> </span>{ <span class="hljs-type">int</span> ret; <span class="hljs-keyword">struct</span> <span class="hljs-title class_">hcsr04_dev</span> *data; <span class="hljs-built_in">dev_info</span>(&pdev->dev, <span class="hljs-string">"probe\n"</span>); data = <span class="hljs-built_in">devm_kzalloc</span>(&pdev->dev, <span class="hljs-built_in">sizeof</span>(*data), GFP_KERNEL); <span class="hljs-keyword">if</span> (!data) <span class="hljs-keyword">return</span> -ENOMEM; <span class="hljs-built_in">mutex_init</span>(&data->lock); data->trig = <span class="hljs-built_in">devm_gpiod_get</span>(&pdev->dev, <span class="hljs-string">"trig"</span>, GPIOD_OUT_LOW); <span class="hljs-keyword">if</span> (<span class="hljs-built_in">IS_ERR</span>(data->trig)) { ret = <span class="hljs-built_in">PTR_ERR</span>(data->trig); <span class="hljs-built_in">dev_err</span>(&pdev->dev, <span class="hljs-string">"failed to get trig gpio: %d\n"</span>, ret); <span class="hljs-keyword">return</span> ret; } data->echo = <span class="hljs-built_in">devm_gpiod_get</span>(&pdev->dev, <span class="hljs-string">"echo"</span>, GPIOD_IN); <span class="hljs-keyword">if</span> (<span class="hljs-built_in">IS_ERR</span>(data->echo)) { ret = <span class="hljs-built_in">PTR_ERR</span>(data->echo); <span class="hljs-built_in">dev_err</span>(&pdev->dev, <span class="hljs-string">"failed to get echo gpio: %d\n"</span>, ret); <span class="hljs-keyword">return</span> ret; } ret = <span class="hljs-built_in">alloc_chrdev_region</span>(&data->dev_num, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, DRIVER_NAME); <span class="hljs-keyword">if</span> (ret) <span class="hljs-keyword">return</span> ret; <span class="hljs-built_in">cdev_init</span>(&data->cdev, &hcsr04_fops); data->cdev.owner = THIS_MODULE; ret = <span class="hljs-built_in">cdev_add</span>(&data->cdev, data->dev_num, <span class="hljs-number">1</span>); <span class="hljs-keyword">if</span> (ret) <span class="hljs-keyword">goto</span> err_unregister; data-><span class="hljs-keyword">class</span> = <span class="hljs-built_in">class_create</span>(DRIVER_NAME); <span class="hljs-keyword">if</span> (<span class="hljs-built_in">IS_ERR</span>(data-><span class="hljs-keyword">class</span>)) { ret = <span class="hljs-built_in">PTR_ERR</span>(data-><span class="hljs-keyword">class</span>); <span class="hljs-keyword">goto</span> err_cdev; } data->device = <span class="hljs-built_in">device_create</span>(data-><span class="hljs-keyword">class</span>, <span class="hljs-literal">NULL</span>, data->dev_num, <span class="hljs-literal">NULL</span>, DRIVER_NAME); <span class="hljs-keyword">if</span> (<span class="hljs-built_in">IS_ERR</span>(data->device)) { ret = <span class="hljs-built_in">PTR_ERR</span>(data->device); <span class="hljs-keyword">goto</span> err_class; } <span class="hljs-built_in">platform_set_drvdata</span>(pdev, data); hcsr04 = data; <span class="hljs-built_in">dev_info</span>(&pdev->dev, <span class="hljs-string">"/dev/%s ready\n"</span>, DRIVER_NAME); <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; err_class: <span class="hljs-built_in">class_destroy</span>(data-><span class="hljs-keyword">class</span>); err_cdev: <span class="hljs-built_in">cdev_del</span>(&data->cdev); err_unregister: <span class="hljs-built_in">unregister_chrdev_region</span>(data->dev_num, <span class="hljs-number">1</span>); <span class="hljs-keyword">return</span> ret; } <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title">hcsr04_remove</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> platform_device *pdev)</span> </span>{ <span class="hljs-keyword">struct</span> <span class="hljs-title class_">hcsr04_dev</span> *data = <span class="hljs-built_in">platform_get_drvdata</span>(pdev); <span class="hljs-built_in">device_destroy</span>(data-><span class="hljs-keyword">class</span>, data->dev_num); <span class="hljs-built_in">class_destroy</span>(data-><span class="hljs-keyword">class</span>); <span class="hljs-built_in">cdev_del</span>(&data->cdev); <span class="hljs-built_in">unregister_chrdev_region</span>(data->dev_num, <span class="hljs-number">1</span>); <span class="hljs-built_in">dev_info</span>(&pdev->dev, <span class="hljs-string">"removed\n"</span>); } <span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">of_device_id</span> hcsr04_of_match[] = { { .compatible = <span class="hljs-string">"alex,hcsr04"</span> }, { } }; <span class="hljs-built_in">MODULE_DEVICE_TABLE</span>(of, hcsr04_of_match); <span class="hljs-type">static</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">platform_driver</span> hcsr04_driver = { .probe = hcsr04_probe, .remove = hcsr04_remove, .driver = { .name = DRIVER_NAME, .of_match_table = hcsr04_of_match, }, }; <span class="hljs-built_in">module_platform_driver</span>(hcsr04_driver); <span class="hljs-built_in">MODULE_LICENSE</span>(<span class="hljs-string">"GPL"</span>); <span class="hljs-built_in">MODULE_AUTHOR</span>(<span class="hljs-string">"Alex"</span>); <span class="hljs-built_in">MODULE_DESCRIPTION</span>(<span class="hljs-string">"HC-SR04 platform driver with gpiod"</span>); <span class="hljs-built_in">MODULE_VERSION</span>(<span class="hljs-string">"0.3"</span>); |
Makefile: збираємо і модуль, і overlay
|
1 2 3 4 5 6 7 8 9 10 11 12 |
obj-m += hcsr04.o KDIR := /lib/modules/<span class="hljs-variable">$(<span class="hljs-built_in">shell</span> uname -r)</span>/build PWD := <span class="hljs-variable">$(<span class="hljs-built_in">shell</span> pwd)</span> <span class="hljs-section">all:</span> make -C <span class="hljs-variable">$(KDIR)</span> M=<span class="hljs-variable">$(PWD)</span> modules dtc -@ -I dts -O dtb -o hcsr04.dtbo hcsr04-overlay.dts <span class="hljs-section">clean:</span> make -C <span class="hljs-variable">$(KDIR)</span> M=<span class="hljs-variable">$(PWD)</span> clean rm -f hcsr04.dtbo |
⚠ Прапорець -@ у dtc критичний — він додає symbols в overlay, без нього посилання &gpio не зарезолвиться. Легко пропустити й потім не розуміти, чому overlay не вантажиться.
◆ Складання і встановлення
|
1 2 3 4 5 6 7 |
<span class="hljs-built_in">which</span> dtc || sudo apt install device-tree-compiler make clean make <span class="hljs-built_in">ls</span> -la hcsr04.ko hcsr04.dtbo -rw-rw-r-- 1 alex alex 536 Jul 6 17:28 hcsr04.dtbo -rw-rw-r-- 1 alex alex 14808 Jul 6 17:28 hcsr04.ko |
Обидва файли на місці. Кладемо overlay і прописуємо його в конфіг завантажувача:
|
1 2 3 |
sudo <span class="hljs-built_in">cp</span> hcsr04.dtbo /boot/firmware/overlays/ <span class="hljs-built_in">echo</span> <span class="hljs-string">"dtoverlay=hcsr04"</span> | sudo <span class="hljs-built_in">tee</span> -a /boot/firmware/config.txt sudo reboot |
⚠ Шлях
/boot/firmware/overlays/— це Raspberry Pi OS Bookworm. На старіших версіях це/boot/overlays/. Перевір свій:
|
1 2 |
<span class="hljs-built_in">ls</span> /boot/firmware/overlays/ >/dev/null 2>&1 && <span class="hljs-built_in">echo</span> <span class="hljs-string">"→ /boot/firmware/"</span> \ || <span class="hljs-built_in">ls</span> /boot/overlays/ >/dev/null 2>&1 && <span class="hljs-built_in">echo</span> <span class="hljs-string">"→ /boot/"</span> |
![]()
Як видно мій результат → /boot/firmware/ чітко підтверджує, що на малинці встановлена нова версія операційної системи (Raspberry Pi OS Bookworm або новіша).
Розділ boot змонтовано саме в /boot/firmware/, тому якщо знадобиться вручну правити конфігураційний файл (наприклад, увімкнути якийсь екран чи апаратний модуль), шукатииму файл config.txt саме там: sudo nano /boot/firmware/config.txt
◆ Момент істини
Після перезавантаження перевіряємо, що overlay створив пристрій, і вантажимо модуль:
|
1 2 3 4 5 6 7 8 9 |
<span class="hljs-built_in">ls</span> /proc/device-tree/hcsr04/ 2>/dev/null && <span class="hljs-built_in">echo</span> <span class="hljs-string">"overlay OK"</span> sudo insmod hcsr04.ko dmesg | <span class="hljs-built_in">tail</span> -5 compatible echo-gpios name phandle status trig-gpios overlay OK [ 431.943354] hcsr04: loading out-of-tree module taints kernel. [ 431.944496] hcsr04 hcsr04: probe [ 431.944845] hcsr04 hcsr04: /dev/hcsr04 ready |
Ось воно. probe. /dev/hcsr04 ready. Жодного -517.
Важливо тут те, що тепер ядро само викликає мій probe() — тоді, коли Device Tree буде готовий віддати GPIO. Модель драйвера тепер архітектурно правильною для сучасного ядра. І перший вимір:
|
1 2 3 |
<span class="hljs-built_in">cat</span> /dev/hcsr04 16 |
Ще раз — 18. Ще — 17. Датчик міряє. Працює.
◆ Опису анального програмного болю у всій красі
Софт запрацював, але поки я доводив систему до стабільних замірів, назбирався цілий букет гемору пасток. Кожна виглядала як «GPIO не доступно» або «не працює» і все це мало свою окрему причину. Ось ці причини.
Ґуля № 1: залишений gpiomon тримає лінію → -16 EBUSY
У нумерології число 16 символізує духовний розвиток, трансформацію та пошук балансу. Оскільки сума цифр (1+6=7) дорівнює 7, воно поєднує лідерство одиниці, турботу шістки та глибоку мудрість сімки. В ангельській нумерології число 16 (і його подвоєння 16:16 на годиннику) — це потужне послання від Всесвіту, яке закликає вас переглянути свої пріоритети, відпустити минуле і знайти баланс між матеріальним та духовним
Під час діагностики я запустив gpiomon у фоні (&), щоб подивитись фронти Echo. Потім вивантажив модуль, перезавантажив — і:
|
1 2 3 |
hcsr04 hcsr04: probe hcsr04 hcsr04: failed <span class="hljs-keyword">to</span> <span class="hljs-keyword">get</span> echo gpio: <span class="hljs-number">-16</span> hcsr04 hcsr04: probe <span class="hljs-keyword">with</span> driver hcsr04 failed <span class="hljs-keyword">with</span> error <span class="hljs-number">-16</span> |
-16 = EBUSY. Але probe цього разу викликався (бачимо в dmesg)! Ресурс реально зайнятий — моїм же фоновим gpiomon, який досі тримав GPIO24. Зверни увагу: помилка інша — -16 EBUSY, а не -517 EPROBE_DEFER. Обидві виглядають як «не можу взяти GPIO», але причини протилежні: -517 = «ще не готове, спробую пізніше», -16 = «зайнято прямо зараз».
Комбінація 16 і 517 — це сильний ангельський знак, який символізує злам старого життя та сприятливі зміни. Всесвіт закликає вас відпустити застарілі страхи або рутини і довіритися новим можливостям, які прийдуть завдяки вашому оптимізму та правильному вибору.
Лікуємо — вбиваючи фонові процеси:
|
1 2 3 |
sudo pkill gpiomon sudo pkill gpioset ps aux | grep -E <span class="hljs-string">"gpiomon|gpioset"</span> | grep -v grep |
Ґуля № 2: Permission denied — та сама, що з /dev/mydev
Модуль завантажений, /dev/hcsr04 існує, а cat без sudo:
|
1 |
<span class="hljs-built_in">cat</span>: /dev/hcsr04: Permission denied |
device_create створює вузол з правами 0600 — тільки root. Точно як у статті Місяць 4, Тиждень 3: пишемо /dev/mydev — character device driver. Частина 13. Рішення те саме — udev rule:
|
1 2 3 4 |
<span class="hljs-built_in">echo</span> <span class="hljs-string">'KERNEL=="hcsr04", MODE="0666"'</span> | sudo <span class="hljs-built_in">tee</span> /etc/udev/rules.d/99-hcsr04.rules sudo udevadm control --reload-rules sudo rmmod hcsr04 && sudo insmod hcsr04.ko <span class="hljs-built_in">ls</span> -la /dev/hcsr04 |
Після цього crw-rw-rw- і cat без sudo працює.
Ґуля № 3: читаємо throttled=0x50000 як бітову маску
У логах раз по раз блимав Undervoltage detected!. Тре брати блок живлення від 2А. Замінив блок живлення, перевіряю
|
1 2 3 |
vcgencmd get_throttled throttled=0x50000 |
Це бітова маска. 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
Запускаю цикл замірів:
|
1 2 3 4 |
<span class="hljs-keyword">while</span> <span class="hljs-literal">true</span>; <span class="hljs-keyword">do</span> <span class="hljs-built_in">printf</span> <span class="hljs-string">"\rDistance: %3s cm"</span> <span class="hljs-string">"<span class="hljs-subst">$(cat /dev/hcsr04)</span>"</span> <span class="hljs-built_in">sleep</span> 0.1 <span class="hljs-keyword">done</span> |
Здебільшого рівні числа — 37, 36, 118, 120, 115. Але подекуди зриви: то timeout falling, то дичина 666, 719. І це не баг коду. Це фундамент. Моя polling-версія крутить cpu_relax() у циклі й міряє час через ktime_get(). Планувальник Linux може перервати цей цикл будь-коли — і тоді виміряний інтервал розтягується, даючи фальшиво велике значення. Linux не гарантує таймінг на рівні мікросекунд. Ось той самий контраст, що тягнеться крізь усю серію: на bare-metal STM32 з апаратним input capture ці ж кілька мікросекунд ловились би таймером точно, без участі планувальника. На Linux — ні. Не тому що Linux гірший, а тому що в нього інша задача: він керує сотнею процесів, а не одним датчиком.
◆ Цікава деталь: після reboot пристрій «зникає»
Ще один момент, який спершу лякає. Після перезавантаження Pi:
|
1 |
<span class="hljs-built_in">cat</span>: /dev/hcsr04: No such file or directory |
Здається, драйвер «зламався». Насправді ні. Device Tree overlay лише повідомляє ядру про існування пристрою. Сам модуль треба або завантажити вручну (insmod), або встановити в систему, щоб він підхоплювався автоматично:
|
1 2 3 |
sudo <span class="hljs-built_in">cp</span> hcsr04.ko /lib/modules/$(<span class="hljs-built_in">uname</span> -r)/extra/ sudo depmod <span class="hljs-built_in">echo</span> hcsr04 | sudo <span class="hljs-built_in">tee</span> /etc/modules-load.d/hcsr04.conf |
Тоді після кожного завантаження overlay створить пристрій, а modules-load підтягне драйвер, і /dev/hcsr04 з’явиться сам.
◆ Висновок
Найцінніший результат цієї статті, як на мене це ангельська нумерологія, хоча може і розуміння того, що більшість навчальних прикладів застарілі, теж цінно, але ж одна помилка. як ми з’ясували -517 виявилась не багом, а ангельською підказкою:
«Не борись із Driver Model. Працюй разом із нею.»
Попереду Місяць 5 — FreeRTOS на STM32. Далі буде…