double arrow

Директивы ассемблера для микроконтроллеров семейства MCS-51

Приведенные команды могут быть переведены с языка Ассемблера в машинные коды микроконтроллера и далее в исполняемую программу при помощи двух программ, называемых транслятором и компоновщиком. Рас­смотрим возможности управления процессами трансляции и компоновки при помощи операторов исходного текста. При разработке программы на Ассемблере необходимо дать задание не только микроконтроллеру, но и транслятору с компоновщиком. Поэтому в Ассемблере существует два вида операторов; команды, которые превращаются транслятором в машин­ные коды, и директивы, в которых даются задания на трансляцию и компоновку. В отличие от команд директивы могут состоять из нескольких строк, то есть быть составными. Область действия таких директив заключена между открывающей и закрывающей строками, Часть директив также переводится транслятором в машинные коды или влияет на них, а некоторые директивы используются только для удобства работы программиста и потому без них в принципе можно обойтись. В отличие от ассемблерного программирования для компьютеров, когда нужно позаботиться еще и о взаимодействие программы с операционной системой, для микроконтроллеров такой проблемы нет. Но это не облег­чает, а усложняет работу программиста, так как он должен сам решать вопросы взаимодействия программы с внешней средой.

Написание и отладка программ содержит творческую и рутинную компоненты. Рутинная часть работы может быть автоматизирована настолько, насколько удается формализовать ее. Для того чтобы ассемблер и компоновщик могли выполнить формализуемую часть работы по переводу исходного текста программы в машинный код, программист должен включить в него директивы. Директивы являются средствами управления ассемблером и компоновщиком. В Ассемблере директивы позволяют определять и использовать дополни­тельные программные объекты, не предусмотренные архитектурой про­цессора. Но они должны быть определены так, чтобы транслятор мог реализовать их имеющимися в микроконтроллере средствами.

Преобразование исходного текста в машинный код осуществляется в три этапа. Сначала этот текст должен быть записан в файл с расширени­ем ASM при помощи обычного текстового редактора, не добавляющего никаких форматирующих символов. Затем текст нужно обработать транс­лятором, который в случае нарушения синтаксических правил выдает сообщения о местонахождении и типе каждой из ошибок. Если ошибок нет, то транслятор формирует объектный файл с расширением OBJ, а также, по желанию программиста, формирует файл протокола трансляции (листинг) с расширением LST. Объектный файл используется редактором связей (компоновщиком) для создания исполняемой программы, имеющей расширение TSK или HEX или DEC или BIN. Поскольку на каждом из этапов работы могут обнаружиться ошибки, приходится возвращаться назад для коррекции исходного текста. И только после получения машинного кода можно приступать к отладке программы.

Для того чтобы понимать сообщения транслятора и компоновщика об ошибках, нужно усвоить общие понятия о процессах трансляции и компоновки и о действиях, производимых транслятором и компонов­щиком по директивам в исходном тексте. Знание принципов трансляции и компоновки существенно облегчает освоение разных диалектов Ассемблера, соответствующих особенностям, как архитектуры процессо­ров, так и математического обеспечения разработки разных фирм.

Упомянутый выше листинг является текстовым файлом, предназна­ченным для программиста. В нем приводится не только исходный текст, но и полученный машинный код в текстовом представлении.

Перевод мнемокодов и имен регистров ассемблерной команды в ма­шинный код производится по заранее заданным таблицам с учетом способа адресации. Обработка символических имен, заданных програм­мистом, представляет более сложную задачу. Программисту нужно понимать, каким образом эти имена обрабатываются транслятором и компоновщиком. Смысл обработки имен транслятором состоит в вы­числении адресов, по которым программные объекты будут размещены в ОЗУ и ПЗУ микроконтроллера. По этим адресам определяются ма­шинные коды команд, загружаемых в ПЗУ. Транслятор и компоновщик (независимо от используемого языка программирования) решают задачу вычисления правильных адресов данных и команды в соответствии с их размещением в памяти.

Начнем ознакомление с проблемой вычисления адресов команд и данных с понятия о сегментировании памяти. В отличие от процессоров, для микроконтроллеров сегментирование связано не со способом вычис­ления исполнительного адреса, а с форматом обрабатываемой информации. Обращение к встроенному ОЗУ и к функциональным регистрам микро­контроллера может производиться как для чтения, так и для записи. Адресное пространство для данных в байтовом и битовом форматах от 00 до FF соответствует размеру байта адресации. В ОЗУ можно адресовать 128 байт (адреса от 00 до 7F) и столько же битов. Битовое пространство занимает байтовые адреса от 20 до 2F, то есть 16 байт того же самого ОЗУ. Остальные адреса используются для обращения к функциональным регистрам. Обращение к внешним ЗУ может производиться только в байто­вом формате, притом к ЗУ команд — только для чтения. Адресное про­странство внешних ЗУ ограничено двумя байтами адресации, что соответ­ствует 65536 байт (адреса от 0000 до FFFF).

