AVR-GCC передаємо аргументи в main() і використовуємо її значення, що повертається, Embedder - s life
AVR-GCC: передаємо аргументи в main() і використовуємо її значення, що повертається
Будь-якій розсудливій людині ясно, що використовувати при написанні прошивки під контролер без ОС запис main() з параметрами і значенням, що повертається, абсолютно безглуздо — операційної системи немає, параметрам взятися звідки, і повертати результат виконання теж нікуди. Однак якщо ніяк, але дуже хочеться, то можна. Як зараз покажу.
Отже, спочатку трохи філософії — звідки взагалі можна взяти аргументи для main() у прошивці? За наявності ОС все ясно – це розібраний командний рядок. А в нас командного рядка немає. Єдине, що здається логічним, — добувати їх ззовні, з портів. Ну, наприклад, підключити до порту DIP-перемикачі та передавати їх стан за допомогою argv[]. Або приймати дані через UART. В принципі, можна зберегти аргументи у FLASH, але це якось нецікаво.
Звичайно, перевіряти DIP-перемикачі можна і в тілі main(), але ж ми не шукаємо легких шляхів, правда? 🙂
Тепер власне про те, як передати параметри в main(), якщо, по ідеї, без ОС управління відразу передається саме туди. Справа в тому, що насправді все не зовсім так — до main() виконується ще достатньо службового коду — я досконально розбирав його в одній із минулих статей. Більше того, після main() лінкер теж завжди ставить заглушку. І, виявляється, існує спосіб ввічливо попросити його включити до службових секцій довільний код.
Власне самі параметри передаються так само, як і в будь-яку іншу функцію.
Отже, для досягнення мети треба лише написати код, що кладе параметри в обумовлені місця, і попросити лінкер поставити його до виклику main(). Ну і заразом (теж домовившись з лінкером)написати те, що вловить значення, що повертається main() і якось його покаже.
Згадані частини коду писати будемо на асмі в окремому файлі, так як тут потрібна активна робота з регістрів загального призначення, що на С зробити важко, а формат вставок в AVR-GCC аж надто заморочений. Але, у принципі, вказувати розміщення коду службових секціях можна і З.
Щоб лінкер помістив код у потрібну секцію, треба передувати такою директивою:
Нас цікавлять секції .init8 (код прямо перед main() ) і .fini1 (відразу після main() ).
Самі параметри передаються у регістрах. Саме нашого випадку в R25:R24 повинен лежати argc, в R23:R22 — покажчик на argv[], тобто, покажчик початку масиву покажчиків на байт.
Я вирішив зупинитися на двох параметрах: перший значення порту B, другий порту D.
Таким чином, щоб здійснити задумане, треба зробити таке:
1. Завести змінні в пам'яті для значень PINB і PIND і заповнити їх відповідними значеннями, зчитаними з портів (зрозуміло, настроївши порти).
2. Завести в пам'яті масив під argv [], і помістити в нього покажчики вищезгадані змінні.
4. Покласти в R25:R24 значення argv, кількість аргументів. Для нашого випадку два.
5. Попросити лінкер покласти все це в область пам'яті перед викликом main().
Отже, заводимо змінні:
І далі все як написано:
З відловом значення, що повертається, все ще простіше — я вирішив виводити його в порт C (аналогічно просимо лінкер укласти цей код після main()) :
Експерименти проводились у Протеусі на моделі ATmega8515:

До порту С підключено два протеусівські примітиви семисегментних дисплеїв, що приймають дані в BCD, програма на С написана з урахуваннямцього.
Основна програма працює за наступним алгоритмом: порівнює значення аргументів, після чого виводить на дисплеї послідовно числа від меншого до більшого; в кінці показує букви (шістнадцяткове число, насправді) AE - Algorithm End. Якщо числа дорівнюють, то виводиться EE. Це зроблено, щоб показати роботу return.
Результат головної програми:
Ось так. В архіві є вихідні і модель в Протеусі для бажаючих побачити все на власні очі.
Навіщо може знадобитися така екзотика? Не знаю. Але працює.