Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Методичка MCU

.pdf
Скачиваний:
18
Добавлен:
23.02.2015
Размер:
1.03 Mб
Скачать

Уральский Государственный Университет (УрГУ) им. А.М. Горького

«Основы работы с микроконтроллерами»

Боярченков А.С.

Rev. 2.001 от 16.04.2009

Екатеринбург 2008-2009

1

2

Оглавление:

Подробный план курса:

2. Основы языка С (кратко, за пол-пары): #include; {int, char, long, void}; ==; !=; for (;;); while(); if () {} else {}; программные скобки «{» «}»; описание функций. переменные глобальные/локальные. #define. сокращенная запись операций.

Изучать язык программирования C будем на примере первой простой программы. К сожалению, при использовании микроконтроллеров часто мы не имеем консоли (клавиатуры и экрана), поэтому простейшая программа типа «hello, world!» не сможет нам ничего показать на экране. Поэтому нашей первой программой будет светофор. Там всего делов-то управлять 3мя светодиодами...

Сразу замечание: С различает большие и маленькие буквы. Идентификаторы variable1 и Variable1 — разные.

После всех законченных конструкций следует ставить « ; ». В некоторых местах это необязательно, но хуже не будет.

Однако, написав, например, if (a>5); DDRA =10; получим

неработающую программу, поскольку поставили « ; » внутри незаконченной конструкции.

«Программные скобки» в Паскале обозначались словами BEGIN и END, а в С - «{» и «}». Их смысл — ограничивать куски кода, чтобы они трактовались как единый оператор. Это нужно, например, в конструкциях while, for, if, switch, или в определении функций.

Операции в С.

Привычными операциями: + - * / можем пользоваться как обычно.

Единственное: нужно учитывать, что здесь нет разделения между целочисленными и floating-point операциями, тип результата определяется из типов ее аргументов. Так, например, если a,b,c — переменные типа int

(integer, целые), то в результате выполнения программы a = 17; b = 3; c = a/b;

в переменную c будет записано 5. Если бы у c был тип double, то результат был бы таким же: сначала, исходя из того, что a и b имеют тип int, производится операция деления, а затем - приведение типа к float. Чтобы получить нормальное число 5.666667, необходимо как-то подсказать

компилятору, что в результате нам необходим тип float например, так:

a = 17; b = 3; c = 1.0 * a / b; // 1.0 — явно f.-p.

константа.

3

Также есть операция взятия остатка от деления. Записывается она

знаком «%»:

a = 17; b = 3; c = a % b;

В результате выполнения операции, c получит значение 2 (остаток от деления 17 на 3).

Важными для нас являются операции побитовые И, ИЛИ, (ИСКЛЮЧАЮЩЕЕ ИЛИ), НЕ: соответственно, &, |, ^, ~. Первые 3 операции являются бинарными (имеют 2 аргумента), последняя — унарной (1

аргумент), например:

b=0x1234;//вот так в С записываются шестнадцатеричные константы

a = b & 0x0F0F; // результат: 0x0204; c = b | 0x0F0F; // 0x1F3F

d = b ^ 0x0F0F; // 0x1D3B e = ~b; // 0xEDCB

Рассмотрим, как действуют эти операции подробнее: вспоминаем таблицы истинности операций (из курса Радиоэлектроники или Математической логики):

Далее необходимо представить оба операнда в двоичном виде,

например

b = 00000001 00000010 00000011 00000100 // 0x01020304 00000000 11111111 00000000 11111111 // 0x00FF00FF

00000001 11111101 00000011 11111011 // 0x01FD03FB =

0x01020304 ^ 0x00FF00FF

Операцию производим для каждого бита по отдельности (столбиком).

Операции побитового сдвига.

Часто нужно взять двоичное число и сдвинуть его по разрядной сетке влево или вправо. В C есть такие операции: соответственно, << или >>. Например,

a = 5 << 3; // cдвинуть 5== 0101 на 3 бита вправо: 0101000 = 40 b = 23 >> 2; // cдвинуть 23== 10111 на 2 бита влево: 101 = 5.

Биты, вышедшие за пределы разрядной сетки, теряются. Сдвиг влево на n битов эквивалентен целочисленному делению на 2^n, а вправо — умножению на 2^n.

Директива компилятора: подключение внешнего файла.

#include <io.h> #include “myprog.h”

эти директивы означают, что они перед компиляцией будут заменены текстовым содержимым указанных файлов, io.h в первом случае и

myprog.h во втором. Разные кавычки означают различные правила поиска

этих файлов (то есть, в какой папке они лежат): если в той же, где и весь проект — то «», если в общей (C:\WinAVR\include\) - то <>.

4

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

Кроме того, при работе в реализациях C просто необходимо подключать так называемый «хедеры» — файлы заголовков — в которых объявляются функции стандартных библиотек, например, #include <avr/io.h> — для работы с портами ввода-вывода, #include <strings.h> — для работы со строками и т.д. Эти директивы обычно пишут в самом начале файла исходного кода.

