Как видите, разобраться довольно сложно, но при некотором навыке и наличии под рукой таблицы двоичных кодов команд вполне возможно. Именно так работает программа, которая превращает код обратно в текст —
Команды перехода (передачи управления)
В языках высокого уровня была всего одна команда перехода на метку (GOTO), и то Дейкстра на нее «набросился». А в ассемблере AVR таких команд пруд пруди, целых 33 шутки! Зачем? На самом деле без доброй половины из них, если не больше, можно обойтись во всех жизненных случаях, т. к. они в значительной степени взаимозаменяемы. Разнообразие это, если угодно, дань памяти великому программисту — для повышения читаемости программ. Мы рассмотрим только ключевые команды из этого перечня.
С командами безусловного перехода rjmp и jmp мы уже познакомились достаточно подробно, так что сразу перейдем к вызову процедур rcall и call (официальный язык фирменных руководств Atmel предпочитает вместо процедуры упоминавшийся нами консервативный термин — подпрограмма, subroutine). Синтаксис у них точно такой же, как у команд безусловного перехода, и, по сути, это тот же самый переход по метке. И разница между этими двумя командами аналогичная: call работает в пределах 64 К адресов памяти (или до 8 М в соответствующем контроллере, поддерживающем такой объем адресного пространства), занимает 4 байта и выполняется за 4 цикла, a rcall годится только для МК с объемом памяти не более 8 кбайт, но занимает 2 байта и выполняется за 3 цикла. Мы в дальнейшем будем пользоваться только командой rcall.
Отличаются они от команд безусловного перехода тем, что здесь в момент перехода к процедуре контроллер автоматически сохраняет в стеке адрес текущей команды для того, чтобы потом знать, куда вернуться (потому длительность выполнения этих команд на такт больше, чем для просто перехода). А как МК «узнает»,
Заметим, что стек — одно из самых употребительных понятий в программировании. Наличие программного стека позволяет, например, организовать привычное для языков высокого уровня разделение переменных на локальные и глобальные. Во всех последующих программах в этой книге мы будем пользоваться только глобальными переменными, и при нехватке регистров общего назначения для них мы будем использовать ячейки SRAM. (Привычку употреблять преимущественно глобальные переменные автор даже перенес в свой стиль программирования на Delphi, как вы увидите из
push var_1 ;переменная var_1 в программе помещается в стек
push var_2 ;переменная var_2 в программе помещается в стек
rcall procedure ;вызывается процедура
pop var_2 ;после нее результат извлекается из стека
pop var_1 ;второй результат извлекается из стека
…
procedure: ;в процедуре извлекается локальная
pop var_loc2 ;переменная var_loc2 со значением var_2
pop var_loc1 ;и переменная var_loc1 со значением var_1
… ;расчеты, расчеты…
push var_loc1 ;результат — в стек
push var_loc2 ;результат — в стек
ret ;возврат из процедуры