Оператори break та continue

Лабораторна робота №2

Тема: Основи мови C для мікроконтролерів.

Новий матеріал: Функції, оператори, коментарі, директива #include, адресація стенду.

1.Короткі теоретичні відомості

1.1. Мова C

· Загальні відомості

Мова C – мова програмування високого рівня розроблена на початку 70-х Кеном Томпсоном та Денісом Рітчі, Bell Labs. Її основними перевагами в порівняні із асемблером є зручність написання, відлагодження і підтримки великих програм та можливість легкого перенесення коду на інший вид контролерів.

· Функції

Програма, написана мовою С складається із набору функції, що викликають одна одну. Функція описується таким чином:

<тип> <назва>([параметри])

{

[тіло]

}

тип Тип результату, що повертає функція. Тип void означає, що функція нічого не повертає.

назва Назва функції. Ідентифікатор, що використовується для виклику функції. Ідентифікатор може складатися із послідовності великих чи маленьких латинських букв, цифр та знаку підкреслення. Першим символом не може бути цифра. C розрізняє великі та малі букви (інакше кажучи sin та Sin не одне й теж).

параметри Список даних, які отримує функція. Параметри розділяються комами. Тип параметру вказується перед назвою. Функція може не приймати жодного параметру.

тіло Послідовність операторів, які реалізують функцію. Оператор від оператора відділяється символом “крапка з комою”.

Яким чином відбувається виклик функції? Виклик функції заміняється компілятором на інструкцію контролера call. Функція завершується інструкцією ret.

Як відбувається передача параметрів у функцію? Компілятор Keil намагається розмістити параметри в регістрах R5-R7. Перед викликом функції компілятор вставляє інструкції, що завантажують дані в регістри. Якщо це можливо, результат повертається через регістри R5-R7.

Приклад 1. Функція a2, яка приймає один цілий параметр і повертає цілий результат.

char a2(char a)

{

return a*a;

}

Приклад 2. Функція send, яка приймає два байти адреси та один байт даних і нічого не повертає.

void send(int addr, char value)

{

P0 = addr;

P1 = addr >> 8;

P2 = value;

WR = 1;

WR = 0;

}

Для того, щоб викликати функцію потрібно вказати її ім’я а далі, в дужках, дані, що передаються функції. Дужки обов’язкові навіть якщо функція не приймає жодного параметру. Наприклад:

init();

b = a2(a);

send(1, b);

· Старт програми

Виконання програми написаної мовою С розпочинається із функції main().

void main()

{

}

Зазвичай для програм мікроконтролерів, функція main() після ініціалізації входить в безкінечний цикл. Тіло циклу може, наприклад, опитувати клавіатуру, обновляти індикатори чи опрацьовувати прапорці, встановлені процедурами обробки переривань. Для реалізації безкінечного циклу можемо скористатися оператором while.

void main()

{

//Ініціалізація програми

while (1)

{

//Тіло циклу

}

}

· Коментарі

В мові C дозволяються коментарі двох типів. Для того, щоб наказати компілятору проігнорувати послідовність від поточної позиції до кінця стрічки можна скористатись послідовністю двох косих рисок: //. Щоб вилучити із процесу компіляції довільну послідовність символів слід заключити її між символами /* та */. Коментарі не можна вкладати. Наприклад:

a = 2; //Це коментар

/*b = a+3;

Закоментований довільний відтинок програми*/

· Оператори

В C існують унарні, бінарні та тринарні оператори. Послідовність виконання операторів можна змінити використовуючи дужки ().

Таблиця 1 – Оператори мови C