В Ассемблере стандартными сегментами (они называются также сек­циями) являются сегменты CODE (предназначен для программы) и DATA (предназначен для данных). Две других секции BSECT (битовая) и RSEC T (регистровая) введены для программирования обращений к ОЗУ в бито­вом и байтовом форматах соответственно. Все эти названия имеются в таблице имен приведенного примера. Любая часть транслируемого текста должна относиться к какой-либо секции, то есть находиться между открывающей и закрывающей строками директивы определения секции:

.CODE

;команды и директивы секции кода

.ENDS

.DATA

;директивы секции данных

.ENDS

.RSECT

;директивы регистровой секции

.ENDS

.BSECT

;директивы битовой секции

.ENDS

Если в начале исходного текста не указано имя секции, то транслятор по умолчанию считает открытой секцию кода (именно поэтому нам удалась трансляция приведенного примера). Допускается вложение одних секции в другие. Строка ENDS закрывает вложенную секцию. Если вложения секции не нужно, то для перехода к другой секции достаточно записать только открывающую строку директивы.

При желании можно задать дополнительные секции при помощи следующих директив:

имя:.SECTION BIT;Описывается новая битовая секция

имя:.SECTION REG;Описывается новая регистровая секция

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

Транслятор переводит данные и команды в машинный формат, рас­полагая их в той последовательности, в какой они будут храниться в отведенном для них адресном пространстве секции. Поскольку программа состоит из нескольких секций, то вычисление адресов для записи двоичных кодов ведется для каждой секции отдельно. Начальный адрес в каждой из секций равен 0. Для использования счетчика текущего адреса в секции применяется предопределенное символическое имя $ (знак доллара) или * (звездочка). Числовое значение содержимого счетчика после трансляции строки исходного текста увеличивается на количество байтов, добавленных в адресное пространство текущего сегмента командой или директивой. Программист может использовать содержимое счетчика текущего адреса в выражениях, входящих в операнды команд или директив, и даже изменять его содержимое при помощи директивы. Запись заданного адреса в счетчик производится директивой

. ORG адрес

Синонимом ORG является ORIGIN.

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

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

Повторное использование имени в поле метки недопустимо, потому что нельзя создавать дополнительный программный объект с тем же самым именем. Исключения из правила уникальности используемых имен допускаются только в том случае, когда их повторное появление не нарушает этого правила. Это относится к директивам переопределяемых программных объектов и к локальным меткам. В таком случае при повтор­ном использовании имени ранее созданный программный объект заменяется другим.

Имя должно начинаться с буквы и состоять из букв, цифр и может включать в себя некоторые другие символы. Его длина может быть очень большой, лишь бы оператор не вышел за пределы строки редактора, но только 32 первых символа будут иметь значение для таблицы имен. Большие и маленькие буквы в имени считаются различными. При соблю­дении этих условий имя в поле метки считается нелокальным и включа­ется в таблицу имен модуля. Имя, подставленное в иоле метки, называет­ся меткой (label). Метку можно начинать с любой колонки, если за именем следует двоеточие. Если двоеточие не используется, то первый символ имени должен располагаться в первой колонке.

Обычная метка наблюдаема (scope) во всем модуле. Однако при про­граммировании можно пользоваться локальными метками, видимость которых ограничена двумя ближайшими нелокальными метками (пред­шествующей и последующей). Локальные метки могут быть использова­ны многократно, поэтому они не включаются в таблицу имен. Отличи­тельным признаком локальной метки является символ $ в начале или конце имени, поэтому имя локальной метки может начинаться с цифры. Впрочем, в качестве признака локальной метки можно использовать другой символ, который указывается директивой

.LLCHAR символ

Эта директива изменяет символ-определитель локальной метки.

До сих пор речь шла об именах, используемых в пределах одного

программного модуля. Если программа состоит более чем из одного

модуля, то вычисление всех адресов на этапе трансляции невозможно. В связи с этим необходимо обратить внимание читателей на то, что адреса команд и данных на разных этапах подготовки программы к выполнению вычисляются с разной степенью определенности. Это каса­ется размещения команд и данных в памяти и, как следствие, ссылок на адреса команд (из управляющих команд) или на адреса данных (из команд, обрабатывающих информацию). В связи с неполнотой информации о разме­щении данных и команд транслятор создает в объектных модулях три вида адресов:

· одна часть адресов определена окончательно и не подлежит

изменению,

· другая часть адресов определена с точностью до размещения

модулей в программе,

· третья часть адресов неизвестна, так как они находятся в других