Объявление переменных.

int a, b; int WeeklyWakeUp[7]; // переменные этого типа могут принимать целые значения от -32768 до +32767, и занимают 2 байта = 16

бит памяти

 

char с, k; char string1[15];

// переменные типа char могут

хранить символы или цифры от -128 до 127 (signed char), или 0-255 (unsigned char)

long l, result; // этот тип называется «длинное целое», хранит

числа от -2'147'483'648 до 2'147'483'647 или 0...4'294'967'296 — чуть меньше 4.3 миллиардов.

void f() {} // тип «ничто». Переменные такого типа создавать

бессмысленно; а вот функции, которые возвращают данные типа «ничто» - в Паскале назывались «процедурами» :-)

Это в С такой способ объявления переменных: сначала тип, потом список идентификаторов переменных.

int WeeklyWakeUp[7] — это массив из 7 элементов. Важно: индексы

элементов

начинаются

с

0,

то

есть

существуют

только

WeeklyWakeUp[0] ...

WeeklyWakeUp[6], а WeeklyWakeUp[7] — уже

нет. Хуже всего, что если попробовать к нему обратиться — ошибки не произойдет, просто будут считаны (а еще страшнее — записаны) данные по адресу какой-то другой области памяти. Поведение программы (и устройства) может стать непредсказуемым.

В реализации WinAVR имеется и полноценная обработка чисел с плавающей точкой (типы float и double), в том числе и вычисление

функций sin(x), cos(x), log(x), ..., однако применять их следует с

осторожностью, поскольку они занимают много памяти программ, да и выполняются долго. При необходимости многократного вычисления fpфункций, программисты обычно применяют либо fixed-point арифметику (по сути, та же работа с целыми числами, однако при этом считая, что, например, оригинальное 1000 — это 1.000 в реальном мире), либо табличное вычисление значений.

if (a>b) {max = a; min = b;} else {max = b; min = a;};

Это операция ветвления: если (условие) { } иначе { }

Операции сравнения

5

== ; != ( две штуки «равно» без пробела между ними!!! )

Это операции сравнения «равно» и «не равно», например: if (A == 5) { print(«A равно 5»);}

if (A != 7) { print(«A не равно 5»);}

Помните, что операция сравнения и операция присваивания — «две

большие разницы». Если написать

if (A = 5) { print(«A равно 5»);},

компилятор отметит это только Warning'ом, но программу запустит. А она будет работать не так как вам этого хочется: выполнится a=5, возвратит 5. 5

— это не 0 (см. след. раздел), поэтому print вызовется обязательно ( a = 5; if (5) {print(«A равно 5»);} )

Зато присвоить всем переменным одно значение можно просто: a=b=value1=0;

Логические операции.

Как такового, в C нет отдельного типа для логических переменных (BOOLEAN в Паскале), логической считается любая целая переменная, в зависимости от контекста ее использования. Значение 0 — это «Ложь»

(false), а любое не нулевое значение — это «Истина» (true).

Контекстом являются и операции. Таким образом, допустимы

следующие конструкции: (пусть int a, b, c)

if (a) {...} // если a не равно 0. while (!a) {...} // пока a равно 0

do {...} while (!a && b || c); // делать {...} пока (a==0 и b != 0) или (с != 0)

for (i=10; i; i--) {}; // цикл пробегает значения по i от 10 до 1. На выходе i == 0.

Видим отличия от Паскаля. «равно» записывается так: « == » (не путать с операцией присваивания « = »!), «не равно» - « != » (в П. было « < > »). «НЕ» это « ! ». А у побитовых операций &, | есть логические «двойники» && и ||. Таблица истинности у них такая же, но действуют они не на каждый бит числа, а трактуют все число как логическое значение ложь (0) или истина (не 0).

Приоритет операций такой: сначала !, затем &&, затем ||, затем == и !=. Если возникают малейшие сомнения — не стесняйтесь использовать скобки (по обычным правилам); единственное: делать это нужно красиво, чтобы с первого взгляда становилось понятно, что Вы хотите сказать.

Результат логической операции — тоже целое число, если «ложь», то 0, а вот если «истина» — от компилятора к компилятору результат будет различаться. Скорее всего, это будет «1», но вполне возможно и «-1», и вообще произвольное ненулевое число.

Таким образом, никто не мешает записать следующее: a = (2 + 2 == 4);

b = (3 * 9 == 45);

После выполнения этих операций, b == 0, а а == 1 (в нашем случае

компилятора GNU C).

Еще один момент: иногда можно сделать ошибку и долго ее не замечать, использовав вместо логической операции побитовую. Например,

6

хотим, чтобы цикл выполнялся пока (a != 0) и (b != 0). Правильно такое условие записывается, например, так: while (a && b) {...}. А можно записать так: while (a & b) {...}. Казалось бы, похоже. Но на самом деле, работать будет неправильно: цикл прекратит выполнение, когда результат операции ( a & b ) станет равным 0. А это произойдет, например, когда a==1, а b==2. Обе переменные не равны 0, а цикл работу прекращает. Причем на простейших тестах (a==1, b==1) работает. Имейте это в виду!

Операторы цикла.

for (начальное действие; условие выполнения; действие в конце каждой итерации) {тело цикла}

Это оператор цикла. Например, чтобы напечатать цифры от 1 до 10

нужно написать:

for (i=1; i <= 10; i = i +1) {pnum( i ); pchar('\n');}

или

for (i=1; i < 11; i = i +1) {pnum( i ); pchar('\n');} или for (i=1; i < 11; i++) {pnum( i ); pchar('\n');} , если

использовать сокр. запись «++». Действия могут быть любыми:

for (i=1; i < 11; pnum(i++), pchar('\n')); допустимо.

(«запятая» разделяет 2 действия.)

Цикл прекращает выполняться, если в конце итерации условие стало ложным.

«вечный» цикл можно записать так: for(;1;) {...} или просто for(;;) {...} (действия и условие можно опускать)

while(условие цикла) {тело цикла};

Это оператор обычного цикла с предпроверкой условия. do {тело цикла} while(условие цикла);

а это - оператор цикла с постпроверкой условия. В отличие от Паскаля, «условие» - это условие выполнения цикла и во втором случае тоже.

«вечный» цикл можно записать так: while(1) {...} или do {...} while(1);

Цикл for можно полностью заменить циклом do-while: i=1;

while (i<5)

{

...

i++;

};

описание функций.

int multiply(int x. int y) // так указывается тип

возвращаемого значения

// и типы аргументов

{

return x * y; // так указывается возвращаемое значение

}

В отличие от Паскаля, в С существуют только функции. Понятие «процедуры» упразднено, она стала частным случаем функции, которая

7

возвращает «ничто» - для этого создан специальный тип «void», который так и переводится.

Еще, в отличие от Паскаля, в С нет главных «BEGIN»/ «END.» после определения всех функций. Основная программа — это на самом деле тоже функция, но с зарезервированным названием «main». Находиться она может не обязательно в самом конце файла, а практически в любом месте. По стандарту, тип возвращаемого ей значения должен быть int. Поскольку наши MCU достаточно простые и не имеют операционной системы, нет смысла выходить куда-то в операционную систему, поэтому функция main должна содержать «вечный» цикл:

int main()

{

long l, m; l = 0; DDRB = 1;

while(1)

{

for (m = 0; m < l; m ++) asm(«nop»);

l++;

}

return 0; // этот оператор никогда не будет выполнен, но, чтобы компилятор не ругался — нужен.

}

Глобальные/локальные. модификатор volatile.

Переменные можно объявлять в разных местах программы. От этого будет зависеть так называемая их область видимости. Рассмотрим пример:

volatile char flags; int T, i, j;

int func1()

{

char z, m; z = T+i; return z;

}

int main()

{

int i;

for (j = 0; j < 5; j ++)

{

int c = 0;

c += i + j + T;

8

}

}

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

Если переменная объявлена внутри функции или даже блока (как в примере с for(j...){}), то ее можно использовать только внутри этой функции или блока. Причем, если имя новой переменной совпадает с именем какойто глобальной переменной, то локальная переменная в своей области видимости «заслоняет» глобальную.

Директива компилятора: макрос.

#define MACRO SUBSTITUTION #define M_PI 3.141592653589 #define N 100

#define min(x,y) ((x<y)?(x):(y))

Это способ определения т. н. макросов. В Паскале можно было определять константы в разделе const. В С функциональность расширена: помимо констант (M_PI, N в примерах выше) можно определять макросы, зависящие от аргументов (как в последнем примере). Причем аргументы подставятся в явном виде еще до компиляции и могут быть чем угодно (числами, переменными, функциями, строками и т.д.). Знак «#» как и для «#include» показывает, что «#define» — это директива препроцессора, то есть, подстановка происходит в текстовом виде до компиляции программы.

Сокращенная запись операций.

Очень часто возникает необходимость записать выражение такого вида:

(переменная1) = (переменная1) (действие) (переменная2);

например, x = x + 5;

ВС для подобных случаев существует сокращенная запись операций: x += 5.

Операции могут быть практически любыми: y *= 14; z /= 55; f &= 7; g |= (f^5); m <<=1; и так далее.

Для записи вида x+=1 и x-=1 существует еще более короткая запись: x++; и x--; соответственно. На самом деле, ситуация даже еще

интереснее: можно записать даже так: y = x++; при этом переменной y будет присвоено значение, которое было в x до увеличения его на 1. А если записать y=++x; то сначала единица прибавится к x, а потом это число запишется в у. Это называется «постфиксная» и «префиксная» запись

операций.

x = 5; y = x++; z = ++x;

После выполнения этих операций x будет равно 7, y == 5; z==7.

9

10