Асемблер AVR для початківців (третій крок) - Мої статті - Каталог статей - Персональний сайт
Отже, мої сумлінні читачі, сподіваюся, ви благополучно освоїли викладений раніше матеріал і впоралися із запропонованими мною завданнями. Настав час збагатити себе новими знаннями.
Минулого разу я розповів, що всі дії, які виконує контролер, визначаються так званими регістрами, і навіть розповів про три з них: PORTB, DDRB, PINB.
Насправді цих регістрів набагато більше. Крім того, існує два їх основні різновиди. Розглянуті раніше відносяться до так званих регістрів введення-виведення (РВВ). Цей тип регістрів визначає функціонування різних вузлів та модулів контролера: портів вводу/виводу, таймерів, переривань, енергонезалежної пам'яті, АЦП, компаратора, цифрових інтерфейсів та ін. Для кожного з цих модулів визначено по одному, а частіше за кілька РВВ. У міру викладу матеріалу я розповідатиму про них докладніше. Сьогодні нам знадобляться тільки вже відомі нам минулого разу РВВ.
Крім РВВ, кількість і склад яких відрізняється у різних мікроконтролерів, є ще звані регістри загального призначення (РОН). Усі мікроконтролери AVR мають по 32 РОН, які називаються дуже просто: r0, r1, r2, . r31. Забігаючи вперед, скажу, що не всі ці регістри рівнозначні, але більша частина команд не має різниці, які з них задіяти. Слід усвідомити і запам'ятати, більшість операцій у контролері виконується саме над РОН. Фактично, розглянуті нами минулого разу команди становлять більшу частину доступних команд для роботи безпосередньо з РВВ. Всі РОН є однобайтовими регістрами, до яких в асемблері можна звертатися, вказуючи їх ім'я.
Але це все слова. Перейдемо тепер до практики. Напишемо програму, яка самостійно блиматиме світлодіодом LED2 зпевною частотою. Створюємо нову папку з ім'ям step3 та змінюємо файл build.bat для асемблювання з цієї папки. Відкриваємо ASM_Ed і пишемо в ньому наступний текст:
.include "F:\Prog\AVR\asm\Appnotes\tn13def.inc" sbi DDRB, 4 ;Лінія РВ4 - вихід main: ;Основний цикл програми sbic PINB, 4; Якщо РВ4=0 (світлодіод запалений), пропустити слід. рядок cbi PORTB,4 ;Установка РВ4 в 0 (включення світлодіода) sbis PINB, 4 ;Якщо РВ4=1 (світлодіод погашений), то пропустити слід. рядок sbi PORTB,4 ;Установка РВ4 в 1 (вимикання світлодіода) ldi r16, 255 ;Завантаження значення в регістр r16 ldi r17, 255 ;Завантаження значення в регістр r17 delay: ;Цикл затримки subi r16, 1 ;Віднімання 1 з регістра r16 sbci r17, 0 ;Віднімання з переносом з регістра r17 brcc delay ;Якщо не було перенесення повернутися до мітки delay rjmp main ; мітці main
Ця програма безперечно вже більша за попередню. Уважний читач може побачити в ній як знайомі раніше команди, так і нові. Спочатку за традицією розповім про нововведення у програмі, а потім про алгоритм роботи програми.
Тепер про нові команди.
Команда ldi має два операнда: перший - РОН, а другий - будь-яка чисельна константа в діапазоні від 0 до 255 (оскільки всі РОН однобайтові, то в них можна записувати лише числа у вказаному діапазоні, при спробі записати більше, транслятор видасть попередження). В результаті виконання цієї команди у вказаний РОН завантажується вказана константа. Тобто, якщо провести аналогію з мовами високого рівня, ця команда подібна до операції присвоєння (РОН = k).
Команда subi за синтаксисом аналогічна вищенаведеній. В результаті її виконання із зазначеного РОН віднімається зазначена константа, а результат заноситься до того жрегістр (РОН = РОН - k).
Команда brcc належить до команд умовного переходу. Єдиним її аргументом є мітка, на яку переміщається програмний лічильник, якщо в результаті виконання попередньої операції не відбулося перенесення, тобто якщо був скинутий біт С статусного регістру. Якщо ж перенесення відбулося, виконується наступна за нею команда (Якщо С=0, то перейти до мітки, інакше перейти до наступної команди).
Команда rjmp є командою безперечного переходу. Її єдиним аргументом також є мітка, на який переміщується програмний лічильник, проте переміщення виконується завжди без будь-яких умов. Таким чином, наступна за rjmp команда ніколи не виконається, якщо там не буде встановлено іншу мітку, на яку буде здійснено перехід за допомогою якоїсь іншої команди переходу.
Ну що ж, наш багаж знань збагатився ще на 5 команд. Тепер у нашому розпорядженні їх 9 штук.
Розглянемо алгоритм роботи програми. Ті рядки, які містять вже відомі нам команди, я описуватиму коротко, вважаючи, що читач з ними знайомий.
1 рядок. Підключення файлу tn13def.inc до нашого асемблерного файлу.
2 рядок. Запис "1" у 4-й біт регістра DDRB для перекладу лінії РВ4 на вихід
3 рядок. Мітка "main". В принципі, цю мітку можна було б і не ставити, але її використання є правилом гарного тону, тому що немає потреби щоразу виконувати ініціалізацію портів введення-виведення. Поки що у нас вона складається всього з однієї команди - це не так страшно, але ж у реальних програмах це десятки команд, і деякі з них скидають модулі, що використовуються, до вихідного стану. А навіщо нам воно потрібне? Тож візьміть собі за правило. Спочатку – ініціалізація, потім – зациклювання програми за межамиподілу ініціалізації.
4 та 5 рядки. Якщо біт 4 у регістрі PINB скинутий (тобто світлодіод погашений, команда sbic), то переходимо до рядка 6, інакше скидаємо цей біт у регістрі PORTB (команда cbi).
6 та 7 рядки. Якщо біт 4 у регістрі PINB встановлений (тобто світлодіод запалений, команда sbis), то переходимо до рядка 8, інакше встановлюємо цей біт у регістрі PORTB (команда sbi).
Таким чином, рядки 4 - 7 виконують самостійне перемикання світлодіода (перевіряється стан світлодіода та змінюється на протилежне). Якщо так і залишити програму, то виявиться, що це перемикання відбуватиметься з дуже високою швидкістю, і його око не побачить, хіба що помітить, що яскравість світлодіода стала меншою. Щоб змусити світлодіод блимати із прийнятною частотою, необхідно здійснити затримку виконання програми. Зазвичай це робиться шляхом багаторазового віднімання чи складання. Оскільки кожна операція потребує певного часу, виконання однотипних команд кілька тисяч разів поспіль якраз і забезпечить нам необхідну затримку. Розгляньмо тепер, як це реалізовано у нас.
Рядок 8. За допомогою команди ldi в РОН r16 завантажується максимально можливе число 255. Чому саме r16, а не r0? Справа в тому, що команди, які мають другим операндом константу, не працюють із регістрами r0-r15. При спробі вказати як перший операнда такий регістр транслятор видасть помилку і відмовиться працювати далі.
Рядок 9. Аналогічна рядку 8, тільки тепер завантажується 255 у регістр r17. Від значень, завантажених у ці регістри, залежатиме наша затримка.
Рядок 10. Мітка "delay". Це початок циклу віднімання. Ми завантажили у два регістри числа 255. Тепер наше завдання віднімати з них одиниці, поки обидва не стануть рівними 0. Коли цестанеться, ми зможемо залишити цей цикл.
Рядок 11. Командою subi ми віднімаємо одиницю з регістру r16. Віднімання проводиться просто, без будь-яких умов та переносів.
Рядок 12. На перший погляд вона здається безглуздою. Ми віднімаємо з регістра r17 нуль. Однак, не варто забувати, що команда sbci віднімає число з урахуванням перенесення. Як це все працює у комплексі, я розповім далі.
Рядок 13. Перевірка біта перенесення С. Оскільки ця команда йде слідом за командою віднімання з регістру r17, перевіряється перенесення саме з цього регістру. Поки вміст регістру r17 буде більшим або дорівнює 0, команда brcc буде відправляти нас до мітки delay. Як тільки спроба віднімання з нуля в r17, ми відразу ж переходимо до рядка 14.
Тепер розглянемо роботу рядків 10-13 у комплексі. Спочатку ми віднімаємо 1 з регістру r16. У наступному рядку віднімаємо 0 з урахуванням перенесення з регістру r17. Поки вміст регістру r16 більший або дорівнює 0, вміст регістру r17 не зміниться. При відніманні одиниці з нуля в регістрі r16 відбуваються такі події: в самому r16 встановлюється значення 255, крім того, встановлюється прапор перенесення С. Тепер при виконанні команди sbci з r17 віднімається 1. Таким чином, віднімання одиниці з r17 відбувається тільки тоді, коли в регістрі r16 відбувається спроба відняти одиницю з нуля. Коли ж у регістрі r17 відбудеться віднімання одиниці з нуля, також встановиться прапор переносу, який, будучи відловленим командою brcc, завершить цикл.
За рахунок такої нехитрої конструкції виходить, що весь цикл виконуватиметься 256 х 256 = 65536 разів. Якщо врахувати, що кожна команда виконується один такт контролера, і на перехід до початку циклу витрачається ще один такт, то кожен прохід циклу виконується за чотири такти. Такимчином маємо затримку 4 х 65536 = 262144 тактів. Якщо врахувати, що частота контролера за умовчанням становить 1 МГц, то затримка дорівнюватиме 262144/1000000 = 0,26 с. Таким чином, світлодіод змінюватиме свій стан кожну чверть секунди, що дасть нам частоту миготіння приблизно рівну 2 Гц.
Ну і нарешті рядок 14, про який ми вже забули. У ній відбувається безумовний перехід до мітки main, у такий спосіб ми виключаємо повторну ініціалізацію виведення РВ4.
Ось таким чином працює написана програма. Зауважу, що задаючи різні значення, що записуються в регістри r16 - r17, можна отримувати різні затримки, менші 0,26, використовуючи вищевикладені розрахунки. А як же бути, якщо потрібно зробити більшу затримку? Тоді потрібно додати ще один регістр, наприклад r18. І реалізація затримки виглядатиме так:
ldi r16, 255 ;Завантаження значення в регістр r16 ldi r17, 255 ;Завантаження значення в регістр r17 ldi r18, 2 ;Завантаження значення в регістр r18delay: ;Цикл затримки subi r16, 1 ;Віднімання 1 з регістра r16 sbci r17, 0 ;Віднімання з переносом з регістра r17 sbci r18, 0 ;Віднімання з переносом з регістра r18<4brcc delay ;Якщо не було перенесення повернутися до мітки delay
Загальна кількість проходів циклу тепер дорівнюватиме 256х256х3 = 196608, а з урахуванням того, що в циклі з'явилася ще одна команда, тепер він виконуватиметься за 5 тактів. Загальна кількість тактів затримки становитиме 196608х5=983040, а період затримки: 983040/1000000 = 0,98 з.
Можливо, уважний читач запитає, а чому ж ми завантажуємо 255 і 2, а множимо 256 і 3. Тут вся справа в тому, що рахунок введеться не з 1, як ми звикли, а з 0, і за рахунок цього відбувається один зайвий прохід циклу. Отак просто це все пояснюється.
Сподіваюся,всі мої міркування та пояснення зрозумілі читачеві. На цьому я вважаю, що крок 3 можна вважати зробленим. Ми дізналися нові команди, ознайомились із різними видами регістрів контролера.
Як самостійно роботи пропоную наступне завдання: організувати почергове перемикання світлодіодів LED1 та LED2 із частотою, максимально близькою до 0,5 Гц, тобто з періодом у 2 с.