8L-Курс, Часть 5 - Таймеры: начало

Таймеры — один из самых важных элементов МК. Ни одна более-менее сложная программа не обходится без них. Все, что так или иначе связано с временными интервалами или подсчетом каких-нибудь событий, реализуется на таймерах.
В STM8L есть несколько таймеров разной сложности. Их можно поделить на три группы:
TIM1 — Это самый сложный и функциональный таймер. 16и разрядный счетчик (максимальное значение — 65535), предделитель, принимающий любые значения от 1 до 65536. Три внешних канала с комплементарными выводами для каждого… и прочие плюшки.
TIM2, TIM3, TIM5(последнего в нашем МК нет) — Чуть по-проще. Внешних каналов всего два. Делитель принимает уже не любое значение, а только степени двойки в диапазоне от 1 до 128. Счетчик по-прежнему 16и разрядный.
TIM4 — Самый простой. 8и разрядный счетчик, делитель — (тоже степени двойки) от 1 до 32768. Внешних каналов нету.
Вот сводная табличка из reference manual, которая рассказывает о характеристиках всех таймеров:

Начнем с TIM1. Он конечно сложнее, чем все остальные, но зато после него работа с другими таймерами не будет вызывать непоняток.
Чтоб не сильно перегружать мозг читателя, в этой части рассмотрим только основные возможности таймера. Функциональная схема TIM1 вносит ужас и смятение в голову неподготовленного зрителя, поэтому я выделил те части, с которыми мы сегодня познакомимся:

Чуть крупнее и без жпег-сжатия
Сегодня нас интересует только TIME BASE UNIT. Он содержит предделитель и счетчик.

На таймер поступает тактовый сигнал. Если мы не трогали настройки CLOCK/TRIGGER CONTROLLER, то это будет системный тактовый сигнал SYSCLK (от которого работает ядро и большинство периферии).
Как и на любое другое периферийное устройство, на таймер перед началом работы надо подать тактирование. Бит PCKEN21 в регистре PCKENR2 отвечает как раз за это.
PCKENR2 |= PCKEN21; //Тактирование подано
Далее тактовый сигнал проходит через предделитель. Для TIM1 делитель может принимать любые значения от 1 до 65536 (а не только степени двойки. Вообще, крайне удобно). Предделитель хранится в регистрах TIM1_PSCRH и TIM1_PSCRL. В первом — старший байт, во втором — младший. Реальное значение предделителя на единицу больше, чем то, что записано в регистрах. Т.е. если в TIM1_PSCRH:TIM1_PSCRL мы запишем 0, то делитель будет 1. Если 65535, то делитель станет = 65536. При записи в предделитель надо сначала писать старший байт (TIM1_PSCRH), а затем — младший.
После делителя сигнал идет на счетчик, который в зависимости от настроек, считает вверх (увеличиваясь при каждом тактовом импульсе) или вниз. За направление счетчика отвечает бит DIR в регистре TIM1_CR1. Если он установлен, то таймер считает вниз, а если сброшен (по умолчанию) — то вверх.
Значение счетчика можно в любой момент прочитать из пары регистров TIM1_CNTRH и TIM1_CNTRL. Читать надо сначала старший байт (CNTRH, при этом младший загружается во временный буфер), а потом — младший. Изменить значение счетчика тоже можно в любой момент, при этом нет разницы — какой байт записывать сначала: никакой буферизации при записи нету. Поэтому надо дважды подумать перед тем, как менять значение счетчика, когда таймер работает — иначе есть возможность случайно поймать срабатывание какого-нибудь прерывания между записью одного регистра и другого.
При счете вверх, таймер тикает до значения в Auto Reload Register (ARR). Когда счетчик совпадает со значением ARR, происходит сброс, после чего таймер продолжает считать начиная с 0.
А при счете вниз, он считает до 0, а затем сбрасывается и начинает считать со значения в ARR.
В некоторых случаях нужно чтобы таймер останавливался после первого переполнения (и запускался только вручную). Для этого есть бит OPM в том-же TIM1_CR1. Если записать в него 1, то таймер будет отключаться после первого переполнения.
ARR, как и счетчик, занимает два регистра: TIM1_ARRH, TIM1_ARRL. Но в отличие от счетчика, тут нет буфера на чтение (читать регистры можно в любом порядке), зато есть буферизация при записи — сначала надо писать старший байт, а потом — младший.
Когда значение счетчика совпадает со значением ARR, кроме сброса счетчика, генерируется событие Update (Update Event — UEV). По этому событию может происходить прерывание, вектор которого назывется TIM1_OVF_UIF_vector
ISR(TIM1_OVF, TIM1_OVR_UIF_vector)
{
//Заготовка для обработчика прерывания
TIM1_SR1_bit.UIF = 0; //Сброс флага прерывания
};
Для того, чтобы разрешить это прерывание, нужно установить бит UIE в регистре TIM1_IER.А так-же по UEV происходит загрузка в таймер нового значения предделителя. То есть если мы изменили их до этого, то новые значения вступят в силу только при следующем UEV.
Даже при инициализации таймера, когда мы настраиваем предделитель, нужно UEV, чтобы он записался в таймер. К счастью, нам дали возможность программно генерировать события таймера. В регистре TIM1_EGR есть бит UG, установив который, мы симулируем нужное нам событие. А чтобы в этот момент не произошло прерывание (которое во время инита таймера явно не в тему), мы устанавливаем бит URS в TIM1_CR1 — если он равен 1, то только переполнение счетчика (точнее совпадение с ARR) будет приводить к прерыванию, а программная генерация UEV — нет.
Итак, мы подали тактирование, настроили таймер, разрешили прерывания и… ничего не работает! Спокойно, мы просто забыли его включить :)
Для запуска таймера служит бит CEN в TIM1_CR1. Установив его, мы запускаем таймер (а сбросив — останавливаем счет).
Частота, с которой таймер выдает UEV (если конечно, не установлен бит OPM и он таймер постоянно) равна SYSCLK / PRESCALLER / ARR.
К примеру, если мы хотим, при тактировании от 16МГц получить прерывания 10 раз в секунду, то предделитель надо поставить 160 (и записать для этого в регистры число 159!), а ARR — 10000. Ну или наоборот :)
Для примера, инициализация таймера может быть вот такой:
CLK_PCKENR2_bit.PCKEN21 = 1; //Включаем тактирование таймера 1
TIM1_PSCRH = 0;
TIM1_PSCRL = 159; //Делитель на 160
TIM1_ARRH = (10000) >> 8; //Частота переполнений = 16М / 160 / 10000 = 10 Гц
TIM1_ARRL = (10000)& 0xFF;
TIM1_CR1_bit.URS = 1; //Прерывание только по переполнению счетчика
TIM1_EGR_bit.UG = 1; //Вызываем Update Event
TIM1_IER_bit.UIE = 1; //Разрешаем прерывание
TIM1_CR1_bit.CEN = 1; //Запускаем таймер
Только есть важный момент — эта инициализация рассчитана на то, что перед ней таймер никто не трогал. То есть во всех регистрах дефолтные значения, оставшиеся с момента сброса. А если таймер перенастраивается с другого режима — нужно убедится что «лишние» настройки сброшены.Для примера… снова помигаем светодиодом :) На этот раз через прерывание от таймера.
Инициализацию сделаем такой, как приведена выше, но предделитель поменяем со 160 на 1600 — тогда прерывание будет происходить раз в секунду:
TIM1_PSCRH = (1599) >> 8;
TIM1_PSCRL = (1599)& 0xFF;
В прерывании от таймера будем мигать светодиодом:
ISR(TIM1_OVF, TIM1_OVR_UIF_vector)
{
PD_ODR_bit.ODR4 ^= 1; //Инвертируем пин со светодиодом (это для модуля PB2)
TIM1_SR1_bit.UIF = 0;
};
Вот так. Если не углубляться сразу во все хитрости таймера, то ничего сложного нет. Все таймеры устроены примерно одинаково, поэтому вы уже сейчас можете работать с TIM2, TIM3 (у них предделитель считается по другому!) или TIM4. Только учтите, что для них тактирование включается другими битами:

