пʼятниця, 24 травня 2013 р.

Експеримент 02. Найпростіша реалізація часових затримок, умовні переходи в асемблері.

     У першому експерименті ми написали найпростішу програму, яка використовує порти вводу-виводу. Але фактично, програма нічого корисного не робить - такий самий результат можна було б отримати, просто з'єднавши кнопки зі світлодіодами. Тому треба спробувати поставити задачу поскладнішу - наші світлодіоди повинні блимати, а не просто світитися.
     А для цього потрібно їх вмикати і вимикати через певний проміжок часу. Отже, необхідно реалізувати таку річ, як часова затримка. У серйозних проектах для цього задіюють таймери, тоді можливо отримати точність затримки (що важливо, наприклад для годинника чи таймера та і для інших цілей). Але для простеньких програмок можна використати інакший спосіб.

Основи створення часових затримок

     Як ми знаємо, МК має певну тактову частоту, і більшість команд МК виконуються за 1 такт. Отже, якщо між включенням і виключенням світлодіода виконувати якісь інші дії, то це дасть ту саму затримку. Якщо робити там нема що, то можна використати команду асемблера nop

Команда NOP не виконує ніякої дії, але займає 1 такт процесорного часу.

     Правда є маленький нюанс, наприклад, у нас тактова частота МК становить 8 Мгц, тобто 8 міліонів тактів в секунду. Отже, щоб отримати затримку у півсекунди, треба вставити 4 млн операцій nop :-).
     Звичайно ж, це нереально (у МК всього 2кілобайти памяті, тобто більше 2000 команд туда аж ніяк не влізе). Але ж ми знаємо, що МК вміє виконувати команди циклічно! У нашій найпростішій програмі використовується циклічне опитування порта В і вивід значень у порт А наприклад. Єдине, що ми не знаємо, як вийти з циклу при досягненні певної кількості повторень!

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

Розглянемо код:

 ldi r17,250; Завантажимо в регістр r17 число 250 (це буде довжина нашої затримки)
 Delay:
 dec r17; Зменшуємо на 1 значення r17
 brne Delay; Якщо не 0 то переходимо до мітки Delay

Отже, у регістр R17 ми завантажили певне число за допомогою відомої нам команди  ldi   Фактично, це кількість повторень, які здійснить цикл.
Потім поставили мітку  Delay: до якої будемо повертатися в циклі
А потім і де команда  dec r17; Зменшуємо на 1 значення r17

Команда DEC [РРЗП] зменшує значення, що міститься в РРЗП на 1. Називаэться ДЕКРЕМЕНТ Працює з усіма РРЗП

Зразу скажу, що є і протилежна по своїй дії команда - 

Команда INC [РРЗП] збільшує значення, що міститься в РРЗП на 1. Називаэться ІНКРЕМЕНТ. Працює з усіма РРЗП

Ці команди досить часто використовуються у програмуванні МК

А далі ми бачимо команду  brne Delay; Якщо не 0 то переходимо до мітки Delay

Команда BRNE [Мітка] здійснює перехід до мітки, якщо флаг Z у регістрі SREG рівний 0.

Саме таке пояснення цієї команди в довідниках, але що за регістр SREG, що за флаги? Це потребує досконалого опису, так як це дуже важлива річ в роботі МК.

РЕГІСТР SREG 

Отже регістр SREG, це так званий статусний регістр МК. Він відображає стан АЛП (ядра) після виконання ним якоїсь дії. 
Біти цього регістра називають флагами! На малюнку показаний їх порядок

Тепер пояснимо їх значення:

0 біт (флаг С) - ФЛАГ ПЕРЕНОСУ (від англ. Carry). В початковому стані рівний 0, змінюється на 1 якщо при операції результат вийшов за межі байту (наприклад у нас в R22 було число 253 (0b11111111), і ми додали до нього число 4. В такому випадку R22 повинен стати 257, але це число не поміщається у 8 розрядів (байт) отже в R22 запишеться число 1 (257-256), а у флаг С регістра SREG встановиться 1 ). Правда э нюанс, операція INC не встановлює цей флаг, тобто при збільшенні регістру до 255 і наступній операціїї INC регістр просто обнулиться а флаг С не буде встановлений!!! Чому так - лишимо розбиратися розробникам ATMEL :-).

