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 виводив диистанцію в сантиметрах.
Userspace │ cat /dev/hcsr04 ▼ read() │ ▼ Trigger GPIO ──► HC-SR04 ──► Echo GPIO │ ▼ Відстань у сантиметрах
Сам алгоритм вимірювання — тривіальний:
- сформувати 10 мкс імпульс на Trigger;
- дочекатися фронту Echo;
- виміряти скільки Echo тримається у HIGH;
- поділити тривалість (мкс) на 58;
- повернути сантиметри.
Формула distance_cm = duration_us / 58 — це швидкість звуку (~343 м/с) з поправкою на те, що сигнал іде туди й назад. Класика, ардуінщика HC-SR04, що робиться 5 хвилин. Я взяв рівно той підхід, що демонструють сотні туторіалів — legacy GPIO API:
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’а:
make sudo insmod hcsr04.ko
insmod<!--TgQPHd||[]-->(від англ. insert module) — це стандартна команда Linux, яка використовується для ручного завантаження окремого модуля безпосередньо в ядро операційної системи. На відміну від більш розумної командиmodprobe<!--TgQPHd||[]-->,insmod<!--TgQPHd||[]-->потребує точного шляху до файлу модуля (із розширенням.ko<!--TgQPHd||[]-->) та не завантажує інші модулі, від яких він залежить.
А insmod одразу викидає такі коні:
insmod: ERROR: could not insert module hcsr04.ko: Unknown error 517
Утиліта dmesg (від англ. diagnostic message) у Linux — це потужний інструмент для перегляду та керування кільцевим буфером повідомлень ядра. Вона дозволяє відстежувати процеси завантаження системи, ініціалізації драйверів та виявляти апаратні або системні помилки.
І в dmesg:
hcsr04: loading out-of-tree module taints kernel. hcsr04: gpio_request(23) failed
Ніяких пояснень. Просто «failed» і загадкове число 517. Цікаво що про це число скаже Соломія мій бот нумеролог.
У нумерології число 517 є потужним символом особистісної свободи, духовного пробудження та позитивних життєвих змін. Воно поєднує енергію трьох цифр:
- 5 — відповідає за зміни, пригоди та адаптивність.
- 1 — символізує нові починання, лідерство та створення власної реальності.
- 7 — уособлює духовність, мудрість та глибокий самоаналіз.
Сума цих цифр (5 + 1 + 7 = 13, а 1 + 3 = 4) зводить число 517 до вібрації четвірки, яка додає енергію стабільності, працездатності та побудови міцного фундаменту. Якщо ви часто бачите 517, це знак від Всесвіту (або ваших ангелів-охоронців), що ваші нещодавні рішення були правильними, і вас чекає період духовного зростання. Ангели постійно показуватимуть вам число 517, коли захочуть привернути вашу увагу. Вони дуже цікавляться вашим життям. Тому не турбуйтеся про речі, які ви не можете контролювати. Нехай з ними розуміються ваші ангели. Зосередьтеся на тому, в чому ви найкраще знаєтеся. Ангели допоможуть вам процвітати незалежно від поточної ситуації.
Отакої, якось вже не дуже мені весело, невже це знак покинути і йти займатись тим на чому я найкраще знаюсь? Піду заварю чаю.
◆ Перша (хибна) гіпотеза: пін зайнятий
Найлогічніше припущення — GPIO вже кимось захоплений. Перевіряємо:
gpioinfo | grep "input" | grep -v "consumer" | head -10 line 0: "ID_SDA" input line 1: "ID_SCL" input line 2: "GPIO2" input line 3: "GPIO3" input line 4: "GPIO4" input line 5: "GPIO5" input ...
Лінії вільні. Жодного consumer. Жодного конфлікту. Далі я зробив те, за що мені соромно: почав перебирати піни. GPIO4/5 — failed. GPIO16/17 — failed. GPIO23/24 — failed. Витратив години, міняючи номери в коді, ловлячи то -517, то -16, і не розуміючи чому навіть явно вільні відповідно доgpioinfo піни не беруться.
✏️ Видно ангели дуже серйозно мною зацікавились. Мабуть кажуть мені, друже не займайся херньою йди зроби щось корисне, можеш в кімнаті поприбиратись
Як з’ясувалось пізніше, видно ресурс чисел в ангелів закінчився), проблема була не в тому, які піни. Проблема була в тому, що я читав -517 як «зайнято», а воно ж то означає зовсім інше.
◆ Що насправді означає -517
Помилка Unknown error 517 (або —517 у системних логах) у Linux — це код EPROBE_DEFER<!--TgQPHd||[]-->. Вона означає, що драйвер пристрою або модуль ядра намагається завантажитись, але потрібний йому базовий компонент (наприклад, шина або контролер) ще не ініціалізовано системою. Ядро автоматично перенесе завантаження на пізніше, коли залежність буде готова.
Розшифровка коду:
-517 = EPROBE_DEFER
Це не «зайнято». Це ядро буквально каже:
«Я ще не готове віддати тобі цей ресурс. Повернись пізніше.»
І ось де захована пастка. Legacy-драйвер вантажиться через module_init(), а module_init() викликається рівно один раз:
module_init() │ ▼ gpio_request() ──► EPROBE_DEFER (-517) │ ▼ insmod завершується з помилкою
«Пізніше» вже ніколи не настане, краще вже не буде, бо, як зясувалось немає механізму, який би повторив спробу. Драйвер, побудований на голому module_init + gpio_request, на сучасному ядрі приречений — і жоден вибір піна цього не виправить. ⚠ Ключове усвідомлення: тре змінювати не код, тре змінювати себе, бо Ангели вже прям кричать про це).
◆ Як Linux хоче, щоб це робилось у 2026
Колись драйвер міг просто запопросити GPIO по номеру. А от сучасний Linux вимагає іншу модель — з відкладеним, керованим ядром стартом:
Device Tree ← описує, який пристрій існує і на яких пінах │ ▼ Platform Device ← ядро створює пристрій з опису DT │ ▼ Platform Driver ← probe() викликається КОЛИ ресурс готовий │ ▼ GPIO 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
Тепер піни живуть не в
// hcsr04-overlay.dts
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target-path = "/";
__overlay__ {
hcsr04: hcsr04 {
compatible = "alex,hcsr04";
trig-gpios = <&gpio 23 0>;
echo-gpios = <&gpio 24 0>;
status = "okay";
};
};
};
};
compatible = "alex,hcsr04" — це «замок», до якого драйвер підбере «ключ». trig-gpios / echo-gpios — імена, які драйвер шукатиме через gpiod.
Драйвер: probe замість module_init
static int hcsr04_probe(struct platform_device *pdev)
{
struct hcsr04_dev *data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
/* беремо лінії за ІМЕНЕМ, не за номером */
data->trig = devm_gpiod_get(&pdev->dev, "trig", GPIOD_OUT_LOW);
if (IS_ERR(data->trig))
return PTR_ERR(data->trig);
data->echo = devm_gpiod_get(&pdev->dev, "echo", GPIOD_IN);
if (IS_ERR(data->echo))
return PTR_ERR(data->echo);
/* ... character device як у статті 13 ... */
}
static const struct of_device_id hcsr04_of_match[] = {
{ .compatible = "alex,hcsr04" }, /* той самий "замок" */
{ }
};
MODULE_DEVICE_TABLE(of, hcsr04_of_match);
static struct platform_driver hcsr04_driver = {
.probe = hcsr04_probe,
.remove = hcsr04_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = hcsr04_of_match,
},
};
module_platform_driver(hcsr04_driver);
Зверни увагу: немає gpio_request, немає номерів пінів, немає ручного module_init. devm_gpiod_get(&pdev->dev, "trig", ...) бере лінію trig, опис якої ядро витягло з overlay. devm_ означає, що ядро само звільнить ресурс при вивантаженні — менше ручного cleanup, менше шансів залишити висіти GPIO.
// hcsr04.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/ktime.h>
#include <linux/delay.h>
#define DRIVER_NAME "hcsr04"
#define BUFFER_SIZE 32
struct hcsr04_dev {
struct gpio_desc *trig;
struct gpio_desc *echo;
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *device;
struct mutex lock;
char buf[BUFFER_SIZE];
size_t len;
};
static struct hcsr04_dev *hcsr04;
static ssize_t hcsr04_measure(struct hcsr04_dev *data)
{
int timeout;
u64 start_us, end_us, duration_us;
long distance_cm;
gpiod_set_value(data->trig, 0);
udelay(2);
gpiod_set_value(data->trig, 1);
udelay(10);
gpiod_set_value(data->trig, 0);
timeout = 100000;
while (!gpiod_get_value(data->echo) && timeout-- > 0)
cpu_relax();
if (timeout <= 0)
return snprintf(data->buf, BUFFER_SIZE, "timeout rising\n");
start_us = ktime_to_us(ktime_get());
timeout = 100000;
while (gpiod_get_value(data->echo) && timeout-- > 0)
cpu_relax();
if (timeout <= 0)
return snprintf(data->buf, BUFFER_SIZE, "timeout falling\n");
end_us = ktime_to_us(ktime_get());
duration_us = end_us - start_us;
distance_cm = duration_us / 58;
if (distance_cm < 2 || distance_cm > 400)
return snprintf(data->buf, BUFFER_SIZE, "error %ld\n", distance_cm);
return snprintf(data->buf, BUFFER_SIZE, "%ld\n", distance_cm);
}
static ssize_t hcsr04_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *ppos)
{
ssize_t ret;
struct hcsr04_dev *data = filp->private_data;
mutex_lock(&data->lock);
ret = hcsr04_measure(data);
if (ret < 0) {
mutex_unlock(&data->lock);
return ret;
}
data->len = ret;
if (*ppos >= data->len) {
mutex_unlock(&data->lock);
return 0;
}
count = min(count, data->len - (size_t)*ppos);
if (copy_to_user(ubuf, data->buf + *ppos, count)) {
mutex_unlock(&data->lock);
return -EFAULT;
}
*ppos += count;
mutex_unlock(&data->lock);
return count;
}
static int hcsr04_open(struct inode *inode, struct file *filp)
{
filp->private_data = hcsr04;
return 0;
}
static const struct file_operations hcsr04_fops = {
.owner = THIS_MODULE,
.open = hcsr04_open,
.read = hcsr04_read,
};
static int hcsr04_probe(struct platform_device *pdev)
{
int ret;
struct hcsr04_dev *data;
dev_info(&pdev->dev, "probe\n");
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
mutex_init(&data->lock);
data->trig = devm_gpiod_get(&pdev->dev, "trig", GPIOD_OUT_LOW);
if (IS_ERR(data->trig)) {
ret = PTR_ERR(data->trig);
dev_err(&pdev->dev, "failed to get trig gpio: %d\n", ret);
return ret;
}
data->echo = devm_gpiod_get(&pdev->dev, "echo", GPIOD_IN);
if (IS_ERR(data->echo)) {
ret = PTR_ERR(data->echo);
dev_err(&pdev->dev, "failed to get echo gpio: %d\n", ret);
return ret;
}
ret = alloc_chrdev_region(&data->dev_num, 0, 1, DRIVER_NAME);
if (ret)
return ret;
cdev_init(&data->cdev, &hcsr04_fops);
data->cdev.owner = THIS_MODULE;
ret = cdev_add(&data->cdev, data->dev_num, 1);
if (ret)
goto err_unregister;
data->class = class_create(DRIVER_NAME);
if (IS_ERR(data->class)) {
ret = PTR_ERR(data->class);
goto err_cdev;
}
data->device = device_create(data->class, NULL, data->dev_num, NULL, DRIVER_NAME);
if (IS_ERR(data->device)) {
ret = PTR_ERR(data->device);
goto err_class;
}
platform_set_drvdata(pdev, data);
hcsr04 = data;
dev_info(&pdev->dev, "/dev/%s ready\n", DRIVER_NAME);
return 0;
err_class:
class_destroy(data->class);
err_cdev:
cdev_del(&data->cdev);
err_unregister:
unregister_chrdev_region(data->dev_num, 1);
return ret;
}
static void hcsr04_remove(struct platform_device *pdev)
{
struct hcsr04_dev *data = platform_get_drvdata(pdev);
device_destroy(data->class, data->dev_num);
class_destroy(data->class);
cdev_del(&data->cdev);
unregister_chrdev_region(data->dev_num, 1);
dev_info(&pdev->dev, "removed\n");
}
static const struct of_device_id hcsr04_of_match[] = {
{ .compatible = "alex,hcsr04" },
{ }
};
MODULE_DEVICE_TABLE(of, hcsr04_of_match);
static struct platform_driver hcsr04_driver = {
.probe = hcsr04_probe,
.remove = hcsr04_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = hcsr04_of_match,
},
};
module_platform_driver(hcsr04_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex");
MODULE_DESCRIPTION("HC-SR04 platform driver with gpiod");
MODULE_VERSION("0.3");
Makefile: збираємо і модуль, і overlay
obj-m += hcsr04.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: make -C $(KDIR) M=$(PWD) modules dtc -@ -I dts -O dtb -o hcsr04.dtbo hcsr04-overlay.dts clean: make -C $(KDIR) M=$(PWD) clean rm -f hcsr04.dtbo
⚠ Прапорець -@ у dtc критичний — він додає symbols в overlay, без нього посилання &gpio не зарезолвиться. Легко пропустити й потім не розуміти, чому overlay не вантажиться.
◆ Складання і встановлення
which dtc || sudo apt install device-tree-compiler make clean make ls -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 і прописуємо його в конфіг завантажувача:
sudo cp hcsr04.dtbo /boot/firmware/overlays/ echo "dtoverlay=hcsr04" | sudo tee -a /boot/firmware/config.txt sudo reboot
⚠ Шлях/boot/firmware/overlays/— це Raspberry Pi OS Bookworm. На старіших версіях це/boot/overlays/. Перевір свій:
ls /boot/firmware/overlays/ >/dev/null 2>&1 && echo "→ /boot/firmware/" \ || ls /boot/overlays/ >/dev/null 2>&1 && echo "→ /boot/"
![]()
Як видно мій результат → /boot/firmware/ чітко підтверджує, що на малинці встановлена нова версія операційної системи (Raspberry Pi OS Bookworm або новіша).
Розділ boot змонтовано саме в /boot/firmware/, тому якщо знадобиться вручну правити конфігураційний файл (наприклад, увімкнути якийсь екран чи апаратний модуль), шукатииму файл config.txt саме там: sudo nano /boot/firmware/config.txt
◆ Момент істини
Після перезавантаження перевіряємо, що overlay створив пристрій, і вантажимо модуль:
ls /proc/device-tree/hcsr04/ 2>/dev/null && echo "overlay OK" sudo insmod hcsr04.ko dmesg | tail -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. Модель драйвера тепер архітектурно правильною для сучасного ядра. І перший вимір:
cat /dev/hcsr04 16
Ще раз — 18. Ще — 17. Датчик міряє. Працює.
◆ Опису анального програмного болю у всій красі
Софт запрацював, але поки я доводив систему до стабільних замірів, назбирався цілий букет гемору пасток. Кожна виглядала як «GPIO не доступно» або «не працює» і все це мало свою окрему причину. Ось ці причини.
Ґуля № 1: залишений gpiomon тримає лінію → -16 EBUSY
У нумерології число 16 символізує духовний розвиток, трансформацію та пошук балансу. Оскільки сума цифр (1+6=7) дорівнює 7, воно поєднує лідерство одиниці, турботу шістки та глибоку мудрість сімки. В ангельській нумерології число 16 (і його подвоєння 16:16 на годиннику) — це потужне послання від Всесвіту, яке закликає вас переглянути свої пріоритети, відпустити минуле і знайти баланс між матеріальним та духовним
Під час діагностики я запустив gpiomon у фоні (&), щоб подивитись фронти Echo. Потім вивантажив модуль, перезавантажив — і:
hcsr04 hcsr04: probe hcsr04 hcsr04: failed to get echo gpio: -16 hcsr04 hcsr04: probe with driver hcsr04 failed with error -16
-16 = EBUSY. Але probe цього разу викликався (бачимо в dmesg)! Ресурс реально зайнятий — моїм же фоновим gpiomon, який досі тримав GPIO24. Зверни увагу: помилка інша — -16 EBUSY, а не -517 EPROBE_DEFER. Обидві виглядають як «не можу взяти GPIO», але причини протилежні: -517 = «ще не готове, спробую пізніше», -16 = «зайнято прямо зараз».
Комбінація 16 і 517 — це сильний ангельський знак, який символізує злам старого життя та сприятливі зміни. Всесвіт закликає вас відпустити застарілі страхи або рутини і довіритися новим можливостям, які прийдуть завдяки вашому оптимізму та правильному вибору.
Лікуємо — вбиваючи фонові процеси:
sudo pkill gpiomon sudo pkill gpioset ps aux | grep -E "gpiomon|gpioset" | grep -v grep
Ґуля № 2: Permission denied — та сама, що з /dev/mydev
Модуль завантажений, /dev/hcsr04 існує, а cat без sudo:
cat: /dev/hcsr04: Permission denied
device_create створює вузол з правами 0600 — тільки root. Точно як у статті Місяць 4, Тиждень 3: пишемо /dev/mydev — character device driver. Частина 13. Рішення те саме — udev rule:
echo 'KERNEL=="hcsr04", MODE="0666"' | sudo tee /etc/udev/rules.d/99-hcsr04.rules sudo udevadm control --reload-rules sudo rmmod hcsr04 && sudo insmod hcsr04.ko ls -la /dev/hcsr04
Після цього crw-rw-rw- і cat без sudo працює.
Ґуля № 3: читаємо throttled=0x50000 як бітову маску
У логах раз по раз блимав Undervoltage detected!. Тре брати блок живлення від 2А. Замінив блок живлення, перевіряю
vcgencmd get_throttled throttled=0x50000
Це бітова маска. 0x50000 = біти 16 і 18. Обидва в старшій половині (біти 16+) — а це означає «траплялось у минулому», не «активне зараз»:
| Біт | Значення | Що означає |
|---|---|---|
0 (0x1) | undervoltage зараз | активна проблема |
1 (0x2) | throttling зараз | активна проблема |
16 (0x10000) | undervoltage траплявся | історія |
18 (0x40000) | throttling траплявся | історія |
0x50000 = біти 16+18, тобто вже залиишки. Жодного біта
Ґуля № 4: стрибки значень і timeout falling — Linux не real-time
Запускаю цикл замірів:
while true; do printf "\rDistance: %3s cm" "$(cat /dev/hcsr04)" sleep 0.1 done
Здебільшого рівні числа — 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:
cat: /dev/hcsr04: No such file or directory
Здається, драйвер «зламався». Насправді ні. Device Tree overlay лише повідомляє ядру про існування пристрою. Сам модуль треба або завантажити вручну (insmod), або встановити в систему, щоб він підхоплювався автоматично:
sudo cp hcsr04.ko /lib/modules/$(uname -r)/extra/ sudo depmod echo hcsr04 | sudo tee /etc/modules-load.d/hcsr04.conf
Тоді після кожного завантаження overlay створить пристрій, а modules-load підтягне драйвер, і /dev/hcsr04 з’явиться сам.
◆ Висновок
Найцінніший результат цієї статті, як на мене це ангельська нумерологія, хоча може і розуміння того, що більшість навчальних прикладів застарілі, теж цінно, але ж одна помилка. як ми з’ясували -517 виявилась не багом, а ангельською підказкою:
«Не борись із Driver Model. Працюй разом із нею.»
Попереду Місяць 5 — FreeRTOS на STM32. Далі буде...
11 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівбуде з кубом чи без?
Без, буду пробувати на HAl робити
«правильна» схема із діодом, а не на резисторах
Так звичніше
дочитав поки що до
ядро ж не RT, як так?
C интересом наблюдаю приключения автора. Но если честно, слишком много пафоса, вставок о нумерологии, словно автор взял на себя роль шута и пробует смешить читателя. Также вставки в духе: «TODO: вставь сюда то-то» говорят о том, что автору лень вычитывать статью после ИИшечке
Спасибо за статью и удачи в развитии. Но очень бы хотелось видеть меньше вставок в дузе «шутка за 300»
для вас є спеціальна підбірка підходящих топіків:
dou.ua/forums/topic/60609
dou.ua/forums/topic/60501
dou.ua/forums/topic/60560
dou.ua/forums/topic/60490
Прийнято. Стосовно нумерології, дійсно тим цікавлюсь. Ну а судічи з вашого коментаря, хіба вони не праві? Тре йти і займатись тим що дійсно виходить і не брати на себя «роль шута». Та не можу з тим нічого зробити, такий мій стиль, якщо мені сумно я додаю суму, якщо весело пробую жартувати, хай і невдало. Мої опуси це пригода, а не технічні мануали
Якби ж то тільки знати чим
😂😂😂🤗 Це точно