Міро Самек
У цій частині ми почнемо розбиратися з кодом, що згадувався в першій частині і доступному на сайті Embedded.com, який містить версії для мов "Сі" та С++ прикладу "Blinky", що блимає чотирма світлодіодами налагоджувальної плати AT91SAM7S-EK фірми Atmel.
[Приклади коду в перекладі даються за вихідними текстами, завантаженими з сайту "Quantum Leaps, LLC" , і, в окремих випадках, з метою покращення читання можуть бути переформатовані та/або скорочені.
Текст у квадратних дужках, виділений кольором аналогічно до цього фрагмента, містить примітки, додані перекладачем.
Варіант для "Сі" розташований у підкаталозі "c_blinky", а варіант для С++ - в "cpp_blinky". Додаток "Blinky" нехитро, але спроектований акуратно і використовує всі підходи та можливості, описані в цій статті. Проект використовує інструменти CodeSourcery G++ GNU для процесора ARM [3].
У цій частині буде описано універсальний стартовий код та низькорівневу ініціалізацію для простої (bare-metal) системи на процесорі ARM. Рекомендується ознайомитися з документом "IAR Compiler Reference Guide" [7], а саме з розділами "System startup and termination" та "Customizing system initialization".
2.1 Стартовий код
Асемблерна реалізація ініціалізації для простих (bare-metal) систем на ARM розміщується у файлі "startup.s", однаковому для проектів на "Сі" та C++. Код проектувався як універсальний та повинен працювати на будь-якому контролері ARM без змін.
Вся ініціалізація процесора та налагоджувальної плати, що проводиться до початку функції "main()", повинна оброблятися процедурою "low_level_init()", яка зазвичай може бути написана на C/C++, але принеобхідності та на асемблері.
Лістинг 2.1 є стартовим кодом, написаним на асемблері GNU. Нижче пояснюються основні моменти процесу початкової ініціалізації.
(1) Директива ".text" повідомляє асемблеру, що код треба додавати в кінець кодового сегмента "text".
(2) Директива ".code 32" вибирає набір 32-бітових інструкцій ARM (значення 16 вибирає набір 16-бітових інструкцій THUMB). Таким чином, ядро починає роботу в стані ARM.
(3) Директива ".global" робить мітку "_vectors" видимої компонувальнику.
(4) Директива ".func" створює для функції "_vectors" налагоджувальну інформацію (тіло функції має закінчуватися директивою ".endfunc").
(6) Початкова таблиця векторів складається з набору нескінченних циклів (передаючих управління самих себе) і використовується досі заміни на таблицю векторів, побудовану оперативної пам'яті. Якщо в процесі заміни виникне виняток, налагоджувальна плата, швидше за все, перейде в стан, з якого процесор не зможе вийти самостійно, тому робочі пристрої повинні мати схему (таку як вартовий таймер з незалежним джерелом тактування), що дозволяє інформувати користувача про цю подію.
(8) Після включення до коду текстового рядка [довільної довжини] необхідно проводити вирівнювання по межі машинного слова. [Правильніше ставити процедуру вирівнювання перед виконуваним кодом, адже вирівнюється саме він.]
(9) На цю мітку відбувається перехід після скидання.
(Функція " low_level_init() " може бути написана мовою C/C++ з урахуванням наступних обмежень. Вона повинна виконуватися в стані ядра ARM і не повинна торкатися ініціалізації секції ".data" або ".bss". Крім того, якщо потрібнореорганізація пам'яті, то проводитися ця операція повинна всередині функції "low_level_init() ", тому що після повернення нею управління код перестає бути позиційно незалежним.)
(15) Мітка "_cstartup" зазначає початок ініціалізації "Сі".
(18) Секція ".bss" використовується для неініціалізованих змінних, які відповідно до стандарту "Сі" повинні бути обнулені (див. третину статті).
(19) Секція ".stack" служить для зберігання стеків. Секція заповнюється спеціальним значенням, яке полегшує спостереження за використанням стека у відладчику.
(20) Ініціалізуються всі покажчики стеків регістрових банків, що перемикаються.
(21) Останнім ініціалізується покажчик стека режиму User/System. Весь наступний код ініціалізації виконується як System .
(22) Бібліотечна функція "__libc_init_array() " викликає всі статичні конструктори C++ (див. третину статті). Вказана функція викликається інструкцією "BX", що дозволяє переключити ядро станом THUMB. У "Сі" ця функція нічого не робить.
(23) Функція "main()" викликається інструкціями "BX", що дозволяє переключити ядро в стан THUMB.
(24) У найпростіших (bare-metal) проектах функція " main() " будь-коли повертає управління, оскільки операційна система відсутня. Якщо все ж таки "main()" поверне управління, відбудеться виклик виключення "Software Interrupt", в якому користувач може уточнити спосіб обробки такої ситуації.
2.2 Низькорівнева ініціалізація
Низькорівнева ініціалізація, що проводиться функцією "low_level_init() ", завжди залежить від конкретної моделі процесора і особливостей процедури реорганізації пам'яті. Раніше говорилося, що функція "low_level_init()" може бути написана на "Сі" абоC++, але має бути скомпільована під набір інструкцій ARM, неспроможна проводити ініціалізацію секцій " .data " і " .bss " чи викликати статичні конструктори C++.