Глава 8 Язык программирования ASM-51

Язык программирования ASM-51 поддерживает модульное написание программ. Графическое изображение процесса разработки программы на языке программирования ASM-51 приведено на рис. 8.1.

Рис. 8.1. Схема процесса написания программы на языке программирования ASM-51

Файл, в котором хранится программа, написанная на языке ASM-51 (исходный текст программы), называется исходным модулем. Его можно создать, используя любой текстовый редактор. Для файла исходного текста программы принято использовать следующие расширения: asm, а51, srs, s51.

Получить объектный модуль можно, указав имя исходного модуля программы в качестве параметра вызова программы-транслятора в DOS-строке или строке командного файла: asm51.exe modul.asm

Получить исполняемый модуль программы можно, указав все имена объектных модулей программы в качестве параметров вызова программы-редактора связей в DOS-строке или строке командного файла: bl51.exe main.obj, modull.obj, modul2.obj

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

Большинство программаторов не может работать с объектным форматом исполняемого модуля программы, поэтому для загрузки машинного кода в микроконтроллер необходимо преобразовать объектный формат исполняемого модуля в общепринятый для программаторов НЕХ-формат. При таком преобразовании вся отладочная информация, содержащаяся в исполняемом модуле, теряется. Машинный код процессора, записанный в НЕХ-формате, называется загрузочным модулем.

Загрузочный модуль программы можно получить при помощи программы-преобразователя oh.exe, передав ей при вызове в качестве параметра имя файла исполняемого модуля, например: oh.exe main

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

Необходимое для отладки программ оборудование показано на рис. 8.2.

Рис. 8.2. Пример системы отладки программного обеспечения для микроконтроллеров

Отдельные участки программы могут быть отлажены и при помощи специальных программ-отладчиков (симуляторов).

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

Исходный текст программы на языке программирования ASM-51

Исходный текст программы представляет собой последовательность операторов языка программирования ASM-51, сгруппированных в сегменты и оформленных в виде файла.

Оператор — это базовая конструкция языка программирования, определяющая действия в программе. В языке программирования ASM-51 в одной строке может быть записан только один оператор! Максимальный допустимый размер строки — 255 символов. Признаком конца оператора является символ «возврат каретки», который вводится в текст про- программы при нажатии на клавишу <Enter> в конце строки.

Оператор языка программирования ASM-51 состоит из трех полей:

<поле метки> <поле операции> <поле комментария>

Любое из полей, в том числе и все поля, могут отсутствовать. Оператор, в котором все поля отсутствуют, называется пустым оператором. Он используется для увеличения наглядности программы. Пример оператора, записанного на языке программирования ASM-51, приведен на рис. 8.3.

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

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

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

В качестве операндов могут использоваться адреса ячеек памяти, обозначения регистров или метки операторов. Операнды отделяются друг от друга запятыми. Вместе с запятыми для увеличения читаемости программы допускается использование символов интервала (пробел или табуляция). Поле операции подробно показано на рис. 8.3.

Рис. 8.3. Пример оператора, записанного на языке программирования ASM-51

Поле комментария начинается с символа «точка с запятой» (;) и может содержать любые ASCII- или ANSI-символы. Это поле используется для записи пояснений к программе. Оператор, в котором присутствует только поле комментария, используется для увеличения наглядности программы. Примеры записи комментариев на языке программирования ASM-51 приведены в листинге. 8.2.

Символы языка ASM-51

Символы исходной программы представляют собой подмножество таблиц символов ASCII для DOS и ANSI для Windows. В исходном тексте программы, написанном на языке программирования ASM-51, могут быть использованы следующие символы:

— символы интервала;

— буквы;

— знаки;

— цифры.

Символы интервала определяют один или несколько пробелов в предложении исходного модуля. К этим символам относятся «пробел» и «табуляция».

В качестве букв воспринимаются латинские буквы верхнего и нижнего регистра:

А, В, С, D, E, F, G, H, I, J, К, L, М, N, О, Р, Q, R, S, T, U, V, W, X, Y, Z, а, Ь, с, d, e, f, g, h, i, j, k, 1, m, n, o, p, q, r, s, t, u, v, w, x, y, z.

Кроме того, в качестве букв могут быть использованы символы вопросительного знака (?) и подчеркивания (_).

Ниже приведен перечень цифр:

0, 1, 2, 3, 4, 5, б, 7, 8, 9.

Для записи шестнадцатеричных цифр дополнительно могут быть использованы следующие символы:

а, Ь, с, d, е, f, А, В, С, D, E, F.

Наименования знаков и их обозначения приведены в табл. 8.1.