На этом пока все. В следующей части будем изучать работу с внешними каналами таймеров.
Проект в IAR
← Часть 4 — Тактирование Содержание Часть 6 — Таймеры, внешние каналы →
- +13
- 13 марта 2013, 22:07
- dcoder
- 1
Файлы в топике:
5_TIM.zip
Вроде бы вот это:
Ну и, раз уж мы говорим о TIME BASE UNIT, наверное стоило бы о repetition counter'е упомянуть, что бы потом не возвращаться.
А вообще — спасибо!
При записи в предделитель надо сначала писать старший байт (TIM1_PSCRH), а затем — младший.Как-то не стыкуется с вот этим:
Инициализацию сделаем такой, как приведена выше, но предделитель поменяем со 160 на 800 — тогда прерывание будет происходить два раза в секунду:
TIM1_PSCRL = (1600)& 0xFF; TIM1_PSCRH = (1600) >> 8;
Ну и, раз уж мы говорим о TIME BASE UNIT, наверное стоило бы о repetition counter'е упомянуть, что бы потом не возвращаться.
А вообще — спасибо!
TIM4, TIM5 (последнего в нашем МК нет) — Самый простой.Гм, а табличка утверждает, что TIM5 — GP таймер, а не basic.
TIM1_PSCRL = 160; //Делитель на 160
TIM1_PSCRH = 0;
Про порядок уже сказали, а я укажу на то, что ты выставил делитель на 161, а не 160. Ну и во втором случае предделитель ставится не 800, а 1601.
При счете вверх, когда счетчик совпадает со значением ARR, происходит сброс, после чего таймер продолжает считать начиная с 0 (а при счете вниз — с 65535).Для STM8S/A RM0016 17.3.5. утверждает что при счете вниз идет счет от ARR до 0. Это разное поведение S и L серий, или опечатка?
По двойной буфферизации регистров знатно помогают их илюстрации в даташите (в указанном рефмене 38-39). Понимаю что для одной статьи это уже много, но в последующем надо бы указать, что можно без буфферизации.
Возникла такая проблема. Скажем нужно сделать временную задержку используя 16-битный таймер, но не используя прерывания от него. Для STM32 код выглядит так:
[операторы]
while(TIM3->CNT<500){};
[операторы]
В случае же stm8 необходимо по очереди сравнивать старший и младший байты счетного регистра таймера? Помнится мне, что в АВРах была возможность непосредственно обращаться к двум 8-битным регистрам как к одному 16 битному. Если у кого-то есть кусочек такого кода для stm8, большая просьба поделиться.
Комментарии (23)
RSS свернуть / развернуть