Продолжаем курс по ассемблеру.
И на данном уроке с помощью полученных знаний по языку мы попытаемся заставить наш светодиод периодично мигать.
Для этого мы также сегодня изучим, что такое флаги и для чего они нужны, также познакомимся, как можно осуществить переход по условию и какие бывают условия и ещё изучим некоторые новые команды.
Схема урока остаётся прежней — обыкновенная недорогая отладочная плата на контроллере STM32F103, подключенная к USB компьютера через программатор
Проект мы сделаем из прошлого урока с именем ASM_GPIO и назовём его ASM_BLINK01.
Создание нового проекта на основе другого для проекта на ассемблере происходит точно также, как и проектов с использованием библиотеки CMSIS. Я объяснял, как это делается, в уроке 166.
Откроем проект в Keil и в файле main.s изменим регистр там, где мы зажигали светодиод на r1, чтобы светодиод у нас, наоборот, потухал
LDR R2, =(PERIPH_BB_BASE + (GPIOC_ODR - PERIPH_BASE) * 32 + GPIO_ODR_ODR13_N * 4)
STR R1, [R2]
А вот в бесконечном цикле мы его зажжём
1 2 |
loop STR R0, [R2] |
Далее нам потребуется задержка. Напишем для неё подпрограмму (процедуру).
Подготовим нашу процедуру (пока без кода), добавив её после окончания нашей основной процедуры
1 2 3 4 5 |
ENDP DELAY PROC ENDP |
Я думаю, вы уже заметили, что для меток (а имя процедуры — это также метка) не нужно создавать никаких прототипов, где бы они не находились — ниже или выше обращения к ним в отличие от имён функций, переменных в языке C. Интерпретатор ассемблера видит в обе стороны.
В теле нашей новой процедуры запишем в регистр число, которое мы затем будем декрементировать, пока оно не достигнет нуля. Данное число подобрано экспериментально
1 2 |
DELAY PROC MOV R3, #0x00100000 |
Далее добавим метку, к которой мы будем возвращаться, если наше число не достигло нуля
1 2 |
MOV R3, #0x00100000 delay_loop |
А далее будет использована новая команда — арифметическая.
Это будет команда вычитания — SUB, которая вычитает одно число из другого, а результат записывает в третье. Выглядит это примерно так:
SUB{S}{cond} {Rd}, Rn, Operand2
В фигурных скобках находятся необязательные параметры. Это постфикс S и условие cond. Затем идёт регистр, в который записывается результат, который также необязателен, далее — регистр, над которым мы проводим операцию, в данном случае уменьшаемое, затем идёт третий операнд, играющий роль вычитаемого. В качестве третьего операнда может быть либо константа, либо значение из памяти.
Чтобы понять, что за постфикс S может появиться на конце арифметических и других команд, нам нужно будет познакомиться с флагами.
Флаги — это биты регистра PSR, отображающие текущее состояние процессора.
Посмотрим, что из себя представляет данный регистр
Всего таких 5 флагов.
N (Negative) – результат операции получился отрицательным,
Z (Zero) – результат операции равен нулю,
C (Carry) — при выполнении операции с беззнаковыми чисоами произошел перенос,
V (oVerflow) – при выполнении операции со знаковыми числами произошло переполнение, результат не помещается в регистр,
Q (Sticky saturation flag) — липкий флаг насыщения. Устанавливается в случае насыщения результата. Используется редко.
Также нужно отметить, что для того, чтобы операции влияли на флаг, то нужно применять в конце мнемоники операции постфикс S, а не как у процессоров типа x86, где операции всегда влияют на флаги.
В зависимости от состояния флагов после той или иной команды можно формировать условия последующей команды, например, осуществить переход на какой-то адрес только в случае выполнения какого-то условия. Для этого также используются постфиксы условий. Вот их список
Думаю, что здесь всё понятно и переводить на русский нет пока смысла, разберёмся потом по мере использования тех или иных условий.
Итак, вернёмся в наш проект и введём в код следующую команду
1 2 |
delay_loop SUBS R3, R3, #1 |
Мы использовали команду вычитания с влиянием на флаги. На какие именно флаги влияет данная команда, есть в таблице в документации. Вот фрагмент из неё
Применив данную команду, мы вычли 1 из значения регистра R3 и результат записали обратно в R3, другими словами произвели операцию декрементирования (отдельных команд инкрементирования и декрементирования для процесоора ARM, к сожалению, нет). Кстати самый первый операнд R3 мы можем не использовать, если у нас результат записывается в тот же регистр, в котором и находится значение, над которым мы производим арифметические действия.
Введём следующую команду
1 2 |
SUBS R3, R3, #1 BNE delay_loop |
Команду B мы уже знаем, а с помощью постфикса NE, как следует из таблицы выше, мы проверяем условие неравенства нулю, то есть, если результат предыдущей операции не равен нулю, то мы осуществляем данный переход, тем самым попадая обратно на метку delay_loop. А если результат будет равен нулю, то данная команда выполняться не будет и мы провалимся ниже по коду.
Следующая команда осуществит переход по адресу, записанному в регистре LR
1 2 |
BNE delay_loop BX LR |
Это новая для нас команда.
BX — команда перехода по адресу, хранящемуся в регистре.
У нас в качестве адреса используется число, хранящееся в регистре LR.
LR — это регистр связи, он же регистр R14. В данный регистр в случае использования команд BL и BLX записывается адрес следующей команды, то есть фактически команда BX LR — это выход из подпрограммы. На ней и закончится наша подпрограмма.
Вернёмся в наш бесконечный цикл и вызовем нашу подпрограмму с помощью команды BL (вызов подпрограммы по метке)
1 2 |
STR R0, [R2] BL DELAY |
Тем самым мы применили задержку после установки низкого уровня ножки.
Далее установим высокий уровень и вызовем ту же подпрограмму, в результате которой у нас опять произойдёт задержка выполнения кода
1 2 3 |
BL DELAY STR R1, [R2] BL DELAY |
Вот, в принципе, и весь код.
Соберём код, прошьём контроллер и светодиод наш начнёт периодически мигать
Итак, на данном уроке мы изучили ещё несколько ассемблерных команд, познакомились с флагами состояния, условиями, в результате чего смогли написать код, который заставил светодиод периодически мигать.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Подскажите, где поискать адекватность? Инженеры STMicroelectronics ведь далеко не глупые люди, у них система очень хорошо построена. Если есть недостатки, то их не так много по сравнению с остальными. Но вот какой у меня вопрос
команда mov Rx, Operand2
я, конечно, нашел в PM0056.pdf раздел 3.3.3. где в разделе Constant написано
• Any constant of the form 0x00XY00XY
• Any constant of the form 0xXY00XY00
• Any constant of the form 0xXYXYXYXY
Да просто потому что у меня
mov R4, #4000000 ; ошибка
а у NarodStream
mov R4, #0x00100000 ; нет ошибки…
почему же мне надо в 2 операции записывать 32-битную константу, а NarodStream делает за одну? И компилятор не ругается на его инструкции…
Вот попробовал я посмотреть что компилируется в разных вариантах.
4F F0 12 04 mov R4, #0x00000012
4F F0 12 14 mov R4, #0x00120012
4F F0 12 24 mov R4, #0x12001200
4F F0 12 34 mov R4, #0x12121212
первые 2 байта — код команды, третий байт — операнд XY, из последнего байта задействованы только 2 бита, характеризующие расположение XY… ну наверное 4 в конце — индекс регистра R4 (его тоже надо как-то идентифицировать).
а вот такие вот последовательности:
4F F4 90 54 mov R4, #0x00001200
4F F4 90 14 mov R4, #0x00120000
4F F0 90 54 mov R4, #0x12000000
41 F2 12 24 mov R4, #0x1212
41 F2 34 24 mov R4, #0x1234
тут вообще непонятно как он зашифровывает 12(hex) ???
Подскажите где можно посмотреть побайтную карту команд STM32 (Cortex-M3) ???
[у intel-процессоров была такая карта, и можно было понять адекватность распределения]
Блин, вот конкретно хочется понять как он их кодирует в машинные коды? Где причина такой (на первый взгляд) кажущейся неадекватности?
используйте макрос LDR R4,=
в команде MOV просто нет столько места под значение регистра… там 8 бит помоему всего задать можно, но это значение можно перед записью в регистр по всякому преобразовать…
не знаю можно ли здесь оставлять ссылки на сторонние ресурсы (ну если удалят — вам не повезло) вот где описано, читайте.
в принципе загрузка значения рулит, один недостаток это лишнее чтение из памяти и еще одно слово во флеше под значение… но думаю сильно напугать это не должно :-))
Загрузить сразу 32-битную константу можно, например: ldr r0,=0x12345678
«почему же мне надо в 2 операции записывать 32-битную константу, а NarodStream делает за одну?»
Потому что это связано с тем, как интерпритатор представляет 32-юитную переменную, вот здесь это описано: http://www.keil.com/support/man/docs/armasm/armasm_dom1359731145835.htm
спасибо! реально работает! =)
«В качестве третьего операнда может быть либо константа, либо значение из памяти»
Здесь неточность: Либо константа, либо регистр с операцией сдвига.
На сайте ARM написано что синтаксис ARMASM считается устаревшим с 6 версии компилятора. Автору надо переписать уроки под ARMCLANG синтаксис.