Причины использования языков высокого уровня
Помимо ассемблеров для программирования приложений используются различные языки высокого уровня, что обусловлено следующими причинами:
? существует множество алгоритмов и готовых программ на языках высокого уровня, которые можно непосредственно вставить в разрабатываемую прикладную программу. Если возникает необходимость переноса программ на другие микроконтроллеры, то такой перенос выполняется намного проще, чем перенос программ на ассемблере;
? при программировании на языках высокого уровня имеется ряд возможностей, которыми не располагают ассемблеры. Например, разработчик может автоматически вводить в программу коды сложных операций, производить эффективный контроль синтаксических ошибок и др. Поэтому составление программ на языках высокого уровня требует меньших затрат;
? при разработке программ на языке высокого уровня значительно упрощается поддержка приложения, связанная с модификацией и отладкой программного кода.
К наиболее распространенным языкам относятся C/ C++, BASIC и Forth . С помощью компиляторов языки высокого уровня преобразуются в язык ассемблера, а затем в объектный код (биты и байты), который выполняется микроконтроллером. Имеется множество компиляторов, разработанных для различных микроконтроллеров. Их эффективность определяется требуемым объемом памяти программ и данных, а также ресурсами, необходимыми для поддержания объектного кода.
Языки высокого уровня характеризуются рядом показателей, реализация которых во встраиваемых микроконтроллерах может оказаться проблематичной, что обусловлено:
? ограниченным объемом памяти программ ( ROM ) и данных ( RAM );
? отсутствием BIOS или операционной системы;
? наличием переопределяемых выводов контроллеров (когда вывод может использоваться как цифровой/аналоговый/последовательный вход–выход).
Рассмотрим основные особенности и показатели языков высокого уровня применительно к микроконтроллерам.
Библиотеки и функции.
Функция — это подпрограмма, при выполнении которой определенные параметры вызываются из исходной программы, а после завершения возвращаются обратно. Библиотека содержит набор функций (для математических операций, преобразования и пересылки данных, консольного ввода–вывода и др.), скомпонованных в виде объектного файла. Встроенные (в стандартные подключаемые библиотеки) функции, необходимые для некоторых приложений, могут оказаться невостребованными для других. Вряд ли найдется приложение, использующее одновременно все функции стандартной библиотеки. Поэтому в микроконтроллерах, имеющих ограниченный объем памяти, использование стандартных библиотек может привести к неоправданным затратам памяти.
Функция стандартной библиотеки предварительно проверяет наличие оборудования (и его характеристики) и только после этого выполняет заданные действия.
Из–за схемных особенностей различных микроконтроллеров (например, контроллер может содержать или не содержать некоторый функциональный узел) необходимость такой проверки приводит к увеличению времени исполнения программы и объема памяти конкретного контроллера.
Указанные проблемы можно решить путем компоновки в программный код (объектный файл, или библиотеку) только тех функций, которые необходимы для конкретного применения с учетом специфики используемого микроконтроллера. Другой подход состоит в том, что включение в объектный код требуемых фрагментов реализуется с помощью условной компиляции. В этом случае объектным файлом является вся программа, т. е. отдельный объектный файл с набором функций не создается.
Типы данных и операции над данными.
Тип данных — это форма представления данных для компьютерной обработки. Важную роль играют типы данных, которые поддерживаются аппаратным обеспечением и для которых требуются специальные форматы.
Типы данных можно разделить на две категории: числовые и нечисловые. Числовые типы данных. Среди них главными являются целые двоичные числа со знаком и без знака. Для их представления обычно используется 8, 16, 32 и 64 бита. В целых числах со знаком старший разряд отводится под знак, поэтому значения N–разрядного числа лежат в пределах от –(2N–1 – 1) до +(2N–1 – 1), диапазон значений чисел без знака составляет 0…2 N – 1.
Для представления нецелых чисел (например, 4.5) используется две формы: с плавающей и фиксированной точкой. Их длина может составлять 32,64 и 80 бит.
В компьютерах имеются отдельные регистры для целочисленных операндов и для операндов с плавающей точкой.
Для представления десятичных чисел используется двоично–десятичный формат, в котором для кодирования одного разряда отводится 4 или 5 бит. Для коррекции результатов десятичной арифметики используются специальные команды. Нечисловые типы данных. Для обработки текстов или управления базой данных необходимо использовать нечисловые типы данных. Для этой цели используются такие символьные коды как ASCII или UNICODE , поддерживающие 7–битные и 16–битные символы соответственно.
В микроконтроллерах широкое распространение получил целочисленный тип данных длиной 8 и 16 бит. Операции над числами, имеющими разрядность больше базовой (8 бит), часто реализуются с помощью библиотечных функций, которые увеличивают время выполнения и объем требуемой памяти. Обработка 16–разрядных данных реализуется с помощью небольшого увеличения объема программного кода и памяти данных. В тоже время 16–разрядные данные обеспечивают достаточно большой диапазон представления обрабатываемых данных. При правильной организации вычислений обработка данных практически для всех встроенных приложений, использующих 8–разрядные микроконтроллеры, может быть выполнена с использованием 8–и и 16–и разрядных целых чисел. Обработка целочисленных данных. При обработке данных с разрядностью более 8 бит необходимо вводить дополнительные команды.
Пример.
Рассмотрим операцию сложения, представленную в виде выражения на языке С: FirstVar = FirstVar + SecondVar Для 8–разрядных операндов эта операция выполняется микроконтроллером 8051 с помощью следующей последовательности команд: MOV A , FirstVar ;Поместить первую переменную FirstVar в ;аккумулятор А ADD A , SecondVar ;Содержимое А сложить со второй ;переменной SecondVar MOV FirstVar , А ;Сумму поместить в ячейку FirstVar Для 16–разрядных переменных программный код усложняется: MOV A , FirstVar ;Сложить младшие 8 бит ADD A, SecondVar MOV FirstVar, А INC SKIP ;Если перенос С не установлен, ;то пропустить увеличение INC FirstVar + 1 ;Увеличить старшие 8 бит результата SKIP MOV A , FirstVar + 1 ;Сложить старшие 8 бит ADD A, SecondVar + 1 MOV FirstVar + 1, А Для выполнения более сложных операций, программный код может быть реализован с помощью ряда стековых операций. Например, для вычисления FirstVar = SecondVar + ( ThirdVar * FourthVar ) последовательность операций, обеспечивающих получение необходимого результата, имеет вид push SecondVar push ThirdVar push FourthVar mul add pop FirstVar Эти стековые операции обеспечивают получение необходимого результата.
Для некоторых компиляторов, процедура, описанная строкой, например: FirstVar = SecondVar + ( ThirdVar * Fourth Var ) более эффективно реализуется в виде следующей последовательности операций: Temp = ThirdVar * FourthVar; FirstVar = SecondVar + Temp; Еще одно возможное представление приведенного выше примера касается того, как компилятор преобразует числа и обрабатывает промежуточные переменные. Если переменная « FirstVar » не определена как порт ввода–вывода, то программный код можно представить в следующем виде: FirstVar = ThirdVar * Fourth Var; FirstVar = FirstVar + SecondVar; При таком представлении компилятор учитывает, что переменная FirstVar не используется при выполнении операции, и ее можно использовать для хранения промежуточных результатов.
Глобальные и локальные переменные.
Во многих языках высокого уровня существует два типа переменных:
? глобальные переменные, которые заданы для всей программы. Их значения не могут переопределяться. Они обычно используются в основном теле программы и при выполнении многих подпрограмм, когда передача значения переменной в качестве параметра подпрограммы не эффективна;
? локальные переменные используются при выполнении конкретной подпрограммы и создаются при обращении к ней. Обычно локальные переменные загружаются в стек при вызове подпрограммы и извлекаются из него при возвращении управления вызывающей программе. Значение локальной переменной теряется после выхода из подпрограммы. Параметры, передаваемые подпрограмме, в большинстве случаев становятся локальными переменными. При этом первоначальные значения параметров сохраняются, а текущие значения при выполнении подпрограммы могут изменяться.
Указатели и структуры данных.
Эти средства служат для повышения эффективности программирования при больших объемах памяти. Указатели предназначены для управления ресурсами памяти. При программировании микроконтроллеров с малым объемом памяти указатели обычно не используются. В таких контроллерах вместо указателей в начале раздела памяти создается массив, который путем индексирования используется для обращения к любому элементу в этой памяти. Чтобы выбрать необходимый элемент в массиве данных, следует к значению индекса, задающего адрес начального элемента массива, прибавить смещение, указывающее позицию выбираемого элемента.
Структурой данных называется блок памяти, который используется для определения стандартной записи данных. Организация структур данных очень полезна при многих применениях микроконтроллеров.
Например, структуру процессорной команды на языке С можно определить следующим образом: struct instruct { // Формат команды int address; // Адрес команды char instruct ; // Команда int value ; //16–битные данные }: Обычно на структуру ссылаются с помощью указателя, но те же данные можно поместить в массив 16 разрядных слов (целочисленные данные типа int на языке С) и ссылаться на него при помощи индекса.
Чтобы выбрать необходимый элемент в массиве данных, следует к значению индекса, задающего адрес начального элемента массива, прибавить смещение, указывающее позицию выбираемого элемента.
Проиллюстрируем использование указателей и структур данных на конкретном примере. При использовании указателя для выбора некоторой величины из области данных необходимый программный код имеет следующий вид: struct inst ruct * Ptr ; //Определение указателя на структуру.
; i = Ptr —> value ; / / Прочесть ” value ” из текущего элемента При организации массива эта процедура выглядит следующим образом: struct inst ruct Array[100]; //Определение массива структур.
; i = = Array [ Index ]. value ; Этот код немного сложнее кода с указателем, но в нем не используется указатель, за которым необходимо следить, чтобы он всегда корректно обращался к требуемому элементу структуры.
Другой распространенной структурой данных является таблица, которую можно представить как одномерный массив неизменяемых строк данных. В строке содержится информация о состоянии системы или сообщение для пользователя, например: char Greeting [13] = “Hello there I”; Компилятор языка размещает таблицы в памяти программ. Информация в таблице выбирается как массив, идентичный массивам, считываемым из оперативной памяти.
Доступ к аппаратным средствам.
Прикладная программа на языках высокого уровня должна обеспечить доступ к таким аппаратным средствам как регистры микроконтроллера. Для обращения к регистрам используется вставка в тело программы ассемблерных инструкций и некоторых позиций адресного пространства данных, используемых в качестве указателей регистров.
Оба способа поддерживаются языками высокого уровня.
Символическая информация.
Для упрощения отладки компиляторы выдают символическую информацию, представляющую собой ссылки на используемые строки, метки, переменные и указание на их размещение в памяти. Эта информация используется симуляторами и эмуляторами, чтобы отображать выполнение программы на уровне исходного текста, а не на уровне машинного кода (ассемблера). Следует иметь в виду, что не все компиляторы выдают одинаковую символическую информацию. Это обстоятельство необходимо учитывать при выборе компилятора.
Из материала по языкам программирования следует, что:
? при программировании управляющих систем чаще всего используются машинно–ориентированный язык ассемблера или языки С / С++. Язык ассемблера применяется при жестких ограничениях на объем требуемой памяти или на время выполнения программных модулей. Такие случаи являются достаточно типичными при решении задач управления, поэтому ассемблеры являются одним из основных средств создания программного обеспечения для микроконтроллерных систем. В тех случаях, когда указанные ограничения не очень жесткие, для создания программного обеспечения используются языки высокого уровня (обычно С / С++);
? при разработке программного обеспечения для универсальных микропроцессорных систем используется достаточно широкий набор языков высокого уровня, для которых имеются соответствующие компиляторы. Чаще всего используются языки С, С++, FORTRAN , Pascal , Forth . Для решения ряда задач применяются языки поддержки искусственного интеллекта Ada , Modula–2 и некоторые другие.