STM32 з нуля без HAL: Device Tree, три UART і Python на борту. Частина 7

Серія “STM32 з нуля без HAL” • Місяць 3 • Тижні 1-2

Luckfox Pico Pro як мозок, STM32 як м’язи — перша справжня embedded Linux архітектура

Про що ця стаття

Привіт! Ми продовжуємо серію про STM32 без HAL. В минулих статтях ми розібрали Buildroot, зібрали свій Linux-образ, навчились додавати пакети і розібрали U-Boot. Тепер час для наступного кроку — Device Tree.

Років 10 тому я робив домашню автоматизацію на Arduino Pro Mini з W5500 Ethernet шилдом. Паяв модулі, підключав RJ45, писав скетчі. Щоб мати Ethernet на мікроконтролері — потрібен був окремий чіп, окрема плата, окремий стек бібліотек. 

 

А тепер Luckfox Pico Pro за $15 має Ethernet вбудований, повноцінний Linux з Python, SSH, веб-сервером, і ще NPU для нейромереж зверху. Шлях від паяння Ethernet модуля до вбудованого мережевого стеку — це і є еволюція embedded розробки. І ця серія статей — спроба цю еволюцію пройти і задокументувати.

Розум. Код. Характер. І кожен байт — це крок до перемоги. З таким девізом ми йдемо далі.

Device Tree — це те, без чого не працює жоден embedded Linux пристрій на ARM. Але коли я вперше відкрив DTS файл, то побачив купу рядків з hex-адресами і подумав: “мда, з розбігу не зазкочити”. А потім згадав, що на STM32 я писав ті ж самі hex-адреси руками — і все стало на місця.

В цій статті ми:

Розберемо DTS файл Luckfox Pico Pro рядок за рядком

Порівняємо кожну DTS властивість з тим, що ми робили на STM32 bare-metal

Змінимо один рядок в DTS — і отримаємо новий serial порт

Підключимо STM32 до Luckfox через UART

Напишемо Python скрипт, який керує STM32 з Linux

І трохи зазирнемо в NPU — що це за звір на борту нашої плати

Частина 1. Device Tree — що це і навіщо

Проблема: Linux не знає що на платі

На STM32 ми писали прямо в регістри:

Ми знали адресу кожного регістра, бо працювали з одним конкретним чіпом. Але Linux — універсальне ядро. Воно працює на тисячах різних плат. Звідки йому знати, що на нашому Luckfox UART2 сидить за адресою 0xff4c0000, а не 0x40013800 як на STM32?

Відповідь — Device Tree. Це файл, який описує все залізо на платі: які контролери є, за якими адресами, які переривання використовують, які піни зайняті. Ядро читає цей файл при завантаженні і знає з чим працювати.

Структура файлів: як матрьошка

Відкриваємо DTS нашої плати і бачимо три include:

Це як в нашому HAL: rv1106.dtsi — це hal.h де описані всі можливості чіпа, а DTS плати — це main.c де ми обираємо що використовувати.

Root node: хто ми такі

model — людська назва плати. compatible — список від конкретного до загального. Ядро спочатку шукає драйвер для rv1103g-38×38-ipc-v10, не знайшло — пробує rv1106g3. Як fallback.

UART2 в DTS vs USART1 на STM32

Ось опис UART2 в базовому rv1106.dtsi:

Тепер порівняємо з тим, що ми робили на STM32 в нашому hal_uart.c:

reg = <0xff4c0000 0x100> — базова адреса і розмір блоку регістрів. На STM32 ми писали #define USART1_BASE 0x40013800. Та сама ідея, тільки в DTS ядро зробить ioremap() замість прямого доступу.

interrupts = <GIC_SPI 27 …> — UART2 використовує переривання 27 на GIC. На STM32 ми знали що USART1 = IRQ37 і писали NVIC_ISER1 |= (1 << 5). Тут замість NVIC — GIC (бо Cortex-A7 замість Cortex-M3), але ідея та сама.

clock-frequency = <24000000> — вхідна частота 24MHz. У нас на STM32 було 8000000UL / baud для розрахунку BRR. Тут ядро зробить аналогічний розрахунок, тільки з 24MHz.

clocks = <&cru SCLK_UART2>, <&cru PCLK_UART2> — два клоки від CRU. Як ми робили RCC_APB2ENR |= (1 << 14) для ввімкнення тактування USART1. Ядро зробить це автоматично через clock framework.