1 біт (флаг Z) - ФЛАГ НУЛЯ (від англ. Zero). В початковому стані рівний 0, Встановлюється в 1, якщо результатом операції є НУЛЬ. Якраз це і використовується у наведеному коді. Коли значення нашого регістра r17 при відніманні 1 командою dec r17 стане 0, встановиться флаг Z
2 біт (Флаг N) - ФЛАГ ВІД'ЄМНОГО ЗНАЧЕННЯ (від англ. Negative). В початковому стані рівний 0, Встановлюється в 1, якщо в результаті операції останній (7-ий) біт регістра-операнда стане рівним 1 (Чому так - це нюанси асемблерної математики з від'ємними числами і поки що не розглядається)

3 біт (Флаг V ) - ФЛАГ ПЕРЕПОВНЕННЯ ДОДАТКОВОГО КОДУ (від англ. oVerflow). 
 Це також використовується у математичних операціях і поки що не розглядається.

4 біт (Флаг S) - ФЛАГ ЗНАКУ (від англ. Sign). Встановлюється в 1 якщо результат операції менше 0.

5 біт (Флаг Н) - ФЛАГ ПОЛОВИННОГО ПЕРЕНОСУ (від англ. Half) - встановлюється в 1 коли здійснювався пернос з молодшої половини байта (3-ій біт) в старшу (4-ий біт), або займання з старшої в молодшу при деяких арифметичних операціях.

6 біт (Флаг Т) - КОПІЙОВАНИЙ БІТ. Використовується у операціях збереження або встановлення біта в регістрі як проміжне сховище. Можна використовувати в користувацьких цілях

7біт (Флаг І) - ФЛАГ ГЛОБАЛЬНОГО ДОЗВОЛУ ПЕРЕРИВАНЬ (від англ. Interrupt).  Переривання будемо розглядати пізніше, тоді і поясню значення цього флага.

     Розглянувши статусний регістр SREG повертаємось до наших  команд умовного переходу.

Продовжуємо про умовний перехід і цикли

     Як бачимо, команда BRNE фактично виконує перехід по мітці, якщо результат попередньої операції не був рівним 0 (тоді флаг Z рівний 0). У деяких довідниках пише що команда виконує "перехїд, якщо не рівно", що досить важко зрозуміти. Насправді, справа в тому, що є команди порівняння двох регістрів (СР), або регістра і константи (СPI)які фактично віднімають значення одного від значення другого. Логічно, що у випадку рівності значень результатом буде 0, а при нерівності - не буде 0. Але ж BRNE використовується не лише після команд порівняння, тому залишимо це на совісті авторів довідників.

     Є також команда, що здійснює перехід, якщо результат рівний 0:

Команда BREQ [Мітка] здійснює перехід до мітки, якщо флаг Z у регістрі SREG рівний 1.


     Отже, наш код циклічно буде виконуватися 250 раз, після чого значення r17 стане рівне 0. Тоді команда BRNE не спрацює і виконання перейде далі, тобто ми вийдемо з циклу.
ТУТ знаходиться АСМ файл. Створимо новий проект EXP2 в АТМЕЛ СТУДІО і скопіюємо код з файлу у нього. Проженемо у відладчику і побачимо, що цикл дійсно виконується (число зменьшене до 25 раз).

Відео (раджу дивитися в повноекранному перегляді, клацнувши на HD )

Реалізація багаточисельних циклів

