У попередніх програмах у нас використовувалась підпрограма затримки на основі вкладених циклів. Таку підпрограму добре використовувати, коли затримка в нас одна, тоді, підбираючи кількості циклів можна досягти потрібної величини затримки. Проте, нехай нам потрібно ввести декілька різних затримок, причому з доволі високою точністю відрахунку інтервалів часу.
Спроба написати таку програму на основі вкладених циклів мені не вдалася, адже я поставив собі завдання написати підпрограму, в яку величина затримки передається, скажімо в мілісекундах. Тут стало на заваді те, що в регістр влазить число лишень до 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)-
Отже така підпрограма може бути використана у будь яких випадках що потребують часових затримок без використання переривань.
Немає коментарів:
Дописати коментар