Оператори # Приклад Дія
Арифметичні
- -a Повертає від’ємне значення a
+ - * / % a+b a%2 Повертають відповідно суму, різницю, результат, частку чи остачу від ділення aі b
++a ++a Префіксний інкримент. Спочатку збільшує на одиницю a і повертає результат
a++ a++ Постфіксний інкримент. Повертає значення a і після цього збільшує на одиницю a.
--a --a Префіксний декримент. Спочатку зменшує на одиницю a і повертає результат
a-- a-- Постфіксний декримент. Повертає значення a і після цього зменшує на одиницю a.
Логічні
! !a Повертає логічне заперечення a
> < >= <= != == a>b, a!=b Порівнює операнди a і b і повертає не нуль якщо умова виконується інакше повертає нуль
&& || a && b a || b Повертають відповідно результати логічної операції «і» та «або»
Бітові
~ ~a Повертає побітове заперечення a
& | ^ a & b a^0x0F Повертають відповідно результати побітової операції «і», «або» чи «виключне або»
<< >> a<<b a>>8 Зсуває a на b біт вліво чи вправо і повертає результат
Присвоєння
= a = b Заносить значення a в b та повертає b
+= -= /= *= %= &= |= ^= a+=b a+=4 a&=0x0f Заносить результат відповідної операції на операндах a і b в a і повертає його.a+=b еквівалентне a=a+b
Адресний
& ptr = &a Повертає адресу змінної
* a = *ptr Непряме звертання до пам’яті
sizeof() sizeof(a)sizeof(int) Повертає розмір змінної чи типу
Умовний
?: max = (a<=b)?b:a op1 ? op2 : op3 Якщо op1 рівний нулю, то повертається op3 інакше – op2

· Директива компілятора #include

Описи стандартних функцій і змінних в мові C прийнято зберігати в файлах-заголовках із розширенням .h. До основної програми вони підключаються за допомогою директиви компілятора #include.

Існує два варіанти її застосування #include <ім’я файлу> та #include “ім’я файлу”. Перший вказує шукати файл у стандартних директоріях операційної системи. Другий – в поточному каталозі. Наприклад:

#include <ADUC841.H>

#include <intrins.h>

#include "stend.h"

Опис регістрів спеціальних функцій мікроконтролера ADuC841 міститься у файлі ADUC841.H. Тому для звертання до периферії слід підключити його за допомогою директиви

#include <ADUC841.H>

 

Наприклад, щоб записати в регістр стану лінійки світлодіодів байт 0x7A потрібно виконати такі операції.

 

WR = 1; //0: Переключити буфер даних на запис.

P0 = 0x7A; //1: Виставити дані на шину

P2 = 7; //2: Записати дані – встановити лінію CS7 в "0"

P2 = 0; //3: Встановити лінію CS7 в "1". Дані записані

Недоліком попереднього прикладу є те, що при записі адреси змінюється стан старшої тетради порту P2. Щоб обійти це, можемо скористатися із побітових операцій. Удосконалений метод запису виглядатиме таким чином.

WR = 1; //0: Переключити буфер даних на запис.

P0 = 0x7A; //1: Виставити дані на шину

P2 &= 0xf0; //Очистка молодшої тетради порту 2

P2 |= 7; //2: Побітове або.

P2 = 0; //3: Встановити лінію CS7 в "1". Дані записані

2.Практична частина

1. В середовищі Keil створіть новий проект для мікроконтролера ADuC841. Налаштуйте параметри завантаження.

2. Створіть та додайте до проекту файл main.c.

3. У файлі main.c реалізуйте такі функції

void write(unsigned char Addr, Data) Запис байту Data в регістр Addr
void CS(unsigned char Addr) Вибір пристрою
void LED(unsigned char Data) Відображення байту Data на лінійці світлодіодів (1 – вмикає світлодіод)
void ClearLatches() Очистка регістрів 1..8 (запис байту 0xFF)

4. Реалізуйте функцію main(), яка очистить регістри, відобразить на лінійці світлодіодів байт 0xAA, а далі виконуватиме порожній цикл.

5. Відкомпілюйте та завантажте програму. Перевірте її роботу. Якщо потрібно – виправте помилки.

3.Контрольні питання

1. Що означає тип void?

2. Які обов’язкові елементи опису функції?

3. Яким чином викликати функцію?

