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

Читать, слущать книги онлайн бесплатно!

Электронная Литература.

Бесплатная онлайн библиотека.

Читать: Встраиваемые системы. Проектирование приложений на микроконтроллерах семейства 68HC12/HCS12 с применением языка С - Стивен Ф. Барретт на бесплатной онлайн библиотеке Э-Лит


Помоги проекту - поделись книгой:

11 #elif processor == М8051

12  Instruction(s) С

13 #else

14  Instruction(s) D

15 #endif

16 }

В этом примере исходный текст программы написан таким образом, что он может быть компилирован для исполнения различными микроконтроллерами: Motorola HC11, Motorola HC12 и Intel 8051. Директивы #if, #elif и #else позволяют для данного сеанса компиляции выбрать конкретный тип МК. Для этого в строках 1…3 программы каждому символьному имени МК присвоено определенное численное значение. Далее в зависимости от выбранного типа МК переменной Processor присваивается желаемое значение. В примере мы собираемся компилировать программу для МК HC12, поэтому присвоили переменной Processor значение 1. В строке 7 компилятор проверяет истинность выражения, записанного в качестве условия директивы #if. Это условие не выполняется, поскольку Processor = 1 ≠ М68НС11 = 0. Поэтому группа инструкций Instruction(s) A не будет включена в программу. Далее в строке 9 компилятор обнаружит выполнение условия директивы #elif, и выражения Instruction(s) B будут присутствовать в конечном варианте программы. Условие строки 11 не выполняется, и группа инструкций Instruction(s) C в исполняемом коде программы присутствовать не будет. Если ни одно из условий для директив #elif не выполнено, то выражения, следующие за директивой #else, будут включены в программу автоматически.

Воспользуемся приведенной конструкцией условной компиляции. Допустим, мы предполагаем исполнение некоторого программного кода как на МК семейства Motorola HC11, так и на МК семейства Motorola 68HC12. Эти МК имеют различные карты памяти, и, соответственно, их порты ввода/вывода расположены по различным адресам. Для возможной адаптации текста программы к одному из типов МК воспользуемся директивами условной компиляции:

1 #if (Processor == 68НС11)

2 #define PORTA *(unsigned char volatile *) (0х1000)

3 #elsif (processor == 68НС12)

4 #define PORTA *(unsigned char volatile *) (0х0000)

5 #endif

В строках 1 и 3 располагаются директивы, которые проверяют условия компиляции. Значение переменной Processor должно быть определено выше по тексту программы директивой #define, или в подключаемом заголовочном файле. Строки 2 и 4 содержат директивы определения адреса для порта PORTA для двух различных типов МК. Директива #endif в строке 5 отмечает окончание фрагмента текста, который подлежит условной компиляции.

Директивы #ifdef и #ifndef используются для организации процесса компиляции при условии, что некоторая переменная с указанным именем была определена (#ifdef) или не определена (#ifndef) в тексте программы. Например:

1 #ifdef OUTPUT

2  Instruction(s) А

3 #else

4  Instruction(s) B

5 #endif

Если переменная с именем OUTPUT была определена в тексте программы до строки 1 с директивой #ifdef, то группа инструкций Instruction(s) А будет включена исполняемый код программы. В противном случае в конечный вариант программы будет включена группа инструкций Instruction(s) B.

Другой пример:

1 #ifndef OUTPUT

2  Instruction(s) А

3 #else

4  Instruction(s) B

5 #endif

Если переменная с именем OUTPUT не была определена в тексте программы до строки 1 с директивой #ifndef, то в конечный вариант программы будет включена группа инструкций Instruction(s) А. Если же эта переменная была определена ранее, то исполняемый код программы будет включена группа инструкций Instruction(s) B.

Директива #define используется в двух случаях. Во первых, она позволяет задать численные значения для символьных констант. Например, константе с именем HIGH необходимо присвоить значение 98:

#define HIGH 98

После записи этого выражения, если в тексте программы будет использовано имя HIGH, то при компиляции оно будет заменяться числом 98. Это удобно, поскольку в тексте программы имя HIGH может быть упомянуто сколь угодно большое число раз. Но для изменения его численного значения понадобится внести изменения только в одну строку с директивой #define.

Во вторых, директива #define используется для определения макросов. Макрос — это набор выражений языка Си, которому поставлено в соответствие определенное имя. При записи этого имени в программе, компилятор произведет замену этого имени обозначенным набором выражений. Например, Вам необходимо разрешить прерывания в МК. Для этого в МК 68HC12 используется команда ассемблера CLI. Для ее записи в тексте программы на Си определяют макрос:

#define CLI() asm("cli\n"); //разрешить маскируемые прерывания

Далее в программе используют только имя макроса:

CLI();

Кроме директивы определения символа или макроса #define, существует директива обратного действия #undef. Приведем пример ее использования:

#define VALUE 10

int number[VALUE];

#undef VALUE

В этом примере мы сначала назначили переменной VALUE значение 10. Далее в строке 2 мы воспользовались этим значением, чтобы определить массив целых чисел из 10 элементов. Далее переменная VALUE нам не нужна. И мы отменили ее определение директивой #undef.

Следующая рассматриваемая нами директива — это директива #include. Ранее мы установили, что эта директива используется для присоединения к разрабатываемому программному модулю другого файла. При этом у программиста появляется возможность использовать в тексте программы ранее объявленные переменные или вызывать функции, которые были определены в другом файле. Присоединяемые файлы называют заголовочными файлами. Например, следующая запись необходима для присоединения к разрабатываемой программе файла стандартных функций ввода/вывода:

#include <stdio.h>

Символы <> указывают на определенное место расположение файла stdio.h в папках директории компилятора.

