понеділок, 1 липня 2013 р.

Підпрограма затримок на основі багатобайтної арифметики. Збереження даних у стек.


     

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


     Спроба написати таку програму на основі вкладених циклів мені не вдалася, адже я поставив собі завдання написати підпрограму, в яку величина затримки передається, скажімо в мілісекундах. Тут стало на заваді те, що в регістр влазить число лишень до 255, якщо число більше, треба використовувати два або й більше регістрів, і тут у вкладених циклах почали вилазити усякі невиліковні баги. Наприклад, в одній з реалізацій усе нормально працювало з числами до 255 включно і більше 257. При заданні затримки у 256 мкс програма видавала усі 511 :-). А позбавлення від цього вимагало додаткових перевірок на 0, що сильно погіршувало точність відрахунків. Але я шукав способи далі, і знайшов таки!
    А суть в тому, що ми можемо обійтися без вкладених циклів!!! Просто задати величину затримки і розмістити її в декількох регістрах (в моїй підпрограмі я використав 4 регістра, що дозволяє отримати затримку у 4294с (2^(8*4)). Тут приходять на допомогу функції
препроцесора асемблера:

LOW ( [константа] ) -  повертає молодший байт константи
BYTE2( [константа] ), або HIGH( [константа] ) - повертає другий байт константи
BYTE3( [константа] ) - повертає третій байт константи
BYTE4( [константа] ) - повертає четвертий байт константи

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

     Тепер, виявляється, можна використати арифметичні операції з багатобайтними числами. Ми будемо проводити декрементування 4-ох байтного числа, що розміщене в 4-ох регістрах (я використав r16,r17,r18,r19)/ Єдине, що, команда DEC тут не підходить, бо вона не виставляє флаг переносу/займу С при переході через 0 (DEC 0 = 255 і все).

Декрементування 4-ох байтного числа на асемблері виглядає таким чином:

 subi r16,1; віднімаємо одиницю від молодшого байта числа (при відніманні від нуля буде                        виставлений флаг С)
 sbci r17,0; віднімаємо від 2-го байта числа 0 і значення флага С (віднімання з врахуванням переносу)
 sbci r18,0; віднімаємо від 3-го байта числа 0 і значення флага С (віднімання з врахуванням переносу)
 sbci r19,0віднімаємо від 4-го байта числа 0 і значення флага С (віднімання з врахуванням переносу)

як це працює:
Припустимо, наше число рівне 261. Тоді молодший байт буде рівний 5, другий - 1, третій і 4 -0
при першому декременті  subi r16,1 ми отримаємо 4 в першому байті, флаг С = 0, тому фактично решта байтів не зміняться. Але при 6-му декрементуванні ми отримаємо в першому байті буде встановлено число 255, але виставляється флаг зайому/переносу С=1. Тоді операція  sbci r17,0 відніме від другого байта (1) число 0 і значення С, тобто результатом буде  r17=0
Флаг С стане рівним 0 і решта байтів не зміняться. Потім можна провести ще 255 декрементувань, які змінюватимуть лише перший байт. Але при наступному декрементуванні вийде, що в першому байті знову 255 і С=1, друга операція -  sbci r17,0 дасть 0-1, тобто  r17=255 і С=1,
третя операція sbci r18,0 дасть аналогічний результат(r18=0-1) -  r18=255 і С=1, і то саме в 4-ій операції sbci r19,0;
     Отже, встановлення флагу С=1 після останньої операції свідчить про те, що наше число зменьшилося до 0 і навіть стало на 1 менше (аналог -1). Тобто, флаг С=1 після останньої операції виникне після 6+255+1=262 декрементувань. Отже дана умова виникає після кількості циклів декрементування, на 1 більшої від заданого числа!

     Відео (повний екран, HD):
     Залишається зациклити декрементування через перевірку флага С операцією BRSH (перехід, якщо флаг С=0).

Сама підпрограма затримки виглядатиме таким чином:
 Delay:
 nop
 nop
 subi r16,1;  віднімаємо одиницю від молодшого байта числа (при відніманні від нуля буде виставлений флаг С)
 sbci r17,0;  віднімаємо від 2-го байта числа 0 і значення флага С (віднімання з врахуванням переносу)
 sbci r18,0;  віднімаємо від 2-го байта числа 0 і значення флага С (віднімання з врахуванням переносу)
 sbci r19,0;  віднімаємо від 2-го байта числа 0 і значення флага С (віднімання з врахуванням переносу)
 brsh Delay;  Якщо С=0 то переходимо до мітки Delay (зациклюємось)
 nop
 nop
 nop
 nop
 nop
 nop
 ret;  повернення з підпрограми

Пусті операції  nop в даному випадку потрібні для отримання величини одного циклу рівно 1мкс (при частоті 8 Мгц), а також для отримання загального часу виконання затримки, кратною 1 мкс, зараз поясню чому.
     Справа в тому, що я вирішив зробити не тільки підпрограму затримки, а ще й макрос для її виклику у вигляді delaymks [число].
Тобто в програмі будуть виконуватись ще операції запису в регістри r16,r17,r18,r19 чисел що відповідають байтам величини затримки. Також, так як тут використовуються аж 4 регістри, слід трошки зекономити пам'ять

В даній програмі я покажу, як можна обійти обмежену кількість регістрів загального призначення. Нехай у нас в програмі регістри  r16,r17,r18,r19 використовуються ще десь, крім підпрограми затримки. Тоді, при виклику підпрограми необхідно зберегти їх попередні значення в ОЗП, а при виході з підпрограми - повернути значення з ОЗП в регістри. Найпростіше це організувати за допомогою збереження у стек. 

Для цього використовуються команди:

PUSH [РРЗП] - записує значення РРЗП у стек
POP [РРЗП] - "витягує" останнє значення зі стеку і записує його в РРЗП.

Так як у стеку останнє записане значення витягується першим, головне витягувати значення у зворотньому порядку. І основне - кількість операцій PUSH повинна відповідати кількості POP - інакше вас чекає зрив стеку з усіма витікаючими наслідками у вигляді повної непрацездатності програми (якщо вона має переходи до підпрограм чи переривання)!

     Відео програми без макроса (повний екран, HD):


Макрос виклику підпрограми таким чином буде мати вигляд:
.macro delaymks
 .set realdel=@0-5; кількість циклів для отримання заданої затримки
 push r16;  зберігаємо r16 в стек
 push r17;  зберігаємо r17 в стек
 push r18;  зберігаємо r18 в стек
 push r19;  зберігаємо r19 в стек
 ldi r16,low(realdel);     записуємо в r16 молодший байт числа циклів realdel
 ldi r17,byte2(realdel);  записуємо в r17 2-ий байт числа циклів realdel
 ldi r18,byte3(realdel);  записуємо в r18 3-ій байт числа циклів realdel
 ldi r19,byte4(realdel);  записуємо в r19 4-ий байт числа циклів realdel
 rcall Delay;                викликаємо підпрограму Delay
 pop r19;                    "витягуємо" зі стеку попереднє значення r19
 pop r18;                    "витягуємо" зі стеку попереднє значення r18
 pop r17;                    "витягуємо" зі стеку попереднє значення r17
 pop r16;                    "витягуємо" зі стеку попереднє значення r16
 .endmacro

Тут усе має бути зрозуміло, крім рядка -
 .set realdel=@0-5; кількість циклів для отримання заданої затримки

Получається, що реальна кількість циклів на 5 менша ніж величина затримки. Чому так? Величина уиклу підігнана за допомогою nop до 1 мкс. Але по перше - кількість циклів, що виконаються завжди на 1 більше ніж задане число (описано вище). Крім того, команди PUSH і POP виконуються за 2 такти,  Тобто сам процес збереження і відновлення 4-ох регістрів становитиме 2 мкс при частоті в 8 мгц. це ще мінус 2 цикла. Ще є 4 команди ldi(по одному такту) rcall(3 такти) і ret(4 такти). Отже тут набігає ще 1,375 мкс. До цілого числа у 2 мкс це доводиться командами nop перед  ret.
     Отже, получається, що ми отримаємо мінімальне значення затримки, рівне 5 мкс. Для менших затримок можна зробити простеньку підпрограму на однобайтному числі циклів!!!
Це, в принципі, єдиний недолік підпрограми, зате вона рахує величину любої затримки, більшої від 5мкс з великою точністю.

НЕ ЗАБУВАЄМО, ЩО МАКРОС ПОТРІБНО ОПИСАТИ ДО ЙОГО ПЕРШОГО ВИКЛИКУ В ПРОГРАМІ!!!

Звертання до підпрограми виконується просто - delaymks 1450 (де 1450 - величина затримки у мкс). Директиви препроцесора дозволяють також спростити звертання для затримок в мс або в секундах. Просто на початку програми оголосимо:

.equ s=1000000
.equ ms=1000

тоді виклик затримки у 454 мс виглядатиме так:
delaymks 454*ms

а у 12 с так:
delaymks 12*s

     Відео відладки програми і демонстрації точності  (дивитися в повний екран, HD)-




     Отже така підпрограма може бути використана у будь яких випадках що потребують часових затримок без використання переривань.   

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

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