Програмування таймера avr. Програмні таймери на асемблері





Таймери лічильники мікроконтролерів AVR (годинник реального часу). Урок AVR 7

Коли я ще починав вивчати мікроконтролери, мені захотілося зробити, щоб. Чесно зізнаюся, я хотів спробувати вмикати телевізор лише з 7 до 8 годин, а решту часу він повинен був бути відключений. Пристрій я зробив, але так його і не застосував.

У всіх мікроконтролерах AVR є кілька інтегрованих таймерів. Їх ще можна поділити на таймери загального призначення та сторожовий таймер, який призначений для перезавантаження МК при зависанні.

Таймери загального призначення можуть:

  • Тактувати від зовнішнього годинникового кварцу на 32 768 герц
  • Вважати різні часові інтервали
  • Вважати зовнішні імпульси в режимі лічильника
  • Генерувати ШІМ-сигнал на певних висновках МК
  • Генерувати переривання за якоюсь подією, наприклад, при переповненні

Таймери лічильники можуть тактувати від внутрішнього генератора тактової частоти та від лічильного входу. Давайте розглянемо функціонал таймера-лічильника 1 мікроконтролера atmega8 . Запускаємо CodeVision AVR, створюємо новий проект та погоджуємося на пропозицію запустити Code WizardAVR

Давайте на прикладі timer2 реалізуємо годинник реального часу з виведенням на lcd дисплей, для цього виставляємо таймер як показано на скріншоті

тут виставляється зовнішнє джерело тактування таймера, як зовнішнє джерело ми будемо використовувати годинниковий кварц на 32768 герц, далі встановимо ділник на 128, тобто таймер буде працювати на частоті 32768/128=256, а лічильний регістр то у нас 8-бітний число 255), виходить, що він буде переповнений раз на секунду, далі ми виставляємо галочку біля Overflow interrupt і клацаємо на file->Generate, save and exit.

Code Wizard cгенерував такий код

#include // Timer2 overflow interrupt service routine interrupt void timer2_ovf_isr(void) ( ) void main(void) ( // Input/Output Ports initialization // Port B initialization PORTB=0x00; DDRB=0x00; // Port C initialization PORTC=0 DDRC = 0x00;// Port D initialization PORTD=0x00; DDRD=0x00; /Counter 1 initialization // Clock source: System Clock // Clock value: 125,000 kHz // Mode: Fast PWM top=00FFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x01; TCCR1B=0x0A; TCNT1H=0x00; TCNT1L=0 ICR1H = 0x00, ICR1L = 0x00, OCR1AH ​​= 0x00, OCR1AL = 0x00, OCR1BH = 0x00, OCR1BL = 0x00; Normal top=FFh // OC2 output: Disconnected ASSR=0x08; TCCR2 = 0x05; TCNT2 = 0x00; OCR2 = 0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off MCUCR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x40; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR = 0x00; // Global enable interrupts #asm("sei") while (1) ( ); )

#include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x12 ;PORTD #endasm #include unsigned char second=0; //Змінна для зберігання секунд unsigned char minute=0; //Змінна для зберігання хвилин unsigned char hour=0; //змінна для зберігання годинника char lcd_buffer; //змінна буфер для виведення на дисплей // Timer2 overflow interrupt service routine interrupt void timer2_ovf_isr(void) ( if (++second==59) //збільшуємо кількість секунд на 1 і перевіряємо рівність 59 (second = 0; if (+ +minute==59) (minute = 0; if (++hour==59) ( hour = 0; ) ) ) lcd_clear(); //чистимо дисплей перед виведенням lcd_gotoxy(0,0); // переводимо курсор в точку x=0 y=0 sprintf(lcd_buffer,"%i:%i:%i",hour,minute,second); // формуємо рядок для виведення lcd_puts(lcd_buffer); // виводимо рядок на дисплей) void main( void) ( // Timer/Counter 2 initialization // Clock source: TOSC1 pin // Clock value: PCK2/128 // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x08; TCCR2=0x05; TCNT2=0x00 OCR2 = 0x00; // Timer (s) / Counter (s) Interrupt (s) initialization TIMSK = 0x40; ) ( );

Програма готова, тепер складемо схему в Proteus

По суті, таймер мікроконтролера - це цифровий лічильник, тільки "наворочений". На вхід лічильника подається тактовий сигнал, з перепадів якого лічильник збільшує своє значення. У разі подій - переповнення лічильника чи збіг його значення із заданим - генерується запит на переривання.

Давайте розберемо, як користуватися таймером Т0 як Normal. У цьому режимі таймер рахує від якогось початкового значення лічильного регістра до максимально можливого (до 255 або 0xFF). Коли таймер Т0 дораховує до максимуму, то наступного такту таймера виникає переповнення рахункового регістра TCNT0 - він обнулюється і встановлюється прапор TOV0. Якщо у програмі дозволені переривання глобально (прапор I регістру SREG) та переривання таймера Т0 по переповненню (прапор TOIE0 регістра TIMSK), мікроконтролер викличе відповідний обробник. Якщо значення лічильного регістру збігається з регістром порівняння OCR0, то встановиться прапор OCF0 і при дозволеному перериванні за подією збіг, запуститься його обробник.

Таймер Т0 у режимі Normal

Розглянемо практичне завдання – нам потрібно кожні 20 мс опитувати кнопку. Частота мікроконтролера 8 МГц, мікроконтролер ATmega16.

Перше, що потрібно зробити - це визначитися з вибором коефіцієнта таймера і розрахувати початкове значення для рахункового регістра TCNT0.

Таймер Т0 може тактуватися від внутрішнього тактового сигналу мікроконтролера або зовнішнього, який подається на висновок Т0. Працюючи від внутрішнього тактового сигналу користувач може вибирати коефіцієнти поділу частоти цього сигналу. У таймера Т0 є п'ять можливих варіантів коефіцієнта ділителя - 1, 8, 64, 256, 1024.

Для вирішення поставленого завдання я розмірковую так. Якби один такт таймера Т0 мав період 1 мс, то мені це підійшло б. 20 тактів дають 20 мс. Який коефіцієнт таймера дозволить отримати близький до 1 мс період тактової частоти? Можна порахувати.

Тактова частота мікроконтролера Fcpu = 8000000 Гц
Період тактового сигналу мікроконтролера Tcpu = 1/Fcpu
Період тактового сигналу таймера Т0 дорівнює Tt0 = (1/Fcpu)/k = k/Fcpu

При k = 1024 період тактової частоти таймера Т0 дорівнюватиме Tt0 = 1024/8000000 = 0.128 мс

Це максимальний період тактового сигналу таймера, який ми можемо отримати за умов (Fcpu = 8 МГц). За менших коефіцієнтів - період вийде ще менше.

Ну гаразд, нехай один такт таймера це 0.128 мс, чи вистачить розрядності лічильного регістру, щоб відрахувати цей часовий інтервал і скільки для цього знадобиться тактів? Ділимо необхідний інтервал часу (20 мс) на тривалість одного такту таймера і отримуємо відповідь.

n = t/Tto = 20 мс / 0.128 мс = 156.25

Округливши до цілого, отримуємо 156 тактів. Це менше 255 (максимального значення лічильного регістра), отже розрядності лічильного регістра TCNT0 вистачить.

Початкове значення для рахункового регістру TCNT0 обчислюємо як різницю між максимальним числом тактів таймера Т0 і необхідним, тобто 256 - 156 = 100. (256 - це максимальна кількість часових інтервалів, які може відрахувати будь-який 8-розрядний таймер.)

Думаю тепер зрозуміло, як розраховувати початкове значення TCNT0 для режиму Normal:

Обчислюємо період одного такту таймера Tt0 = k/Fcpu,
- обчислюємо необхідну кількість тактів для заданого інтервалу n = t/Tto,
- Обчислюємо початкове значення для рахункового регістра TCNT0 = 256 - n.

Можна автоматизувати цю процедуру з допомогою макросів. Наприклад, так:

