Перейти до основного вмісту

Налагодження HardFault на Cortex-M: від загадки до рядка коду

· 10 хв читання

Кожен розробник вбудованих систем знає цей момент: прошивка працювала, а потім перестала. Налагоджувач показує, що програма сидить у нескінченному циклі з назвою на кшталт HardFault_Handler або Default_Handler, а стек викликів — це кілька беззмістовних фреймів. Цей цикл — типовий обробник зі startup-коду, і він не каже абсолютно нічого про те, що сталося. У збою є причина, і процесор уже її записав: у вісім регістрів на стеку та в чотири регістри стану. Їх залишається лише прочитати.

Ця стаття показує, як перетворити цей цикл на рядок вихідного коду, на двох реальних прикладах, які я запускав у QEMU (моделі плати з STM32F4) і досліджував за допомогою GDB: виклик NULL-вказівника на функцію та запис за адресою, де нічого немає. Усі виводи справжні.

Запуск справжньої прошивки без апаратної частини: симулятор у CI

· 8 хв читання

У статті про unit-тестування з Unity та CMock я казав, що тести на ПК не знаходять «відмінностей між ПК і MCU». Ця стаття — про ці відмінності та про інструмент, який їх знаходить: симулятор, що запускає справжній бінарний файл. Не логіку, зібрану для ПК, а саме той .elf, який створив ARM-компілятор, зі справжнім startup-кодом і справжнім linker script, на моделі процесора та його периферії.

Я покажу два експерименти, які провів: той самий вихідний код тесту, що проходить на ПК і падає на Cortex-M4, та прошивку, яка пише в регістри USART за справжніми адресами STM32F4, а її текст з'являється в терміналі. Наприкінці — чесний перелік того, чого симулятор вам не скаже.

Відтворювані збірки: зафіксуйте інструменти, зафіксуйте вихідний код, приберіть годинник

· 9 хв читання

Замовник телефонує щодо firmware, яке ви поставили чотирнадцять місяців тому. Ви завантажуєте тег, збираєте, прошиваєте — і помилки немає. Помилку виправлено чи це інше firmware? Якщо ваша збірка не відтворювана, ви не можете цього сказати. Цього не може сказати ніхто, бо бінарний файл, який ви щойно зібрали, відрізняється від поставленого так, що ви не можете ні побачити, ні пояснити: інша версія компілятора, бібліотека, оновлена на ПК, шлях до теки, час доби.

Відтворювана збірка має просте визначення: ті самі вхідні дані дають ті самі байти. Ця стаття показує, які вхідні дані має збірка firmware, як платформа Embedbits їх фіксує (Artifacts Handler), і експеримент, який я провів: дві збірки того самого коду в двох теках у два різні моменти дають два різні бінарні файли. А потім, після трьох змін, вони дають той самий — до останнього біта.

Проєктування API периферії: вісім рішень за модулем Gpio

· 9 хв читання

GPIO — найпростіша периферія мікроконтролера: пін має високий або низький рівень. Саме тому це добра тема для статті про проєктування інтерфейсу. Тут немає апаратної складності, за якою можна сховатися, і кожне рішення — це вибір проєктувальника: як називається пін, що повертає функція, де живе полярність світлодіода. MCAL-модуль Gpio з BSP від Embedbits — реальний приклад із реальними відповідями, і я пройдуся по них одну за одною, з альтернативами та ціною.

Код модуля цитується з гілки STM32H5 репозиторію Bsp-Mcal-Gpio. Приклади, що його використовують, були скомпільовані та запущені з справжніми Gpio_Port.h і Gpio_Types.h та невеликою заглушкою реалізації, щоб API можна було спробувати на ПК.

Doxygen для embedded C: документація, про яку неможливо забути

· 7 хв читання

У кожного проєкту є документація, і в кожного проєкту є документація, яка бреше. Word-файл з описом інтерфейсу був правильним у тиждень, коли його писали. Коментар над функцією чесніший, бо він за кілька рядків від коду, який описує, але його пишуть ті самі люди, які забувають. Вихід не в більшій дисципліні, а в інструменті, який читає коментарі, будує з них документацію й ламає збірку, коли чогось бракує. Цей інструмент - Doxygen.

