marți, 18 martie 2014

Введение в программирование ASM. Стек память. Подпрограмма и подпрограмма с параметрами.


    Введение в программирование ASM

    Основная сущность языка ASM - это команда.
    Программы - это последовательность команд,работающих одна за другой.
   Программирование на ASM представляет собой доступ к памяти и модификацию значений по данным адресам , при помощи команд. Язык позволяет группировать последовательности команд в подпрограммы, выполнение программы ведётся по условным и безусловным переходам.

    Структура программы ASM

МК в любой момент времени должен выполнять команду, определённую программистом. В то время , когда МК выполняет команду определённую программистом, тогда говорят, что МК находится под контролем программы. В обратном случае, если МК выполняет команду , не определённую программистом, тогда говорят, что МК вышел из под контроля. Программа, которая управляет МК должна постоянно контролировать МК, что подразумевает собой- бесконечный цикл.

    Разделим программу на 2 основных раздела:
1) Инициализация-последовательность команд в начале программы , которая служит для инициализации МК для его дальнейшей работы.
2) Обработка-решение задач и постоянный контроль МК , при помощи бесконечного цикла.

Программа на ASM имеет следующею структуру:

init:               ; точка входа в программу
    ...
    ...
    ...             ; инициализация МК
    ...
end_init:           ; индикатор конца инициализации
main_loop:          ; начало раздела обработки 
    ...
    ...             ; тело раздела обработки
    ...             ; включённой в бесконечный цикл
    ...
end_main_loop:      ; конец раздела обработки    
    rjmp main_loop  ; переход к началу бесконечного цикла

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


    Директива предварительной обработки компилятора.
    Директивы препроцессора компилятора-это команды компилятора , которые выполняются компилятором перед генерацией двоичного кода. Их роль- сконфигурировать компилятор и обозначить компилятору модуль в котором должны быть описаны команды. 

    В данной фазе обозначим некоторые директивы препроцессора:
.include<файл> -  вставляет текст из указанного файла.
.cseg  -  определяет начало программного сегмента.
.org <addr> -указывает , что линии кода будут зарегистрированы начиная со специального адреса в текущем сегменте памяти.

.include "m16def.inc" ; включаем файл периферийных адресов
                      ; для ATmega16
.cseg                 ; определяем начало программного сегмента
.org 0                ; программа зарегистрирована начиная 
                      ; с 0x00 адреса
init:                 ; точка входа в программу
    ...
    ...

    Стек(Stack)

    Стек-это своего рода механизм , который позволяет временное хранение данных по принципу LIFO(Last Input First Output).
    Заполнение стека выполняется командой PUSH R-введение содержимого РОН в стек (R -> STACK). Извлечение данных командой POP R (R <- STACK).
    Также стек является механизмом,без которого не возможны были бы подпрограммы, т.к. механизм стека позволяет временное сохранение счётчика программ (PC),во время выполнения подпрограммы. Соответственно,команды типа CALL и RET также вставляют и извлекают данные из стека.
    Механизм стека реализован на базе памяти RAM и указательного региста стека SP(Stack Pointer). 
    SP-показывает текущий адрес для записи в RAM.
    SP является двойным регистром (16-бит), построенный на  [ SPH : SPL ] .

    Выполняя команду PUSH R содержимое регистра запишется в текущий адрес SP , после которого SP декрементируется:
  • PUSH R
    1. RAM[SP] = R
    2. SP = SP - 1  

    Для команды POP R выполняется инкрементирование SP,после чего в РОН записывается значение текущего адреса:
  • POP R
    1. SP = SP + 1
    2. R = RAM[SP]
    Добавление элементов в стек будет включать в себя увеличение стека. Исходя из изложенного, мы убеждаемся, что стек растет в направлении уменьшения адресов RAM.


    Инициализация стека.
    Регистр SP находится в регистрах ввода вывода,поэтому запись и чтение значении происходит путём ввода команд IN и OUT.Т.к. значение указателя стека SP растёт с увеличением стека , инициализацию стека рекомендуется выполнять в RAM. Такое значение может быть постоянным RAMEND содержащее последний адрес существующей внутренней памяти микроконтроллера RAM. Инициализация стека будет состоять из инициализации указателя стека SP.

SP = RAMEND,         [SPH:SPL] = RAMEND

Пример инициализации стека на ASM:

ldi R16, LOW(RAMEND)    
       out SPL, R16          ; инициализация младшего байта SP
       ldi R16, HIGH(RAMEND)   
       out SPH, R16          инициализация старшего байта SP 

