Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
4. ИСПОЛЬЗОВАНИЕ ЯЗЫКОВ ВЫСОКОГО УРОВНЯ ДЛЯ
ПРОГРАММИРОВАНИЯ МИКРОКОНТРОЛЛЕРОВ
СЕМЕЙСТВА MCS-51
4.1. Компилятор С51 фирмы Keil
Объектные файлы: STARTUP.A51, INIT.A51. Стандартная библиотека Си — C51S.LIB.
4.1.1. Инициализация после сброса МК
Имеется два файла, уже проассемблированных и включенных в Си-библиотеку
(C51S.LIB), содержимое которых можно модифицировать под специальную ситуацию.
При компоновке программы код из библиотеки автоматически включается в приложение.
Чтобы использовать модифицированные файлы, вы должны включить их в свой проект и
в компоновку.
PDATA — страничное ОЗУ.
IBPSTACK — стек для хранения данных вызванных программ, которые были вызваны
еще раз.
Начало
После сброса МК
Сюда можно поставить останов
WatchDog
Выполняется код из файла STARTUP.A51
Выполняется код из файла INIT.A51
к функции main
Очистка памяти
данных и установка
стека
Содержит процедуру
инициализации для явно
инициализированных
переменных, определенных
в программе
Рис. 4.1. Схема системной инициализации микроконтроллера
4.2. Начальные элементы языка Си
4.2.1. Комментарии
Комментарии начинаются с пары символов /* и завершаются парой символов */ либо
начинаются с пары символов //. В первом случае комментарии могут содержать любое
число строк, начинаться и заканчиваться в любом месте программы. Т.е. комментарием
будет непосредственно то, что заключено между ними. Вложенные комментарии не
допускаются. Во втором случае комментарии могут содержать только одну строку.
4.2.2. Идентификаторы
Идентификаторы — это имена, присваиваемые переменным, функциям, константам,
макросам и типам данных. Они могут включать символы «a-z», «A-Z», «0-9» и «_» (знак
подчеркивания), но не должны начинаться с цифры.
И хотя языком не запрещено начинать идентификатор со знака подчеркивания, но
рекомендуется это не делать, т.к. имена многих переменных библиотечных функций
начинаются со знака подчеркивания. Обычно при написании программ на языке Си
следуют соглашению: имена констант и макросов составляют из прописных букв, имена
переменных – из строчных. Строчные и заглавные буквы в компиляторе различаются.
Длину меток желательно делать до 31 символа.
4.2.3. Ключевые слова
Зарезервированные в языке идентификаторы называются ключевыми словами. Последние
не могут использоваться в качестве идентификаторов переменных, функций, констант и
макросов. Ключевые слова должны набираться строчными буквами. Ниже представлен
полный список ключевых слов Сх51, сгруппированных по функциональному назначению
и с пометками расширения стандарта ANSI С.
Таблица 4.1. Ключевые слова Сх51
4.2.4. Константы (литералы)
Константа (литерал) — это явное представление значения. Константы в языке Си могут
быть следующих типов:
целые (int) и длинные целые (long int);
с плавающей точкой (float);
символьные (char);
строковые (string);
перечисляемые (enum).
Целые константы
Целые константы могут быть записаны в десятичной, шестнадцатиричной или
восьмеричной системе счисления. Запись десятичной константы не должна начинаться с
нуля. Представление шестнадцатиричной константы должно начинаться с пары символов
0х (0Х), восьмеричной – с цифры 0. Запись константы может завершаться суффиксом L
(l), U (u) ИЛИ UL (ul). Суффиксы означают: L — константа типа long; U — беззнаковая
константа; UL — константа типа unsigned long. По умолчанию (суффиксы не
использованы) константа относится к определенному целому типу по следующим
правилам:
o константы в диапазоне 0 – 32767 относятся к типу int;
o константы в диапазоне 32768 – 65535 относятся к типу unsigned int;
o константы в диапазоне 65536 – 2 147 483 647 относятся к типу long;
o константы в диапазоне 2 147 483 648 – 4 294 967 295 относятся к типу unsigned
long.
#define имя текст
#define С1 23
— определение константы
-//- С3 (С1*С2)
—> возможно переполнение и неправильное
определение типа
Константы с плавающей точкой
Константы с плавающей точкой могут быть представлены в традиционной (пример — 1.0
или 12F) и в научной нотациях (пример — 1е-4). Т.к. компилятор Сх51 поддерживает
числа с плавающей точкой только одинарной точности, то все константы с плавающей
точкой имеют тип float.
Символьные константы
Наиболее часто для кодирования символов используется код ASCII. В наборе кодов ASCII
выделяются две группы кодов: первой соответствуют печатные символы (имеющие
графическое представление), второй – непечатные. Последним соответствуют
специальные управляющие коды в диапазоне 0-31. В языке Си для отображения
управляющих кодов и некоторых других печатных символов используются так
называемые эскейп-последовательности. Эскейп-последовательность начинается с
обратной косой черты «\», которая указывает компилятору, что следующий символ или
символы надо интерпретировать особым образом. В табл. 4.2 представлен полный список
эскейп-последовательностей. Как следует из табл. 4.2, символы \, ‘ и “ хотя и являются
печатными, но должны быть указаны с помощью эскейп-последовательностей. Это
позволяет использовать их в символьных и строковых константах.
Таблица 4.2. Эскейп-последовательности и соответствующие им управляющие коды
Последние две эскейп-последовательности в табл. 4.2 позволяют задавать код любого
символа в восьмеричной или шестнадцатиричной системе счисления. Например, часто для
записи символа с нулевым кодом (null-символ) используется следующая эскейппоследовательность – «\0».
Символьная константа — это печатный символ, заключенный в апострофы (одиночные
кавычки), или эскейп-последовательность, заключенная в апострофы. Значение
символьной константы — числовой код символа в принятой кодировке на данной машине.
К символьным константам применимы такие же операции, как и к другим целым, хотя
чаще они используются в операциях сравнения с другими символами.
Строковые константы
Строковая константа — это последовательность из нуля или более символов, заключенная
в кавычки (двойные кавычки). В строковые константы могут входить эскейппоследовательности. При компиляции отдельно заданные строковые константы
объединяются в одну строку. Это дает возможность разбивать длинные строки на части и
располагать их на отдельных строчках, что улучшает читаемость программы.
Строковую константу можно рассматривать как массив символов. При размещении строк
в памяти компилятор отводит по одному байту на каждый символ, а в конце обязательно
добавляет null-символ «\0». Поэтому памяти для строки требуется на один байт больше,
чем число символов, заключенных в кавычки. Такие строки часто называют ASCIIZстроками, где нулевой байт — это ограничитель строки. Это означает, что нет
ограничения на длину строки. Однако чтобы определить длину строки, необходимо
просмотреть всю строку.
Перечисляемые константы
Перечисляемые константы являются идентификаторами, обозначающими значения типа,
определенного пользователем [3]. Более подробные сведения о перечисляемом типе
изложены в [1].
Перечисление — это список целых констант. Это удобный способ присвоить константам
имена. Например: еnum boolean {NO, YES};
Если для значений констант не было явных присваиваний, то в данном примере NO = 0,
YES = 1. В следующем примере используется явное присваивание константам значений:
enum escapes {BELL = '\а', BACKSPACE = '\b',
TAB = '\t',
NEWLINE = '\n',
RETURN = '\r', VTAB = ' \ v '};
Имена в различных перечислениях должны отличаться друг от друга, а значения внутри
одного перечисления могут совпадать.
Именованные константы
Именованные константы облегчают чтение программы и упрощают ее модификацию.
Определения именованных констант обеспечиваются препроцессором языка Си.
Определение константы имеет следующий вид:
#define NAME
Text
Отметим, что в конце строки определения именованной константы точка с запятой не
ставится. С момента определения константы при любом появлении ее имени NAME оно
будет заменяться на соответствующий ему текст Text. Принято имена констант набирать
заглавными буквами, чтобы они отличались от переменных, набираемых строчными
буквами. В качестве текста-подстановки может использоваться константа, ранее
определенная именованная константа или константное выражение — выражение,
оперирующее только с константами. Такие выражения вычисляются во время
компиляции, и поэтому их можно использовать в любом месте, где допустимы константы.
Во избежание неожиданных интерпретаций рекомендуется константные выраженияпараметры в макровызовах заключать в круглые скобки. С этой же целью рекомендуется
при определении макроса в замещающем имя макроса тексте параметры заключать в
круглые скобки [3]:
#define R(x,y) sqrt((х)*( х ) + ( у ) * ( у ) )
Это позволяет в макровызовах использовать в качестве фактических параметров сложные
выражения без нарушения приоритетов операций.
4.2.5. Базовые типы данных
Компилятор Сх51 поддерживает стандартные для языка Си типы данных и несколько
типов данных, уникальных для платформы (семейства микроконтроллеров 8051).
В табл. 4.3 перечислены поддерживаемые Сх51 типы данных.
Таблица 4.3. Базовые типы данных
Ɨ The bit, sbit, sfr, and sfr16 data types are not provided in ANSI C and are unique to the Cx51 compiler.
These data types are described in detail in the following sections.
Типы, отмеченные знаком «Ɨ», являются уникальными для Сх51. Базовые типы данных
(кроме void) могут иметь различные спецификаторы (называются также описателями,
модификаторами и квалификаторами), предшествующие им в тексте программы. Полный
список спецификаторов типов: signed, unsigned, long, shot.
bit Fg —битовая переменная; Для РСФ введены специальные обозначения: sfr — адрес
регистра, sfr16 — адрес 16-разрядного регистра, sbit — адресация бита регистра; volatile
— дополнительная добавка (спецификатор) для переменной (отключение оптимизации
чтения содержимого, в этом компиляторе не используется); void — функция ничего не
возвращает (пустой тип), т.к. в Си в качестве подпрограммы применяются функции;
signed, unsigned (long, shot, … ) — знаковый и беззнаковый тип соответственно.
Средство typedef
Язык Си, используя ключевое слово typedef, предоставляет возможность давать типам
данных новые имена:
typedef
тип
новое_имя_типа
Следует подчеркнуть, что данное объявление не создает новый тип данных, а всего лишь
определяет новое имя существующего типа. Тип определяет множество значений и набор
допустимых операций.
Расширенные целые типы
Новый стандарт С99 определяет несколько расширенных целых типов. Расширенные
типы включают в себя типы с точной разрядностью, минимальной разрядностью,
максимальной разрядностью и самый быстрый целый тип. Например:
Расширенный тип
int16_t
uintl6_t
int_least16_t
int_fast32_t
intmax_t
uintmax_t
Описание
Тип 16-разрядных целых
Тип 16-разрядных целых без знака
Тип целых, содержащий не менее
16
разрядов
Самый быстрый тип целых, содержащий не
менее 32 разрядов
Тип самых больших целых
Тип самых больших целых без знака
Расширенные типы облегчают написание переносимого кода. Объявления новых имен
этих типов содержатся в файле и выполнены с помощью средства typedef:
*.c — главный файл; *.h — файл заголовков.
#INCLUDE “*.h”
— искать в той папке, где лежит проект; если там его нет, то ищет в
папке среда_разработки\INCLUDE\ ...
#ifndef — директива условного компилирования «если не определено»
4.2.6. Выражения и операции
Операторы
Операторы
(операции)
Операторы
управления
могут
использоваться
в выражениях
организуют
исполнение
программы
Выражение — правило для получения значения. Составляющими элементами выражения
языка Си являются данные и операции. Данные могут быть представлены переменными,
константами или значениями, возвращаемыми функциями. Операции обозначаются
определенными символами, называемыми знаками операций.
Существует четыре основных класса операций: арифметические, логические, поразрядные
и операции сравнения. Кроме них, есть также некоторые специальные операции,
например, операция присваивания.
Операции присваивания
Оператор присваивания может присутствовать в любом выражении языка Си. Общая
форма оператора присваивания:
имя_переменной = выражение;
Выражение может быть просто константой или сколь угодно сложным выражением. Для
присваивания используется знак «=».
Множественное присваивание
В одном операторе присваивания можно присвоить одно и тоже значение многим
переменным, например: х = у = z = 1;
Выражение i = i + 2, в котором стоящая слева переменная повторяется и справа,
можно написать в сжатом виде: i += 2; комбинация знаков += называется операцией
присваивания. Большинству бинарных операторов соответствуют операторы
присваивания вида «ор=», где ор — один из операторов +, -, *, /, %, <<, >>, &, ^, |.
х *= у+1 эквивалентно х = х*(у+1)
i *= 2 эквивалентно i = i*2
(Выражение 1) = (Выражение 1) op (Выражение 2)
(Выражение 1) op= (Выражение 2)
Типом и значением любого выражения присваивания является тип и значение его левого
операнда после завершения присваивания.
Арифметические операторы
Бинарными (т.е. с двумя операндами) арифметическими операторами являются +, -, *, / и
оператор деления по модулю %. Последний не применяется к операндам с плавающей
точкой. Арифметические операции одного приоритета выполняются слева направо.
x % 5 — остаток от деления x на 5
Операции отношения и логические операции
Операции отношения: >, >=, <, <= — имеют одинаковый приоритет; сразу за ними идет
приоритет операций сравнения на равенство: ==, или неравенство: !=.
Выражения между логическими операциями && (лог. «И») и || (лог. «ИЛИ») слева
направо. Вычисления прекращаются, как только становится известна истинность или
ложность результата. Результат логических операций True = 1 и False = 0.
Операции инкремента и декремента
Операция ++ добавляет 1 к своему операнду, а операция декремента -- вычитает 1. Эти
операции можно использовать и как префиксные, так и постфиксные: ++i; i++. Но
выражение ++i увеличивает i до того, как его значение будет использовано, i++ – уже
после. Выражения х = i++ и х = ++i дают одинаковый результат, а х = x++ + у и х = ++i + у
– разный. Здесь в первом случае i увеличивается после использования в y, а во втором – i
увеличивает до использования в y.
Побитовые операции
Обозначаются следующим образом: & («И»), | («ИЛИ»), ^ («ИСКЛЮЧАЮЩЕЕ ИЛИ»),
<< (сдвиг влево), >> (сдвиг вправо). Операции << и >> сдвигают влево или вправо свой
левый операнд на число битов, задаваемое правым операндом, который должен быть
неотрицательным. Сдвиг влево всегда логический, независимо от знака левого операнда.
Сдвиг вправо отрицательного операнда – арифметический.
Условная операция
Инструкция:
if (а > b) z = а;
else z = b;
Условное выражение, написанное с помощью тернарной операции (т.е. имеющей три
операнда) «?», представляет собой другой способ записи этой и подобных ей
конструкций.
В выражении
(Выр.1) ? Выр.2 : Выр.3
Выр.1 вычисляется первым; если оно истинно, то вычисляется Выр.2 и его значение
присваивается всему выражению; если Выр.1 ложно, то вычисляется Выр.3 и всему
выражению присваивается его значение. Например: z = (а > b) ? а: b;
Операция определения размера sizeof
Унарная операция sizeof, выполняемая во время компиляции программы, позволяет
определить длину операнда в байтах. Результат этой операции рассматривается как
константа.
Для вычисления размера типа переменной имя типа должно быть заключено в круглые
скобки. Имя переменной заключать в скобки не обязательно, но ошибки в этом не будет.
sizeof(int) — имя типа; sizeof x — имя переменной x
Преобразование типов
Если операнды операции принадлежат к разным типам, то они приводятся к некоторому
общему типу. Приведение выполняется в соответствии с небольшим числом правил.
Обычно автоматически производятся лишь те преобразования, которые без потери
информации превращают операнд с меньшим диапазоном значений в операнд с большим
диапазоном значений. Например: ffloat + iint.
Выражения, в которых могла бы теряться информация (длинные целые присваиваются
более коротким или значение float целым переменным), могут повлечь за собой
предупреждение, но они допустимы.
Преобразование имеет место и при присваиваниях: значение правой части приводится к
типу левой части, который и является типом результата.
И наконец, для любого выражения можно явно («насильно») указать преобразование его
типа, используя унарную операцию, называемую приведением типа. Конструкция
(имя_типа) выражение
приводит выражение к указанному в скобках типу по перечисленным выше правилам.
Приоритет и очередность операций
Таблица 4.4. Приоритет и очередность операций
Операция «запятая»
x = (y = 3, y + 1) — результатом всего выражения будет последнее
выражение.
4.2.7. Операторы языка Си
Любое выражение может быть преобразовано в оператор добавлением к нему точки с
запятой. Запись вида
выражение;
является оператором. Значение выражения игнорируется. Действие такого оператора
состоит в создании побочного эффекта вычислением значения выражения. Обычно это
оператор присваивания или оператор вызова функции, не возвращающей значения.
Исключения составляют выражения, входящие в заголовок цикла for.
Пустой оператор
Пустой оператор обозначается точкой с запятой:
;
Его использование необходимо в тех случаях, когда логически не требуется выполнения
каких-либо действий, но в соответствии с правилами синтаксиса присутствие оператора
обязательно.
Составной оператор (блок)
{ определения и описания;
оператор_1;
…
onepaтop_N;
}
// необязательны
Составной оператор — заключенная в фигурные скобки последовательность операторов.
Если среди операторов внутри фигурных скобок имеются определения и описания, то
составной оператор превращается в блок. Блок может содержать последовательность
описаний, за которыми следует последовательность операторов. После правой
закрывающей фигурной скобки в конце блока (составного оператора) точка с запятой не
ставится. Внутри блока (и составного оператора) несоставной оператор должен
оканчиваться точкой с запятой.
Операторы выбора
Условный оператор if-else
Синтаксис условного оператора имеет две формы:
if (выражение) if (выражение)
оператор;
оператор;
else
оператор;
Если значение выражения не равно нулю (истина), то выполняется первый оператор, в
противном случае выполняется второй оператор (если присутствует).
Отсутствие else-части в одном из вложенных друг в друга условных операторов может
привести к неоднозначному толкованию записи. В языке Си else-часть связывают с
ближайшим if, у которого нет своего else.
if (n > 2)
if (a > b) z = a; // else относится к внутреннему if
else z = b;
Если требуется иная интерпретация, то следует расставить фигурные скобки:
if (п > 2)
{ if (а > b) z = а ; }
else z = b;
Переключатель switch
Синтаксис переключателя таков:
switch (выражение) {
case конст. выр.1: последовательность операторов
break;
case конст. выр.2: последовательность операторов
break;
……..
default: последовательность операторов;
}
Оператор switch передает управление к тому из помеченных с помощью case операторов,
для которых значение константного выражения совпадает со значением выражения.
Последнее должно быть целочисленным. Любая последовательность операторов в
конструкции switch может быть отмечена одной или несколькими метками вида:
case константное выражение:
Последовательность операторов выполняется до оператора break. Если этот оператор
отсутствует, выполнение последовательности операторов продолжается до тех пор, пока
не встретится break (в другой метке) или не кончится тело оператора switch. Если
значение выражения не совпадает ни с одним из константных выражений, то выполняется
переход к оператору, отмеченному меткой default:. Эта метка может отсутствовать. В
этом случае при отсутствии совпадений не выполняется ни один оператор.
Операторы цикла
Onepaтоp for (итерационный цикл)
for (инициализация_цикла; выражение-условие; инкремент)
оператор;
Инициализация цикла, выражение-условие и инкремент могут быть любыми
выражениями. Инициализация производится до входа в цикл. Последний выполняется до
тех пор, пока проверка – TRUE. Инкрементирование происходит после каждого
выполнения оператора. Любое из этих трех выражений может отсутствовать, но точку с
запятой опускать нельзя. При отсутствии первого или третьего выражения считается, что
их просто нет в конструкции цикла; при отсутствии второго выражения предполагается,
что его значение как бы всегда истинно. Например,
f o r ( ; ; ) { . . . } // бесконечный цикл, который прерывается
// каким-то другим способом (break)
Там, где есть простая инициализация и пошаговое увеличение значения некоторой
переменной, больше подходит цикл for, т.к. в этом цикле организующая его часть
сосредоточена в начале записи. Например, начало цикла, обрабатывающего первые n
элементов массива, имеет следующий вид: for (i = 0; i < n; i++).
Оператор while (цикле предусловием)
while (выражение-условие) оператор;
Выполнение оператора повторяется до тех пор, пока выражение-условие – TRUE (отлично
от нуля). Проверка выражения осуществляется до первого выполнения оператора, так что
оператор не будет выполнен, если выражение исходно – FALSE.
Оператор do ... while (цикл с постусловием)
do {
оператор;
} while (выражение-условие);
Оператор циклически выполняется до тех пор, пока выражение – TRUE. Значение
выражения проверяется после первого выполнения оператора, так что оператор всегда
выполняется, по крайней мере, один раз. Если оператор не является блоком, фигурные
скобки не обязательны, но их часто ставят, чтобы оператор достаточно наглядно
отделялся от выражения-условия.
Операторы перехода (передачи управления)
Оператор goto
Имеет вид:
goto label;
………..
label:
Выполнение немедленно передаётся от оператора goto к оператору, следующему за
соответствующей меткой label. Передача управления разрешена на любой отмеченный
оператор в теле функции.
Оператор return
Оператор возврата из функции имеет вид:
return выражение; или return;
Отнесение этого оператора к группе операторов перехода обусловлено тем, что он
заставляет программу перейти в точку вызова функции. Выражение присутствует только в
том случае, когда функция возвращает значение. Внутри функции может присутствовать
произвольное количество операторов return. Выход из функции происходит тогда, когда
встречается один из них.
Операторы break и continue
Имеют следующий вид:
break;
continue;
Оператор break осуществляет выход из непосредственно его содержащего оператора
while, do while, for или switch; управление передаётся на оператор, следующий за
оператором, из которого осуществлён выход.
Оператор continue прекращает выполнение текущей итерации ближайшего
охватывающего его цикла while, do while или for. Если условиями цикла допускается
новая итерация, то она выполняется, в противном случае цикл завершается.
4.2.8. Массивы и указатели
Массивы
Массив — это набор переменных (элементов) одного типа, расположенных в
непрерывной области памяти (расположены подряд). Массивы могут быть одномерными
и многомерными.
Как определяется массив? Определение одномерного массива:
тип_элементов_массива имя_массива[размер_массива];
размер_массива — константное выражение, задающее количество элементов массива: по
определению имя массива является указателем-константой.
При определении массива ему выделяется память, но после этого имя массива
воспринимается как константный указатель того типа, к которому отнесены элементы
массива. В языке Си элементы массива всегда нумеруются, начиная с нуля.
Пример определения массива:
int а[20]; // определяет массив из 20 элементов типа int
Доступ к элементу массива осуществляется с помощью имени массива и индекса,
заключенного в квадратные скобки после имени массива:
имя_массива[индекс]
Индексом может быть любое целое выражение, образуемое целыми переменными и
целыми константами. Например:
а[0] … а[19]
a[i]
Как выполняется инициализация массивов?
Массив можно инициализировать в его определении с помощью заключенного в
фигурные скобки списка инициализаторов, разделенных запятыми. Например:
int а[] = {0, 1,
… , 9}; // неявная индексация
int a[10] = {0, 1, 2}; // инициализация первых трех
Для определения размера массива a применяется команда sizeof a. Так, например,
выражение (sizeof a) / (sizeof int) определяет количество элементов в массиве a.
Строка — массив символов.
Если размер массива не указан, то длину массива компилятор вычисляет по числу
заданных инициализаторов; в нашем примере их равно 10. Инициализация символьных
массивов-строк особый случай: вместо конструктора с фигурными скобками и запятыми
можно использовать строку символов:
char string[] = “Строка - массив символов”;
символьного массива на 1 больше числа символов
//
размер
Указатели
Указатель — это переменная, значением которой является адрес некоторого объекта
(обычно другой переменной) в памяти.
Определение указателя:
тип_объекта *имя_указателя;
Например:
int *р;
float *р;
void *р;
// указатель на тип int
int
↑p
p+1 →
||
int
p+2
При увеличении на единицу указатель переместится к следующей переменной данного
типа.
Как инициализировать (присвоить адрес указателю) указатель? Для этого используется
унарный оператор &:
Имя_указателя = &имя_объекта;
Например:
int х;
р = &х;
int *p = &x;
Унарный оператор «*» есть оператор косвенного доступа. Он используется для получения
значения объекта, на который указывает (ссылается) указатель.
*имя_указателя // косвенная адресация
Возвращает значение объекта, которому соответствует указатель имя_указателя.
Примеры:
*р = 1; // x = 1
void *p; // указатель на переменную типа void
Если p указывает на переменную x соответствующего типа (кроме void), то указатель *p
можно использовать в любом месте, где применимо x.
*p = *p + 1; // x = x + 1
++*p;
(*p)++;
p = q; // q — также указатель на тип int;
//
копируется содержимое самого указателя
Операции с указателями
Можно производить следующие операции с указателями:
присваивание значения указателя другому указателю того же типа;
сложение и вычитание указателя и целого;
вычитание и сравнение двух указателей, указывающих на элементы одного и того
же массива;
присваивание указателю нуля и сравнение указателя с нулем.
Указатели и целые не являются взаимозаменяемыми. Константа нуль — единственное
исключение из этого правила: ее можно присвоить указателю, и указатель можно сравнить
с ней. Чтобы показать, что нуль — это специальное значение указателя, вместо цифры
нуль часто записывают NULL-константу, определенную в файле stdio.h.
Преобразование типа указателя
В языке Си допускается присваивание указателя типа void * указателю любого другого
типа (и наоборот) без явного преобразования типа указателя. Тип указателя void *
используется, если тип объекта неизвестен. Например, использование типа void * в
качестве параметра функции позволяет передавать в функцию указатель на объект любого
типа, при этом сообщение об ошибке не генерируется. Также он полезен для ссылки на
произвольный участок памяти, независимо от размещенных там объектов.
В отличие от void *, преобразование всех остальных типов указателей должны быть
всегда явными, т.е. должна быть указана операция приведения типов по следующей
схеме:
(имя_типа_указателя)указатель
int
Примеры:
↑p
||
int
p+1
int i;
int *pi = &i;
s8 *ps; // 8-разрядное знаковое значение
u8 p1; // unsigned type (беззнаковый), 8-разрядный
int16_ti;
int16_t *pi = &i;
intS_t *ps;
p1 = (u8 *)p;
ps = (s8)*pi;
ps = (intS_t *)pi;
Связь между указателями и массивами
Примеры:
int a[5]; // массив из пяти элементов типа int
a[0] … a[4], где a[0] (или просто a) — адрес первого
элемента массива a
p = &a[0]; // p = a; // p+1 → a[1], p+i → a[i]
*(p+i); // a[i]
В Си имя массива передается функции всегда по ссылке (адрес начального элемента
массива).
char *pc;
pc = “Это пример”; // это присваивание поместит в указатель
//
адрес на начальный символ строки
4.2.9. Структуры
Структура — это совокупность переменных, в общем случае разных типов, объединенных
под одним именем.
Структуры могут копироваться, их можно передавать функции в качестве аргумента, а
функции их возвращать в качестве результата.
Как определяется структура?
struct тег_структуры {
тип имя_элемента_структуры;
………………………………..
тип имя_элемента_структуры;
};
struct {
} x, y;
struct тег_структуры x, y;
При определении структуры за словом struct может следовать имя, называемое тегом
структуры. Тег дает название структуре данного типа и далее может служить кратким
объявлением той части структуры, которая заключена в фигурные скобки. Если структура
имеет тег, то этим тегом далее можно пользоваться при определении структурных
переменных. Заключение в фигурные скобки обозначает элементы (поля) структуры.
Объявление в структуры объявляет тип (пользовательский). За правой скобкой могут
следовать переменные точно также как они могут быть указаны. Если в объявлении
структуры переменные отсутствуют, то память не выделяется.
Примеры:
struct point {
int х;
int у; };
или
struct point {
int x;
int y;
} pt;
struct point pt;
struct rect {
struct point pt1;
struct point pt2;
………………… };
На тип элемента структуры ограничений нет. Поэтому структуры могут быть вложены
друг в друга.
Как инициализируется структура?
Структурную переменную при ее определении можно инициализировать, формируя
список инициализаторов ее элементов в виде константных выражений:
struct point maxpt = {320, 200};
Как осуществляется доступ к элементам (членам, полям) структуры?
Доступ к отдельному элементу структуры осуществляется посредством конструкции вида:
имя_структурной_переменной.имя_элемента_структуры
Например:
pt.x = 100;
pt.y = 120;
pp = &pt;
struct point *pp; // указатель на структуру
*pp → (*pp).x // *pp ↔ pt // *pp.x
Указатели на структуры используются достаточно часто, поэтому для доступа к ее
элементам была придумана еще одна, более короткая форма записи. Если р — указатель
на структуру, то:
р->имя_элемента_структуры
есть ее отдельный элемент.
Например:
рр->х;
рр->у;
++рр->х;
Чтобы изменить порядок выполнения операций, нужны явные скобки. Например:
(++рр)->х;
(рр++)->х;
4.2.10. Объединения
Объединение — это переменная, которая может содержать (в разные моменты времени)
объекты (переменные) различных типов и размеров. Фактически объединение — это
структура, все элементы которой имеют нулевое смещение относительно ее базового
адреса и размер которой позволяет поместиться в ней самому большому ее элементу, а
выравнивание этой структуры удовлетворяет всем типам объединения. Операции,
применимые к структурам, годятся и для объединений.
Объединения часто используются тогда, нужно выполнить специфическое
преобразование типов, потому что хранящиеся в объединениях данные можно обозначать
совершенно разными способами. Например, используя объединения, можно
манипулировать байтами, составляющими значение float, чтобы выполнять какое-либо
необычное округление.
Как определяется объединение?
union тег {
тип имя_элемента;
…………………..
тип имя_элемента;
};
Например:
union u_fc {
float f;
char ch[4];
};
typedef union { u32 U32; s32 S32;
u16 U16[2]; s16 S16[2]; u8 U8[4];
s8 S8[4]; } UU32;
Инициализировать объединения можно только значением, имеющим тип его первого
элемента. Например:
union u_fc fc = {123.5};
Как осуществляется доступ к элементам (членам, полям) объединения?
Синтаксис доступа к элементам объединения в точности такой же, как в структурах:
имя_объединения_переменной.имя_элемента_объединения
или
указатель_на_объединение->имя_элемента_объединения
4.2.11. Битовые поля
Битовое поле может быть членом структуры или объединения.
Общий вид определения битового поля:
тип имя:ширина; // тип – знаковое или беззнаковое целое;
// ширина — длина десятичного слова
Для обращения к полям используются те же конструкции, что и для обращения к
обычным элементам структуры. Поля могут не иметь имени. С помощью этого
организуется пропуск необходимого количества разрядов. Особая ширина, равная нулю,
используется в случае, когда требуется выйти на границу следующего слова. Размещение
полей может быть слева направо и наоборот.
Примеры:
struct t_flags
{ u16 fg1:1; u16 fg2:1; u16 fg3:3;
u16 :2; u16 fg4:1; } flags;
flags.fg1 = flags.fg2:1;
Поля ведут себя как малые целые и могут участвовать в арифметических операциях. Поля
не имеют адресов и не могут быть массивами, поэтому указывать на них нельзя.
4.2.12. Функции
Использование функций является фактически единственным способом справиться с
потенциальной сложностью больших программ. Определения всех функций имеют
одинаковый вид:
тип имя_функции(список_формальных_параметров)
{ описания
// Тело
операторы
// функции
}
Тип — тип возвращаемого функцией значения (в том числе void); функция может
возвращать любой тип данных, за исключением массивов;
имя_функции — идентификатор;
список_формальных_параметров — это список элементов вида: тип имя_параметра,
которые отделяются друг от друга запятыми.
Функция может быть и без параметров, тогда ее список будет пустым. Такой пустой
список можно указать в явном виде, поместив для этого внутри скобок ключевое слово
void. В Си функцию нельзя определять функцию внутри другой функции.
Тело функции — это всегда блок или составной оператор, т.е. последовательность
описаний и операторов, заключенная в фигурные скобки. Важный оператор тела функции
— оператор возврата в точку вызова:
return выражение;
или
return; // может отсутствовать
Функция не обязана возвращать какое-либо значение; оператор RETURN, не содержащий
никакого выражения, приводит к такой же передаче управления, как «сваливание на
конец» функции при достижении конечной правой фигурной скобки, но при этом в
вызывающую функцию не возвращается никакого значения.
Вызов функции:
имя_функции(список_фактических_параметров)
Согласование по типам между формальными и фактическими параметрами требует, чтобы
в модуле-файле до первого вызова функции было помещено либо ее определение, либо ее
описание (прототип), содержащее информацию о типе возвращаемого результата и о
типах всех параметров. Наличие такого прототипа позволяет компилятору
контролировать соответствия типов параметров. Прототип (описание) функции может
внешне почти полностью совпадать с заголовком ее определения:
тип имя_функции(список_формальных_параметров); // достаточно
// указать лишь типы параметров
Основное различие – точка с запятой в конце описания (прототипа). Второе отличие –
необязательность имен формальных параметров в прототипе даже тогда, когда они есть в
заголовке определения функции. В языке «С» все аргументы функций передаются «по
значению». Это означает, что вызванная функция получает значения своих аргументов с
помощью временных переменных, а не их адреса. Поэтому вызванная функция не может
изменить переменную из вызывающей функции; она может менять только свою
собственную временную копию. Если тип функции – void, то ее вызов: f(); при этом
необязательно использовать возвращающий тип, возвращаемое значение.
Указатели и аргументы функции:
Можно организовать указатель на функцию. Например:
int (*p)(char); // возвращает указатель на тип int
4.2.13. Класс хранения: область действия и время жизни
Областью видимости имени считается часть программы, в которой это имя можно
использовать.
Временем жизни объекта данных называется отрезок времени, в течение которого
значение этого объекта доступно для использования в некоторой части программы. Время
жизни объекта может быть столь коротким, как время исполнения операторов блока, или
столь же длинным, как время исполнения всей программы.
В языке Си предусмотрены пять классов хранения:
автоматический;
регистровый;
внутренний статический;
внешний;
внешний статический.
Программа на Си обычно оперирует с множеством внешних объектов: переменных и
функций. Прилагательное «внешний» противоположно прилагательному «внутренний»,
которое относится к аргументам и переменным, определяемым внутри функций. Внешние
переменные определяются вне функций и потенциально доступны для многих функций.
Сами функции всегда являются внешними объектами, поскольку в Си запрещено
определять функции внутри других функций.
Внутренние объекты (переменные)
Автоматические переменные
Класс хранения таких объектов, как аргументы функций, называется автоматическим auto
(другие названия – локальный, рабочий). Такое название означает, что область памяти для
хранения этих элементов данных выделяется автоматически при вызове функции и также
автоматически освобождается, когда выполнение этой функции завершается. Область
действия аргументов ограничена текущей функцией. Кроме того, автоматические
переменные могут быть определены внутри любого блока операторов языка Си.
Регистровые переменные
Служебное слово register указывает компилятору, что надо попытаться хранить
соответствующий объект (например, счетчик) в быстродействующих машинных
регистрах, если это возможно.
Идея состоит в том, чтобы переменные, объявленные register, разместить на регистрах
машины, благодаря чему программа, возможно, станет более короткой и быстрой. Однако
компилятор имеет право игнорировать это указание. Данное объявление может
применяться только к автоматическим переменным и к формальным параметрам функции.
Внутренние статические переменные
Объявление static можно использовать и для внутренних переменных. В отличие от
автоматических переменных они не возникают только на период работы функции, а
существуют постоянно. Это значит, что внутренние статические переменные
обеспечивают постоянное сохранение данных внутри функции.
Внутренние статические объекты имеют ту же область действия, что и автоматические
объекты. Однако временем жизни внутренних статических объектов является время
исполнения всей программы.
Для следующего заголовка функции { static int x = 0 } справедливо, что когда
функция заканчивает работу, то значение переменой сохраняется (для обработки
прерываний).
Внешние и внешние статические объекты (переменные)
Внешние переменные
Поскольку внешние переменные доступны всюду, их можно использовать в качестве
связующих данных между функциями как альтернативу связей через аргументы и
возвращаемые значения. Для любой функции внешняя переменная доступна по ее имени,
если это имя было должным образом объявлено.
Внешние переменные существуют постоянно, так что их значения сохраняются и между
обращениями к функциям. Таким образом, если двум функциям приходится пользоваться
одними и теми же данными и ни одна из них не вызывает другую, то часто бывает
удобнее оформить эти общие данные в виде внешних переменных, а не передавать их в
функцию и обратно через аргументы.
Область действия внешней переменной или функции простирается отточки программы,
где она объявлена, до конца файла, подлежащего компиляции. Однако если на внешнюю
переменную нужно сослаться до того, как она определена, или если она определена в
другом файле, то ее объявление должно начинаться словом extern. Важно отличать
объявление внешней переменной от ее определения.
Внешние статические объекты
Указание static, примененное к внешней переменной или функции, ограничивает область
соответствующего объекта концом файла. Это способ скрыть имена. Указание static чаще
всего используется для переменных, но с равным успехом его можно применять и к
функциям. Обычно имена функций глобальны и видимы из любого места программы.
Если же функция помечена словом static, то ее имя становится невидимым вне файла, в
котором она определена.
Таблица 4.5. Характеристики классов хранения
Класс
хранения
Область
действия
Время
жизни
Внешний
Внешний
статический
Аргументы
функции
Автоматический
Регистровый
Внутренний
статический
Программа
Модуль
Функция
Блок
Блок
Блок
Программа
Программа
Функция
Блок
Блок
Программа
4.3. Расширения языка Сх51
4.3.1. Модели памяти
Модель памяти определяет, какой по умолчанию тип памяти используется для
аргументов функций, автоматических переменных и декларации без явного указания
спецификатора типа памяти. Поддерживаются три модели памяти: SMALL (малая),
COMPACT (компактная) и LARGE (большая).
Исключая очень специально выбранные приложения, всегда используйте модель памяти
SMALL, устанавливаемую по умолчанию. В этом случае генерируется самый быстрый и
самый эффективный код. Явным объявлением переменной со спецификатором типа
памяти можно перекрыть тип памяти по умолчанию, навязываемую моделью памяти.
Модель памяти Small
В этой модели памяти все переменные по умолчанию размещаются во внутреннем ОЗУ
микроконтроллеров семейства 8051. Данная модель обеспечивает очень эффективный
доступ к переменным.
Модель памяти Compact
При использовании компактной модели все переменные по умолчанию размещаются в
одной странице внешнего ОЗУ (это соответствует явному объявлению спецификатора
типа памяти PDATA). Эта модель может поместить максимум 256 байтов переменных.
Ограничение объясняется используемым способом адресации, который является
косвенным через регистры R0 и R1. Эта модель памяти не так эффективна как малая
модель, поэтому доступ к переменным не так быстр. Однако компактная модель более
быстрая, чем большая модель.
Модель памяти Large
В большой модели все переменные по умолчанию размещаются во внешней памяти
данных (до 64 кВ). Регистр DPTR используется для адресации. Доступ к памяти через
этот регистр неэффективен, особенно для переменных с длиной 2 и более байтов. Этот
тип механизма доступа к данным генерирует больший код, чем малая или компактная
модель.
Явно декларируемые типы памяти
Каждой переменной может быть явно назначено специфицируемое пространство памяти
по схеме:
тип_данных тип_памяти имя_переменной [= значение];
где тип_памяти = {code, data, idata, bdata, xdata, pdata}.
Аргументы функций и автоматические переменные, которые не могут быть размещены в
регистрах, т а к ж е хранятся в области памяти по умолчанию. Тип памяти по умолчанию
определяется управляющими директивами компилятора: SMALL, COMPACT и LARGE.
Примеры:
u8 data v1; // здесь data можно не писать, т.к. по умолчанию для
// класса памяти small все переменные содержатся в
// области data
uintS_t data v1;
uint8_t code mes[] = “Это пример";
float xdata buff [100]; // 400 байт
float idata x, y, z;
uint8_t bdata flags; // переменная как байтовая или по отдельным
//
битам
4.3.2. Новые типы данных
Битовый тип
Язык Сх51 предоставляет тип данных bit, который может использоваться для декларации
переменных, списков аргументов и значений, возвращаемых функциями. Переменные
типа bit декларируются также, как и другие типы данных Си, например:
static bit flag = 0;
bit func (bit fg)
{
…….
return (0);
}
Тип bit допускает только прямую адресацию, поэтому указателей на биты не может быть,
соответственно, и битовых массивов не может существовать.
Бит-адресуемые объекты
Бит-адресуемые объекты — это объекты, которые могут быть адресованы как байты или
как биты. В эту категорию попадают только объекты данных, которые занимают битадресуемое пространство внутренней памяти микроконтроллеров семейства 8051.
Компилятор Сх51 помещает переменные, декларированные с типом памяти BDATA в эту
бит-адресуемую область. Например:
int bdata ibase;
char bdata a[4];
Индивидуальные биты этих переменных могут быть прямо доступны и модифицированы.
Для этого используйте ключевое слово sbit, чтобы декларировать новые переменные для
доступа к битам переменных, декларированных с BDATA. Например:
sbit bit0 = ibase^0; // где 0 — номер бита
sbit bit15 = ibase^15;
sbit a_bit0 = a[0]^0;
sbit a_bit3_7 = a[3]^7;
bit15 = 1; bit15 = 0;
Представленные выше примеры представляют декларации, а не присваивание битам
переменных ibase и а.
Регистры специальных функций
Эти регистры используются для управления различными периферийными устройствами
микроконтроллеров. РСФ могут быть доступны как биты, байты и слова.
В разных типах микроконтроллеров семейства 8051 число РСФ варьируется. Заметим, что
имена РСФ в компиляторе Сх51 не определены. Однако декларации для РСФ
поставляются во включаемых файлах. Каждый файл содержит декларации для РСФ,
соответствующих данной производной семейства микроконтроллеров 8051. Компилятор
Сх51 обеспечивает доступ к РСФ с помощью типов данных: sfr, sfr16 и sbit.
SFR
РСФ декларируются таким же образом как другие переменные. Единственное отличие –
тип данных специфицируется как sfr. Например:
sfr Р0 = 0x80; // порт 0, адрес 80Н
sfr РЗ = 0хВ0; // порт 3, адрес 0В0Н
Здесь Р0-Р3 — декларированные имена РСФ. Любое имя может быть использовано в srfдекларации. Спецификация адреса после знака равенства должна быть числовой
константой (выражение с операторами не допускается). Диапазон константы должен
соответствовать диапазону адресов РСФ (0x80 - 0xff).
SFR16
Многие новые производные микроконтроллеров семейства 8051 используют два РСФ с
последовательными адресами для специфицирования 16-битовых значений. Например,
микроконтроллеры 8052 использует адреса 0хСС и 0xCD для младшего и старшего
байтов таймера-счетчика 2 (ТС2). Компилятор Сх51 предоставляет тип данных sfr16 для
доступа к двум РСФ как к одному 16-битовому РСФ. Доступ к 16-разрядному РСФ
возможен только когда младший байт непосредственно предшествует старшему байту.
Младший байт используется как адрес в декларациях sfr16. Например:
sfr16 Т2 = 0хСС; // таймер 2: T2L = 0CCh, Т2Н = 0CDh
Декларации sfr16 подчиняются тем же самым правилам, как и sfr-декларации.
SBIT
Часто в приложениях необходим доступ к индивидуальным битам бит-адресуемых РСФ.
Компилятор Сх51 делает это возможным с помощью типа данных sbit. Любое имя может
быть использовано в декларации sbit. Выражение справа от знака равенства указывает
абсолютный адрес бита для соответствующего имени. Имеются три варианта задания
адреса:
sfr nsme^int const
// определение бита регистра,
//
где nsme — имя регистра,
//
const — целочисленная константа
sbit EA = IE^7;
int constant const
sbit EA= 0хА8^7;
int const
sbit EA = 0xAF;
Размещение переменных по абсолютному адресу
Переменные могут быть локализованы на абсолютные ячейки памяти, используя
ключевое слово _at_.
тип [тип_памяти] имя_переменной _at_ константа;
тип_памяти — тип памяти для переменной; если отсутствует в декларации, то
устанавливается по умолчанию; константа — адрес локализации переменной.
Примеры:
float х _at_ 50;
int xdata a[200] _at_ 500;
Абсолютный адрес, следующий за _at_ , должен удовлетворять физическим границам
пространства памяти для переменной. Компилятор Сх51 проверяет недопустимый адрес.
Следующие ограничения применимы к абсолютной локализации переменной:
1) абсолютные переменные не могут быть инициализированы при описании;
2) функции и переменные типа bit не могут размещаться по абсолютному адресу.
Указатели
Обобщенные или универсальные указатели (generic pointers) декларируются таким же
образом, как стандартные указатели Си. Эти указатели всегда хранятся в трех байтах:
первый байт – тип памяти, второй – старший байт смещения и третий – младший байт
смещения. Обобщенные указатели могут быть использованы для доступа к любой
переменной, независимо от ее расположения в памяти. Однако код, генерируемый для
работы с обобщенными указателями, более громоздкий и менее быстрый, чем код,
генерируемый для указателей, специфичных для конкретного типа памяти (memoryspecific pointers). Если эффективность генерируемого компилятором кода важна, то
рекомендуется использовать типизированные указатели. Типизированные указатели
всегда включают тип памяти в свою декларацию. Например:
char data *str; // указатель на переменную типа char,
// которая находится в data
Поскольку тип памяти известен во время компиляции, то в этом случае не нужен байт для
хранения кода типа памяти. Типизированные указатели хранятся в одном байте (idata,
data, bdata, pdata-указатели) или в двух байтах (code- и xdata-указатели).
Кроме того можно указать тип памяти, где хранится сам типизированный указатель:
char data *xdata str; // сам указатель будет храниться в
//
xdata
Объявления функций
Компилятор Сх51 предоставляет ряд расширений для декларации стандартных Сифункций:
специфицирование функции как процедуры обработки прерывания;
выбор используемого банка регистров;
выбор модели памяти;
спецификация реентерабельности.
Используется следующий стандартный формат декларации функции:
возвращаемый_тип имя_функции([список формальных параметров])
[{small | compact | large}][reentrant][interrupt n][using k]
reentrant — указывает, что функция рекурсивная или реентерабельная; interrupt —
указывает, что функция — обработчик прерывания; using — специфицирует
используемый функцией банк регистров.
Пример:
uintS_t sec;
void ISR_TC0(void) interrupt 1 using 2{
static uint16_t i= 0
if (++i == 4000) { sec++; i = 0;}
}
Встроенные функции
Компилятор Сх51 поддерживает ряд встроенных библиотечных функций (intrinsic
function). Невстроенные функции генерируют инструкции ACALL или LCALL, чтобы
исполнить библиотечную подпрограмму. Встроенные функции генерируют машинный
(in-line – линейный) код, чтобы исполнить библиотечную подпрограмму. При
компиляции вызов такой функции заменяется ее кодом. Генерируемый линейный код
много быстрее и более эффективнее, чем вызов подпрограммы. Следующие функции
имеются во встроенной форме:
Таблица 4.6. Встроенные функции
Циклические сдвиги
_crol_
_irol_
_lrol_
_cror_
_iror_
_lror_
Команда NOP
Тестирование бита и его сброс
_nop_
_testbit_
Прототипы этих функций содержит заголовочный файл . Пример описания
функции _nop_ в этом файле: void _nop_(void);
4.4. Рекомендации по оформлению исходного текста программ
4.4.1. Комментарии
Хорошее правило — это включать комментарии в процессе написания программы,
именно в это время наиболее хорошо вникать в работу программы. Существует три типа
комментариев: оглавление, вводные, пояснительные.
Если программа большая по объему текста кода, то целесообразно в ее начале (главном
файле) помещать оглавление в виде комментариев. Оглавление должно содержать
название каждого модуля, их описание и содержание.
Вводные комментарии могут быть к модулю-файлу или к подпрограмме (назначение,
входные и выходные параметры, способы их передачи, память).
Пояснительные комментарии используются для пояснения отдельных программных
фрагментов.
4.4.2. Выбор символических имен
Имена должны быть выбраны так, чтобы они наилучшим образом определяли те
величины, которым они предназначены. Избегать схожих имен и подобных по написанию
символов. Запрещено использование ключевых слов в качестве имени.
Если самостоятельно необходимо придумать аббревиатуру, то это можно сделать,
сократив имя. При этом, число символов в сокращении должно быть не менее трех, в
аббревиатуру должны быть включены первые буквы слов, причем согласные важнее
гласных. Иногда разумно разделять символы аббревиатуры знаком «подчеркивание»
(«__») или выделять большой буквой. Числовые значения лучше помещать в конце имени.
4.4.3. Другие приемы улучшения читаемости программы
1) Пропуск строк — позволяет логически разделить группы команд. Рекомендуется делать
пропуск строки за командой безусловного перехода, указывая на нарушение
последовательного выполнения команд.
2) Пробелы следует ставить везде, это приводит к улучшению читаемости программы;
обычно ставятся в перечислениях.
DB 1, 11, 123, 0A3h, …
………………………….
3) Упорядочивание списков по алфавиту облегчает поиск имени в списке.
EXTRN Data (ALPHA, CHI, DELTA, BETA, EPSIL, ETA, PHI, OMEGA)
EXTRN Data(GAMMA, MU, KAPPA, LAMBDA)
EXTRN Data(ALPHA, BETA, CHI, DELTA, EPSIL)
…………………………………………………….
4) Скобки улучшают восприятие порядка выполнения операторов.
5) Форматный ввод строк.
Например:
1
метка:_13
_31;_комментарий
MOV
LCALL
_
19……………
Файл STARTUP.A51: