Навчальний курс avr

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

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

Розглянемо практичну задачу - нам потрібно кожні 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Навчальний курс avr


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

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

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

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

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

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

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

TCNT0 = TCNT0 + startValue;

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

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

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

Навчальний курс AVR. Таймер - лічильник Т0. Регістри. Ч1


Навчальний курс avr

Схожі статті