Базовий набір команд мови ASSEMBLER для мікроконтролерів AVR містить 120 інструкцій, які можна розділити на 4 групи: команди пересилання даних; арифметичні й логічні команди; інструкції для роботи з бітами; команди керування ходом виконання програми.
Команди пересилання даних. Група команд пересилання даних містить у собі інструкції із завантаження значень констант, пересилання даних типу регістр - регістр, регістр - пам'ять, регістр - порт вводу/виводу. Команди даної групи є двух-операндными, причому першим операндом є приймач даних, а другим - джерело даних.
Команда завантаження констант ldi R, K застосовується для запису безпосереднього значення К у регістр – приймач R. Як регістр - приймач можуть використовуватися регістри загального призначення R16 - R31. Якщо константа представлена у двійковій або шістнадцятковій системах числення, то перед значенням константи K необхідно вказати специфікатор системи числення 0b - для двійкової, 0х - для шістнадцяткової відповідно. Приклади:
ldi R16, 125 завантаження в R16 десяткового числа 125;
|
|
ldi R20, 0xFF завантаження в R20 шістнадцяткової константи FFh;
ldi R23, 0b11011001 завантаження в R23 двійкової константи 11011001;
Команда пересилання даних між регістрами mov Rd, Rs використається для пересилання значення з регістра-джерела Rsу регістр-приймач Rd. Операнды в команді є винятково регістрами загального призначення R0 - R31.
Приклади:
mov R16, R0 завантаження в R16 значення з регістра R0;
mov R17, R20 завантаження в R17 значення з регістра R20;
У командах пересилання даних між регістром і коміркою пам'яті використовується механізм непрямої адресації, при якому адреса комірки пам'яті заноситься в один з 16-розрядних регістрів Х,Y,Z (cм. рисунок 1.3). Формати команд:
ld R8, (R16) – завантаження даних з комірки пам'яті, адреса якої перебуває в 16-розрядному регістрі R16, у регістр загального призначення R8
st (R16), R8 – завантаження даних з регістра загального призначення R8 у комірку пам'яті, адреса якої перебуває в 16-розрядному регістрі R16 ,
ldd R8, (R16+Q) – завантаження даних у регістр загального призначення R8 з комірки пам'яті, адреса якої перебуває як сума значення, що перебуває в 16-розрядному регістрі R16 , і зсуву Q,.
std (R16+Q), R8 – завантаження даних з регістра загального призначення R8 у комірку пам'яті, адреса якої перебуває як сума значення, що перебуває в 16-розрядному регістрі R16 , і зсуву Q,.
Приклади:
ld R2, X завантаження в R2 значення з пам'яті за адресою, зазначеному в Х;
st Y, R5 завантаження значення з регістра R5 в пам'ять за адресою,
зазначеній в Y.
ldd R5, Z+1 завантаження в R5 байта з пам'яті за адресою Z+1;
std Y+4, R7 завантаження байта з регістра R7 в пам'ять за адресою Y+4.
|
|
Для звертання до портів вводу/виводу в мікропроцесорі передбачені спеціальні команди in і out:
in R, P ввід даних з порту з адресою Р у регістр загального призначення R;
out P, R вивід даних з регістра загального призначення R у порт із адресою Р;
Приклади:
in R10, 0x15 ввід даних з порту з адресою 15h у регістр загального призначення R10;
out 0x2F, R8 вивід даних з регістра загального призначення R8 у порт із адресою 2Fh;
Арифметичні й логічні команди. Для роботи із цілими двійковими числами цілочисельне АЛУ мікроконтролера AVR MEGA128 підтримує більше десятка арифметичних і логічних команд.
Основними арифметичними командами є інструкції додавання, вирахування й множення. Операндами в командах даної групи можуть бути тільки регістри загального призначення. Результат операції (крім множення) записується за адресою першого операнда.
Основні команди для виконання операцій додавання, вирахування й множення (для чисел без знака):
add Rd, Rs команда додавання (addition), дія: Rd=Rd+Rs;
sub Rd, Rs команда вирахування (subtraction), дія: Rd=Rd–Rs.
mul Rd, Rs команда множення (multipl.), дія: R1, R0=Rd*Rs;
Команди змінюють прапори переносу С, переповнення V, знака N, S, і нуля Z. При виконанні операції множення n -значних чисел місцезнаходження результату розрядністю 2n фіксовано й не вказується в команді: при множенні двох байтів результат розміром у слово заноситься в регістрову пару (R1, R0), в R0 – молодше слово, в R1 – старше слово.
Приклади:
add R10, R15 R10 = R10 + R15;
sub R2, R7 R2 = R2 – R7;
mul R5, R16 R1,R0 = R5 * R16.
Команди позитивного й негативного збільшення (інкремента й декремента):
inc R інкремент (increment), дія R=R + 1;
dec R декремент (decrement), дія R=R – 1;
У якості операнда в цих командах допускається використати тільки регістр загального призначення.
Приклади:
inc R20 дія R20 = R20 + 1;
dec R16 дія R16 = R16 – 1;
Основними логічними командами мікроконтролера AVR MEGA128 є:
or Rd, Rs логічне “або”; дія: Rd = Rd or Rs;
and Rd, Rs логічне “і”; дія: Rd = Rd and Rs;
eor Rd, Rs “ щовиключає або”; дія: Rd = Rd eor Rs.
Дані команди виконують операції поразрядного логічного “або”, логічного “і”, “ щовиключає або” (див. таблицю 1.2) над операндами, що перебувають у регістрах загального призначення, причому результат записується за адресою першого операнда. Приклади:
or R7, R11 дія: R7 = R7 or R11;
and R10, R11 дія: R10 = R10 and R11;
eor R25, R30 дія: R25 = R25 eor R30.
Таблиця 1.2 - Таблиці істинності логічних операцій or, end, eor
оr (або) | and (і) | eor (виключ. або) | ||||||
Вхід | Вихід | Вхід | Вихід | Вхід | Вихід | |||
А | В | Q | A | B | Q | А | В | Q |
Зазначені команди використовуються для виконання операцій порозрядного маскування: or – для установки одиниць у заданих розрядах, and – для установки нулів, еor – для з'ясування збігів значень битов першого операнда з маскою. Команди змінюють прапори нуля, Z, знака N і переповнення V. Приклади порозрядних логічних операцій, що ілюструють застосування механізму маскування бітів, приводяться в таблиці 1.3.
Таблиця 1.3 - Приклади порозрядних логічних операцій
Приклад порозрядного маскування or | Приклад порозрядного маскування and | Приклад порозрядного маскування eor | |||
Rd | хххххххх | Rd | хххххххх | Rd | |
Rs | Rs | Rs | |||
Rd=Rd or Rs | ххх1хх1х | Rd=Rd and Rs | х0хх0х0х | Rd=Rd eor Rs |
Команда порозрядного інвертування:
com R логічне заперечення; дія: R = 0b11111111 – R,
виконує зміну значень двійкових розрядів операнда (регістр загального призначення) на протилежні.
Приклад:
com R3 дія: R3 = 0b11111111 – R3.
Повний перелік арифметичних і логічних команд мікроконтролера AVR MEGA128 приводиться в Додатку 2.
Команди для роботи з бітами. Доповнюють сукупність логічних операцій команди скидання, установки й перевірки значень окремих бітів.
|
|
Команди скидання cbi P, n і установки sbi P, n бітів призначені для присвоювання значень 0 і 1 окремим бітам портів вводу/виводу. Першим операндом у цих командах є адреса порту вводу/виводу, другим - номер біта (від 0 до 7).
Приклади:
cbi 0x17, 5 дія: 0x175 = 0;
sbi 0x40, 1 дія: 0x401 = 1.
Команда логічного зсуву lsl R здійснює зсув вліво на одну позицію всіх битов операнда, а в молодший розряд додається нуль. Старший біт операнда надходить у прапор переносу С. У якості операнда можуть використовуватися тільки регістри загального призначення. Команда lsr R виконує зсув вправо на одну позицію всіх бітів операнда, а в старший розряд додається нуль. Молодший біт операнда надходить у прапор переносу С. Механізм роботи й синтаксис аналогічний команді lsl. Приклади використання команд логічного зсуву:
lsl R17 | виконати логічний зсув вліво всіх розрядів в R17; |
lsr R9 | виконати логічний зсув вправо всіх розрядів в R9. |
Поміняти місцями молодшу й старшу тетрады байта, завантаженого в регістр загального призначення, можна за допомогою команди swap R. Наступний фрагмент ілюструє дію команди swap:
ldi R19, 0b01001101 Завантажити константу 0b01001101 у регістр R19;
swap R19 У результаті виконання команди swap у регістрі R19 буде збережене значення 0b11010100.
Доповнюють перелік команд для роботи з бітами інструкції для скидання/установки значень прапорових розрядів у регістрі стану SREG, опис яких приводиться в Додатку 2.
Команди порівняння, умовного й безумовного переходу. Команда порівняння cp Rd, Rs – здійснює дію Rd–Rs і встановлює прапори нуля Z, негативного результату N, переповнення V, переносу C і додаткового переносу H. Результат не зберігається за адресою першого операнда, а тільки формуються прапори. Операндами можуть бути тільки регістри загального призначення.
Команди умовного переходу викликаються відразу після команд порівняння (або інших інструкцій, що викликають зміни бітів регістра стану SREG) і на основі аналізу прапорів здійснюють перехід по зазначеній адресі (мітці) у пам'яті команд.
Найпоширенішими серед команд цієї групи є:
|
|
breq M перехід на М, якщо дорівнює;
brne M перехід на М, якщо нерівно;
brlo M перехід на М, якщо менше;
brsh M перехід на М, якщо більше або дорівнює.
Приклад спільного використання команд порівняння й умовного переходу:
cp R1, R5 зрівняти значення в регістрах R1 і R5;
breq lbl1 виконати перехід на мітку lbl1, якщо значення в регістрах R1 і R5 рівні (R1–R5=0).
Команда rjmp М здійснює безумовний перехід по зазначеній 8-розрядній адресі (мітці, label) у пам'яті команд. Приклад:
rjmp lbl2 безумовний перехід на мітку lbl2.
Команда jmp М здійснює безумовний перехід по зазначеній 16-розрядній адресі (мітці, label) у пам'яті команд. Приклад:
rjmp lbl3 безумовний перехід на мітку lbl3.
Повний перелік команд порівняння й переходу приводиться в Додатку 2.
1.1.4. Синтаксис і основні оператори мови С.
Складні програмні проекти можна більш компактно описувати (у порівнянні з мовою Assembler) за допомогою мови програмування С, що має можливості мов низького й високого рівня, а так само великою бібліотекою функцій.
Алфавіт мови С складається з рядкових і заголовних букв латинського алфавіту, цифр (0 - 9) і спеціальних символів. Причому, при записі ідентифікаторів і ключових слів необхідно враховувати регістр символів. Так, ідентифікатори sysreg і Sysreg не є однаковими. Всі ключові слова повинні бути набрані малими літерами. Роздільником між операторами є символ; Закомментованні рядки починаються з розміщених підряд двох символів //.
Константи в мові З декларуються за допомогою директиви #define у відповідність із синтаксисом:
#define ім'я константи значення;
При роботі з апаратними засобами зручно записувати константи у двійковій і шістнадцятковій формах. Перед значенням констант ставляться символи 0b і 0x для двійкового й шістнадцяткового подань відповідно. Регістр символів при записі шістнадцяткових констант не має значення. Приклади оголошення констант:
#define k 25; // оголошена десяткова константа k=25;
#define KX 0x5; // оголошена шестнадцатеричная константа KX=F5h;
#define k2 0x6e; // оголошена шестнадцатеричная константа k2=6Eh;
#define KB 0b1010; // оголошена двійкова константа KB=0b1001;
Базовими цілими типами даних у мові С є: сhar (розмір 1 байт, діапазон значень – 128 ¸ 127) і int (розмір 2 байти, діапазон значень – 32768 ¸ 32767). Модифікатор unsigned, записывыемый перед ім'ям базового типу, дозволяє інтерпретувати значення наведених вище типів даних як числа без знака: unsigned сhar (розмір 1 байт, діапазон значень 0 ¸ 255), unsigned int (розмір 2 байти, діапазон значень 0 ¸ 65535). При цьому старший розряд є бітом даних, а не знаковим бітом числа.
Всі змінні повинні бути декларовані до їхнього використання в програмі. При записі імен змінних необхідно враховувати регістр символів. Формат оголошення змінної:
тип даних ім'я змінної [=початкове значення];
Якщо змінні мають однаковий тип даних, то при оголошенні їхні ідентифікатори можна перелічити через кому. Початкове значення змінної можна не вказувати. Приклади:
char A=10;
int B, C, D;
unsigned int E, F;
Розглянемо основні оператори мови С.
Оператор присвоювання має наступний синтаксис:
ідентифікатор = вираження;
У вираженнях над операндами можуть використовуватися наступні арифметичні й логічні операції мови С:
Арифметичні операції:
+ додавання;
– віднімання;
* множення,
/ ділення.
Логічні операції:
|| логічне АБО;
&& логічне І;
! логічне НЕ;
| побітова операція АБО;
& побітова операція І;
^ побітова операція виключаюче АБО;
~ побітова операція НЕ;
<< логічний зсув вліво;
>> логічний зсув вправо.
Мова С відноситься до строго типізованих мов програмування: змінним одного типу не можна безпосередньо привласнювати значення іншого типу даних. Для однозначного визначення пріоритету операцій у вираженнях необхідно використати круглі дужки (). Приклад оператора присвоювання:
A = (B+C)*D;
Оператор умови if/else дозволяє виконувати одне із двох дій залежно від умови. Синтаксис оператора:
if (умова) вираження1
[ else вираження2];
Умова являє собою вираження, задане за допомогою операцій відношення:
== дорівнює;
!= не дорівнює;
< менше;
<= менше або дорівнює;
> більше;
>= більше або дорівнює.
Якщо умова істинно, то виконується вираження1, якщо ложно – те виконується вираження2. Частина else може бути відсутня. Якщо, залежно від умови необхідно виконати фрагмент програми, що складається з декількох операторів, то їх необхідно помістити у фігурні дужки { }. Приклад використання оператора умови:
if (A==B) {C=D+E; I=N+5}
else {C=D–E; I=N–5;}
Оператор вибору switch/case дозволяє вибірково виконати фрагмент програмного коду, залежно від значення вираження. Формат оператора:
switch (цілочисельне вираження) {
case константа1: вираження1;
break;
case константа2: вираження2;
break;
...
case константа: выражение;
break;
[ default: дії за замовчуванням;] }
Оператор break повинен перебувати у всіх галузях, у противному випадку порушиться вибіркове виконання команд у галузях після case. Галузь default можна не вказувати. Приклад використання оператора вибору:
switch (num) {
case 0: A=B+C; C=D+2;
break;
case 1: A=B–C; C=D+10;
break;
case 5: A=B*C; C=D+15;
break; }
Оператор циклу з параметром for використовується в тих випадках, коли заздалегідь відома кількість ітерацій циклу. Синтаксис оператора циклу for наведений нижче:
for (ініціалізуюче вираження; умовне вираження; вираження, що модифікує)
{
оператори тіла циклу;
}
Розглянемо роботу циклу for: ініціалізуюче вираження при першому запуску циклу привласнює початкове значення лічильнику циклу; потім аналізується умовне вираження (цикл виконується поки умова істинна). Щораз після всіх рядків тіла циклу виконується вираження, що модифікує, у якому відбувається зміна лічильника циклу. Вихід із циклу відбудеться, як тільки умовне вираження одержить значення false. Приклад оператора циклу:
s=0;
m = 1;
for (i=1; i<=10; i++)
{ s=s+i;
m=m*i; }
Оператор циклу із передумовою while застосовується, коли число повторень невідомо, але необхідно виконати деяку умову. Формат оператора наведений нижче:
while (умовне вираження)
{
оператори тіла циклу;
}
Оператор починається із ключового слова while, за яким іде логічне вираження, що повертає значення false або true. Оператори, укладені у фігурних дужках, утворять тіло циклу. Приклад використання оператора циклу while:
a=0;
while (a<10)
{ b=(c+d)*a-f;
a=a+2; }
Підпрограми мовою С оформляються у вигляді функцій. Опис функції має наступний синтаксис:
[Тип повертаємого значення,] ім'я функції ([список параметрів])
{
[декларації локальних змінних]
оператори тіла функції;
[ return вираження;]
}
Якщо функція не повертає значення, то тип повертаємого значення, не вказується й секція return не використовується. При відсутності параметрів після імені функції обов'язково вказуються порожні круглі дужки (). Тіло функції заключається у фігурні дужки { }. Змінні, оголошені в тілі функції, є локальними (видимими тільки в межах тіла функції). Функції, що повертають значення, можна використати в правій частині операторів присвоювання.
Розглянемо приклад декларації функції, повертаюча, залежно від значення аргументу С, суму або різницю двох цілих чисел:
int sum (int A, int B, char C)
{
if (C>=0) return A+B;
else return A-B;
}
Приклад функції, що не має аргументів і не повертає значення:
init_data () {
A=10;
B=100+А; }
При програмуванні мікроконтролера часто виникає необхідність використати ассемблерний код у програмі, написаної мовою С. У цьому випадку фрагмент програми, складений з ассемблерних операторів, міститься в операторних дужках:
#asm
оператори мови асемблер
#endasm;
Приклад фрагмента програми мовою Assembler:
#asm
mov r1,r5
ldi r17,0xf5
#endasm;
Одиночний ассемблерний оператор у тілі С – программмы, може бути записаний у вигляді директиви #asm("Оператор мови Assembler”);.
Приклад:
#asm ("out 0x12,r16"); // виконати команду out 0x12,r16.
Функції, написані на асемблері, повертають значення через регістр R30 для типів char і unsigned char, і регістрову пару (R31, R30) для типів int і unsigned int (в R31 – старший, а в R30 – молодший байти). Параметри функції передаються через стек, на вершину якого вказує регістр Y, причому старший байт слова записується по старшій адресі. Механізми виклику й повернення з функції здійснюються засобами компілятора С. Директива #pragma warn- забороняє компіляторові генерувати попередження про те, що функція не повертає результат стандартним способом (за допомогою оператора return).
Як приклад розглянемо ассемблерну функцію, що виконує підсумовування двох цілих чисел і повертає значення типу int (пояснення до ассемблерного коду додаються):
int summa (int A, int B)
{
#asm
ldd R27, Y+3 завантажити старший байт параметра А;
ldd R26, Y+2 завантажити молодший байт параметра А;
ldd R25, Y+1 завантажити старший байт параметра В;
ld R24, Y; завантажити молодший байт параметра В;
add R24, R26 виконати підсумовування молодших байтів А и В;
adc R25, R27 виконати підсумовування старших байтів А и В
c обліком прапора переносу С;
mov R30, R24 записати молодший байт суми в регістр R30;
mov R31, R25 занести старший байт суми в регістр R31;
#endasm
}
Виклик даної функції може здійснюватися в такий спосіб:
...
int С;
C=summa (10, 15);
...
Часто в ассемблерних підпрограмах виникає необхідність одержати доступ до значень змінних, оголошених у С-програмі. Розміщення змінних (у регістрах процесора або пам'яті даних) можна визначити з файлу з розширенням *.map, ім'я якого збігається з ім'ям файлу вихідного коду програми. Даний файл генерується компілятором і перебуває в одному каталозі з вихідним модулем програми.
Розглянемо структуру програми на С. Текст вихідного модуля програми починається з директив препроцесора #include <ім'я файлу.h>, за допомогою яких у програмний код проекту довантажуються файли заголовків, що містять оголошення різних констант, ідентифікаторів і прототипи функцій. Приклад використання даної директиви, яка завантажує заголовний файл mega128.h:
#include <mega128.h>
Далі випливають оголошення констант (за допомогою директив #define) і глобальних змінних.
Потім записуються декларації функцій, використовуваних у тексті головної програми, розташованої в тілі функції main(), з операторів якої починається виконання програми. Тіло функції main() розташовано усередині фігурних дужок {}.