Знаки, комбинации знаков (<>, >=, <=), а также символы интервала являются разделителями конструкций языка. До и после знака-разделителя в любой конструкции языка могут быть вставлены символы интервала.

ASCII- или ANSI-символы, не входящие в перечень основных символов алфавита языка, считаются дополнительными. Они могут использоваться в комментариях для пояснений в исходном тексте программы, а также для определения символьных констант.

Из символов формируются идентификаторы и числа.

Идентификаторы

Идентификатор — это символическое обозначение объекта программы.

В качестве идентификатора может быть использована любая последовательность букв и цифр. При этом в качестве буквы может быть использована любая буква латинского алфавита, а также вопросительный знак (?) и знак «нижнее подчеркивание» (_). Идентификатор может начинаться только с буквы! Это позволяет отличать его от числа. В идентификаторах язык программирования ASM-51 различает буквы верхнего и нижнего регистров.

Количество символов в идентификаторе ограничено только длиной строки (255 символов), но при этом транслятор языка программирования ASM-51 различает идентификаторы по первым 31 символам.

Примеры записи идентификаторов:

ADD5, FFFFH,? ALFA_1.

В языке программирования ASM-51 имеются три категории идентификаторов:

1. Ключевые слова.

2. Встроенные имена.

3. Определяемые имена.

Ключевые слова

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

В языке программирования ASM-51 имеются следующие категории ключевых слов:

— инструкции;

— директивы;

— вспомогательные слова;

— операции.

Инструкции по форме записи совпадают с мнемоническими обозначениями команд микроконтроллеров семейства MCS-51 и совместно с операндами составляют команды микроконтроллера. Список инструкций:

ACALL, ADD, ADDC, AJMP, ANL, CALL, CJNE, CLR, CPL, DA, DEC, DIV, DJNZ, INC, JB, JBC, JC, JMP, JNB, JNC, JNZ, JZ, LCALL, LJMP, MOV, MOVC, MOVX, MUL, NOP, ORL, POP, PUSH, RET, RETI, RL, RLC, RR, RRC, SETB, SJMP, SUBB, SWAP, XCH, XCHD, XRL.

Директивы совместно с вспомогательными словами определяют действия, которые должны быть выполнены ассемблером в процессе преобразования исходного текста программы в объектный код. В языке программирования ASM-51 используются:

Директивы:

BIT, BSEG, CODE, CSEG, DATA, DB, DBIT, DS, DSEG, DW, END, EQU, EXTRN, IDATA, ISEG, NAME, ORG, PUBLIC, RSEG, SEGMENT, SET, USING, XDATA, XSEG.

Вспомогательные слова:

AT, BIT, BITADDRESSABLE, CODE, DATA, IDATA, INBLOCK, INPAGE, NUMBER, PAGE, UNIT, XDATA.

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

AND, EQ, GE, GT, HIGH, LE, LOW, LT, MOD, NE, NOT, OR, SHL, SHR, XOR.

Встроенные имена

Встроенные имена присвоены адресам регистров специальных функций, адресам флагов специальных функций AR0—AR7, рабочим регистрам R0—R7 текущего банка регистров, а также аккумулятору А и флагу переноса С. Список встроенных имен приведен в табл. 8.2.

Определяемые имена

Определяемые имена объявляются пользователем. В языке программирования ASM-51 имеются следующие категории определяемых идентификаторов:

— метки;

— внутренние и внешние переменные адресного типа;

— внутренние и внешние переменные числового типа;

— имена сегментов;

— названия программных модулей.

Числа и литеральные строки

В языке программирования ASM-51 используются целые беззнаковые числа, представленные в двоичной, восьмеричной, десятичной и шестнадцатеричной формах записи. Для определения основания системы счисления используется суффикс (буква, следующая за числом):

— В определяет двоичное число (разрешенные цифры 0, 1);

— QO определяет восьмеричное число (разрешенные цифры 0, 1, 2, 3, 4, 5, 6, 7);