4. Як впливають розриви рядків та додаткові пробіли в тексті програми на результат?

5. Як впливає використання великих та малих літер в програмі написаною мовою C?

6. Які вимоги ставляться до ідентифікатора?

7. Яким чином можна вказати компілятору проігнорувати відрізок тексту?

8. З чого розпочинається виконання програми?

9. Для чого служить головний цикл програми?

10. Які групи операторів ви знаєте?

11. Що таке директива компілятору?

12. Для чого служить директива #include?

 

Лабораторна робота №3

Новий матеріал: опис і використання змінних, констант, вказування типу пам'яті, масиви. Семисегментний індикатор.

1.Короткі теоретичні відомості

1.1. Опис змінних на мові C

Змінна мовою C описується таким чином

[const] <тип> [тип пам’яті] <назва>;

<назва> будь-який, ще не використаний ідентифікатор.

<тип> тип змінної, що вказує на її розмір і вміст. Стандартні типи змінних наведені в таблиці 1.

[тип пам’яті] необов’язковий параметр, що вказує компілятору де саме розміщувати змінну. Типи пам’яті, що підтримує компілятор Keil наведені в таблиці 2. Якщо тип пам’яті не вказаний, то компілятор намагатиметься спершу розмістити змінну в регістрах, а якщо це не вдалося – то в data.

[const] визначає константу. Змінити її значеня в програмі не можна. Тип пам’яті code, автоматично означає опис константи.

Таблиця 1 – Базові типи

Базовий тип Назва Розмір, байт Діапазон
signed char,char Однобайтна ціла зі знаком -128..127
unsigned char Однобайтна ціла без знаку 0..255
signed int, int Двобайтна ціла зі знаком -32768..32768
unsigned int Двобайтна ціла без знаку 0..65535
signed long Чотирибайтна ціла зі знаком -2147483648.. 2147483647
unsigned long Чотирибайтна ціла без знаку 0.. 4294967295
float Число з плаваючою крапкою 1.5E-45..3.4E38 7-8 значущих цифр

Таблиця 2 – Типи пам’яті

Тип пам’яті Назва
code Пам’ять програм. Лише константи Звернення через MOVC @A+DPTR
data Прямоадресована внутрішня пам’ять (0..127). Найшвидший доступ.
idata Внутрішня пам’ять із непрямою адресацією. Доступ до всіх комірок 0..255
bdata Внутрішня пам’ять із можливістю побітової адресації.
xdata Зовнішня пам’ять. Доступ по MOVX @DPTR
far Розширена зовнішня пам’ять (адреси до 16МБайт).
pdata Зовнішня пам’ять із посторінковою адресацією MOVX @Rn

Якщо описати змінну поза межами функцій, то вона буде глобальною – звертатися до неї зможе будь-яка функція. Для того, щоб обмежити область видимості змінної тільки для однієї функції потрібно описати її на початку тіла програми. В наступному прикладі константа a – глобальна і видима для кожної функції. Змінні b та c – локальні і доступні лише в межах функції main().

const unsigned char code a = 5;

void main()

{

unsigned char b;

int c;

с = b*a;

}

Щоб описати масив потрібно після назви в квадратних дужках вказати його розмір.

<базовий тип> [тип пам’яті] <назва>[<розмір>]

Для ініціалізації масиву констант потрібно після його опису у фігурних дужках через кому перелічити список значень елементів масиву.

const <базовий тип> [тип пам’яті] <назва>[n] = {значення 0, значення 1, ..., значення n-1}

Наприклад:

unsigned char D[16]; //Масив із 16 змінних типу unsigned char

const float codecoefs[3] = {0.1, 0.25, -0.01}; //Масив сталих коефіцієнтів

Звернутися до окремого елементу можна вказавши у квадратних дужках індекс елементу. Елементи масиву нумеруються з нуля. Наприклад:

D[0] = 1;

D[1] = D[0]+1;

y = coefs[i]*x;