модулях (так называемые внешние ссылки).

Так что при трансляции каждого оператора, порождающего машинный код, транслятор прежде всего проверяет, какого рода адреса используются в операторе, по какому адресу этот код должен быть записан и какое символическое представление объекта программы должно быть сохранено для последующей сборки.

При использовании нескольких модулей большинство адресов, вы­численных транслятором, не являются окончательными, и подлежит уточнению в процессе сборки. При сборке объектных модулей производится их перекомпоновка с целью объединения частей программы, содержащих одноименные секции, что позволяет вычислить все адреса. Имена секции сохраняются в объектных файлах для того, чтобы при сборке модулей объединять одноименные секции. При трансляции по умолчанию исполь­зуется относительный режим, чтобы при сборке не происходило наложения адресных пространств. В относительном режиме сборка производится так, чтобы адресное пространство очередного модуля располагалось непосредственно за адресным пространством предыдущего. Таким образом, для каждой из секций размер адресного пространства программы равен сумме адресных пространств модулей, если программист не задал смещения для собираемых частей секций.

При необходимости можно обеспечить фиксированные (не изменяемые компоновщиком) адреса в пределах 0 страницы ЗУ переключением транслятора в абсолютный режим директивой

.ABSOLUTE

Абсолютный режим допустим только для данных. Выполняемые команды всегда должны ассемблироваться в относительном режиме. Для возврата к относительному режиму используется директива

.RELATIVE

При переходе из одной секции в другую атрибуты ABSOLUTE и RELATIVE не изменяются,

В современных компьютерах большинство программ должно быть перемещаемыми, то есть работоспособными при их загрузке в произвольную часть ОЗУ. В микроконтроллерах все адреса окончательно определяются уже в процессе редактирования связей. Совершенно ясно, что транслятор может определить самостоятельно только первые два из трех перечис­ленных выше видов адреса. Притом адреса второго вида в объектном коде помечаются специальными символами, обнаружение которых используется компоновщиком для пересчета. Чтобы транслятор не выда­вал сообщения об ошибках по третьему виду адресов, программист должен включить в программу соответствующие директивы. Связи между модулями осуществляются по данным и по управлению, Эти связи осуществляются при помощи символических имен, которые в модулях, вызывающих связь, объявляются как внешние при помощи директивы

.EXTERNAL список

В списке должны быть перечислены имена, определяемые в других модулях. Регистры могут быть заданы как внешние двумя путями. Если директива используется в регистровой секции, то внешние имена опреде­ляются как регистровые. Во всех остальных секциях необходимо перед каждым именем регистровой переменной в списке вставить слово REG. Синонимами EXTERNAL являются EXTERN и XREF.

Вызываемые имена должны быть определены в соответствующих модулях как доступные при помощи директивы

.PUBLIC список

Определив имя как доступное, можно ссылаться на него из других модулей, Синонимами PUBLIC являются XDEF и GLOBAL, хотя последний мне­мокод в других диалектах Ассемблера используется для объявления и внешних, и доступных имен.

Существует еще один способ сделать имена доступными для исполь­зования другими модулями. Для этого используется директива

.GLOBALS ON

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

Эта директива не действует на локальные метки. Ее действие отменяется переходом к трансляции другого модуля или директивой

.GLOBAL OFF

Имена, использованные в разных модулях и не внесенные в соответст­вующие списки, не включаются в объектные файлы, В этом случае при компоновке редактор связей выдает сообщение об ошибке.

Использование промежуточного объектного файла при переводе исходного текста в исполняемую программу характерно для большинства современных языков программирования. Хотя получение исполняемой программы непосредственно из исходного текста в принципе возможно, по ряду причин сложилась практика двухступенчатого перехода. В случае программирования для микроконтроллеров это оправдано тем, что для разработки более или менее сложных программ удобно сначала отладить отдельные функционально законченные модули независимо друг от друга. Кроме того, набор отлаженных объектных файлов, которые могут использоваться во многих программах, можно объединить при помощи программы-библиотекаря в специальный файл с расширением LIB. После отладки всех модулей их объектные файлы (включая библиотечные) собираются в один исполняемый файл для комплексной отладки про­граммы.

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

.MODULE

а в конце модуля — директиву

.ENDMOD

Вложение модулей в другие модули не допускается. Транслятор осущест­вляет перевод модулей в машинный код независимо от других модулей. Количество модулей в файле не ограничивается. Выходной файл в этом случае имеет расширение .РАК. Эта пара директив предназначена для получения объектных файлов, пригодных для работы с программой-библиотекарем. Их использование позволяет транслировать всю библиотеку в целом, вместо того чтобы транслировать каждую подпрограмму отдельно, а затем собирать объектные модули в один файл. Обычно библиотеки составляются из большого числа небольших по размерам программ. Когда компоновщик не может найти глобальное имя (идентификатор) в каком-либо из компонуемых файлов, он может продолжить его поиск в библиотеках.

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

