Программирование 16-разрядных микропроцессоров на Ассемблере
Допустимые символы языка ассемблера состоят из прописных и строчных букв (латинских), цифр, специальных знаков +, -, *, /, =, (), [], ’, ’’,.,;, @, &,?, <, >, % и символов: перевод строки ПС (ОАН), возврат каретки ВК (ОDН), табуляции (О9Н). Любой другой символ воспринимается как пробел. Наименьшей конструкцией модуля является идентификатор – последовательность букв и цифр (не более 31), начинающийся с буквы. К зарезервированным именам относятся имена регистров, операций, псевдокоманд. Модуль представляет собой последовательность операторов языка, записанных в одной строке и заканчивающихся возвратом каретки и переводом строки. Если в первой позиции оператора стоит символ &, то оператор является продолжением предыдущего. Операторы разделяются на командные и директивы. Команды порождают одну машинную команду. Директивы (псевдокоманды) содержат управляющую информацию для ассемблера. Оператор команды имеет вид:
{метка:} {префикс} {мнемоника} {операнд(ы)}; { комментарий}.
Значения метки являются текущим значением счетчика в данном сегменте кода, то есть, представляет собой адрес команды. Префикс позволяет формировать байты блокировки LOOK или повторения REP. Мнемоника идентифицирует тип генерируемой команды. В зависимости от функции команды может быть один операнд, два или ни одного. Более двух операндов указывается в макрокоманде. Комментарии поясняют смысл команды. Директивы ассемблера имеют несколько другой формат:
{имя} директива {операнд(ы)} {; комментарий}
Имя директивы имеет другой смысл по сравнению с меткой и не заканчивается двоеточием. В ряде директив имя отсутствует. Директивы используются для распределения памяти, связей между модулями, манипуляции с символами и т.д. В отдельных директивах допускаются списки операндов. Операнды могут быть ключевыми словами в директивах PROG, SEGMENT. Для определения макрокоманд используется оператор вида:
MACROCODE имя {операнд(ы)}; {комментарии}
Переменная – это единица данных, имеющая имя. Она имеет три атрибута: сегмент, смещение и тип. Сегмент SEG определяет сегмент, содержащий переменную. Смещение OFFSET, расстояние от начала сегмента до переменной, тип – число байтов переменной (1,2 или 4). Метка, представляющая имя ячейки памяти, имеет атрибутами сегмент, смещение, расстояние. Константа отличается от переменной и метки тем, что она определяет только число. Символьные цепочки заключаются в апострофы и обычно имеют длину до 255 знаков. Для определения и инициализации данных предназначены директивы: DB - определить байт (Define Byte) DW - определить слово (Define Word) DD - определить двойное слово (Define Double Word). Формат директивы: имя DX <начальное значение>, [< начальное значение>] Пример определения переменных:
Z1 DB 0ABH; один байт, равный AB Z2 DW 1000H; одно слово, равное 1000 Z3 DD 1235H; 1000H; младшее слово 1000, старшее 1235.
Для указания произвольного значения ячеек памяти используется символ? (значение переменной не оговаривается, резервирование ячейки). Для распределения и инициализации нескольких ячеек памяти используется конструкция DUP: DB 100 DUP(0); сто нулевых байтов (Dublicate) DW 20 DUP(?); 20 слов без значения ADR DD 10 DUP(ADR); десять полных адресов DB 10 DUP(80DUP); десять слов, содержащих 80 пробелов
В директивах DW, DD допускаются символьные цепочки длиной 1 и 2 символа, например: DW ’K1’; DD ’G’. В выражениях, где запрашивается тип используемой информации, применяются операторы TYPE, LENGTH, SIZE. TYPE (тип) сообщает число байт, отведенных для переменной (1,2,4). LENGTH (длина) определяет число единиц памяти переменной (байт, слов, двойных слов). SIZE (размер) сообщает размер переменной в байтах, причем размер переменной равен длине, умноженной на тип.
В ассемблере вводится понятие логического сегмента, под которым понимается часть программы, которая может включать сегменты для машинного кода, данных и стека. Каждый логический сегмент должен начинаться с директивы SEGMENT и заканчиваться директивой ENDS. Логическому сегменту присваивается имя, данное программистом, и список параметров (атрибутов), которые не обязательны, но необходимы в случае программы, включающие несколько модулей. Пример определения простого сегмента: DATA SEGMENT [<список атрибутов>] F1 DB? F2 DW? F3 DD? DATA ENDS До использования сегментного регистра в формировании адреса он должен быть инициализирован, для чего используется имя логического сегмента. Пример: MOV AX, DATA MOV DS, AX
До использования стека необходимо инициализировать регистры SS и SP. В общем случае в программе первыми командами являются команды инициализации регистров DS, SS, SP. В языке ассемблера К1810 допускаются вложенные сегменты. Это означает, что если определен некоторый сегмент S1 (парой директив SEGMENT и ENDS), затем определяется сегмент S2, а потом в новой директиве SEGMENT снова появляется сегмент с именем S1, то содержащиеся в новом сегменте данные и (или) команды будут автоматически размещаться за операторами, которые находятся в старом сегменте с именем S2. Вложенный сегмент должен заканчиваться раньше внешнего сегмента. Параметры директивы SEGMENT. 1. Выравнивание. Определяет границу начала сегмента. Обычное значение - PARA, по которому сегмент устанавливается на границу параграфа. В этом случае адрес кратен 16. При отсутствии этого операнда ассемблер по умолчанию принимает PARA; адрес сегмента ХХХ0. Бывает: PAGE=ХХ00; WORD=ХХХЕ (четная граница); BYTE=ХХХХ – любая шестнадцатеричная цифра. 2. Объединение. Определяет, объединяется ли данный сегмент с другими сегментами в процессе компоновки после ассемблирования. Возможны следующие типы объединений: STACK, COMMON (общий), PUBLIC (общедоступный), AT- и MEMORY. Все PUBLIC - сегменты, имеющие одинаковое имя или класс, загружаются компоновщиком в смежные области. Все такие сегменты имеют один общий базовый адрес.
Для сегментов COMMON с одинаковыми именами и классами компоновщик устанавливает один общий базовый адрес. При выполнении происходит наложение одного сегмента на другой. Размер общей области определяется самым длинным сегментом. АТ-параграф: он должен быть определен предварительно. Данный операнд обеспечивает определение меток и переменных по фиксированным адресам в фиксированных областях памяти, таких как ROM или таблица векторов прерываний. Например, для определения адреса дисплейного видеобуфера используется
VIDEO_RAM SEGMENT AT 0B800h.
Для сегмента стека используется объединение STACK. Если программа не должна объединяться с другими программами, то указание типов объединения должно быть опущено (за исключением STACK). 3. Класс. Данный элемент, заключенный в апострофы, используется для группирования относительных сегментов при компоновке. Он может содержать любое имя и используется ассемблером для обработки сегментов, имеющих одинаковые имена и классы. Типичными примерами являются классы ‘STACK’ и ‘CODE’. (См. П. Абель с. 55 и 396). Директива ASSUME(присвоить). Необходимая ассемблеру информация о значении сегментных регистров сообщается в директиве ASSUME, которая имеет формат:
ASSUME <SR: базовое значение>, [<SR: базовое значение>]…
Директива ASSUME DS: data1 требуется для транслятора, указывая ему, что сегмент сопоставляется с регистром DS. Описание в директиве ASSUME соответствия сегментного регистра DS сегменту данных избавляет нас от необходимости указывать в каждой строке, содержащей ссылку на имя данных, в каком сегментных регистров находится сегментный адрес этих данных. По умолчанию будет подразумеваться регистр DS. Если регистр ES в директиве ASSUME не описан, его следует в явной форме указывать во всех строках программы, где выполняется адресация к дополнительному сегменту данных: Mov BX, ES: mas[DI] Однако, и в этом случае занесение в регистр ES требуемого сегментного адреса должно быть сделано в явном виде: mov ax, data1 mov ex, ax
Поле SR содержит имя одного из сегментных регистров CS, DS, SS, ES, а базовое значение указывает на начало области памяти, адресуемой через этот регистр. Одним из наиболее часто используемых типов базового значения является имя сегмента, например
ASSUME DS: DATA Такая директива сообщает ассемблеру, что к определенным в сегменте DATA переменным можно обратиться через сегментный регистр DS. Директива ASSUME необходима перед использованием сегментных регистров и перед каждой точкой в программе, в которой может модифицироваться содержимое сегментных регистров. Если какой-либо сегмент не будет использован в модуле, то директива будет иметь вид ASSUME NOTHING. Директива ORG (origin - начало). Основной внутренней переменной ассемблера является счетчик адресов, который при ассемблировании выполняет функцию, аналогичную функции программного счетчика при выполнении программ. Этот счетчик сообщает ассемблеру адрес следующей ячейки памяти (имеется в виду смещение в сегменте), которая предназначена для размещения следующего байта команды или данных. Первое появление директивы <имя> SEGMENT определяет начало сегмента с заданным именем. При этом организуется новый счетчик ячеек, в который загружается нулевое значение, так как первый байт в сегменте имеет нулевое смещение. При распределении каждого следующего байта производится инкремент счетчика ячеек. Директива ENDS с тем же именем отключает данный счетчик до тех пор, пока этот же сегмент не будет открыт еще одной директивой SEGMENT. В этом случае счетчик продолжает счет распределяемых байт со старого значения. Текущее значение счетчика адресов может быть принудительно изменено программистом с помощью директивы ORG <выражение>. При выполнении директивы ORG вычисляется значение выражение и полученный результат загружается в счетчик ячеек (адресов). Ассемблирование следующих команд или элемента данных производится по полученному адресу (смещению в текущем сегменте), который изменяется в диапазоне 0-65535 (вычисление осуществляется по модулю 64К). Директива EQU. Директива позволяет определить символические имена для часто используемых выражений. Формат директивы: <имя> EQU <выражение> Поля выражения может определять константы, адреса, регистры и даже мнемокоды макрокоманд. Именам целесообразно придавать содержательный смысл. Допустим, в программе необходимо многократно применять размер таблицы. Тогда его можно определить: SIZE TABL EQU 100; 100 – размер таблицы. Затем можно использовать имя SIZE TABL в любой команде, где требуется использовать этот размер. Такой прием улучшает понимание программы, а при расширении таблицы достаточно заменить в директиве EQU число 100 другим размером и произвести повторное ассемблирование программы. Ассемблер автоматически заменит каждое появление в новой команде SIZE TABL новым размером.
Присвоенные имена сохраняют одно и тоже значение во всей программе, если не будут отмененыдирективой PURGE, которая имеет формат PURGE <имя>, [<имя>]… Если какое-то имя удалено, то с помощью директивы PURGE, его можно переопределить. Пример использования директивы EQU: CR EQU ODH; численная константа LF EQU OAH BET EQU ALPHA [SI]+3; адресное выражение COUNT EQU CX; регистр
Имена, присвоенные директивами EQU, можно использовать в любых операторах исходного модуля, например: MOV CL, LF; загрузить в CL значение OA INC COUNT; инкремент содержания регистра CX, ; используемого в качестве счетчика. Процедура (замкнутая подпрограмма) представляет собой законченную командную последовательность, которая приводится в действие командой вызова CALL. Процедура является одним из средств разработки модульных программ. Каждая процедура допускает автономную отладку, что позволяет ускорить разработку и отладку всей прикладной программы. Команда CALL содержит метку одной из команд процедуры. Метка в процедуре, которой передает управление команда CALL, называется точкой входа процедуры. Процедура выполняется до тех пор, пока не встретится команда возврата RET. Процедура обычно разрабатывается как функциональный блок, который формирует набор выходных данных путем преобразования четко определенных входных данных, называемых параметрами. Поэтому суть действий с процедурами сводится к передаче им параметров и получении из них результатов. Для организации процедур в языке ассемблера предназначены директивы PROC и ENDP. Директива PROC отмечает точку входа процедуры, а директива ENDP – окончание процедуры. Формат этих директив имеет вид: [имя] PROC [тип] . . тело процедуры . [имя] ENDP Имя представляет точку входа в процедуру. Тип процедуры (NEAR или FAR) (по умолчанию NEAR) используется ассемблером для определения вида генерируемой команды CALL для вызова процедуры. Если указан тип NEAR, то процедура находится в том же сегменте кода, в котором находятся все команды CALL, вызывающие эту процедуру. В этом случае ассемблер генерирует команду внутрисегментного вызова, то есть машинная команда CALL содержит только смещение точки входа. Если указан тип FAR, процедура находится в сегменте, отличающимся от того сегмента кода, в котором находятся команды вызова CALL. То есть ассемблер должен генерировать длинную команду межсегментного вызова, которая содержит базу и смещение точки входа. При передаче управления в стеке приходится запоминать, а затем восстанавливать содержимое регистров CS и PC. В соответствии с двумя форматами команд вызова необходимы и две разновидности команды возврата RET. Ассемблер определяет генерируемую разновидность команды RET на основе типа, указанного в директиве PROC. Пример определения процедуры: SRED PROC NEAR MOV CX, AX; передать <AX> в <CX> ADD CX, DX; прибавить <DX> к <CX> RCR CX,1; сдвинуть <CX> вправо (разделить на 2) RET SRED ENDP
Эта процедура определяет среднее значение содержимого регистров DX и AX помещает его в CX. Для вызова процедуры используется команда CALL SRED. К переменным или меткам внутри тела процедуры можно обращаться из любого места программы. Для предотвращения случайного выполнения процедуры без вызова, ее рекомендуется размещать выше тех программ, которые ее вызывают, а также избегать вложенных определений PROG - ENDP. Наиболее употребляемые директивы ассемблера МП 8086 приведены в табл.6.1.
Таблица 6.1 – Директивы ассемблера
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|