ARM это просто (часть 2)


Учимся говорить с электроникой на одном языке

Не только люди друг с другом общаются на языке, но и люди с компьютерами (а микроконтроллеры можно тоже назвать микрокомпьютером) общаются на языке, называемом языком программирования. Как и у людей существует множество языков, русский, английский, немецкий, китайский и т.п., также существуют и различные языки программирования, такие как С, Pascal, Basic, Fortran, Java, PHP и прочие.

Каждый язык программирования имеет свои особенности и в связи с этим для использования в различных применениях предпочтение отдается разным языкам. Так, например, скриптовый язык программирования PHP имеющий богатые возможности по работе со строковыми данными, и функциями связанными с работой с текстом, совершено не подходит для написания программ на МК, поскольку, как и любой другой скриптовый язык, он требует для своей работы специального установленного ПО, исполняющего написанный на этом языке код. Таким образом для нас подходят только компилируемые языки, такие как например С, Pascal, Basic и т.п.. Наиболее простым в освоении считается Basic, но в связи с его упрощением он создает далеко не идеальный программный код, в связи с чем, и без того не особо богатые ресурсы микроконтроллера расходуются слишком расточительно. Pascal, среди названных трех языков, является средним языком. Самым лучшим по оптимальности получаемого кода считается язык программирования С. Но даже он не всегда обеспечивает нужного результата, поэтому также порой используется Assembler, данный язык представляет ни что иное как непосредственная работа с микроконтроллером на низком уровне. С точки зрения электроники, написание программ на нем можно сравнить с разработкой и сборкой усилителя на транзисторах, а написание программ на языках высокого уровня со сборкой усилителя на микросхемах. Написание программ на ассемблере является более трудоемкимPпроцессом, но при должном умении он дает возможность получить более лучшие результаты по быстродействию и занимаемым программой ресурсам. Также как и усилитель, собранный на транзисторах, может дать лучшие параметры звучания, но только в том случае если этот усилитель собирается по хорошо [вылизаннойk схеме с правильной разводкой платы и при соблюдении всех необходимый требований. Именно по этим причинам, наиболее часто применяемым языком программирования, для написания программ для МК является язык программирования Си, с которым я и предлагаю начать наше знакомство. Те, кто знаком с данным языком, могут пропустить данную часть и приступить к изучению следующей части, поскольку в данном материале отображены только базовые знания, необходимые для написания программ, описанные максимально доступным для начинающих языком.

Комментарии

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

// Комментарии в программе это заметки, которые игнорируются языком Си

//и не считаются частью программного кода.

// Они записываются в одной и более строках, и заключаются между символами /* и */

// или в одной строке, начинающейся с последовательности //

// Так может быть записан только однострочный комментарий,

//этот комментарий правильный

А это уже не является комментарием

/* Так записываются многострочные комментарии,
он заканчивается там где встречается комбинация символов */

/* многострочные комментарии могут вставляться в часть кода*/

Это какой-то код /* это комментарий */ а это снова код

Структура программ

Любая программа, как и предложение в нашем языке, состоит из различных частей [речиk, таких как константы, операторы и переменные. Например, в предложении [Если я прийду домой в 18 часов, то успею посмотреть фильм, иначе он закончитсяk имеются оператор условия [Если, то успею, иначеk, константы [яk и переменная [18 часовk. Примерно вот так эта запись будет выглядеть, если её записать на языке Си

// Здесь оператор условия [ifk, в скобках указывается само условие,

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

if (я прийду домой в 18часов) успею посмотреть фильм;

// Если условие не выполнилось, то программа выполнит условие идущее после [elsek,

//else не является обязательным и может отсутствовать

else фильм закончится;

Условие состоит из трех частей, это константа, знак сравнения, и переменная. В данном случае [яk является константой, [прийду домой вk знаком сравнения, и [18 часовk переменная. Более подробнее об использовании условных операторов будет рассказано далее.

Переменные

Все константы, операторы и пр. располагаются во flash памяти (ПЗУ, ROM, память программ), содержимое этой памяти не может быть изменено программой. Для хранения данных, которые изменяются во время выполнения программы, используется оперативная память (ОЗУ, RAM, память данных). Все переменные располагаются в RAM памяти, для создания переменной используются ключевые слова:

char создает переменную размером 1 байт (8 бит), данная переменная может содержать 28=256 значений;

int размер создаваемой переменой зависит от используемой архитектуры, так например для МК AVR Atmega8 и ему подобных размер переменной будет 16 бит, для STM32 он составит 32 бита;

short - создает переменную размером 16 бит, данная переменная может содержать 65536 значений и занимает в памяти 2 байта;

long 32 битная переменная, занимает в памяти 4 байта и содержит 232 значений;

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

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

unsigned указывает на то, что создаваемая переменная имеет беззнаковый тип, т.е. unsigned char создаст переменную с диапазоном от 0 до 255;

signed указывает на то, что создаваемая переменная имеет знаковый тип, т.е. signed char создаст переменную с диапазоном от -128 до +127

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

unsigned char array_name[10]

Любые переменные, а также функции и константы должны выбираться с учетом следующих правил:

1. Они должны начинаться с буквы латинского алфавита (а,...,z, А,...,Z) или с символа подчеркивания [_k.

2. В них могут использоваться буквы латинского алфавита, символ подчеркивания и цифры (0,...,9). Использование других символов запрещено.

3. В языке Си буквы нижнего регистра (а,...,z), применяемые в идентификаторах, отличаются от букв верхнего регистра (А,...,Z). Это означает, что следующие идентификаторы считаются разными: name, NaMe, NAME и т.д.

4. Они могут иметь длину в соответствии со стандартом ANSIC не превышающую 32 символов.

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

По окончании объявления переменной необходимо написать точку с запятой [;k.

Если перед значением числа добавлено выражение [k, это означает что данное число записано в шестнадцатеричном виде, например 0х12.

Попробуем создать несколько переменных.

/* Например, некая переменная может изменяться в диапазоне от 0 до 100,
она не превышает размерности 1 байта, и имеет только положительные значения,
значит, для нее подходит тип unsigned char */

unsigned char imja_peremennaya1;

/*Если мы захотим создать переменную, изменяющуюся в диапазоне, например, от 50 до 1000 нам необходимо объявить ее как unsigned short, не смотря на то, что она также входит и в диапазон unsigned long. Её также возможно объявить типом unsigned long, но это не рационально, поскольку при этом будет зарезервирована бОльшая область памяти, чем требуется для данной переменной*/

unsigned short imja_peremennaya2;

/* Для объявления переменной изменяющейся в диапазоне от -30 до +150 подходит тип signed int */

signed int imja_peremennaya3;

/* Массивы определяются так же, как и переменные, но дополнительно в квадратных скобках указывается размер массива */

/*Данный массив представляет набор из 100 переменных типа unsigned int имеющих имя imja_massiva. При обращении к массиву необходимо в квадратных скобках указать номер элемента, к которому происходит обращение. */

unsigned int imja_massiva[100];

/* Инициализировать строку можно следующим образом: */

//В квадратных скобках указывается резервируемое в ОЗУ место

unsigned char stroka1[7] = "Строка";

//Еще один вариант объявления массива для строк

unsigned char stroka2[ ] = {'С', 'т', 'р', 'о', 'к', 'а', ''};

/* Следует помнить, что любая строка должна заканчиваться байтом равным 0, это является признаком конца строки */

P

Таблица 1. Диапазон возможных значений переменной для различных типов переменной

Тип

Диапазон значений для unsigned

Диапазон значений для signed

char

0 ... 255

-128 ... 127

short

0 ... 65535

-32768 ... 32767

int (для Cortex-M3)

0 ... 4294967295

-2147483648 ... 2147483647

long

0 ... 4294967295

-2147483648 ... 2147483647

long long

0 ... 18446744073709551615

-9223372036854775808 ... 9223372036854775807

Операторы

Для совершения операций с переменными существуют операторы действия

Оператор

Пример

Описание

+

a+b

Математическое сложение

-

a-b

Математическое вычитание

*

a*b

Умножение, перемножает две переменные a и b

/

a/b

Делит переменную a на b

%

a%b

Определение остатка от деления a на b. Если, например, а=6, b=4 получим 6/4=1,5 следовательно остаток равняется 6-(1*4)=2.

Если а=35, b=6, то остаток будет 5

=

a=b

Присваивание.

Переменной а присваивается значение переменой b.

a

Операция сравнения - меньше

>P

a>b

Операция сравнения - больше

<=

a<=b

Операция сравнения - меньше, либо равно

>=

a>=b

Операция сравнения - больше, либо равно

==

a==b

Математическая операция сравнения - равенство

!=

a!=b

Математическая операция сравнения - не равенство

++

a++

Инкремент числа а.

Операция производит увеличение на единицу значения числа а.

Эквивалентно выражению а=а+1

--

а--

Декремент числа а.

Операция производит уменьшение на единицу значения числа а.

Эквивалентно выражению а=а-1

<

a<

Побитовый сдвиг влево.

Сдвигает переменную а, на количество бит b влево. Пример 23<<1, число 23 при представлении в двоичной системе (http://wiki.mvtom.ru/index.php/Двоичная_система_счисления, http://www.wikiznanie.ru/ru-wz/index.php/Двоичная_система_счисления) счисления выглядит как 0001 0111, сдвинем его на 1 в влево, получим 0 0010 1110, что соответствует числу 46 в десятичной системе.

Данную операцию можно записать эквивалентным математическим выражением: а*2b.

Числа, не вошедшие в диапазон переменной, будут отброшены.

Побитовый сдвиг влево

>>P

a>>b

Побитовый сдвиг вправо.

Сдвигает переменную а, на количество бит b вправо. Пример 23>>1, в результате выполнения операции получим 0000 1011, что соответствует числу 11 в десятичной системе.

Данную операцию можно записать эквивалентным математическим выражением: а/2b.

Побитовый сдвиг вправо

&

a&b

Поразрядное логическое "И".

В результате выполнения данной операции получим число в котором имеются только разряды установленные в числах а Иb.

Например, имеем выражение 109&180, представим эти числа в двоичном представлении:
0110 1101 &
1011 0100__
0010 0100

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

|

a|b

Поразрядное логическое "ИЛИ".

Устанавливает разряды, имеющиеся хотя бы в одном из чисел логическую единицу.

В результате выполнения операции 109|180 получим:

0110 1101 |
1011 0100_

1111 1101

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

^

a^b

Поразрядное исключающее "ИЛИ"

Устанавливает в 1 каждый разряд, где соответствующие разряды каждого выражения, но не оба из них, равны 1.

В двоичном виде

0110 1101 ^
1011 0100__
1101 1001

~

~a

Возвращает инвертированное значение а. Т.е. все логические единицы всех разрядов становятся равными нулю, а все нули единицами.

PPPа=0110 1101,
~a==1001 0010

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

Составная операция

Эквивалентная запись

a+=b

a=a+b

a-=b

a=a-b

a*=b

a=a*b

a/=b

a=a/b

a%=b

a=a%b

a<<=b

a=a<

a>>=b

a=a>>b

a&=b

a=a&b

a|=b

a=a|b

a^=b

a=a^b

Наиболее часто встречающиеся комбинации операторов для установки и сброса битов при написании программ для МК:

a |= (1установку бита с номером b, в байте а

a |= ((1b и с в байте а.

a &=~ (1b в байте а.

a &=~ ((1b и с в байте а.

Познакомившись с тем, что такое переменная, как её создавать (объявлять), а также с основными действиями над переменными, можно рассмотреть небольшой пример работы с переменными.

/*Для начала нам надо объявить переменную, предположим, что переменные [perem1k и [xk будут изменяться в диапазоне от нуля до 100*/

//Объявим переменную [perem1k и сразу присвоим ей начальное значение 20

unsigned char perem1=20;

//подобным образом поступим со второй переменной [хk

unsigned char x=5;

//Создадим еще одну переменную, в которую запишем результат сложения [perem1k и [хk

unsigned char otvet;

//После выполнения данного действия. переменная [otvetk l будет иметь значение равное 25

otvet = perem1+x;

otvet *= 10;PPP //Умножим содержимое переменной [otvetk на константу 10

//Теперь в переменной [otvetk содержится число 250

//Вычтем из переменной [otvetk константу 30 и разделим получившееся на 15

otvet = (otvet-30)/15;

/* После выполнения данных действий мы должны получить число 14,66(6), но поскольку переменная [otvetk объявлена как unsigned char, т.е. целочисленная беззнаковая переменная, то дробная часть будет отброшена и в результате в переменной [otvetk будет записано только число 14*/

//Добавим к переменной [otvetkPчисло 250

otvet = otvet +250;

/*В результате совершенной операции мы должны получить число 264 (в двоичном виде 1 0000 1000), но по поскольку [otvetk имеет тип unsigned char, он может иметь только положительные значения находящиеся в диапазоне от 0 до 255 (в диапазоне одного байта), число 264 превышает размерность одного байта и поэтому значение переменной будет ограничено размерностью одного байта, таким образом, мы получим в переменной [otvetk значение 8 (в двоичном виде 0000 1000)*/

//Отнимем от переменой [otvetk переменную [perem1k и запишем результат в [otvetk

otvet -= perem1;

/*В результате совершенной операции мы должны получить число (8-20) число -12, но Pпоскольку [otvetk имеет тип unsigned char, он не может иметь отрицательных значений, аналогично описанному примеру выше, в переменной [otvetk будет записано значение 244.*/

//Увеличим на 1 значение переменной [perem1k

perem1++;

//Теперь переменная содержит значение 21

//Посчитаем остаток от деления переменной [perem1k на переменную [хk

otvet = perem1%х;

/*В результате операции мы получим значение равное 1, поскольку при делении 21/5 ответ получается равным 4 + 1/5 */

Логические операторы

Но язык программирования не язык без использования предложений, в смысле операторов. В языке Си четыре основных оператора, это if, for, while, switch.

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

Синтаксис записи данного оператора следующий:

if (условие)
{
PPPКод, выполняемый при выполнении [условияk
}
else
{
PPPДанный код выполняется если [условиеk не верно.
}

В круглых скобках идущих сразу после if указывается условие, при выполнении которого, будет исполнен код, объединенный в первых фигурных скобках. Если условие не верно, то выполняется условие во вторых фигурных скобках идущих после else. else не является обязательным, т.е. если не требуется выполнения кода в случае если [условиеk не выполнено, то else может отсутствовать.

В качестве условия могут использоваться выражения сравнения, например a==b, ab, a!=b и т.п.. Также в качестве условия может стоять переменная, если переменная не равна нулю, то считается что условие выполнилось.

Фигурные скобки обозначают начало и конец кода, выполняющегося по определенному условию.

Если требуется одновременное выполнение сразу нескольких условий, то такие условия объединяются символами логического условия && и ||. Символ && означает одновременное выполнение первого И второго условия, а символ || требует выполнениеPпервого ИЛИ второго ИЛИ сразу двух условий.

Рассмотрим небольшой пример использования оператора if

unsigned char a, b, с;P/* Во время объявления нескольких переменных с одним типом допускается перечислять объявляемые переменные через запятую*/

//Зададим значения переменным

a=10;
b=15;
c=5;

/*Создадим простое условие, в соответствии с данным условием, если переменная [аk меньше пяти, то переменной [аk будет присвоено значение 5*/

if (a<5) a=5;PP/* Если в коде выполняемом по условию содержится только одно действие,
то фигурные скобки можно не ставить,
также в этом примере не требуется выполнения кода, если условие [а<5k не выполнилось,
поэтому else отсутствует */

if (a<=b)PPP //Если переменная [аk меньше или равна [bk
PPP{PPP// тогда выполнится данное условие
PPPa=b;P//переменная [аk примет значение [bk
PPPb++;P//переменная [bk увеличит свое значение на единицу
PPP}
elsePPP//иначе если [аk больше [bk, то выполнится это условие
PPP{
PPPa=b-c;P // [аk примет значение равное разнице [bk минус [сk
PPP}

if (a) a--;PPP//Если переменная [аk не равна нулю, то выполнится её декремент
else a=100;PPP//иначе если переменная [аk равна нулю, ей присвоится значение 100

// Запишем двойное условие.

if ( (a PPP{/* Данное условие выполнится только в том случае, если выполнится одновременно два условия, переменная [аk будет меньше переменой [bk И переменная [bk будет меньше переменной [сk */
PPPa--;
PPPc++;
PPP}
elsePPP
PPP{ /* Иначе если переменная [аk будет больше либо равна переменной [bk, ИЛИ если переменная [bk будет больше либо равна переменной [сk, ИЛИ оба этих условия, то будет выполнятся данный код */
PPPa=0;
PPPb=5;
PPP}

Для создания циклов, дающих возможность многократно повторять один и тот же код, до выполнения определенного условия существуют операторы циклов for и while.

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

while (условие) {код}

unsigned char w=20;

while (w>=10)PP
PPP{PPP// Данный код будет выполняться пока переменная [wk больше либо равна 10
PPPw--;
PPP}

Использовать оператор while следует с осторожностью, поскольку с его применением, возможно создать такую ситуацию, когда необходимое условие может никогда не выполниться, что приведет к зависанию программы. Более безопасным является использование цикла for. Данный цикл предназначен для выполнения заранее заданного количества повторений кода. Синтаксис написания цикла for следующий:

for (задание начального условия; условие; операция по изменению условия) {код}

unsigned char i, a;

a=0;

/*
Задаем начальное значение для переменной [ik (i=0).
Пишем условие, при выполнении которого будет выполняться код (тело цикла) (i<10).
Задаем условия для изменения переменной [ik (i++).
*/

for (i=0; i<10, i++)
PPP{PP // Этот код в данном случае выполнится 10 раз и переменная [аk станет равной 10
PPPa++;
PPP}

/* Выход из любого цикла также может быть осуществлен раньше выполнения условия цикла, для этого используется [breakk. */

a=0;

for (i=0; i<100; i++)PP//Данный цикл может выполняться до 100 раз.
PPP{
PPPa++;

PPPif (a>=50) break;P/* Но переменная [аk станет равной 50 раньше, чем перестанет выполняться условие цикла, поэтому цикл for прервется после выполнения 50 циклов */
PPP}

Функции

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

Предпосылками к созданию функций в программе, являются:

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

2. Желание структурировать программу в виде отдельных блоков с общим функциональным назначением.

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

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

возвращаемый_тип_переменной Function_name (передаваемый тип переменной)
{
PPPТело функции
}

Если функция не должна принимать данные, и/или не должна возвращать данные, то в качестве типа данных указывается void. Любая функция может возвращать только одну переменную, но при этом функция может принимать одну или несколько различных переменных перечисляемых через запятую.

/*Создадим простейшую функцию, которая ничего не принимает, ничего не возвращает и вообще ничего не делает */

void Function_Name (void)
{
//А тут пусто
}

/*А на этот раз напишем полезную функцию, которая делает задаваемую программную задержку */

void Delay (unsigned int n) //Передадим функции число «n»
{
while (n) //Крутимся в цикле пока « не станет равным нулю
{
n--; //А сами, пока, каждый раз будем отнимать от « по одному
}
}

/*Наша функция готова принять две переменных «х1» и «х2» типа unsigned char
и вернуть одну переменную типа unsigned int*/

unsigned int Summa (unsigned char x1, unsigned char x2)
{
unsigned int rezult; //Объявляем переменную для результата
rezult=x1+x2; //Производим необходимые вычисления
return rezult; //return производит выход из функции и возврат значения «rezult»
}

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

/*Создадим функцию которая принимает две переменные («x1» и «х2»), суммирует их и
делит результат на глобальную переменную «, а получившееся значение возвращает*/

unsigned char z=2; //объявим глобальную переменную « и присвоим ей значение 2

unsigned int Summa2 (unsigned char x1, unsigned char x2)
{
unsigned int rezult; //Объявляем переменную для результата
rezult=(x1+x2)/z; //Производим необходимые вычисления
return rezult; // «return» производит выход из функции и возврат значения «rezult»
}

/* Выполнение любой программы всегда начинается с функции «main», данная функция обязательно должна присутствовать в любой программе написанной на Си */

void main (void) // Это самая главная функция программы
{
// Для вызова другой функции используется следующая запись:
unsigned int r; //Объявим переменную, в которую запишем возвращенный результат

r = Summa2 (10, 5); //Вызовем функцию, и передадим ей в качестве «х1» значение 10, «х2»=5

/* После выполнения данной функции, в переменной « будет содержаться результат вычисления равный 7 */
}

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

void Function_Name (void);

void Delay (unsigned int n);

unsigned int Summa (unsigned char x1, unsigned char x2);

unsigned int Summa2 (unsigned char x1, unsigned char x2);

Поскольку функция main ниоткуда не вызывается, то её можно не объявлять.

Заголовочные файлы

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

#include "main.h"

Для удобства читаемости исходного кода обычно создается несколько .c файлов исходного кода, и соответствующие им заголовочные .h файлы, куда выносятся все объявления функций, которые должны быть видны из других файлов с исходными кодами. Эти заголовочные фалы также подключаются через директиву #include к файлу с исходным кодом, в котором требуется вызов функций из других файлов. Помимо объявления функций, в заголовочные файлы также выносят указания на глобальные переменные, доступ к которым хотят сделать доступным из других файлов, для этого к объявлению переменной добавляют слово extern.

extern unsigned char z;

Пример заголовочного файла main.h:

void Function_Name (void);

void Delay (unsigned int n);

unsigned int Summa (unsigned char x1, unsigned char x2);

unsigned int Summa2 (unsigned char x1, unsigned char x2);

extern unsigned char z;

Макросы (предпроцессорные директивы)

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

#define имя-макроса заменяемая-строка

Команда #define используется для организации замены строки по всему файлу, где она указана. Другими словами, #define приводит к тому, что компилятор (препроцессор) проходит по всему файлу и делает замену "имя-макроса" на "заменяемая-строка". Использование вместо настоящих функций макросов дает одно существенное преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций.

Предпроцессорная директива #define может использовать логические условия, подобные условию if(…) else… Форма записи данных условий следующая:

#ifdef ASD //Если макрос ASD был определен ранее

//Действия, выполняемые, если ASD был определен ранее.

#else //Если ASD не был определен ранее

//Действия если ASD не был определено ранее

#endif //Конец условия

Помимо #ifdef также используются и другие условия:

#ifndef – подобно условию #ifdef, но выполняющимся, если указанный макрос не был ранее определен.

#if – подобно условию if(…) else…, сравнивает выполнение логического условия для макросов.

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

Пример программы

Пример заголовочного файла main.h нашей программы

#define PROCESSOR_CLOCK 24000000L //Значение тактовой частоты процессора

void Delay(unsigned long n); //Объявляем функцию

Пример файла исходного кода main.с нашей программы

#include "main.h" //Подключаем заголовочный файл main.h

#include "LED.h" //Заголовочный файл с описанием функций управления светодиодом

void Delay(unsigned long n) //Функция программной задержки
{
unsigned long i;
for (i=0; i//Инкрементируем «i» пока она не станет равным «n»
}

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

void main(void)
{
while(1) //Основной цикл, будем находиться в нем бесконечно
{
Delay(PROCESSOR_CLOCK); //Ждем пока выйдет время
LED(); //Вызываем функцию работы со светодиодом
}
}

Пример заголовочного файла управления светодиодом LED.h

//Выставим бит 5 в порте А (к нему должен быть подключен светодиод)

#define LED_ON PORTA|=(1<<5)

//Сбрасываем бит 5 в порте А

#define LED_OFF PORTA&=~(1<<5)

//Считываем бит 5 из порта А

#define LED_STATE PORTA&(1<<5)

//Объявляем функцию управления светодиодом

void LED(void);

Пример файла исходного кода управления светодиодом LED.с

#include "LED.h" //Заголовочный файл с описанием функций управления светодиодом

//Функция управления светодиодом

void LED(void)
{
//Если LED_STATE не равно нулю, то светодиод включен, значит надо его выключить
if (LED_STATE!=0) LED_OFF;
else LED_ON; //Иначе надо включить
}

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

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

#define DELAY 40000 //Количество циклов для программной задержки

//Объявим глобальную переменную «i», для подсчета количества пройденных циклов программной задержки

unsigned char I

//Объявляем нашу функцию MyFunction

void MyFunction(void);

//Собственно сама наша функция MyFunction

void MyFunction(void)
{
signed int a; //Объявляем переменную «а»

if (a<1000) a=+3; //Если переменная меньше 1000, увеличим значение переменной на 3

if (a>1000) //Если переменная «а» стала больше 1000,
{
a=+10; //то будем увеличивать ее за каждый вход в эту функцию на 10
}

if (a>=40000) //Как только переменная «а» достигла 40000,
{
а=0; //сбросим ее в нуль
}
}

//Наша главная функция

void Main(void)
{
i=0; //Сбрасываем значение счетчика «i» на 0
//Основной цикл, будем находиться в нем бесконечно
while(1)
{
i++ //Увеличим счетчик циклов задержки

if (i = DELAY) //Если счетчик стал равным значению задержки
{
i=0; //сбросим счетчик
MyFunction();
}
}
}

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

Список использованных источников:
1. Википедия;
2. ПИЭ.Wiki;
3. Ю.Ю.Громов, С.И.Татаренко ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ СИ Тамбов, 1994


Добавил:  Павел (Admin)  [email protected] | 

Автор:  Сторожев Денис  Рейтинг@Mail.ru