Назначение директивы #error — упрощение процесса отладки разрабатываемой программы. Вы можете записать следующее выражение:

#error Programm made a logic error

Если в процессе выполнения программа достигнет приведенной строки, то на экран будет выведено приведенное сообщение.

Также для целей отладки используется директива #line. Эта директива отмечает номерами те инструкции программы, которые следуют за директивой. В результате, в процессе отладки можно идентифицировать тот фрагмент программы, который исполняется в текущий момент отладки.

Функции директивы #pragma определяются конкретным типом используемого компилятора. Для компилятора ICC12 эта директива определения сегментов данных и программы в исходном тексте программы на Си, для объявления подпрограмм прерывания, а также для присвоения желаемых значений ячейкам памяти с фиксированными адресами. Последнее позволяет инициализировать таблицу векторов прерываний в микроконтроллерах. Приведенный ниже пример демонстрирует использование директивы #pragma для объявления подпрограммы с именем TOISR в качестве подпрограммы прерывания:

#pragma interrupt_handler TOISR()

void TOISR(void);

Объявление подпрограммы TOISR как подпрограммы прерывания информирует компилятор о том, что в конце этой подпрограммы он должен расположить ассемблерную инструкцию возврата из прерывания rti. В конце обычной функции компилятор подставляет инструкцию возврата из подпрограммы rts.

Директива #pragma также используется для задания начального адреса расположения в памяти сегментов программного кода или кодов данных. Запишем вектор прерывания для подпрограммы TOISR в таблицу векторов прерывания МК. Мы знаем, что в соответствие с картой памяти МК, вектор прерывания по переполнению таймера должен располагаться по адресу 0x0B1E. Следующая запись помещает адрес начала подпрограммы TOISR в две ячейки памяти, начиная с адреса 0x0B1E:

#pragma abs_adress:0xB1E

void (*Timer_Overflow_interrupt_vector[])() = {TOISR};

#pragma end _abs_adress

Более подробно оформление подпрограмм прерывания мы обсудим в главе 4.

3.7. Конструкции программирования

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

3.8. Операторы для организации программных циклов

В языке Си существует несколько операторов, которые позволяют реализовать циклические вычисления (итерации). В этом параграфе мы рассмотрим программные конструкции циклов с операторами for, while, do while.

3.8.1. Оператор FOR

Оператор for предназначен для реализации циклов со счетчиком. В операторе for могут автоматически реализоваться сразу три операции: инициализация счётчика цикла, проверка его значения и модификация. Синтаксис оператора for:

for (<выражение1>; <выражение2>; <выражение3>)

 <операторы тела цикла>

Рассмотрим типичный пример реализации цикла с оператором for:

1 for(i = 0; i < 10; i = i++)

2 {

3  inst 1;

4  inst 2;

5  :

6  :

7  inst n;

8 }

В строке 1 записывается сам оператор for, за которым обязательно следуют три выражения, заключенные в круглые скобки. Выражение 1 вычисляется один и только один раз перед проверкой условия цикла. В нашем примере выражение 1 присваивает начальное значение переменной i. Выражения два и три могут иметь произвольный характер, но обычно их используют для проверки и модификации условия продолжения цикла. Выражение 2 задаёт условие продолжения цикла. Если его значение отлично от нуля (истина), будут выполнены операторы 3…7, составляющие тело цикла. После этого вычисляется выражение 3, указанное в скобках первой строки. В нашем примере выражение i = i++ = i + 1 осуществляет увеличение на 1 внутреннего счетчика циклов оператора for. Поэтому операторы тела цикла 3…7 будут выполняться 10 раз при значениях переменной цикла i от 0 до 9. В конце первого цикла значение i будет равно 1, в конце второго — i=2, и т.д. В конце десятого цикла переменная i примет значение 10. Далее начнется исполнение 11-ого цикла оператора for, но, проанализировав условие выражения 2 оператора for, программа выйдет из цикла, не исполняя операторов тела цикла.

Достаточно часто переменная счетчика циклов оператора for используется также в теле цикла этого оператора. Например, следующий программный фрагмент вычисляет таблицу соответствия значений температуры, записанных по шкале Цельсия и по шкале Фаренгейта, и последовательно выводит эти значения на экран монитора. Диапазон исходных значений температуры составляет от –10°C до +40°C.

1 for(k = –10; k <= 40; k++)

2 {

   //преобразовать значения температуры, измеренные по шкале Цельсия

   // к численному значению по шкале Фаренгейта

3  Temperature = k*9/5+32;

4  printf("Current temperature is \%f\n", Temperature);

5 }

Как видите, значение переменной k используется как параметр для вычисления новых значений температуры в теле цикла.

3.8.2. Оператор WHILE

Второй способ организации циклов использует оператор while. Применение оператора while иллюстрирует следующий программный фрагмент:

1 k = -10;

2 while(k < 40)

3 {

4  Temperature = k*9/5+32;

5  k++;

6  printf("Current temperature is \%f\n", Temperature);

7 }

В отличие от оператора for, переменная k, используемая в качестве счетчика циклов, должна быть инициализирована перед оператором while, например, в строке 1. Обратите внимание, что в строке 2 в скобках оператора while записано всего лишь одно выражение, которое называется условием цикла. Выполнение оператора while начинается с вычисления этого выражения. Если значение выражения отлично от нуля («истина»), то выполняются операторы 4…6 тела цикла. После выполнения операторов тела цикла снова вычисляется выражение условия и процесс повторяется. Таким образом, выполнение тела цикла происходит пока значение выражения условия цикла отлично от нуля («истина»).

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

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

1 while (1)

2 {

3  Instructions //выполнение блока операторов

4 }

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



Поделиться книгой:

На главную
Назад