Але нам потрібно не 250 циклів, а значно більше для організації нашої півсекундної затримки. А регістри у нас 8 розрядні, тобто більше 255 туда число не влізає. Що ж робити? А в програмуванні є такий прийом, як вкладені цикли!  Тобто ми ркутимо один цикл всередині другого, всередині якого є ще третій, наприклад! А отже ми можемо отримати вже не 255 а 255х255х255 = 16,5 млн циклів!!! Цього вже цілком достатньо. Для початку реалізуємо це саме так.
Визначимо, скільки ж циклів нам потрібно на затримку 0,5с. Команда декременту виконується за 1 такт, команда умовного переходу - за 2 такти, отже цикл виконується за 3 такти. Вставимо ще одну команду NOP перед декрементом, щоб виконання циклу зайняло 4 такти.  Тоді 1 цикл виконується за 4/8,000,000=0,0000005 с. отже, нам потрібно 0,5/0,0000005 = 1,000,000 циклів!
Отже простіше ділити 1000000 на 250, тобто внутрішній цикл має виконуватись 250 раз, тоді зовнішній цикл має виконатись 1 000 000/250 = 4000 раз. Знову не влазимо в байт - зробимо ще один цикл поверх них - тобто цикл 2 рівня теж пустимо 250 раз, тоді цикл третього рівня повинен відбутися 4000/250=16 раз


 ldi r17,250; Завантажимо в регістр r17 число 16
 Cycle3:
 ldi r18,250; Завантажимо в регістр r18 число 250
 Cycle2:
 ldi r19,250; Завантажимо в регістр r19 число 250
 Cycle1:
 nop ; Просто тратимо такт впусту
 dec r19; Зменшуємо на 1 значення r19
 brne Cycle1; Якщо не 0 то переходимо до мітки Cycle1


 dec r18; Зменшуємо на 1 значення r18
 brne Cycle2; Якщо не 0 то переходимо до мітки Cycle2

 dec r17; Зменшуємо на 1 значення r17
 brne Cycle3; Якщо не 0 то переходимо до мітки Cycle3

Оформимо для зрозумілості код у стилі мов програмування високого рівня



 ldi r17,250; Завантажимо в регістр r17 число 16
 ;Цикл 3 рівня
 Cycle3:
 ldi r18,250; Завантажимо в регістр r18 число 250
      ;Цикл 2 рівня 
      Cycle2:
      ldi r19,250; Завантажимо в регістр r19 число 250
           ; Внутрішній цикл
           Cycle1:
           nop; Просто тратимо такт впусту
           dec r19; Зменшуємо на 1 значення r19
           brne Cycle1; Якщо не 0 то переходимо до мітки Cycle1
           ; кінець внутрішнього циклу


      dec r18; Зменшуємо на 1 значення r18
      brne Cycle2; Якщо не 0 то переходимо до мітки Cycle2
      ;Кінець циклу 2 рівня

 dec r17; Зменшуємо на 1 значення r17
 brne Cycle3; Якщо не 0 то переходимо до мітки Cycle3
; Кінець циклу 3 рівня

Тобто ми бачимо, що у внутрішньому циклі декрементується регістр R19 від 250 до 0, коли значення стає рівним 0 то декрементується  R18, перевіряється умова чи не 0 і здійснюється перехід на завантаження в регістр R19 числа 250, після чого знову вступає в дію внутрішній цикл. Зовнішній цикл виконається 1 раз, коли R18 дойде до 0, тобто за цей час внутрішній цикл пройде 250х250 раз. А ще зовнішній має пройти 16 раз. Насправді така побудова не дасть точно 0,5с затримки, адже крім внутрішнього циклу, який займає рівно 4 такти, кожен перехід у циклах вищіх рівнів дасть свої пару тактів. В принципі, для нашої задачі це неважливо.

Програмуємо блимання світлодіода

     Тепер можемо написати код, який буде вмикати і вимикати перщий світлодіод на порті А. На словах це виглядатиме так - включили - затримка - виключили - затримка. Все це має обертатися у головному циклі програми.
    АСМ ФАЙЛ ПРОЕКТУ
Відео (раджу дивитися в повноекранному перегляді, клацнувши на HD):
Скомпілюємо і прошиємо МК.

Відео роботи нашої макетки з цією прошивкою:

   
     Таким чином, робити затримки ми навчилися, але сама програма побудована не зовсім оптимально, вона займає досить багато місця, тепер там купа міток, і це щоб просто поблимати діодиком!!! В наступній статті я розкажу про важливі прийоми, які дозволять скоротити розміри програми і зробити її більш зрозумілою для сприйняття!!! А також викладу довідкові дані по всім командам асемблера для МК.



Немає коментарів:

Дописати коментар