pinctrl-0 = <&uart2m1_xfer> — мультиплексор пінів. Як ми ставили PA9 в alternate function (0xB) для TX. Тут ядро зробить це через pinctrl підсистему.

status = “disabled” — за замовчуванням вимкнений. В базовому DTSI ВСІ контролери disabled. Плата вмикає тільки те що реально підключено. Як на STM32 — периферія є в чіпі, але без тактування не працює.

Бачите 0xff4c0000? Ця ж адреса з’являється в bootargs ядра: earlycon=uart8250,mmio32,0xff4c0000. DTS описує адресу, bootargs використовує її для ранньої консолі. Коло замкнулося.

Pinctrl: як DTS описує фізичні піни

Формат: <bank pin function pull_config>. Bank 1, Port B pin 2, alternate function 2 = UART2 TX, підтяжка вгору. Порівняйте з нашим STM32 кодом:

Та ж ідея — ми ставили PA9 в alternate function для UART TX. Тут DTS каже: “GPIO1_B2 — це alternate function 2, що означає UART2 TX”. Тільки на STM32 ми знали це з даташиту і прописували вручну, а тут ядро читає DTS і робить все автоматично.

M0, M1, M2 — варіанти пінів, але не свобода

M1 в назві — це варіант розводки пінів (mux variant). RV1106 дозволяє вивести один і той же UART на різні фізичні піни. На STM32F103 аналог — AFIO_MAPR remap (наприклад USART1 можна перекинути з PA9/PA10 на PB6/PB7).

Але є важливий нюанс: ви не можете довільно призначити будь-який пін на будь-яку функцію. Варіанти M0/M1/M2 — це фіксовані комбінації, зашиті в кремнії чіпа. Можна обрати тільки з того що виробник передбачив.

Ми це з’ясували на практиці. На нашій платі конектор SIM800L потрапляє на піни 11-12. Пін 11 — це UART0_TX_M1 (є TX). А пін 12 — SPI0_CS0_M0 (це вже зовсім інша периферія, не RX). Виникло питання: а може є варіант M0 або M2 де UART0_RX потрапить на потрібний пін?

Перевіряємо всі варіанти в pinctrl:

Жоден варіант не кладе UART0_RX на пін 12. TX є, а RX фізично не доступний поруч. Software UART (bit-bang RX) на Linux — це не варіант: планувальник може в будь-який момент забрати CPU на інший процес, і ми втрачаємо біти. На bare-metal STM32 можна крутитись в tight loop і ловити кожен біт з мікросекундною точністю, але Linux — не RTOS.

Висновок: коли обираєте плату або проектуєте свою PCB для Luckfox — спочатку перевірте pinctrl в DTS. Варіанти M0/M1/M2 фіксовані, і якщо потрібний пін не виведений на гребінку — ніякий DTS overlay не допоможе.

Для SIM800L рішення просте — підключаємо через інший конектор (U3 → UART1), де TX і RX поруч. А конектор SIM800L використовуємо для чогось що потребує тільки TX.

Консоль: FIQ Debugger

Цікава знахідка: ми шукали де вмикається UART2 для консолі і не знайшли звичайного status = “okay”. Виявилось, Rockchip використовує FIQ Debugger:

FIQ (Fast Interrupt Request) — переривання з вищим пріоритетом ніж звичайний IRQ. Rockchip перехоплює UART2 через FIQ, щоб консоль працювала навіть коли ядро зависло. Тому консоль — це ttyFIQ0, а не ttyS2.

Це означає що UART2 зайнятий. Для зв’язку зі STM32 потрібно використовувати інший UART — і ось тут починається найцікавіше.

Частина 2. Один рядок в DTS — і все працює

Знаходимо вільні UART

В DTS нашої плати бачимо:

Обидва вимкнені. Дивимось на pinout Luckfox — UART3 виведений на піни 19-20 (конектор U4 на нашій платі), UART4 — на піни 6-7 (гребінка U11). Ідеально.

Що робимо? Змінюємо один рядок:

Pinctrl вже прописаний в базовому rv1106.dtsi (pinctrl-0 = <&uart3m1_xfer>), тому більше нічого додавати не треба.

Збираємо і прошиваємо