— D определяет десятичное число (разрешенные цифры 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

— Н определяет шестнадцатеричное число (разрешенные цифры 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F).

Для десятичного числа суффикс может отсутствовать. Количество символов в числе ограничено размером строки, однако значение числа определяется по модулю 216 (т. е. диапазон значений числа находится в пределах от 0 до 65535).

Примеры записи чисел:

011101b, 1011100B, 735Q, 45бо, 256, 0fah, 0CBH.

Число всегда начинается с цифры. Это необходимо для того, чтобы отличать шестнадцатеричное число от идентификатора. Например:

— ADCH — идентификатор;

— ADCH — число.

Часто бывает удобно выполнить некоторые вычисления для того, чтобы получить число. При этом, если поместить в текст программы предварительно вычисленное на калькуляторе значение, то может возникнуть вопрос: откуда взялось это значение. Лучше ввести формулу расчета и сами значения непосредственно в исходный текст программы. Язык программирования ASM-51 позволяет выполнять беззнаковые операции над числами. В таких выражениях допустимо использовать следующие арифметические операции:

+ суммирование;

 вычитание;

* умножение;

/ деление;

mod вычисление остатка от целочисленного деления.

В языке программирования ASM-51 также определена одноместная операция изменения знака — «минус» (-). Для нее требуется только один операнд — тот, которому она предшествует.

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

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

— not побитовая инверсия операнда;

— and логическое «И»;

— or логическое «ИЛИ»;

— хоr «исключающее ИЛИ» (суммирование по модулю два);

— функции выделения старшего «HIGH» и младшего «LOW» байта 16-разрядного числа.

Пример использования выражений языка программирования ASM-51 для определения числовой константы приведен в листинге 8.3.

Часто операнд используется для представления символов на экране дисплея. В этом случае для определения его значения удобнее воспользоваться не числом, а литеральной константой.

Литеральная константа заключается в апострофы:

'a', 'W'

mov SBUF, #'Б'    ;Передать по последовательному порту ANSI код буквы 'Б'

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

Nadp: DB 'Ошибка в блоке 5'

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

Директивы языка программирования ASM-51

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

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

При чтении программы трудно отличить константы от переменных, ведь в командах они отличаются только видом адресации. Это приводит к увеличению времени написания программы, т. к. каждый раз приходится решать загадку: что же подразумевалось под конкретной цифрой — переменная, константа или маска?

Преодолеть эту трудность можно при помощи идентификаторов. Можно назначить какой-либо ячейке памяти идентификатор и работать с ним, как с переменной. Более того! При необходимости изменить адрес переменной в памяти данных можно просто изменить значение идентификатора, а не просматривать всю программу, каждый раз решая, является ли найденное число адресом переменной, маской или константой!

Директива equ позволяет назначать имена для констант и значения адресов для переменных. При использовании этой директивы можно назначить идентификатору переменной адрес в одном месте программы и пользоваться идентификатором этой переменной во всей программе. Правда, за использование идентификатора именно в качестве переменной отвечает программист. Если он попытается использовать адрес переменной как константу, то транслятор не выдаст никакого сообщения об ошибке. Он спокойно оттранслирует оператор. При использовании назначения адресов переменных при помощи директивы equ изменение адреса при необходимости можно сделать в одном месте программы, а не просматривать всю программу, судорожно пытаясь вспомнить, где же еще была использована эта ячейка памяти. Все необходимые изменения в машинном коде программы сделает сам транслятор!

Пример назначения имен переменных с использованием директивы equ приведен в листинге 8.4.

Как видно из приведенного примера, использование идентификаторов значительно повышает наглядность программы, т. к. в них отражается назначение соответствующих констант и переменных.

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

В приведенном в листинге 8.4 примере имена Funcset и _8bit являются константами, но это становится понятным только по использованию непосредственной адресации в команде mov.

Один раз назначенный при помощи директивы equ идентификатор уже не может быть изменен, и при повторной попытке назначения точно такого же имени идентификатора транслятором будет выдано сообщение об ошибке.

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

Константы, назначаемые директивами equ или set, могут быть использованы только в качестве операндов команд микроконтроллера. В то же время достаточно часто требуется работа с таблицей констант, такой как таблица перекодировки, таблицы элементарных функций или синдромы помехоустойчивых кодов. Такие константы используются не на этапе трансляции для формирования машинного кода инструкций, а при исполнении программы. Они заносятся в память программ микроконтроллера. Для помещения значений констант в память программ микроконтроллера используются директивы db и dw.

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

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

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

Эта же директива позволяет легко размещать в памяти строки, которые в дальнейшем потребуется высвечивать на встроенном дисплее или экране дисплея универсального компьютера, подключенного к разрабатываемому устройству через какой-либо интерфейс. Пример использования директивы db для занесения строк в память программ микроконтроллера приведен а листинге 8.6.

Директива dw позволяет заносить в память программ двухбайтные числа. В ней, как и в директиве db, можно записывать несколько чисел, разделенных запятой. Пример фрагмента программы приведен в листинге 8.7.

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

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

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

Директива org предназначена для записи в счетчик адреса сегмента значения своего операнда. То есть при помощи этой директивы можно поместить команду (или данные) в памяти микроконтроллера по произвольному адресу. Пример использования директивы org для размещения подпрограмм обработки прерываний в нужных адресах показан в листинге 8.9.

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

Директива using. При использовании прерываний критичным является время выполнения подпрограммы-обработчика прерываний. Его можно значительно сократить, выделив для обработки прерываний отдельный банк регистров при помощи директивы using. Номер используемого банка регистров указывается в директиве в качестве операнда. При этом реальное включение банка регистров производится записью необходимой константы в регистр PSW. Пример использования директивы using для подпрограммы обслуживания прерываний от таймера 0 приведен в листинге 8.10.

Остальные директивы ассемблера предназначены для управления сегментами памяти и будут рассмотрены позднее.

Управляющие команды

Кроме директив, для управления процессом трансляции используются команды языка программирования. С их помощью можно управлять работой компилятора ASM-51. Команды могут задаваться как параметры в DOS-строке вызова компилятора или как управляющие строки в исходном тексте файла.

Если знак доллара ($) стоит в самом крайнем левом поле строки, то такая строка воспринимается компилятором как управляющая. Управляющие строки должны начинаться знаком доллара и могут содержать одну или более команд, разделенных пробелами.

Примеры управляющих строк:

$PRINT(A: PROG.LST) OBJECT(PROG.OBJ)

$LIST DEBUG XREF

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

Команда include. Это, пожалуй, наиболее часто используемая команда языка программирования ASM-51. Она позволяет включать в состав программы участки текста из другого файла. Это удобно при многофайловом написании программы, например, для того, чтобы вынести описания внутренних регистров микроконтроллера в отдельный файл. Пример использования команды include для включения файла описания внутренних регистров микроконтроллера 89с51 выглядит следующим образом:

$INCLUDE (RBG51.INC)

При использовании этой команды все содержимое включаемого файла помещается в выходной листинг программы, в результате чего становится трудно читать этот листинг. Поэтому в состав команд языка программирования ASM-51 включены команды list/nolist.

Команды list/nolist позволяют включать и выключать листинг исходного текста соответственно. При активной команде nolist в файл листинга будут помещаться только сообщения об ошибках. Пример запрета размещения содержимого включаемого файла в листинге программы будет выглядеть следующим образом:

$NOLIST //Запретить создание листинга включаемого файла $INCLUDE (RBG51.INC)

$LIST //Разрешить создание листинга дальнейшего текста

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

Команда pageiength (n) определяет максимальное число строк на странице файла листинга. Это число включает заголовок страницы. Количество строк в странице может изменяться в пределах от 10 до 65535.

Команда pagewidth (n) определяет максимальное число символов в строке листинга. Количество символов в строке листинга может изменяться от 72 до 132.

Реализация подпрограмм на языке ASM-51

Подпрограммы на языке программирования ASM-51 выносятся отдельно от основного текста программы. Обычно при программировании на языке ассемблера подпрограммы размещают после основного текста программы для того, чтобы случайно не передать управление подпрограмме не командой ее вызова, а последовательным выполнением операторов основной программы. Такая ситуация может произойти из-за того, что ассемблер назначает адреса машинным командам в порядке их написания. Если в начале поместить исходный текст подпрограммы, то именно она будет размещена по нулевому адресу памяти программ и после сброса будет выполнена раньше, чем основная программа. При завершении подпрограммы команда ret передаст управление по неопределенному адресу памяти программ, что может привести к непредсказуемым последствиям. Если в начале поместить текст основной программы, то после сброса начнется ее выполнение. После инициализаций (включая установку начального значения указателя стека) основная программа всегда содержит бесконечный цикл. Это означает, что попасть в подпрограмму в результате последовательного выполнения операторов невозможно. Управление в нее может быть передано только с помощью команды вызова подпрограммы lcall.

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

В главе 7 говорилось, что подпрограммы бывают двух видов: подпрограммы-функции и подпрограммы-процедуры. На языке программирования ассемблер проще реализуются подпрограммы-процедуры, поэтому сначала рассмотрим их.

Реализация подпрограмм-процедур на языке ASM-51

Подпрограмма-процедура вызывается командами процессора lcall и асаll. В языке программирования ASM-51 допустимо использование директивы call. Выполняя ее, компилятор автоматически подбирает наиболее подходящую к данному случаю по размеру команду. В листинге 8.11 приведен пример использования подпрограммы-процедуры для управления последовательным портом.

Передача переменных-параметров в подпрограмму

В приведенном на листинге 8.11 примере байт передается в подпрограмму через глобальную переменную G_Per. Однако программа будет эффективнее при использовании подпрограммы с параметрами. Мы уже знаем, что параметр подпрограммы — это локальная переменная, а при использовании локальных переменных могут значительно снизиться требования к памяти данных, т. к. локальные переменные разных подпрограмм располагаются в одних и тех же ячейках памяти микроконтроллера. Для размещения локальных переменных лучше всего использовать внутренние регистры процессора, т. к. кроме экономии памяти данных, использование регистровых переменных приводит к сокращению длины машинных команд, а значит и длины всей программы в целом. Кроме того, использование подпрограмм с параметрами позволяет вызывать подпрограмму саму из себя, например, при реализации рекурсивных алгоритмов. На языке ASM51 для передачи параметра размерностью один байт обычно используется аккумулятор, как показано в примере программы, приведенном в листинге 8.12.

Часто пример использования подпрограммы с параметрами более понятно выглядит на языке высокого уровня. Вызов подпрограммы с одним параметром, приведенный в листинге 8.12, на языке программирования С выглядел бы следующим образом:

PeredatByte(56);    //Передать число 56

PeredatByte(37);    //Передать число 37

Часто в подпрограмме требуется обрабатывать большие объемы данных, такие как массивы или структуры. При обращении к массивам или структурам, расположенным во внутренней памяти данных в качестве указателя адреса обычно используются регистры R0 или R1. Пример передачи в подпрограмму массива в качестве параметра, написанный на языке программирования ASM-51, приведен в листинге 8.13.

Если требуется, чтобы подпрограмма обработала такого же вида данные, но расположенные во внешней памяти данных или в памяти программ, то начальный адрес этих данных можно передать через двухбайтовый параметр-указатель. В качестве такого указателя обычно используется регистр-указатель данных DPTR. Пример передачи в подпрограмму адреса начального элемента строки в качестве параметра, написанный на языке программирования ASM-51, приведен в листинге 8.14. Строка расположена в памяти программ. Ее размещение в памяти программ показано в последней строке этого же листинга.

Если в подпрограмму нужно передать в качестве параметра двухбайтовое число, то для этого используется пара регистров (обычно это регистры R6 — старший байт и R7 — младший байт). Пример вызова подпрограммы с передачей в нее двухбайтового параметра, написанный на языке программирования ASM-51 приведен в листинге 8.15.

Если в подпрограмму нужно передать четырехбайтовое значение (параметр типа long, unsigned long или float), то обычно используются регистры R4—R7 (регистр R4 — старший байт). Пример вызова подпрограммы с передачей в нее четырехбайтового параметра, написанный на языке программирования ASM-51, приведен в листинге 8.16.

В примере показана передача в подпрограмму константы, но точно также можно передавать и переменную, расположенную во внутренней или внешней памяти данных. Для этого достаточно просто скопировать переменную в регистры R4—R7.

Реализация подпрограмм-функций на языке ASM-51

Часто требуется Передавать результат вычислений из подпрограммы в основную программу. Для этого обычно используется подпрограмма-функция. Наиболее наглядным примером использования подпрограмм-функций является вычисление элементарных функций. Подпрограмма-функция для вычисления синуса в программе на языке высокого уровня вызывается следующим образом:

Y=sin(x);   //Вызов подпрограммы-функции

Как видно из приведенного примера, использование подпрограмм-функций значительно увеличивает наглядность программ и приближает запись на языке программирования к общепринятой математической форме. На языке программирования ASM-51 этот же вызов подпрограммы-функции выглядит следующим образом:

mov А, х    ;Передать в подпрограмму вычисления синуса параметр х

call sin       ;Вызвать подпрограмму вычисления синуса

mov Y,A     ;Запомнить значение, которое вернула подпрограмма в Y

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

Элементарные операции на языке программирования ассемблер чаще всего вычисляются табличным способом, подобным тому, как проводилась перекодировка чисел, приведенная в примере листинга 8.5. Сами значения функций при этом рассчитываются заранее с использованием компьютера и вводятся во внутреннюю память микроконтроллера при помощи директивы db.

Подпрограмма-функция может возвращать и многобайтовые переменные. Для этого можно использовать два или четыре регистра. Обычно используются регистровая пара R6, R7 или регистры R4—R7. Можно также рассчитать и записать во внутренней памяти полученную в результате вычислений в подпрограмме переменную-массив или структуру и передать в вызывающую программу адрес этой переменной при помощи регистра-указателя R0 или R1.

Кроме подпрограмм-процедур и подпрограмм-функций существует особый класс подпрограмм. Это подпрограммы обработки аппаратных прерываний.

Реализация подпрограмм обработки прерываний на языке ASM-51

Подпрограммы обработки прерываний вызываются аппаратурой в произвольный момент времени и не могут иметь параметров. Подпрограммы обработки прерываний не могут быть подпрограммами-функциями. При переходе на подпрограмму обслуживания прерывания автоматически запрещается возникновение последующих прерываний, поэтому при возвращении из подпрограммы обработки прерывания должны быть разрешены прерывания. Команда возвращения из подпрограммы ret не снимает запрет на обработку прерываний, поэтому возврат из подпрограммы обработки прерывания может быть осуществлен только специальной командой возврата из подпрограммы обслуживания прерывания reti. Пример подпрограммы обработки прерывания на языке программирования ASM-51 приведен в листинге 8.17.

Достаточно часто требуется обработка прерываний от нескольких источников. В результате подпрограмма обработки прерываний не может уместиться между векторами прерываний на участке памяти длиной 8 байтов, поэтому подпрограммы выносятся из области векторов прерывания. Для перехода на эти подпрограммы используются команды безусловного перехода. В листинге 8.17 директива ORG использована для того, чтобы поместить команду перехода на подпрограмму обработки прерывания точно на вектор прерывания таймера 0.

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

Если подпрограмма обработки прерывания использует несколько регистров, то на сохранение регистров в стеке и на восстановление их из стека тратится достаточно много времени. Микроконтроллеры семейства MCS-51 предлагают возможность использовать для подпрограмм прерываний отдельный банк регистров. Переключение банков регистров производится при помощи регистра psw. В языке программирования ASM-51 то, что программа использует не нулевой банк регистров, указывается при помощи директивы using. Переключение банков регистров в подпрограмме обработки прерывания от таймера Т0, а также резервирование первого банка регистров при помощи директивы using показано в листинге 8.18.

Структурное программирование на языке ASM-51

Применение структурного программирования позволяет увеличить скорость написания программ и облегчить их отладку. Языки программирования С, PASCAL, PL/M разрабатывались на основе принципов структурного программирования, поэтому в состав этих языков входят структурные операторы. Ассемблер не относится к структурированным языкам программирования. Тем не менее, структурное программирование возможно и на языках программирования низкого уровня, в том числе и на языке программирования ASM-51, где не предусмотрено структурных операторов.

При разработке программы с использованием методов структурного программирования она может быть оттранслирована и выполнена на любом этапе написания, при этом можно отследить все алгоритмические действия программы, реализованные к этому времени. При использовании методов структурного программирования процесс написания программы не отличается от процесса создания алгоритма. Более того! Эти этапы создания программы можно объединить!

Для реализации методов структурного программирования огромное значение имеет использование «говорящих» меток, обозначаемых не просто M0, M1 и т. д., а в названии которых отображается действие, выполняемое программой. Для людей, не владеющих иностранным языком, ограничение в использовании для назначения меток букв только латинского алфавита создает определенные трудности. Однако, используя транслитерацию, и латинскими буквами можно писать русские слова.

При этом для обозначения действия может потребоваться несколько слов, использование же пробелов внутри метки недопустимо! Выйти из такой ситуации можно двумя способами:

— применять специальные символы-разделители;

— начинать каждое новое слово внутри метки с буквы верхнего регистра.

В качестве разделителей внутри метки можно использовать символы подчеркивания (_) и вопроса (?). Примеры назначения «говорящих» меток:

Priem_Comandy            ;Использование символов-разделителей

ProveritBitGotovnosti    ;Использование букв верхнего регистра

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

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

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

ProchitatPort          ;Прочитать порт

Vklychitlndikator     ;Включить индикатор

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

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

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

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

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

Пример реализации линейной цепочки операторов на языке программирования ASM-51 приведен в листинге 8.19.

Вторая структурная конструкция управления — условное выполнение оператора. Как уже рассматривалось в предыдущей главе, эта конструкция может быть двух видов — с одной ветвью и с двумя ветвями.

Если реализуется конструкция условного выполнения оператора только с одной ветвью, то можно воспользоваться любой командой условного перехода, входящей в набор команд микроконтроллера. Соответствующий пример приведен в листинге 8.20. В приведенном примере переменная SV1 и константа NajKnZvezd должны быть объявлены ранее (например, при помощи директивы equ).

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

Третья структурная конструкция — это цикл с проверкой условия после тела цикла. Такая конструкция легко реализуется на ассемблере при помощи одной команды условного или безусловного перехода. Отличие этой конструкции от условного выполнения операторов заключается в том, что передача управления осуществляется не вперед, а назад. Однако в системе команд микроконтроллера MCS-51 для реализации цикла предусмотрена специальная команда, выполняющая сразу два алгоритмических действия, необходимых для реализации цикла, — DJNZ. Пример использования этой команды для реализации цикла с проверкой условия после тела цикла приведен в листинге 8.22.

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

Пример реализации цикла с проверкой условия до тела цикла приведен в листинге 8.23.

Многомодульные программы

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

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

В языке программирования ASM-51 для этой цели используется директива public (общедоступные). Директива может быть использована в любом месте исходного текста модуля, однако обычно она размещается в его начале. Имена переменных в этой директиве перечисляются через запятую. Если в результате получается слишком длинная строка, то можно использовать несколько таких директив. Глобальное имя может быть объявлено только в одном модуле программы. Несколько глобальных переменных или подпрограмм с одним и тем же именем недопустимы.

Пример использования директивы public на языке программирования ASM-51:

PUBLIC Buflnd, Parametr

PUBLIC Podprogr,?Podprogr?Byte

Для ссылки на переменную или метку, объявленную в другом модуле, используется директива extrn. Идентификатор в пределах одного модуля не может быть одновременно объявлен как public и как extrn. В директиве extrn перечисляются через запятую имена подпрограмм и переменных, числовое значение которых редактор связей должен получить из других модулей и модифицировать все команды, в которых эти метки или переменные используются. Кроме того, для правильного использования инструкций микроконтроллера в директиве extrn должен быть указан тип памяти (data, bit, code, idata или xdata), в которой расположена переменная или метка. Для передачи числовых констант между модулями можно воспользоваться вспомогательным словом number Директива extrn может располагаться в любом месте исходного текста модуля. Идентификатор внешнего имени не может быть переопределен в программном модуле каким-либо способом. Рекомендуется с целью оптимизации результирующего объектного кода (в частности, команд JMP и CALL) размещать директиву EXTRN до ссылки на соответствующее внешнее имя (как правило, в начале исходного текста программного модуля или программного сегмента). Пример использования директивы extrn на языке программирования ASM-51:

EXTRN DATA (Buflnd, ERR), CODE (ASC_BIN, BIN_ASC), NUMBER (LIMIT)

EXTRN CODE (Podprogr)

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

Файл-заголовок на языке программирования asm-51 записывается на диск с расширением *.inc (сокращение от английского слова include — включать) и включается в исходный текст программы при помощи директивы include. Например:

$INCLUDE (REG51.INC)

При работе с пакетом программ, поставляемым фирмой keil, программа-транслятор с языка программирования ASM-51 «понимает» и файлы-заголовки, написанные для языка программирования С-51. Поэтому можно воспользоваться и этими файлами. Например:

#include <at87f51rc.h>

$include (init.inc)

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

Приведем пример вызова редактора связей из командной строки DOS для объединения трех модулей:

r151.exe progr.obj, modul1.obj, modul2.obj

В результате работы редактора связей в этом примере будет создан исполняемый модуль с именем progr. Формат записи информации в этом файле остается прежним — объектным. Это позволяет объединять модули по частям, т. е. при желании можно из нескольких мелких модулей получить один более крупный.

В настоящее время часто пользуются интегрированными средами программирования, например, фирм Franclin или keil, в состав которых входит язык программирования ASM-51. В этих программах создание строки вызова редактора связей производится автоматически при настройке программного проекта, а вызов редактора связей при помощи этой строки производится в ходе построения программного проекта. Настройка программного проекта происходит при подключении к нему новых программных модулей и при изменении его свойств, таких как разрешение или запрет создания карты памяти программы, выбор папки для хранения выходных файлов, разрешение или запрет помещения в выходной файл отладочной информации, разрешение или запрет создания загрузочного НЕХ-файла.

Использование сегментов в языке программирования ассемблер

При трансляции программы по частям возникает вопрос, как с этими частями работать. Иначе говоря, дает о себе знать проблема сегментов.

Справедливости ради необходимо отметить, что даже когда мы не задумываемся о сегментах, в программе присутствуют два сегмента: сегмент кода и сегмент данных. Если внимательно присмотреться к программе, то можно обнаружить, что кроме кодов команд в памяти программ хранятся константы, т. е. в памяти программ микроконтроллера располагаются, по крайней мере, два сегмента: код и данные. Чередование кода и данных может привести к нежелательным последствиям. Вследствие каких-либо причин данные могут быть случайно выполнены в качестве машинных команд или наоборот коды машинных команд могут быть восприняты и обработаны как данные.

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

1) кода;

