Для уменьшения размеров кода и улучшения его читабельности, группы инструкций, выполняющие какую-либо элементарную задачу, могут быть оформлены в виде «процедур». Удобство процедур в том, что мы можем обращаться к ним из любого места программы, любое число раз.
Объявляются процедуры с помощью ключевых слов PROC / ENDP или FUNCTION / ENDFUNC. Keil-овский ассемблер для ARM-ов не делает различий между процедурами и функциями. При этом, по желанию, можно разместить функцию в какую-то специально выделенную область памяти. Выглядит объявление процедур следующим образом:
AREA MyProcedure, CODE, READONLY Procedure1 PROC ... ; тело процедуры ... ENDP |
Нужно понимать, что в приведённом примере используются только ключевые слова, никакого кода они не формируют. Они используются только для красоты оформления. Можно вообще никаких ключевых слов не использовать, просто поставить метку и всё, а в конце процедуры поставить команду возврата.
Следующий вопрос, — как в ARM-контроллерах осуществляется переход из основной программы в процедуру, а также возврат из процедуры в основную программу. Тут возможны несколько вариантов:
- Использование команды BL label для перехода к процедуре и команды BX LR для возврата в основную программу.
- В этом случае можно сохранить адрес возврата в стеке командой PUSH. Тогда, при правильном управлении стеком, можно будет перед возвратом восстановить оттуда нужный адрес возврата и воспользоваться командой BX LR. Более того, можно вместо двух команд POP{LR}, BX LR восстанавливать адрес возврата сразу в PC, используя команду POP{PC}
- Третий вариант — использовать для выхода из процедуры команду LDR или LDM с PC в качестве регистра-приёмника. Например, этими командами можно восстанавливать значение прямо из стека (аналогично предыдущему варианту), только нужно понимать, что сам стек при этом останется неизменным (то есть указатель стека не изменится).
Команда BL label выполняет переход на начало процедуры (заносит в PC адрес первой команды процедуры) и записывает в регистр LR адрес следующей за BL label команды (чтобы знать куда потом вернуться).
... ; основная программа bl Procedure1 ; вызов процедуры ... ; продолжение основной программы AREA MyProcedure, CODE, READONLY Procedure1 PROC ... ; тело процедуры ... bx LR ; возврат в основную программу ENDP |
Вызванная процедура сама может вызывать какие-то процедуры, такие вызовы называются вложенными. Если при этом использовать описанный выше механизм, то каждый новый вызов будет затирать содержимое регистра LR и вернуться назад, к месту первого вызова, будет невозможно.
... ; основная программа bl Procedure1 ; вызов процедуры ... ; продолжение основной программы AREA MyProcedure, CODE, READONLY Procedure1 PROC push {LR} ... ; тело процедуры bl Procedure2 ; вложенный вызов процедуры, который затрёт LR ... pop {PC} ; возврат в основную программу (в стеке у нас LR) ENDP |
... ; основная программа bl Procedure1 ; вызов процедуры ... ; продолжение основной программы AREA MyProcedure, CODE, READONLY Procedure1 PROC push {LR} ... ; тело процедуры bl Procedure2 ; вложенный вызов процедуры, который затрёт LR ... LDR PC,[SP] ; читаем значение PC из стека (там у нас LR), но ; сам стек при этом не меняется ENDP |
В принципе, для переходов можно использовать любые команды обработки данных, позволяющие сохранить результат в регистр, например MOV или AND и тому подобные. Не смотря на то, что в руководствах по командам THUMB-2 написано, что в них нельзя использовать PC в качестве регистра-приёмника и Keil-овский ASM будет при компиляции писать Warning, на самом деле всё будет работать. Это конечно уже для гиков и бог его ещё знает что там будет, скажем, с конвейером, но теоретически вы вполне можете скомпилить и запустить что-то подобное (я, например, запускал, чисто ради интереса):
dcd Procedure1 ; адрес процедуры dcd Continue1 ; адрес продолжения ENTRY ldr R1,[PC,#-12] ; теперь у нас в R1 адрес начала процедуры, ldr R2,[PC,#-12] ; а в R2 адрес продолжения nop ... ; основная программа mov PC,R1 ; вызов процедуры Continue1 ... ; продолжение основной программы AREA MyProcedure, CODE, READONLY Procedure1 PROC ... ; тело процедуры ... mov PC,R2 ; возврат из процедуры ENDP |
Подробнее про процедуры можно почитать в книжке Assembly Language Programming, Vincent Mahout, главы 3.3.5 (стр.35) и 7.1 (стр.119). Книжка эта на английском, русский перевод мне не встречался.
- Часть 1. Установка MDK, создание проекта, основы Keil uVision
- Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
- Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
- Часть 4. Регистры, старт и режимы работы контроллеров STM32
- Часть 5. Как залить прошивку в контроллер
- Часть 6. Настройка системы тактирования
- Часть 7. Работа с портами ввода-вывода
- Часть 8. Процедуры на ассемблере для STM32
- Часть 9. Система прерываний
- Часть 10. CMSIS, использование стандартных библиотек и функций
- Часть 11. Подключение и использование драйверов из пакета StdPeriph
- Часть 12. Работа с модулями USART и UART.
- Часть 13. Работа с модулями ADC
- Часть 14. Использование DMA
- Часть 15. Таймеры. Глава 1 — Введение. Простейшие таймеры
- Часть 15. Таймеры. Глава 2 — Таймеры общего назначения TIM9 — TIM14
- Часть 15. Таймеры. Глава 3 — Таймеры общего назначения TIM2 — TIM5
- Часть 15. Таймеры. Глава 4 — Продвинутые таймеры TIM1, TIM8
- Часть 16. Создание устройства USB HID в Keil uVision при помощи библиотечного компонента USB
- Приложение 1. Набор инструкций THUMB-2 и особенности их использования
- Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
- Приложение 3. Драйвера и функции библиотеки StdPeriph