Ця стаття показує, як у моїх проєктах виглядає задокументований модуль, як його генерують і як зробити документацію частиною CI, яку не можна оминути. Приклади зібрано з Doxygen 1.9.8 та Graphviz, а виводи - справжні.

CMake для embedded firmware: збірка, яку можна прочитати

· 10 хв читання

Проєкт в IDE - це файл, який ніхто не читає: кілька тисяч рядків XML, які IDE пише і IDE читає, і які неможливо переглянути в pull request. Збірка тоді існує лише на комп'ютері, де хтось її склікав. CMake розв'язує це іншою філософією: збірка - це текст, який ви читаєте, рев'юїте, версіонуєте й запускаєте в CI точнісінько так само, як і на своєму столі.

Система збірки платформи Embedbits (EmBi_Platform) зроблена на CMake, і ця стаття пояснює елементи, потрібні кожній embedded-збірці на CMake, на невеликому проєкті, який я зібрав і запустив: toolchain-файл, прапорці з іменами, типи збірки, модулі як бібліотеки, linker script, файли після лінкування та тест, що запускає firmware в симуляторі. Усі числа й виводи - справжні.

Переривання та main(): як безпечно ділити дані

· 9 хв читання

Обробник переривання та головний цикл - це дві програми, що працюють в одній пам'яті й нічого не знають одна про одну. Компілятор C не знає, що переривання існує, а CPU не знає, що дві змінні пов'язані між собою. Знає лише програміст, а помилки, які з цього випливають, найгірші з можливих: вони трапляються раз на тисячу запусків, зникають, коли підключаєш налагоджувач, і ніколи не з'являються на code review, бо код виглядає правильно.

Ця стаття розбирає три проблеми спільних даних, одну за одною, на коді, який не працює, а потім показує шаблони, які працюють. Експерименти виконувалися на Cortex-M4 (у QEMU, на моделі плати STM32F4) або на PC, а виводи - справжні.

Скінченні автомати на практиці: кнопка з debounce та довгим натисканням

· 9 хв читання

У першій статті про скінченні автомати я дав вам шаблон і закінчив словами «далі буде». Це продовження, а найкращий спосіб продовжити теорію — це задача. Я обрав ту, що є в кожному embedded-проєкті і яку ніхто не робить правильно з першого разу: кнопка.

Кнопка виглядає тривіальною: пін, високий або низький рівень. Але механічний контакт деренчить (bounce), тож пін робить 1 0 1 1 0 1, перш ніж заспокоїться, а продукт зазвичай хоче від тієї самої кнопки двох різних речей: коротке і довге натискання. Якщо написати це з прапорцями та лічильниками в головному циклі, вийде кілька if, що залежать один від одного так, що за місяць ніхто не зможе пояснити як. Скінченний автомат вирішує це так, що пояснити можна таблицею.

Один BSP, багато родин STM32: чому гілки Git і чого вони коштують

· 9 хв читання

STM32 - це не один мікроконтролер, а десяток родин: G4, H5, U5, F4 і так далі. Вони мають те саме ядро Cortex-M, але різну периферію, різні назви регістрів і різний пакет драйверів від виробника. Якщо ви хочете, щоб одна архітектура firmware працювала на всіх них, потрібно вирішити, де житимуть відмінності. Безкоштовного варіанту немає, і ця стаття описує той, який я обрав для Embedbits BSP, що про нього кажуть цифри зі справжніх репозиторіїв і чого він коштує.

Що відбувається перед main(): скидання, startup-код і linker script

· 10 хв читання

Кожен підручник із C починається з int main(void). Ніхто не пояснює, хто його викликає. А втім, коли ви пишете uint32_t counter = 5; як глобальну змінну і перший рядок main() зчитує 5, вже була виконана велика робота. Коли ця робота не виконана, симптом — змінна з випадковим значенням, яка «вчора працювала».

У цій статті ми простежимо шлях мікроконтролера від скидання до першого рядка main(): що апаратна частина робить сама, що каже linker script і що має зробити startup-код. Це вміст двох модулів BSP Embedbits (Linker і Startup), але принцип той самий на кожному Cortex-M. Увесь код у статті зібрано за допомогою arm-none-eabi-gcc 13.2.1 і запущено в QEMU на моделі плати STM32F405, тож адреси та виводи справжні.