.DS размер

Синонимами этой директивы являются DEFS и RMB. По директиве резервирования памяти значение счетчика текущего адреса в транслируе­мой секции увеличивается на число, указанное в операнде директивы, независимо от записи имени в поле метки. В битовой секции директива резервирует адреса для битов, а в остальных — для байтов. Инициализа­ция данных при этом не производится. В резервируемую область не записываются никакие коды. Регистровые и битовые секции отличаются от секций кода и данных тем, что в них можно только отводить адресное пространство для данных, но нельзя инициализировать содержимое ОЗУ. Инициализация данных в битовой и регистровой секциях должна произ­водиться программой сразу после включения микроконтроллера.

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

.RADIX Основание

в которой операнд может принимать следующие числовые или буквенные значения:

2 или В (двоичная система),

8 или О или Q (восьмеричная система),

10 или D (десятичная система),

16 или Н (шестнадцатеричная система).

По умолчанию в этой директиве происходит возврат к десятичной сис­теме. Следует отметить, что если задано основание системы счисления 16, то не существует способа описания десятичного или двоичного числа, поскольку суффиксы D и В являются допустимыми шестнадцатеричными цифрами.

Директива инициализации данных байтового формата имеет вид

.DB значение

Синонимами этой директивы являются DEFB, BYTE, FCB и STRING. Операндом директивы может быть как одно значение, так и список значений, разделенных запятыми. Каждому элементу списка отводится один байт. При отсутствии операнда инициализируется одно нулевое значение. Допускается использование числового или символьного формата данных. Строки символов следует заключать в кавычки. Каждому символу строки отводится один байт (ограничители не включаются в объектный файл). Для включения кавычки в состав строки в качестве ограничителя следует использовать другой вид кавычек. При инициализации символов в машинном коде программы записываются числа, соответствующие загруженной в системе MS DOS кодовой странице.

Эта директива позволяет программисту записывать в любые места секции кода команды в машинных кодах. Таким образом, транслятор позволяет при желании вернуться к самому древнему методу программи­рования.

Для инициализации массива данных, имеющих одинаковые значения, можно использовать директиву

.BLKB размер, значение

Количество инициализируемых байтов определяется размером. По умол­чанию в каждом байте записывается нуль.

Существуют специальные директивы для инициализации данных

символьного формата. Директива

.ASCII строка

инициализирует коды символов ASCII, за исключением ограничителей строки. Если в строке появляется символ вертикальной черты (шестнадцатеричное 7Сh), то этот и последующие символы не инициализируются.

Еще одна директива инициализации данных символьного формата

.DC строка

использует в качестве ограничителей первый символ строки и следующий совпавший с ним. Инициализируются только символы, расположенные между ограничителями, притом в старший бит кода последнего инициа­лизируемого символа записывается 1. Синонимом этой директивы явля­ется FCC.

Для ввода некоторых неотображаемых символов можно использовать их символическое представление при помощи пары букв:

"СR" или 'CR' - carriage return (возврат каретки)

"LF" или 'LF' — line feed (перевод строки)

"SP" или 'SP' - space (пробел)

"HT" или 'НТ' - horizontal tab (горизонтальная табуляция)

"NL" или 'NL' - null (нуль)

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

директива

.TWOCHAR ON

По умолчанию используется режим выключения ввода неотображаемых символов. При необходимости можно выключить этот режим директивой

.TWOCHAR OFF

Чтобы отличать символ от числа, когда-то было принято записывать еди­ницу в старший бит каждого кода символа при инициализации символьных данных директивами ASCII или DB. Для включения и выключения режима записи 1 в седьмой бит кода символа используются директивы

.BIT7 ON

.BIT7 OFF

По умолчанию режим записи 1 в седьмой бит кода символа выключен.

Существуют директивы инициализации данных, состоящих из двух байтов (слов). Инициализация отдельных значений или списков значении осуществляется только в числовом формате директивой

.DW значение

Синонимами этой директивы являются DEFW, WORD и FDB. Для ини­циализации массива одинаковых значении в формате слова используется

директива

.BLKW размер, значение

Она инициализирует заданное количество 16-разрядных слов с заданным значением. По умолчанию инициализируются значения нуль.

Приведенные директивы инициа­лизации данных в формате слова располагают старший байт по младшему адресу без соблюдения четности адресов.

Существуют также директивы инициализации числовых данных в форматах LONG, FLOAT и DOUBLE, но для микроконтроллеров в подавляющем большинстве случаев использование таких форматов является экзотикой.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



Сейчас читают про: