Ця стаття не входить в цикл експериментів, так як вона більше теоретична. В програмах для МК часто зустрічається задача, коли необхідно відраховувати декілька різних відрізків часу одночасно. Зазвичай для цього використовують апаратні таймери, але їх число обмежене (наприклад в ATTiny 26 є 2 таймера). З цієї ситуації є вихід, особливо коли немає потреби у високій точності відмірювання часу.
ПРОГРАМНИЙ ТАЙМЕР
Цей спосіб називається програмним таймером. Він дуже гарно стикується з архітектурою флагового автомата іможе бути реалізований як на основі переривань від апаратного таймера, так і на основі звичайної часової затримки. Що ж мається на увазі?
Припустимо, нам потрібно блимати 5-ма світлодіодами, але з такою особливістю:
1-ий засвічується і гасне з періодом 0,3с
2-ий засвічується і гасне з періодом 0,4с
3-ій засвічується і гасне з періодом 0,1с
4-ий засвічується і гасне з періодом 0,8с
5-ий засвічується і гасне з періодом 1,2с
Як бачимо, тут кожен світлодіод має різні періоди включення. І нам потрібно 5 таймерів щоб реалізувати таку роботу. Тут на допомогу приходить програмний таймер. Принцип його роботи полягає в тому що ми знайдемо спільний дільник для усіх необхідних нам періодів (в даному випадку це 0,1с, але ми використаємо значення 0,01с) і запустимо основний таймер з таким періодом. Поки ми незнайомі з перериваннями і апаратними таймерами, ми використаємо просто часову затримку у 0,01с в головному циклі програми.
Визначимо 5 змінних, що будуть відповідати кожному з "віртуальних" таймерів. Наприклад використаємо регістри r0...r4 надавши ім символічні імена tim0...tim4. Сама структура обробки програмного таймера виглядає таким чином:
Тобто спочатку ми перевіряємо змінну таймера на нульове значення, якщо 0 то він не використовується, якщо ненульове то зменшуємо значення на одиницю. Повторно перевіряємо змінну на 0, якщо 0 то таймер відрахував заданий проміжок часу і виставляється керуючий флаг у заданому нами регістрі керування флаговим автоматом. Послідовно перевіряються усі змінні "віртуальних" таймерів.
Сама ця конструкція повинна знаходитись або в обробнику переривання апаратного таймера, що налаштовується на період 0,01с, або у головному циклі після виконання затримки, можна також встроїти його в сам флаговий автомат, де флаг на запуск цієї структури виставляється у обробнику переривання апаратного таймера. Суть в тому, що кожні 0,01с ми зменшуємо кожну з наших змінних на 1, а при досягеннні якоюсь із них нульового значення висьавляється відповідний керуючий флаг. Отже флаг буде виставлений через проміжок часу timn(початкове)*0.01. Звичайно, що інтервал у 0,01с вибраний нами для конкретного випадку, він може бути довільним.
Головний цикл згідно концепції "флагового автомата" виглядатиме таким чином:
1. Надаємо початкові значення таймерам, що відповідають timex/(2*0.01). двыйка в знаменнику з'явилася тому, що ми запускаємо таймери на половину періоду, адже ми будемо виконувати блимання за допомогою кінцевих автоматів, звертаючись до них раз на півперіода - половину періода світлодіод світиться, половину не світиться.
Отже tim0=15; tim1=20; tim2=5; tim3=40; tim4=60.
2. Ініціалізуємо флаговий регістр, надаючи йому нульове значення.
3. Запуск структури флагового автомата:
3.1 перевіряємо флаг flagtim0 - якщо одиниця, виконуємо підпрограму-кінцевий автомат блимання 1-го світлодіода, обнулюємо flagtim0, повторно задаємо tim0=15
3.2 перевіряємо флаг flagtim1 - якщо одиниця, виконуємо підпрограму-кінцевий автомат блимання 2-го світлодіода, обнулюємо flagtim1, повторно задаємо tim1=20
3.3 перевіряємо флаг flagtim2 - якщо одиниця, виконуємо підпрограму-кінцевий автомат блимання 3-го світлодіода, обнулюємо flagtim2, повторно задаємо tim2=5
3.4 перевіряємо флаг flagtim3 - якщо одиниця, виконуємо підпрограму-кінцевий автомат блимання 4-го світлодіода, обнулюємо flagtim3, повторно задаємо tim3=40
3.5 перевіряємо флаг flagtim4 - якщо одиниця, виконуємо підпрограму-кінцевий автомат блимання 5-го світлодіода, обнулюємо flagtim4, повторно задаємо tim4=60
4. Виконуємо підпрограму затримки на 0,01с
5. Виконуємо структуру програмного таймера
6. Зациклюємось, переходячи до п.3
Як ми бачимо, під час проходів циклу перевіряються флаги, що виставляються програмним таймером. Якщо таймер спрацював (відрахував свій проміжок часу), то флаг виставляється, і відповідний світлодіод засвічується або гасне. при цьому ми обнулюємо флаг (щоб у наступному циклі не виконувати цей рядок) і заново задаємо значення змінній таймера, тим самим запускаючи його на повторне виконання.
Теоретично, кількість програмних таймерів обмежена тільки розміром оперативної пам'яті МК, Крім того, сама програма може змінювати налаштування таймера, наприклад при спрацюванні його один раз, у флаговому автоматі цілком можливо виставити інше початкове значення для наступного виконання. Таким чином можна отримувати, наприклад, сигнали складної форми.
Тепер реалізуємо цю програму на асемблері.
Скачати АСМ файл.
Відео (HD дивитись на повний екран):
Демонстрація відладки (HD дивитись на повний екран):
;****** Ця макрооперація дозволяє завантажити константу в "молодші" РРЗП *****
;*******************************
.macro ldiall
ldi temp,@1
mov @0,temp
.endmacro
;********************************
Вони служать для спрощення вигляду програми, реально вони не дають переваги у розмірі коду, кожен виклик макрооперації у програмі при компіляції замінюється на даний блок. Макрооперація може не мати аргументів, або їй можуть передаватися аргументи у вигляді імен регістрів, констант або виразів, імен міток. В тексті самого макроса вони позначаються @0 ,@1 і так далі. Тобто @0 - це аргумент, який вказується першим при виклику макроса ,@1 - другий і так далі.
Сам виклик макроса здійснюється так, ніби це звичайна команда асемблера
Наприклад -
ldiall tim0,settim0; (тут аргумент tim0 - це @0, а settim0 - @1)
При компіляції цей рядок буде замінений на:
ldi temp,settim0
mov tim0,temp
Макрооперації часто використовують, щоб спростити читання програми, коли в ній є багато подібних наборів рядків, що відрізняються лише даними, що задіяні в командах. Наприклад, можна було б замінити на макрооперацію блок обробки кожного з програмних таймерів:
tst tim0; Перевіряємо регістр tim0 на нульове значення
breq outtim1; Якщо 0 то переходимо до наступного таймера
dec tim0; Зменшуємо tim0 на 1
brne outtim1; Якщо не стало 0 то переходимо до наступного таймера
ori timflag,0b00000001; Якщо ж стало 0 то виставляємо флаг у нульовому біті timflag
Таким чином:
.macro ptimcount
tst @0; Перевіряємо регістр таймера на нульове значення
breq @2; Якщо 0 то переходимо до наступного таймера
dec @0; Зменшуємо регістр таймера на 1
brne @2; Якщо не стало 0 то переходимо до наступного таймера
ori timflag,@1; Якщо ж стало 0 то виставляємо флаг у відповідному біті timflag
.endmacro
Виклик макроса виглядатиме так:
ptimcount tim0,0b00000001,outtim1
outtim1:
А вся структура програмних таймерів матиме вигляд:
ptimcount tim0,0b00000001,outtim1 ; Виконуємо для першого програмного таймера
outtim1:
ptimcount tim1,0b00000010,outtim2 ; Виконуємо для другого програмного таймера
outtim2:
ptimcount tim2,0b00000100,outtim3 ; Виконуємо для 3-го програмного таймера
outtim3:
ptimcount tim3,0b00001000,outtim4 ; Виконуємо для 4-гоо програмного таймера
outtim4:
ptimcount tim4,0b00010000,outtim5 ; Виконуємо для 5-го програмного таймера
outtim5:
Програма такого вигляду знаходиться тут
Тепер можна створити проект і виконати компіляцію. Прошиємо контролер і подивимося на результат виконання.
Використання програмного таймера має дуже широке коло застосувань. Наприклад, його можна використати для програмного уникнення такого явища як "дрижання" контактів кнопок. Це явище, коли після натискання кнопки, контакт не замикається зразу, а деякий час здійснює коливальні рухи викликаючи багаторазові включення-виключення. Швидкодія МК така, що він легко може сприйняти це за багатократне натискання кнопки. Тому зазвичай після отримання сигналу про натискання кнопки припиняють зчитування сигналу з неї на деякий час (зазвичай, "дрижання" триває 0,1-0,2с). Якщо є потреба обробляти сигнали з декількох кнопок незалежно то можна ввести декілька програмних таймерів (для кожної кнопки свій), кожен з яких запускатиметься при натисканні відповідної кнопки з одночасним онуленням флагу, що дозволяє опитування кнопки. після відпрацювання таймера флаг встановлюватиметься знову.
От така от корисна штука...
Скачати АСМ файл.
Відео (HD дивитись на повний екран):
Демонстрація відладки (HD дивитись на повний екран):
МАКРООПЕРАЦІЇ АСЕМБЛЕРА
Розглядаючи програму, натикаємось на такий блок:;****** Ця макрооперація дозволяє завантажити константу в "молодші" РРЗП *****
;*******************************
.macro ldiall
ldi temp,@1
mov @0,temp
.endmacro
;********************************
Такі блоки, що починаються з директиви препроцесора .macro [ім'я макроса] і закінчуються директивою .endmacro називають макроопераціями або макросами.
УВАГА! МАКРОС ПОВИНЕН РОЗМІЩУВАТИСЬ У ТЕКСТІ ПРОГРАМИ ДО ПЕРШОЇ КОМАНДИ ЙОГО ВИКЛИКУ! БАЖАНО РОЗМІЩУВАТИ МАКРОСИ НА ПОЧАТКУ ПРОГРАМИ ДО МІТКИ Reset!!!
Сам виклик макроса здійснюється так, ніби це звичайна команда асемблера
Наприклад -
ldiall tim0,settim0; (тут аргумент tim0 - це @0, а settim0 - @1)
При компіляції цей рядок буде замінений на:
ldi temp,settim0
mov tim0,temp
Макрооперації часто використовують, щоб спростити читання програми, коли в ній є багато подібних наборів рядків, що відрізняються лише даними, що задіяні в командах. Наприклад, можна було б замінити на макрооперацію блок обробки кожного з програмних таймерів:
tst tim0; Перевіряємо регістр tim0 на нульове значення
breq outtim1; Якщо 0 то переходимо до наступного таймера
dec tim0; Зменшуємо tim0 на 1
brne outtim1; Якщо не стало 0 то переходимо до наступного таймера
ori timflag,0b00000001; Якщо ж стало 0 то виставляємо флаг у нульовому біті timflag
Таким чином:
.macro ptimcount
tst @0; Перевіряємо регістр таймера на нульове значення
breq @2; Якщо 0 то переходимо до наступного таймера
dec @0; Зменшуємо регістр таймера на 1
brne @2; Якщо не стало 0 то переходимо до наступного таймера
ori timflag,@1; Якщо ж стало 0 то виставляємо флаг у відповідному біті timflag
.endmacro
Виклик макроса виглядатиме так:
ptimcount tim0,0b00000001,outtim1
outtim1:
А вся структура програмних таймерів матиме вигляд:
ptimcount tim0,0b00000001,outtim1 ; Виконуємо для першого програмного таймера
outtim1:
ptimcount tim1,0b00000010,outtim2 ; Виконуємо для другого програмного таймера
outtim2:
ptimcount tim2,0b00000100,outtim3 ; Виконуємо для 3-го програмного таймера
outtim3:
ptimcount tim3,0b00001000,outtim4 ; Виконуємо для 4-гоо програмного таймера
outtim4:
ptimcount tim4,0b00010000,outtim5 ; Виконуємо для 5-го програмного таймера
outtim5:
Програма такого вигляду знаходиться тут
Тепер можна створити проект і виконати компіляцію. Прошиємо контролер і подивимося на результат виконання.
Використання програмного таймера має дуже широке коло застосувань. Наприклад, його можна використати для програмного уникнення такого явища як "дрижання" контактів кнопок. Це явище, коли після натискання кнопки, контакт не замикається зразу, а деякий час здійснює коливальні рухи викликаючи багаторазові включення-виключення. Швидкодія МК така, що він легко може сприйняти це за багатократне натискання кнопки. Тому зазвичай після отримання сигналу про натискання кнопки припиняють зчитування сигналу з неї на деякий час (зазвичай, "дрижання" триває 0,1-0,2с). Якщо є потреба обробляти сигнали з декількох кнопок незалежно то можна ввести декілька програмних таймерів (для кожної кнопки свій), кожен з яких запускатиметься при натисканні відповідної кнопки з одночасним онуленням флагу, що дозволяє опитування кнопки. після відпрацювання таймера флаг встановлюватиметься знову.
От така от корисна штука...
Немає коментарів:
Дописати коментар