Чому flashcp і mtd3? На Luckfox flash пам’ять поділена на MTD розділи. MTD (Memory Technology Devices) — це підсистема Linux для роботи з flash напряму. Boot розділ (ядро + DTB) сидить на mtd3. flashcp стирає блок і записує нове ядро. Це як st-flash write firmware.bin 0x08000000 на STM32 — та ж ідея, тільки через Linux підсистему.

Повна розмітка MTD на Luckfox:

Перевіряємо результат

Після ребуту — один SSH запит і бачимо:

Один рядок в DTS — і з’явився /dev/ttyS3. Ядро побачило status = “okay”, завантажило драйвер snps,dw-apb-uart, зробило ioremap(0xff4d0000), налаштувало pinctrl, і створило пристрій. Все те що ми робили на STM32 руками за десятки рядків коду — одна декларативна властивість.

Вмикаємо всі три UART

Раз вже розібрались — вмикаємо одразу три UART для максимальної гнучкості:

Перезбираємо, прошиваємо, і тепер:

Три serial порти для периферії. Підключай три STM32, або STM32 + SIM800L + щось ще.

Частина 3. Плата Pi Pico Sim800 — як макетка для Luckfox

У мене є кастомна плата Pi Pico Sim800 v3.1 — розроблена під Raspberry Pi Pico для IoT проекту з SIM800L і реле (про цей проект — окрема стаття на DOU). Luckfox Pico Pro має такий же форм-фактор і гребінку 2×20, тому вставляється замість Pi Pico.

Але є нюанс — піни мають різні функції. На Pi Pico пін 19 — це GP14 (I2C SDA), а на Luckfox — це UART3_TX. Тому я зробив повний маппінг цих двох плат може комусь теж буде корисно.

Raspberry Pi Pi Pico

LuckFox pico

Що реально розпаяно на платі

Плата — це по суті breakout board з конекторами. Єдине що розпаяно постійно — зсувний регістр SN74HC595N на пінах 4, 5, 9 (DATA, LATCH, CLOCK), з якого виведено 8 реле. Решта — знімні конектори для модулів: SIM800L, BMP180, PIR.

Маппінг конекторів

Ключова таблиця — які конектори плати відповідають яким UART на Luckfox:

