РадиоКот :: Создание функций в Ассемблере. Бегущий огонек v1.1
Например TDA7294

РадиоКот >Обучалка >Микроконтроллеры и ПЛИС >Микроконтроллеры AVR - пишем, компилируем, прошиваем... >

Теги статьи: Добавить тег

Создание функций в Ассемблере. Бегущий огонек v1.1

Автор:
Опубликовано 01.01.1970

На данный момент у нас уже бежит огонек, и мы бесконечно этому рады. Однако, программа, увы и ах, оставляет желать лучшего по своей компактности. Вот как выглядит ее алгоритм (без настройки портов):

1.Вывод 00000001
2.Задержка 1
3.Вывод 00000010
4.Задержка 2
5.Вывод 00000100
6.Задержка 3
7.Вывод 00001000
8.Задержка 4
9.Перейти в начало

А теперь посмотрите: все задержки как две капли воды похожи друг на друга. Так зачем же прописывать тыщу раз одно и то же? Надо написать задержку один раз, а потом обращаться к ней столько, сколько нужно!

Те, кто учил языки высокого уровня, знакомы с понятием "функция". Так вот, нам нужно оформить задержку именно как функцию.

Для этого, познакомимся еще с парой команд:

rcall - (Relative Call) - вызов подпрограммы

ret - выход из подпрограммы. При этом продолжится выполнение программы, вызвавшей данную подпрограмму, с места вызова. Т.е., с команды, следующей за командой rcall.

Пример:

;вызывающая программа
          ldi Temp,45
          rcall Delay    ;вызов подпрограммы по метке Delay
          out PortB,Temp
          ...

;подпрограмма
Delay:    ldi Temp1,0    ;начало подпрограммы

Loop:     dec Temp1
          brne Loop
          ret            ;конец подпрограммы.
                         ;Возврат к команде out PortB,Temp

Вообще, что такое метка и как происходит переход?

В самом самом начале, мы говорили что программа контроллера записана в ПЗУ. Причем, каждая команда имеет свой уникальный адрес. Существует счетчик адресов, который последовательно вызывает команды данной программы.

Так вот, при компиляции кода, компилятор заменяет все наши метки на соответствующие им адреса ПЗУ, и вычисляет расстояние от каждой метки до каждой команды, обращающейся к этой метке. Почему, собственно, команды называются rjmp (RELATIVE jump), rcall (RELATIVE call) - потому что они содержат не абсолютный адрес метки, а относительный. То есть - число, на которое нужно увеличить/уменьшить текущий адрес ПЗУ, чтоб оказаться на метке.

Когда при выполнении программы, процессор натыкается, скажем, на команду rjmp, он берет из нее число и прибавляет его к значению счетчика адресов ПЗУ...

Команда rcall отличается от rjmp лишь тем, что прежде чем изменить текущее значение счетчика, оно записывается в ОЗУ. Далее - прибавляем константу и переходим к выполнению подпрограммы.
Команда ret в конце подпрограммы, загружает в счетчик адрес из ОЗУ, то есть, мы возвращаемся туда, откуда ушли. Вот и все…

Для того, чтобы команда rcall смогла работать, нам надо-таки инициализировать стек. То есть - поставить указатель стека.

Что такое СТЕК?
Это такая хитрая память, в которую данные укладываются как вещи в рюкзак: одно за другим. Кто хоть раз пытался достать что-то со дна длинного рюкзака - поймут меня. Чтобы добраться до необходимого байта в стеке, нужно примерно то-же самое, а именно - "выкинуть" из него все, что было положено позже.
Такую организацию памяти называют "FILO" - "First In - Last Out" (Первым вошел - последним выйдешь :)).
Еще стек можно сравнить с вагоном метро в час пик… :)

Ну так вот, для того чтоб стек стал работать, нам нужно указать адрес ОЗУ, который станет "дном рюкзака". Каждый следующий байт будет сохраняться в ячейке памяти с адресом, на 1 меньше предыдущего. Обычно, указатель стека ставится на последний адрес ОЗУ. Это делается так:

ldi Temp,RamEnd  ;загрузить в Темп адрес последней ячейки ОЗУ
out SPL,Temp     ;вывести в SPL значение из Temp

RamEnd - это константа, равная значению последнего адреса ОЗУ. Она инициализируется в файле def.inc.
SPL - это регистр указателя стека, Stack Pointer Low.

Почему LOW? Дело в том, что в контроллере 2313 разрядность адреса ОЗУ не превышает 8 бит. Значит, и указатель стека должен быть 8-битным. Для его хранения, соответственно, используется один 8-битный регистр.
У некоторых других контроллеров объем ОЗУ больше, и для его адресации используют два 8-битных регистра. Соответственно, младшие разряды указателя стека в таком случае будут храниться в регистре SPL, а старшие - в SPH (Stack Pointer High). В целях унификации кода для всех контроллеров, в 2313 у SPL оставили на конце букву L, хотя никакого SPH в данном контроллере нет и быть не может.

Это было лирическое отступление…

Итак, давайте перепишем нашу программу, выделив задержку как отдельную подпрограмму (функцию, если хотите).
Кстати, команду вывода регистра в порт мы тоже вынесем в подпрограмму задержки: она ведь тоже повторяется без изменений.

.cseg
.org 0

          ldi Temp,RamEnd       ;инициализация стека
          out SPL,Temp

          ldi Temp,0b11111111   ;настройка портов
          out DDRB,Temp

Begin:    ldi Temp,0b00000001   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b00000010   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b00000100   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b00001000   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b00010000   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b00100000   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b01000000   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          ldi Temp,0b10000000   ;вывод на индикацию
          rcall Delay           ;вызов подпрограммы задержки

          rjmp Begin            ;поехали по новой!


;****************************************************
;Задержка

Delay:    out PortB,Temp

          ldi Temp1,0
          ldi Temp2,0
          ldi Temp3,10

Loop:     dec Temp1
          brne Loop

          dec Temp2
          brne Loop

          dec Temp3
          brne Loop

          ret

Ну вот, теперь наша программулина стала намного компактнее. Однако, это не предел! Дальше я расскажу, как можно еще сильнее ее уменьшить :)

<<--Вспомним пройденное----Поехали дальше-->>




Как вам эта статья?

Заработало ли это устройство у вас?

28 0 0
15 2 1