1.2. Семисегментний індикатор

Рисунок 1 ­– Семисегментний індикатор

В навчальному стенді доступ до статичної індикації здійснюється шляхом запису байту стану в регістри з адресами 1..4. При цьому «0» засвічує відповідний сегмент: PGFEDCBA2. Наприклад, щоб засвітити цифру 7 в найстаршій позиції потрібно записати за адресою 4 число 111110002= F816. Отже, щоб відобразити шістнадцятькову цифру потрібно перевести її в семисегментний код. Для цього можемо скористатися масивом констант.

unsigned char code LEDD[16] =

{0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x98, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};

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

void Static(unsigned int A)

{

write(4, LEDD[A & 0xF]);

write(3, LEDD[(A >> 4) & 0xF]);

write(2, LEDD[(A >> 8) & 0xF]);

write(1, LEDD[(A >> 12) & 0xF]);

}

2.Практична частина

1. Відкрийте проект, створений на попередньому занятті.

2. Доповніть його такими функціями

void Static(unsigned int A) Відображення на індикаторі двобайтного шістнатцяткового числа
void StaticL(unsigned char A) Відображення в молодших розрядах індикатора однобайтного шіснатцяткового числа
void StaticH(unsigned char A) Відображення в старших розрядах індикатораоднобайтного шіснатцяткового числа
void ClearStatic() Очистка індикатора (запис у регістри 1..4 0xFF)

3. Функцію main() змініть таким чином, щоб після старту програми вона очистила регістри та відобразила на статичному індикаторі число 0x1234.

4. Відкомпілюйте та завантажте програму. Перевірте правильність її роботи.

3.Контрольні питання

1. Які основні типи даних підтримує мова C?

2. В чому різниця між цілими із знаком та без знаку?

3. В чому різниця між змінними та константами?

4. Як описати масив констант?

 

 

Лабораторна робота №4

Тема: Умовний оператор

Новий матеріал: складений оператор, умовний оператор, оператор switch, матрична клавіатура та дискретні кнопки.

1.Короткі теоретичні відомості

1.1. Складений оператор

Складений оператор застосовується у випадку якщо потрібно виконати декілька операторів там, де синтаксис C передбачає лише один. Загальний вигляд складеного оператора такий

{

[оголошення змінних, констант, типів]

...

[оператори];

...

}

Вкінці складеного оператора крапка з комою ставити не потрібно. Змінні, оголошені на початку складеного оператора, доступні лише в його межах. Так в наступному прикладі змінна с, доступна лише в межах складеного оператора {…}, а змінна i має інше значення а ніж в межах основної програми.

void main()

{

int a, b;

char i;

...

if (...)

{

int c, i;

...

}

...

};

1.2. Умовний оператор

Умовний оператор застосовується для створення розгалужень програми. Загальний його вигляд такий:

if (умова) оператор1; else [оператор2;]

Його виконання розпочинається із оцінки умови. Якщо результат обчислення умови ненульовий, то виконується оператор1, інакше виконується оператор2. Замість одного оператора можна використати складений. Наприклад:

if (a>b)

max = a;

Else

max = b;

Зверніть увагу! Типовою помилкою є використання оператора присвоєння = замість оператора порівняння == в умові. Наприклад, вираз if (x=0)є цілком правильним з точки зору синтаксису C, але замість очікуваної дії порівняння змінної x з нулем виконається операція присвоєння, а вираз поверне нульове значення. Для порівняння слід застосувати оператор ==

if (x==0)

...

Умовні оператори допускається вкладати. Для спрощення прочитання програми та уникнення невизначеності користуйтеся складеним оператором. Наприклад:

if (key!=255)

{

if (key==0)

{

ProcessKey(Readkey());

}

Else

ProcessKey(key);

};

1.3. Оператор switch

Оператор switch використовується, якщо потрібно реалізувати розгалуження на декілька віток. Його загальний вигляд такий:

switch (вираз)