2) переменных;

3) стека;

4) констант.

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

Пример размещения сегментов в адресном пространстве памяти программ и внутренней памяти данных приведен на рис. 8.4. На этом рисунке видно, что при использовании нескольких сегментов переменных во внутренней памяти данных редактор связей может разместить меньший из них на месте неиспользованных банков регистров. Под сегмент стека обычно отводится вся область внутренней памяти, не занятая переменными. Это позволяет создавать программы с максимальным уровнем вложенности подпрограмм. Сегмент переменных, расположенный на рис. 8.4 во внешней памяти данных, при использовании современных микросхем, таких как AduC842, может находиться и в ОЗУ, расположенном на кристалле микроконтроллера.

Наиболее простой способ определения сегментов — это использование абсолютных сегментов памяти. При этом способе распределение памяти ведется вручную точно так же, как это делалось при использовании директивы EQU. В этом случае начальный адрес сегмента жестко задается программистом, и он же следит за тем, чтобы сегменты не перекрывались друг другом в памяти микроконтроллера. Использование абсолютных сегментов позволяет более гибко работать с памятью данных, т. к. теперь байтовые переменные в памяти данных могут быть назначены при помощи директивы резервирования памяти DS, а битовые переменные при помощи директивы резервирования битов DBIT.

Рис. 8.4. Разбиение памяти программ и памяти данных на сегменты

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