#define F_CPU 8000000UL
#define TIME_MS(time, k) (256L - ((time)*(F_CPU))/(1000L*(k)))

Але з таким макросом потрібно бути напоготові, при певних значеннях time і k можуть виникати помилки.

Тепер переходимо до коду. Щоб використовувати таймер Т0 (та й будь-який інший теж), його потрібно налаштувати (ініціалізувати) та описати обробник переривання (якщо вони використовуються).

Ініціалізація таймера складається з наступних кроків:

Зупинка таймера
- Завдання режиму Normal в TCCR0 без старту,
- Встановлення початкового значення TCNT0,
- скидання прапорів у регістрі TIFR,
- дозвіл переривання з переповнення TIMSK,
- Установка предделителя в TCCR0, тобто старт таймера

У цій послідовності можливі варіації.

Для нашого завдання код ініціалізації виглядатиме так:


/ * значення для рахункового регістру * /
#define T_POLL 100

TCCR0 = 0;
TCCR0 = (0<TCNT0 = T_POLL;
TIFR = (1<TIMSK | = (1<TCCR0 |= (1<

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

Скидання прапорів переривань у регістрі TIFR виконується записом 1 відповідний розряд.Цю операцію потрібно виконувати саме перезаписом регістру, а не за допомогою побітового АБО. І ось чому.

Припустимо, у регістрі TIFR встановлено два прапори переривання - TOV1 та TOV0. TOV0 нам потрібно скинути. При встановленні необхідного розряду за допомогою АБОвідбувається приблизно така річ.


//TIFR має значення 0b00000101
//встановлені прапори TOV1 та TOV0
//виконується код TIFR |= (1<
//TIFR копіюється в R16
IN R16, 0x38

//Р16 встановлюється розряд TOV0
//хоча він і так уже встановлений
ORI R16, 0x02

//R16, рівний 0b00000101, записується в регістр TIFR
OUT 0x38, R16

В результаті скинуто обидва прапори, а ми хотіли скинути один.

Продовжуємо.

Синтаксис опису обробників переривання у різних компіляторів трохи відрізняється. Для IAR`a обробник переривання таймера Т0 за подією переповнення виглядатиме так:



{
TCNT0 = T_POLL;

/*тут має бути опитування кнопки*/

TIMER0_OVF_vect - це адреса вектора переривання події переповнення. Він береться із заголовних файлів на мікроконтролер. У цьому випадку я взяв його з iom16.h файлу.

Перший рядок обробника (TCNT0 = T_POLL;) виконує перезапис лічильного регістру, то встановлює його початкове значення. Якщо цього не зробити, таймер продовжить рахунок із 0. Перезапис рахункового регістру потрібно виконувати на початку оброблювача переривання.

Весь код нашого завдання буде виглядати приблизно так. (Код наведений для IAR`a. Для інших компіляторів потрібно змінити заголовні файли та обробник переривання.)

#include
#include
#include

#define T_POLL 100

int main(void)
{
/*ініціалізація таймера*/

TCCR0 = 0;
TCCR0 = (0<TCNT0 = T_POLL;
TIFR |= (1<TIMSK | = (1<TCCR0 |= (1<

/*ініціалізація решти периферії*/
DDRB | = (1<

Enable_interrupt();
while(1);

/ * обробник переривання T0
за подією переповнення*/
#pragma vector = TIMER0_OVF_vect
__interrupt void TimerT0Ovf(void)
{
/*перезапис лічильного регістру*/
TCNT0 = T_POLL;

/*опитування кнопки*/

/*інверсія PB0 для налагодження*/
PORTB ^= (1<

Управління виводом OC0

У режимі Normal таймер Т0 може змінювати стан виведення OC0 при збігу рахункового регістру та регістру порівняння. Причому навіть без переривань. Варіанти керування визначаються розрядами COM01 та COM00 регістра TCCR0.

Ось приклад програми, що генерує прямокутний сигнал на виведенні ОС0.

#include
#include

int main(void)
{
/*ініціалізація таймера Т0*/

TCCR0 = 0;
TCCR0 = (0<TCNT0 = 0;
OCR0 = 0;
TIMSK = 0;
TCCR0 |= (1<

/*ініціалізація OC0*/
DDRB | = (1<

While(1);
return 0;
}

Висновок ОС0 змінюватиме свій стан на протилежне при нульовому значенні рахункового регістру.

Декілька моментів щодо використання таймера

Обробник переривання таймера (та й будь-якої іншої периферії) потрібно робити якнайкоротше.

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

І останнє. Може трапитися ситуація, що обробка переривання таймера затримається (наприклад, з вини іншого обробника) і регістр TCNT0 вже порахує кілька тактів. Якщо просто перезаписати значення TCNT0, то наступне переривання викликатиметься пізніше, ніж потрібно. Вийде, що попереднє (затримане) та нове переривання не витримають необхідний інтервал.

Цю ситуацію можна згладити, якщо виконувати перезапис рахункового регістру так:

TCNT0 = TCNT0 + startValue;

Додавання поточного значення лічильного регістру з ініціалізованим, врахує ці зайві такти.Щоправда є одне АЛЕ! При великих значеннях startValue операція додавання може викликати переповнення рахункового регістру.

Наприклад, startValue = 250, а таймер встиг дорахувати до 10. Тоді операція додавання приведе до такого результату:

10 + 250 = 260

Беремо 8 розрядів від 260, отримуємо 4. У TCNT0 запишеться 4.

Розглянемо, як зробити таймер своїми руками на мікроконтролері ATmega8, хоча код досить просто адаптувати і до МК AVR інших серій. Електронний таймер потрібний пристрій у всіх областях, де потрібне виконання певних дій через певний проміжок часу.

Управління таймера складається з чотирьох кнопок:

- Збільшення значення числа;

- Зменшення значення числа;

- Старт таймера;

- Скидання таймера.

Як індикатор спрацьовування таймера застосовується генератор звукової частоти динаміком. Генератор запускатиметься за допомогою транзисторного ключа Q5, який у свою чергу відкривається позитивним потенціалом, що надходить з порту PC2 мікроконтролера.

Спрощено таймер працює в такий спосіб. Кнопками "+" і "-" встановлюється необхідна кількість секунд; кнопкою "старт" запускається таймер. Коли таймер відрахує до нуля, на виведенні мікроконтролера PC2 ATmega8 з'явиться високий потенціал, який відкриє Q5. Далі транзисторний ключ запустить генератор і пролунає звук у динаміці. Скидання таймера здійснюється при натисканні кнопки скидання. Генератор звукової частоти зібраний на двох транзисторах Q6 і Q7 різних напівпровідникових структур. З принципом роботи та описом схеми подібних генераторів можна ознайомитися, перейшовши по .

Алгоритм роботи таймера на мікроконтролері

Наш таймер буде відраховувати зворотний час рівно по одній секунді, хоча можна задати і будь-який інший час, наприклад, хвилини, години, соті секунди і т.п.

Для формування інтервалу часу за одну секунду ми скористаємося першим таймер-лічильником мікроконтролера ATmega8. Усі його налаштування ми визначимо у функцію start. Спочатку розділимо робочу частоту мікроконтролера 1000000 Гц на 64 та отримаємо нову частоту 15625 Гц. За це відповідають біт CS10, CS11 та CS12 регістра TCCR1B. Далі дозволяємо переривання за збігом і в регістр порівняння (старший і молодший) записуємо двійкове число рівне десятковому 15625. Потім обнулюємо лічильний регістр TCNT1 і встановлюємо в одиницю біт WGM12 регістра TCCR1B, що викликає скидання лічильного регістру при його захід регістри порівняння.

void start (void)

TCCR1B &= ~(1<

TCCR1B |= (1<

TIMSK | = (1<

OCR1AH ​​= 0b00111101;

OCR1AL = 0b000001001; // Регістр порівняння 15625

TCNT1 = 0;

TCCR1B |= (1<

Коли таймер відрахує рівно одну секунду – викликається переривання. У тілі функції переривання ми знижуватимемо значення змінної на одиницю. При досягненні нуля на другий вихід порту мікроконтролера C з'явиться високий потенціал, який відкриє транзисторний ключ і запустить генератор, в результаті чого ми почуємо звук в динаміці.

ISR (TIMER1_COMPA_vect)

Z-;


Опис роботи таймера/лічильника 1.
Переривання від TC1

Таймер/лічильник 1 (TC1) являє собою 16-бітний модуль, що містить 10 8-бітних регістрів. Ці регістри фактично є набором із 5 16-бітних регістрів. Рахунок відбувається в регістрах TCNT1H (Timer counter 1 High byte) і TCNT1L (Low byte), що разом складають 16-бітовий регістр TCNT1. УВАГА! Якщо використовувати пряме читання 8-бітових регістрів TCNT1H і TCNT1L, то не можна бути впевненим, що ці реєстри прочиталися одночасно. Може статися така ситуація: Лічильник містив значення $01FF, Ви рахували TCNT1H (що містить значення 01 в якусь змінну). За цей час відбувся лічильний імпульс, і вміст TCNT1L дорівнював $00, а в TCNT1H записалося значення $02. Тепер Ви читаєте значення TCNT1L в іншу змінну, отримуєте в цій змінній значення $00 (адже таймер-лічильник вже зробив рахунок). 16-бітове значення цих змінних вийшло $0100, але на момент зчитування старшого байта вміст лічильника був $01FF, і молодший байт у Вас повинен був прочитатися як FF. Для запобігання такій ситуації служить тимчасовий регістр, що міститься в блоці лічильника таймера. Цей регістр прозорий, тобто. діє автоматично. При зчитуванні значення регістра TCNT1L змінну, вміст TCNT1H потрапляє у цей регістр. Потім під час читання старшого байта в змінну, зчитується значення тимчасового регістру. Тимчасовий регістр абсолютно прозорий для користувача, але для його коректної роботи необхідно дотримуватися такої послідовності дій:
Для 16-бітної операції запису старший байт повинен бути записаний першим. Молодший – другим.
Для операції 16-бітного читання молодший байт повинен бути прочитаний першим, а вміст старшого байта зчитується другим.
Реєстр TCCR1Aслужить для завдання режимів роботи таймера/лічильника 1:

Біти COM1A1, COM1A0, COM1B1 та COM1B0 - контролюють поведінку висновків OC1A та OC1B.
Біти FOC1A, FOC1B, WGM11 і WGM10 служать завдання роботи ТС1 як широтно-импульсного модулятора.
Швидкість рахунку ТС1 можна встановити у регістрі TCCR1B:

Де біти ICNC1, ICES1, WGM13 і WGM12 також служать для PWM, а CS12, CS11 і CS10 налаштовують швидкість рахунку наступним чином:


У разі, якщо ці біти записані значення 000, ТС0 зупинений. Якщо записано 001, тактова частота процесора подається через схему дільника без змін, і на кожен такт процесора ТС1 збільшує значення в регістрі TCNT1. Відповідно, якщо в CSxx записано 101, то збільшення значення в TCNT1 відбувається на кожен 1024 такт процесора.

16-бітові регістри OCR1Aі OCR1Bслужать завдання значення, при досягненні якого в режимі рахунку, ТС1 генерує відповідні переривання.

Обробка переривань від TC1

ТС1 при переповненні значення TCNT1 посилає процесору сигнал Timer/Counter 1 Overflow. Також процесору надсилається сигнал Timer/Counter 1 A або B Compare Match при збігу значень у регістрах TCNT1 і OCR1A і OCR1B відповідно. Реакція процесора на ці сигнали (виклик відповідних переривань) залежить від значення регістрів TIMSKі прапор I в Status регістрі процесора.
Для завдання реакції на події TC1 у регістрі TIMSK служать чотири біти:

Біт 2 - TOIE1 - Коли цей біт встановлений в 1 і дозволені переривання, процесор реагує на сигнал переповнення ТС1 і викликає переривання вектором $010 (OVF1addr).
Біт 3 - OCIE1B - Коли цей біт встановлено в 1 і дозволені переривання, процесор реагує викликом переривання вектором $00E (OC1Baddr) на подію збігу рахунку з константою в регістрі OCR1B. Біт 4 - OCIE1A - Коли цей біт встановлено в 1 і дозволені переривання, процесор реагує викликом переривання вектором $00C (OC1Aaddr) на подію збігу рахунку з константою в регістрі OCR1A. Біт 5 - TICIE1 - Якщо встановлено цей біт і дозволено переривання, дозволено переривання захоплення ТС1, розташованого за вектором $00A (ICP1addr).

Це досить просто. Якщо ліньки читати, просто скачайте приклади, що додаються, і подивіться, а я поки продовжу.

Навіщо це треба?

Відраховувати час програмно, в тілі основної програми – не найкращий спосіб. Щоб відрахувати секунду, програма тільки й робитиме, що вважати цю секунду.

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

Щоразу, відраховуючи хоча б мілісекунду, програма ніби висне на цей час. А якщо треба відрахувати місяць? – пристрій зависне на місяць?Що ж робити?

Найкраще довірити відлік мінімального інтервалу часу таймеру з компаратором. Тобто таймеру з встановленням числа для відліку.

Створюємо проект у MPLAB IDE з таймером TMR2

Виберемо контролер 16F628A. Скачаємо на нього "даташит" (Data Sheet). У ньому є таймер із компаратором TMR2. Створимо в MPLAB-і проект. Шаблон знайдете у себе на системному диску, наприклад:

C:\Program Files\Microchip\MPASM Suite\Template\Code\16F628ATEMP.ASM

Зайві коментарі у шаблоні можна прибрати та додати свої.

Для створення проекту краще скористатися Майстром проектів у меню MPLAB/Project/Project Wizard…

Спробуємо скомпілювати те, що вийшло.

Так, MPLAB лається на свій же шаблон !!!??? Перевіримо у файлі P16F628A.INC як має бути.

Знайти його можна там же, у каталозі Microchip:

C:\Program Files\Microchip\MPASM Suite\ P16F628A.INC

Можна скопіювати цей файл, про всяк випадок, у свій проект, знадобиться.

Подивимося як там записано та поправимо у шаблоні:

CONFIG _CP_OFF ​​& _DATA_CP_OFF

на

CONFIG _CP_OFF ​​& DATA_CP_OFF

Різниця невелика, але суттєва. Тепер все гаразд, компілюється.

У програмуванні дрібниць немає. Тож не вірте всьому, що пишуть, перевіряйте:-)

Включимо вменюсимулятор Debugger / Select Tool / MPLAB SIM

У Debugger / Settings ... виберемо частоту кухонних виробів 4 MHz

Там в Debugger беремо секундомір Stopwatch

На початку програми main налаштуємо контролер.

Перед таймером встановимо предделитель:4 і число.249 регістр передустановки.

Тепер у нас: 4 МГц / 4 = 1 машинний цикл = 1 мікросекунда.

Помножуємо на ділник 4 і на встановлене число 250 (від 0 до 249), отримуємо = 1 мілісекунду.

На початку підпрограми переривання, що починається з ORG 0x004 додамо перевірку на переривання від TMR2, щоб не плутати з іншими перериваннями. У нас поки що інших переривань немає, але, можливо, потім з'являться. Так що краще зробити одразу:

Bcf PIR1, TMR2IF; Скидання переривання за таймером TMR2.

; І відразу ставимо мітку, подвійним клацанням на рядку зі скиданням переривання від TMR2:

Компілюємо програму та запускаємо симулятор. Програма зупинилася на нашій мітці.

Скидаємо секундомір і знову запускаємо симулятор.

Цього разу на секундомірі вказує 1000 МЦ (машинних циклів), а нижче 1 мілісекунду.

Те, що нам треба. Переривання відбуваються через рівні проміжки часу в 1 мілісекунду.

Що ми маємо.

Сталася подія, контролер відрахував 1 мсек. Так і озаглавимо ці рядки:

Event_Time_1ms

Btfss PIR1, TMR2IF; Перевірка переривання TMR2.

Goto other_interrupts; інакше переходимо на перевірку інших переривань.

Bcf PIR1, TMR2IF; Скидання переривання за таймером.

Все що нижче буде відбуватися з періодичністю в 1 мсек.

Тут можна піднімати прапори з цією періодичністю для підпрограм, яким потрібні такі прапори.

Ці підпрограми зможуть здійснювати дії кожну мілісекунду або відраховувати будь-яке необхідне

кількість мілісекунд. Можуть робити дії через час кратний одній мілісекунді. Наприклад,

кожні 17 мілісекунд.

У перериванні зручніше буде розміщувати лічильники кратні якимось стандартним чи зручним інтервалам

часу. Наприклад: 10 мсек, 0,1 сек, 1 сек, 1 хв, 1 год і т.д.

Втім, за бажання лічильник на ті ж 17 мілісекунд можна додати відразу, у перериванні.

Додаємо лічильники таймерів

Для кожного інтервалу часу буде потрібно регістр для рахунку:

Reg_Time_10ms; Регістри лічильників часу.

Reg_Time_01sec

Reg_Time_1sec

Reg_Time_1min

Reg_Time_1hour

Reg_Time_1day

У ці регістри завантажуватимемо константи. Назвемо їх відповідним чином.

#Define TIME_10ms.10; Константи для регістрів лічильників часу.

#Define TIME_01sec .10;

#Define TIME_1sec .10;

#Define TIME_1min.60;

#Define TIME_1hour .60;

#Define TIME_1day .24;

Всі ці константи необхідно завантажити у відповідні регістри на початку програми.

Обмежимося днями, т.к. всі вони однакові, по 24 години. Тижня теж однакові, але тижнями рідко вважають,

хіба що вагітність:-).

З місяцями складніше, там кількість днів різна, рахунок ускладниться і для прикладу не підходить. Тож далі,

простіше використовувати мікросхеми реального часу типу PCF8583 тощо.

Лічильники можуть бути з іншими інтервалами, не 10 ms, а 100 ms, наприклад. Це як вам зручно.

Дивіться файл Time_intervals1.asm, що додається.

Лічильники запишемо в такому вигляді:

Event_Time_10ms

Decfsz reg_Time_10ms,F; Віднімаємо 1, якщо не нуль, пропускаємо наступну інструкцію.

Goto other_interrupts; інакше переходимо на перевірку інших переривань.

Movlw TIME_10ms; Константу завантажуємо

Movwf reg_Time_10ms; назад у регістр.

Решта лічильників - такі ж.

Тепер, якщо виставити точку зупинки в кінці будь-якого лічильника (ставте точки зупинки тільки по одній),

програма зупинятиметься там

із відповідною періодичністю.

Як використовувати лічильники програм?

Створимо в Proteus-е модель зі світлодіодами та поморгаємо ними. Дивіться файл Time_intervals1.DSN

Можна, звичайно, перемикати піни порту прямо в перериванні, але зробимо по-іншому.

Виділимо ще один регістр індикатора і назвемо його indicator.

Індикатор перемикатиметься лише за потреби.

Пишемо підпрограму LED_Indicator.

На початку підпрограми перевіряється прапор події EV_indicator і підпрограма продовжить виконання,

тільки якщо ця подія була, і прапор був піднятий.

Організуємо перемикання світлодіода один раз на секунду. Для цього виставимо прапор LED_1sec після

переривання 1 сек.

Цю подію треба опрацювати. Напишемо ще одну програму Switch_ LED_1sec

Як і попередня підпрограма, вона перевірятиме прапор своєї події EV_LED_1sec.

Перемикаємо світлодіод на індикаторі маскою LED_1sec.

Movlw LED_1sec; Перемикаємо світлодіод LED_1sec

Xorwf indicator, F; на індикаторі.

Наприкінці підпрограми піднімемо прапор події для індикатора EV_indicator.

Якщо світлодіод треба перемикати не кожну секунду, а з частотою в 1 сек, тобто. перемикати кожні 0,5 сек? Виділяємо регістр і робимо врізання для окремого лічильника. Тактову частоту можна вибрати 0,1 с, помноживши на 5, або 0,01 с, помноживши на 50, її і візьмемо. Константа у нас виходить 50. Лічильник маємо в місці підняття прапора 10 мсек. Наприкінці лічильника як завжди піднімаємо прапор. Так, і не забудьте про попереднє встановлення на початку програми.

Event_Time_05sec

Decfsz reg_Time_05sec,F; Віднімаємо 1, якщо не нуль, пропускаємо наступну інструкцію

Goto Event_Time_01sec; інакше переходимо до наступного лічильника.

Movlw TIME_05sec

Movwf reg_Time_05sec

BSF event,EV_LED_05sec ; Піднімаємо цей прапор події раз на 0,5 сек.

Ну і підключимо ще один світлодіод і додамо підпрограму, що перемикає його.

Чому для кожного світлодіода окрема підпрограма? Це для прикладу. Події у нас різні, а цього висновку може бути підключений не світлодіод, а насос або вентилятор, сигналізація або нагрівач. І все в програмі підключатиметься окремо, і працюватиме незалежно, не заважаючи один одному. Ніщо вам не заважає підключити блок світлодіодів і виділити для цього лише одну підпрограму. Блок цей може містити сотні світлодіодів, а керуватися буде по 2-3 проводах однією підпрограмою, яка буде викликатися тим самим прапором. Я ще не розповів, як зробити затримку? Так само. Можна виділити один або кілька регістрів, залежно від того, які інтервали часу вам потрібно відраховувати, та з якою точністю. Якщо на вході лічильника такти в 1 мсек, то точність буде відповідна. Якщо точність потрібна більше, то рахуйте машинні цикли. Як робити врізання, ми вже знаємо. А запускається лічильник просто. Завантажуєте константи в лічильник і скидаєте прапор. Наприкінці рахунку прапор підніметься.

Підіб'ємо підсумок Здається, що вийшло занадто багато коду. Насправді, це не так. Деякі рядки написані про запас, для відліку часток секунд, хвилин, годин і днів. Поки вони не використовуються, і ви можете видалити їх, або використовувати в своїй програмі. Те, що використовується, виконується точно в певний час і дуже швидко. Наприклад, перемикач світлодіода спрацьовує лише раз на секунду. Підпрограма індикатора також спрацьовує за необхідності. Наприклад я зробив ще одну програму (див. у папці Flasher101). Там 8 таймерів перемикають 8 світлодіодів. Перший світлодіод блимає раз на секунду, а кожен наступний – на 1% довше. Тобто через 1% х 100 включень, вони знову блимають разом. Виходять цікаві візуальні ефекти. І ще один таймер вимикає всю цю мигалку за 5 хвилин. Вийшло просто, точно та ефективно.

Зі звичайними затримками програми це було б складно зробити, заплутаєшся. А додати в таку програму щось ще, взагалі було б неможливо, не змінюючи попередній алгоритм. Крім того, підпрограму індикатора можна викликати не з інших підпрограм, а за часом. Наприклад, кожні 10 мсек або частіше. Це буває необхідно за динамічної індикації. А можна навпаки, викликати раз на секунду, щоб показання не миготили, і їх було зручно зчитувати. Це буває необхідно при швидко мінливих параметрах, що виводяться на індикацію. Також можна поєднати динамічну індикацію та зміну поточних показань раз на секунду. Тобто маємо тимчасові проміжки у будь-яких поєднаннях. І не тільки для індикації, а й для сканування клавіатури, енкодера, роботи з датчиками тощо. І при всьому цьому, у контролера залишиться купа машинного часу на інші потреби та обчислення. А підпрограми, ретельно налагоджені під роботу із прапорами, легко переноситимуться в інші проекти.

Всім удачі! Вихідні файли, проекти в Протеусі та інші матеріали до цієї статті можна взяти