{

[оголошення]

[case константний_вираз1:][оператори1]

[case константний_вираз2:][оператори2]

...

[case константний_виразN:][операториN]

[default: [оператори]]

}

Вираз, записаний в дужках після ключового слова switch може бути довільним виразом C, який повертає ціле значення. Тіло оператора switch складається із декількох наборів операторів помічених ключовим словом case разом із константним виразом. Всі константні вирази в межах одного switch мають бути унікальними. Допускається одна секція default, яка виконується якщо не виконалася жодна попередня секція. Для того, щоб перервати послідовність виконання служить ключове слово break.

Виконання оператора switch відбувається таким чином.

1. Оцінюється значення виразу, записаного у фігурних дужках після ключового слова switch.

2. Отриманий результат послідовно порівнюється із константними виразами.

3. Якщо результат співпадає з константним виразом, то програма продовжує виконання із відповідного оператора.

4. Оператори виконуються один за одним доти, доки не зустрінеться ключове слово break.

5. Якщо результат не співпав із жодним константним виразом, то виконуються оператори, помічені ключовим словом default.

Приклад використання оператора switch

Key = Readkey();

switch (Key)

{

case 10:

GoBack();

break;

case 11:

Execute();

break;

default:

UpdateCommand(Key);

}

1.4. Дискретні кнопки стенду

Дві дискретні кнопки стенду суміщені з механічним енкодером і джойстиком та приєднані до ліній P3.2 (INT0) та P3.3 (INT1) відповідно. Відпрацювати їх натиск можна шляхом опитування бітів INT0 та INT1 або обробником переривання. Наприклад опитування можна реалізувати таким чином:

if (INT0)

LED(0);

Else

LED(1);

3.Контрольні питання

1. Для чого використовується складений оператор?

2. Як записати мовою C складений оператор?

3. Як описати умовний оператор?

4. Як виконати більше ніж один оператор в кожній із двох гілок розгалуження?

5. Для чого застосовується оператор switch?

6. Якого типу може бути ключовий вираз в дужках після оператора switch?

7. Яке значення ключового слова default?

8. Для чого використовується оператор break?

9. Опишіть алгоритм опитування матричної клавіатури.

 

Лабораторна робота № 5

Тема: Використання циклів. АЦП.

Новий матеріал: Цикл for, цикл while, цикл do while, оператори break та continue. АЦП.

1 Короткі теоретичні відомості

Цикли використовуються для програмування багаторазового повторення однієї і тієї ж послідовності операцій. Організувати цикл мовою C можна за допомогою оператора for, while, do while. Багаторазове повторення певної дії також може бути запрограмоване використовуючи рекурсію чи оператор goto.

Якому способу надати перевагу? Використання goto не рекомендується, через утруднене читання програми. Рекурсія, хоч і елегантний, проте ресурсоємний спосіб який вимагає динамічного виділення пам’яті, а тому для програм мікроконтролерів вона малопридатна. Серед решти операторів циклів слід обрати той, за допомогою якого програмована дія буде записана найзрозуміліше, найочевидніше.

Цикл for

Цикл for складається із заголовка та тіла. Заголовок розпочинається ключовим словом for і містить три частини записані в дужках через крапку з комою. Перша частина виконується найпершою один раз і найчастіше використовується для ініціалізації змінних циклу. Друга частина – умова – перевіряється передкожною ітерацією циклу. Якщо її значення не рівне нулю (істина), то виконується тіло циклу, інакше виконання циклу завершується. Якщо умова хибна ще перед першою ітерацією, то тіло циклу може не виконатися жодного разу. Третя частина – оператор реініціалізації – виконується щоразу після виконання тіла і найчастіше використовується для модифікації змінної циклу.

Тілом вважається наступний після заголовка оператор. Якщо в циклі потрібно повторювати більше ніж одну операцію, то слід скористатися складеним оператором {}. Якщо всі необхідні дії виконує заголовок, то тіло можна залишити порожнім. В такому випадку після заголовку відразу ж ставлять крапку з комою – порожній оператор.