— BSEG — абсолютный сегмент в области битовой адресации;

— CSEG — абсолютный сегмент в области памяти программ;

— DSEG — абсолютный сегмент в области внутренней памяти данных;

— ISEG — абсолютный сегмент в области внутренней памяти данных с косвенной адресацией;

— XSEG — абсолютный сегмент в области внешней памяти данных.

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

Пример использования директивы BSEG для объявления битовых переменных приведен в листинге 8.24.

Директива cseg позволяет определить абсолютный сегмент в памяти программ по определенному адресу. Директива не назначает имени сегменту, т. е. объединение сегментов из различных программных модулей невозможно. Для определения конкретного начального адреса сегмента применяется атрибут at. Если атрибут AT не используется, то начальный адрес сегмента предполагается равным нулю. Пример использования директивы CSEG для размещения подпрограммы обслуживания прерывания от таймера 0 приведен в листинге 8.25.

Директива dseg позволяет определить абсолютный сегмент во внутренней памяти данных по определенному адресу. Предполагается, что к этому сегменту будут обращаться команды с прямой адресацией. Эта директива не назначает имени сегменту, т. е. объединение сегментов из различных программных модулей невозможно. Для определения конкретного начального адреса сегмента применяется атрибут AT. Если атрибут at не используется, то начальный адрес сегмента предполагается равным нулю. Пример использования директивы DSEG для объявления байтовых переменных приведен в листинге 8.26.