где:

  • LOW - макрос для указания младшего байта константы.
  • HIGH - макрос для указания старшего байта константы.

    В цикле программы количество записей должно быть равным количеству извлечений для избегания ошибок и утечки памяти.
    Подпрограмма
    Подпрограмма-последовательность команд сгруппированных по метке и команд -возврата из подпрограммы RET. Метка, представляет собой адрес в памяти программ , с присвоенным именем доступа. В программе , будет представлена с определённым именем,следующим образом:

MySub:      ; Имя подпрограммы представлено 
            ; меткой начала подпрограммы
    ...
    ...     ; Тело подпрограммы
    ...
    ...
    ret     ; возврат из подпрограммы

    Подпрограмм может быть вызвана по имени , используя такие команды как : CALL,RCALL и ICALL.
    ...
    ...
    rcall MySub  ; вызов подпрограммы MySub
    ...
    ... 

    Механизм вызова подпрограммы состоит в сохранения текущего адреса исполнения в стеке для того,чтобы иметь возможность восстановить выполнение следующей команды после возврата из подпрограммы.
Команда вызова подпрограммы CALL.
    При вызове подпрограммы происходит сохранение в стеке счётчика программы и восстановление его после возврата из подпрограммы.

  1. PC -> STACK - сохранение текущего адреса в стеке
  2. jmp MySub      - переход по адресу по метке  MySub

    Возврат из подпрограммы происходит по команде RET , как правило находящейся в конце подпрограммы . Например:

  1. PC <- STACK - возврат адреса из стека
  2. PC <- PC + 1   - переход к следующей команде после возврата

    Не забывая, что счётчик программ PC - это 16-битовый регистр , он будет занимать 2 места в стеке, выполняя 2 операции для сохранения в стеке.
    Т.к. вызов подпрограммы предполагает использование стека , рекомендуется перед первым вызовом стека быть инициализированным, а самое подходящее место для этого-место инициализации программы.

.include "m16def.inc" ; включаем файл адресов
                      ; для ATmega16
.cseg                 ; начало сегмента кода
.org 0                ; программа зарегистрирована по 
                      ; адресу 0x00
init:                 ; место входа в программу
    ldi R16, LOW(RAMEND) ; инициализация стека
    out SPL, R16
    ldi R16, HIGH(RAMEND)
    out SPH, R16
    ... 

Защищённая подпрограмма

    Защищённая подпрограмма - это подпрограмма , которая не влияет на значения регистров при её выполнении. Механизм данной подпрограммы состоит в сохранении рабочих регистров , используемых в подпрограмме,в стеке в начале подпрограммы и восстановление этих регистров до выхода из подпрограммы.

MySub:          ; Имя подпрограммы по метке
         
    push r15    ; сохранение регистра r15
    push r16    ; сохранение регистра r16
    ...
    ...         ; Тело подпрограммы
    ...         ; использование регистров r15 и r16
    ...
    ...
    pop r16     ; восстановление r16
    pop r15     ; восстановление r15
    ret         ; возврат из подпрограммы

    Обратите внимание, что порядок извлечения рабочих регистров является обратным сохранению по принципу LIFO стека. 

Подпрограмма с параметрами
    В качестве параметров могут использоваться РОН. В этом случае для передачи данных входа подпрограммы , перед вызовом подпрограммы необходимо,чтобы используемые регистры были инициализированы входными значениями , желаемыми для этих параметров. А после выполнения подпрограммы - извлечение значений из зарезервированных регистров.
    Например,если у нас есть подпрограмма  которая использует R16 и R17 как входные параметры и R18 как выходной параметр в котором подпрограмма сохраняет результат : 

 a = MySub(K1, K2)

    Вызов подпрограммы будет иметь следующий вид:
    ...
    ...
    ldi r16, K1  ; инициализация первого параметра
    ldi r17, K2  ; инициализация второго параметра
    rcall MySub  ; вызов подпрограммы MySub
    sts a, r18   ; возврат результата подпрограммы
    ...
    ...

    Регистры,взятые как параметры входа-выхода НЕ сохраняются внутри подпрограммы. Механизм сохранения регистров , как параметров , в целях защиты текущих данных этих регистров , осуществляется следующим образом :
параметры будут сохраняться в стеке и перед инициализацией и восстанавливаться после приема результата подпрограммы.
    ...
    ...
    push r16     ; сохранение параметра в r16
    push r17     ; сохранение параметра в r17
    push r18     ; сохранение параметра в r18
    ldi r16, K1  ; инициализация первого параметра
    ldi r17, K1  ; инициализация второго параметра
    rcall MySub  ; вызов подпрограммы MySub
    sts a, r18   ; приём результата подпрограммы
    pop r18      ; восстановление параметра r16
    pop r17      ; восстановление параметра r17
    pop r16      ; восстановление параметра r18
    ...
    ... 
ещё раз напомним,что, восстановление параметров обратно их сохранению.