Розглянемо цикл for на прикладах. Наступний фрагмент викличе функцію write вісім разів. При цьому змінна i набуватиме значення від 1 до 8. Після останньої ітерації, коли змінна i стане рівна 9, а умова, записана в другій частині заголовку (i!=9), стане хибною (поверне 0) виконання циклу перерветься.

for (i=1; i!=9; i++)

write(i, 0xff);

Будь-яка, а то й всі частини заголовку можуть бути порожніми. В такому випадку, для виходу із циклу потрібно в тілі додатково передбачити виклик оператораbreak. Наприклад:

for (;;)

{

...

if (a>b) break;

...

}

Кожна частина заголовку може містити і більше ніж один оператор – їх можна записати через кому. Наприклад обернення масиву можна реалізувати так:

for (i=0,j=last;i<=j;i++,j--)

{

t = a[i];

a[i] = a[j];

a[j] = t;

}

Як приклад циклу із порожнім тілом розглянемо фрагмент пошуку першого нульового елементу масиву:

for (j=0; a[j]!=0; j++);

Цикл while

Цикл while або цикл з передумовою зручно використовувати у випадку якщо наперед невідома кількість повторень. Заголовок циклу з передумовою складається із ключового слова while і умови записаної в дужках. Умова перевіряється перед кожною ітерацією. Якщо вона істинна, то виконується тіло циклу, інакше цикл переривається. Тіло циклу теж може бути складеним чи порожнім оператором. Наприклад наступний код інтегрує вхідну напругу доти, доки сума не перевищить значення 32768:

while (sum<32768)

sum += GetADC(0);

Цей же цикл можна було б записати інакше, але зрозумілість коду втрачається:

while ((sum+=GetADC(0))<32768)

Завжди істинна умова часто використовується для організації основного циклу програми. Якщо такий "вічний" цикл потрібно завершити, то можна скористатися оператором break. Наприклад:

while (1)

{

...

if (errCode!=0) break;

...

}

Цикл із порожнім тілом всі необхідні дії виконує в заголовку. Наприклад наступний фрагмент очікуватиме одиничного рівня на лінії T0:

while (!T0);

Цикл do-while

На відміну від циклів for та while, цикл do...while перевіряє умову виходу після кожної ітерації. Це означає, що такий цикл виконається хоча б раз. Опис оператора розпочинається ключовим словом do, далі записується один або декілька операторів тіла циклу, а завершується опис ключовим словом while разом із умовою записаною в дужках після нього. Після кожного проходу перевіряється умова і якщо вона істинна, то тіло виконується ще раз, інакше цикл зупиняється. Зверніть увагу, що фігурні дужки необов’язкові навіть якщо тіло складається із декількох операторів.

Наступний приклад очікує натиску клавіші і записує її код в змінну Key:

Do

Key = ReadKey();

while (Key!=255)

Оператори break та continue

Оператори break та continue використовуються для керування ходом виконання циклу в його тілі. Виконання оператора break перериває поточну ітерацію і виходить з циклу. Оператор continue – перериває виконання поточної ітерації і розпочинає наступну. Для циклів while та do...while continue означає негайний перехід до перевірки умови виходу. Для циклу for перед перевіркою умови виходу виконується оператор реініціалізації.

Наприклад для того, щоб знайти суму всіх невід’ємних елементів масиву можна скористатися таким фрагментом коду:

for (i=0; i!=last; i++)

{

if (a[i]<0) continue;

s += a[i];

}

Цю ж дію можна записати так:

for (i=0; i!=last; i++)

if (a[i]>=0)

s += a[i];

 

1.5. Вкладені цикли

Тіло циклу можуть складати різні оператори також і цикли. Це надає можливість створювати вкладені цикли. Наприклад запрограмувати затримку можна так:

unsigned char i, j;

for (i=0; i!=255; i++)

for (j=0; j!=255; j++);