Последний пример связан с примером, приведенным в листинге 8.24. То есть команды, изменяющие битовые переменные RejInd, RejPriem или Flag, одновременно будут изменять содержимое переменной Rejim, и наоборот команды, работающие с переменной Rejim, одновременно изменяют содержимое флагов RejInd, RejPriem или Flag. Такое объявление переменных позволяет написать наиболее эффективную программу управления контроллером и подключенными к нему устройствами.

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

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

Директива xseg позволяет определить абсолютный сегмент во внешней памяти данных по определенному адресу. Эта директива не назначает имени сегменту, т. е. объединение сегментов из различных программных модулей невозможно. Для определения конкретного начального адреса сегмента применяется атрибут AT. Если атрибут AT не используется, то начальный адрес сегмента предполагается равным нулю. До недавнего времени использование внешней памяти не имело смысла, т. к. это значительно увеличивало габариты и цену устройства. Однако в последнее время ряд фирм стал размещать на кристалле значительные объемы ОЗУ, доступ к которому осуществляется как к внешней памяти. Так как эта директива применяется так же, как DSEG, то отдельный пример приводиться не будет.

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

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

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

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

— data — размещает сегмент во внутренней памяти данных с прямой адресацией;

— idata — размещает сегмент во внутренней памяти данных с косвенной адресацией;

— bit — размещает сегмент во внутренней памяти данных с битовой адресацией;

— xdata — размещает сегмент во внешней памяти данных;

— code — размещает сегмент в памяти программ.

После определения имени сегмента можно использовать этот сегмент при помощи директивы rseg.

Директива rseg позволяет поместить в конкретный перемещаемый сегмент переменные или фрагмент кода программы. Обращение к одному и тому же сегменту может осуществляться в разных местах исходного текста программы (даже в разных файлах). При этом все участки сегмента будут находиться в соседних участках области памяти микроконтроллера, выделенного для этого сегмента.

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

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

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

Еще один пример использования директив segment и rseg приведен в листинге 8.29. В этом примере директива segment используется для определения сегмента битовых переменных.

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

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

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

Итак, подведем итоги

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

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