В функциях LedOn и LedOff в начале и в конце есть команды:
PUSH {LR} ; Сохранить регистр LR в стеке.
; Полезный код
POP {PC} ; Поместить адрес возврата в PC. Выход из подпрограммы.
Вообще, необходимо придерживаться следующего соглашения о вызовах для удобства и совместимости с функциями, написанными на Си.
Аргументы передаются в регистрах R0-R3, первый в R0 и так далее. Если аргументов больше, то они передаются уже через стек.
Содержимое остальных регистров общего назначения, которые в процедуре используются, надо сохранить на стеке же. В том числе и LR, если есть вложенные процедуры.
Вообще, необходимо придерживаться следующего соглашения о вызовах для удобства и совместимости с функциями, написанными на Си.
Аргументы передаются в регистрах R0-R3, первый в R0 и так далее. Если аргументов больше, то они передаются уже через стек.
Содержимое остальных регистров общего назначения, которые в процедуре используются, надо сохранить на стеке же. В том числе и LR, если есть вложенные процедуры.
Сравните:
; Подпрограмма без вложенных вызовов подпрограмм
Func0 PROC
; бла-бла-бла
BX LR
ENDP
; Подпрограмма с возможностью вложенного вызова подпрограмм
Func1 PROC
PUSH {LR}
; бла-бла бла
BL Func0
; бла-бла-бла
POP {PC}
ENDP
Адрес вершины стека в кортексах записан первым числом в таблице векторов! Регистр SP инициализируется при сбросе именно этим числом.
_Vectors DCD __initial_sp ; Вершина стека!
DCD Reset_Handler ; Таблица векторов, первым идёт вектор сброса
DCD NMI_Handler
...
Локальные метки можно использовать с помощью директивы ROUT, но наименование жуть какое неудобное, мне не нравится.
PUSH {LR} ; Сохранить регистр LR в стеке.
; Полезный код
POP {PC} ; Поместить адрес возврата в PC. Выход из подпрограммы.
Вообще, необходимо придерживаться следующего соглашения о вызовах для удобства и совместимости с функциями, написанными на Си.
Аргументы передаются в регистрах R0-R3, первый в R0 и так далее. Если аргументов больше, то они передаются уже через стек.
Содержимое остальных регистров общего назначения, которые в процедуре используются, надо сохранить на стеке же. В том числе и LR, если есть вложенные процедуры.
Вообще, необходимо придерживаться следующего соглашения о вызовах для удобства и совместимости с функциями, написанными на Си.
Аргументы передаются в регистрах R0-R3, первый в R0 и так далее. Если аргументов больше, то они передаются уже через стек.
Содержимое остальных регистров общего назначения, которые в процедуре используются, надо сохранить на стеке же. В том числе и LR, если есть вложенные процедуры.
Сравните:
; Подпрограмма без вложенных вызовов подпрограмм
Func0 PROC
; бла-бла-бла
BX LR
ENDP
; Подпрограмма с возможностью вложенного вызова подпрограмм
Func1 PROC
PUSH {LR}
; бла-бла бла
BL Func0
; бла-бла-бла
POP {PC}
ENDP
Адрес вершины стека в кортексах записан первым числом в таблице векторов! Регистр SP инициализируется при сбросе именно этим числом.
_Vectors DCD __initial_sp ; Вершина стека!
DCD Reset_Handler ; Таблица векторов, первым идёт вектор сброса
DCD NMI_Handler
...
Локальные метки можно использовать с помощью директивы ROUT, но наименование жуть какое неудобное, мне не нравится.
Пример программы
; Test STM32F4-Assembler project
; Ня
AREA |.text|, CODE, READONLY
RCC_AHB1ENR EQU 0x40023830
RCC_AHB1ENR_GPIODEN EQU 0x00000008 ; 3 бит
GPIOD_BASE EQU 0x40020C00
GPIO_MODER_OFFSET EQU 0x00000000
GPIO_MODER_MODER12_0 EQU 0x01000000
GPIO_MODER_MODER13_0 EQU 0x04000000
GPIO_MODER_MODER14_0 EQU 0x10000000
GPIO_MODER_MODER15_0 EQU 0x40000000
GPIO_PUPDR_OFFSET EQU 0x0000000C
GPIO_PUPDR_PUPDR12_0 EQU 0x01000000
GPIO_PUPDR_PUPDR13_0 EQU 0x04000000
GPIO_PUPDR_PUPDR14_0 EQU 0x10000000
GPIO_PUPDR_PUPDR15_0 EQU 0x40000000
GPIO_BSRRL_OFFSET EQU 0x00000018 ; Сброс
GPIO_BSRRH_OFFSET EQU 0x0000001A ; Установка
DELAY_COUNTER EQU 0x000F4240 ; 1000000UL
GPIO_BSSR_PIN12 EQU 0x00001000 ; 12 бит
GPIO_BSSR_PIN13 EQU 0x00002000 ; 13 бит
GPIO_BSSR_PIN14 EQU 0x00004000 ; 14 бит
GPIO_BSSR_PIN15 EQU 0x00008000 ; 15 бит
; Инициализация системы тактирования
; EXPORT SystemInit
;SystemInit PROC
;
; BX LR ; Возвращаемся обратно
;
; ENDP
ROUT
; Получить маску для зажигания светодиода по его номеру
; R0 - номер светодиода, от 0 до 3
; Результат: R0.
; Если номер не верный, в R0 будет 0.
GetPinMask PROC
; Другой способ выбора:
CMP R0, #0
BEQ GetPinMask_led0
CMP R0, #1
BEQ GetPinMask_led1
CMP R0, #2
BEQ GetPinMask_led2
CMP R0, #3
BEQ GetPinMask_led3
; Ошибка
B GetPinMask_error
GetPinMask_led0 ; Светодиод 0
MOV R0, #GPIO_BSSR_PIN12 ; 12 бит
B GetPinMask_return
GetPinMask_led1 ; Светодиод 1
MOV R0, #GPIO_BSSR_PIN13 ; 13 бит
B GetPinMask_return
GetPinMask_led2 ; Светодиод 2
MOV R0, #GPIO_BSSR_PIN14 ; 14 бит
B GetPinMask_return
GetPinMask_led3 ; Светодиод 3
MOV R0, #GPIO_BSSR_PIN15 ; 15 бит
B GetPinMask_return
GetPinMask_error ; Ошибка
MOV R0, #0
GetPinMask_return
BX LR
ENDP
; Погасить светодиод по его номеру
; R0 - номер светодиода, от 0 до 3
LedOff PROC
; Сохраним в стеке адрес возврата
PUSH {LR}
; Получить маску
BL GetPinMask
; Если 0, то выходим
CMP R0, #0
BEQ LedOff_return
; В R0 теперь маска
; Погасить выбронный светодиод
LDR R1, =GPIOD_BASE
STR R0, [R1, #GPIO_BSRRH_OFFSET]
LedOff_return
; Возврат из процедуры
POP {PC}
ENDP
; Зажечь светодиод по его номеру
; R0 - номер светодиода, от 0 до 3
LedOn PROC
; Сохраним в стеке адрес возврата
PUSH {LR}
; Получить маску
BL GetPinMask
; Если 0, то выходим
CMP R0, #0
BEQ LedOn_return
; В R0 теперь маска
; Зажечь выбронный светодиод
LDR R1, =GPIOD_BASE
STR R0, [R1, #GPIO_BSRRL_OFFSET]
LedOn_return
; Возврат из процедуры
POP {PC}
ENDP
; Программная задержка
; R0 - величина задержки
delay PROC
; Вычитаем единицу
SUBS R0, R0, #1
; Пока не обнулилась, крутим дальше
BNE delay
BX LR
ENDP
EXPORT __main
__main PROC
; Разрешим тактирование порта D
; Загрузим в регистр R0 адрес регистра RCC_AHB1ENR
LDR R0, =RCC_AHB1ENR
; Прочитаем в регистр R1 его содержимое
LDR R1, [R0]
; Установим бит тактирования порта D
ORR R1, R1, #RCC_AHB1ENR_GPIODEN
; Запишем обратно
STR R1, [R0]
; Установим режим работы выводов (выход)
LDR R0, =GPIOD_BASE
; Прочитаем в регистр R1 его содержимое
LDR R1, [R0, #GPIO_MODER_OFFSET]
; Установим необходимые биты для выводов 12-15
ORR R1, R1, #GPIO_MODER_MODER12_0
ORR R1, R1, #GPIO_MODER_MODER13_0
ORR R1, R1, #GPIO_MODER_MODER14_0
ORR R1, R1, #GPIO_MODER_MODER15_0
; Запишем обратно
STR R1, [R0, #GPIO_MODER_OFFSET]
; Подтяжка к питанию
LDR R0, =GPIOD_BASE
LDR R1, [R0, #GPIO_PUPDR_OFFSET]
; Установим необходимые биты для выводов 12-15
ORR R1, R1, #GPIO_PUPDR_PUPDR12_0
ORR R1, R1, #GPIO_PUPDR_PUPDR13_0
ORR R1, R1, #GPIO_PUPDR_PUPDR14_0
ORR R1, R1, #GPIO_PUPDR_PUPDR15_0
; Запишем обратно
STR R1, [R0, #GPIO_PUPDR_OFFSET]
; В регистр R2 запишем величину задержки
LDR R4, =DELAY_COUNTER
__mainloop
; Зажжём светодиод 0
MOV R0, #0
BL LedOn
; Задержка
MOV R0, R4
BL delay
; Погасим светодиод 0
MOV R0, #0
BL LedOff
; Зажжём светодиод 1
MOV R0, #1
BL LedOn
; Задержка
MOV R0, R4
BL delay
; Погасим светодиод 1
MOV R0, #1
BL LedOff
; Зажжём светодиод 2
MOV R0, #2
BL LedOn
; Задержка
MOV R0, R4
BL delay
; Погасим светодиод 2
MOV R0, #2
BL LedOff
; Зажжём светодиод 3
MOV R0, #3
BL LedOn
; Задержка
MOV R0, R4
BL delay
; Погасим светодиод 3
MOV R0, #3
BL LedOff
; На очередной круг
B __mainloop
ENDP
END
Комментариев нет:
Отправить комментарий