U4 (BMP180 #1), піни 19-20 → UART3 — основний для STM32

U3 (BMP180 #2), піни 21-22 → UART1 — другий STM32 або SIM800L

U11 (гребінка), піни 6-7 → UART4 — додаткова периферія

Піни 1-2 → UART2 (FIQ debugger) — зайнятий консоллю

Крім UART, на гребінці U11 є повний SPI0 (піни 14-17) — для дисплеїв, flash, тощо.

SIM800L конектор — половинка UART

Конектор SIM800L (піни 11-12) потрапляє на UART0_TX і SPI0_CS0 на Luckfox. TX є, а RX на сусідньому піні немає. Software UART на Linux, як на Ардуїно робити ризиковано бо планувальник може забрати CPU і буде втрачено біти. Тому у моєму випадку для SIM800L краще використати інший конектор — наприклад U3 (UART1).

Зсувний регістр — GPIO bit-bangSN74HC595N підключений до пінів 4, 5, 9. На Luckfox ці піни не збігаються з SPI контролером, тому апаратний SPI не вийде. Але GPIO bit-bang працює чудово — для 8 реле швидкості вистачить з головою.

Це тема для окремої статті — як керувати зсувним регістром з Linux через sysfs.

Частина 4. Підключаємо STM32 — перший міст

Ніяких паразитних сигналів, тільки чистий UART.

Фізичне підключення

Обидві плати 3.3V — конвертер рівнів не потрібен. Ті самі три дроти що раніше йшли до CH340 USB-TTL адаптера, тепер йдуть прямо в Luckfox.

STM32 сторона — нічого не міняємо

На Blue Pill працює той самий код з нашого HAL — uart_init(9600), uart_gets(), uart_puts(). STM32 не знає і не повинен знати що на іншому кінці замість minicom тепер Linux. Для нього це просто UART.

Linux сторона — перший тест

З Luckfox по SSH:

LED на Blue Pill загорівся, в терміналі з’явилось “LED on”. Luckfox керує STM32. Мозок командує м’язами.

Частина 5. Python контролер — робимо по-людськи

echo в /dev/ttyS3 працює, але це не серйозно. Пишемо нормальний Python скрипт:

Скрипт підтримує змінну оточення STM32_PORT для роботи з різними UART:

Тестуємо blink — три рази блимнути LED:

Linux керує STM32 через Python. Це вже не мигання LED — це архітектура. Мозок (Luckfox з Linux) дає команди м’язам (STM32 з bare-metal прошивкою).

Частина 6. А що тут за NPU?

Поки розбирались з DTS, звернули увагу на цікаву ноду:

NPU — Neural Processing Unit. На нашій $15 платі є нейроприскорювач на 0.5 TOPS. Перевіряємо:

Драйвер працює, C бібліотека є. А в SDK Luckfox знаходимо проект rk_smart_door з готовими моделями нейромереж: детекція облич, розпізнавання, визначення повороту голови. Python binding, правда, немає — тільки C API через rknn_api.h.

Це відкриває цікаву перспективу: камера на Luckfox знімає, NPU розпізнає обличчя, через UART дає команду STM32, а той вмикає реле через зсувний регістр. Повний ланцюжок від камери до фізичної дії — на двох платах за $20.

Але це вже тема наступної статті. Поки що залишаємо NPU як інтригу.

Філософія: SOLID для заліза

Є спокуса одразу будувати щось велике — розумний будинок, систему відеоспостереження, автономного робота. Але великі проекти вбивають мотивацію, бо результат далеко, а проблеми — ось вони, прямо зараз.

Ми в цій серії дотримуємось іншого підходу, який нагадує принцип єдиної відповідальності з SOLID: одна задача — один проект. Кожен проект маленький, закінчений і самодостатній. Але з інтерфейсами для з’єднання з іншими.

Подивіться що ми вже маємо:

STM32 HAL — bare-metal бібліотека з чистим UART інтерфейсом. Не знає нічого про Linux.

Buildroot образ — Linux система з Go сервером, SSH, Python. Не знає нічого про STM32.

Device Tree — один рядок status = “okay” з’єднує ці два світи через /dev/ttyS3.

Python контролер — абстракція над UART. send_command(“on”) і все.

Плата Pi Pico Sim800 — фізична абстракція. Конектори як інтерфейси: встроми модуль і він працює.

Кожен шматочок можна використати окремо. Але разом вони починають складатись в систему: IoT розетка з SMS-керуванням (Pi Pico + SIM800L) → IP-камера зі стрімом (Luckfox) → Linux керує STM32 через UART → NPU розпізнає обличчя і вмикає реле.

Не треба будувати ракету одразу. Треба будувати модулі з правильними інтерфейсами — і одного дня вони самі складуться в ракету.

 

Підсумок

За два тижні ми пройшли від “DTS — це китайська грамота” до працюючої системи де Linux керує STM32:

Розібрали DTS свого пристрою і побачили ті ж концепції що на STM32 bare-metal

Змінили один рядок — отримали три нових UART

Підключили STM32 до Luckfox і написали Python контролер

Зрозуміли як працює MTD, flashcp, і часткова прошивка

Зазирнули в NPU і побачили що там чекає

Далі — GStreamer для відеостріму і детекція облич через NPU. Камера вже працює (стрім ми налаштували раніше), тепер додамо до нього мізки.

А якщо хочете побачити звідки ростуть ноги всієї цієї екосистеми — ось попередні проекти:

IoT PowerHub — Pi Pico + SIM800L + реле + MQTT — dou.ua/forums/topic/56126/

Та сама плата Pi Pico Sim800, тільки з Pi Pico замість Luckfox. Реле, зсувний регістр, GSM — все що ми сьогодні маппили на нові піни.

Luckfox Pico Pro — від ATmega8 до відеостріму — dou.ua/forums/topic/58244/

Перше знайомство з Luckfox, камера, MJPEG стрім. Тепер додаємо до цього STM32 і NPU.

Бачите патерн? Маленькі проекти з правильними інтерфейсами поступово складаються в щось більше. Може колись і на Автобота комп’ютерний зір прикрутимо — камера є, NPU є, UART до мотор-контролера теж є. Всі модулі на місці.

Довідники

dts_theory_guide.md — теорія DTS з порівнянням STM32 bare-metal на кожному кроці

dts_commands_cheatsheet.md — практичні команди для пошуку, перегляду, компіляції і діагностики DTS

Репозиторій: github.com/pipicosim800-maker/stm32F103

 

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

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