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

← Часть 4 — Тактирование Содержание Часть 6 — Таймеры, внешние каналы →

Таймеры — один из самых важных элементов МК. Ни одна более-менее сложная программа не обходится без них. Все, что так или иначе связано с временными интервалами или подсчетом каких-нибудь событий, реализуется на таймерах.

В 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

Комментарии (23)

RSS свернуть / развернуть
Вроде бы вот это:
При записи в предделитель надо сначала писать старший байт (TIM1_PSCRH), а затем — младший.
Как-то не стыкуется с вот этим:
Инициализацию сделаем такой, как приведена выше, но предделитель поменяем со 160 на 800 — тогда прерывание будет происходить два раза в секунду:
TIM1_PSCRL = (1600)& 0xFF;
TIM1_PSCRH = (1600) >> 8;

Ну и, раз уж мы говорим о TIME BASE UNIT, наверное стоило бы о repetition counter'е упомянуть, что бы потом не возвращаться.

А вообще — спасибо!
+1
Спасибо, исправлено
0
TIM4, TIM5 (последнего в нашем МК нет) — Самый простой.
Гм, а табличка утверждает, что TIM5 — GP таймер, а не basic.
TIM1_PSCRL = 160; //Делитель на 160
  TIM1_PSCRH = 0;

Про порядок уже сказали, а я укажу на то, что ты выставил делитель на 161, а не 160. Ну и во втором случае предделитель ставится не 800, а 1601.
0
  • avatar
  • Vga
  • 14 марта 2013, 04:00
>_<
Чтоб я еще раз поспешил выкладывать статью… да никогда
0
TIM1_PSCRH = (1559) >> 8;
TIM1_PSCRL = (1559)& 0xFF;

Тебя по прежнему глючит)
0
А там ещё и
TIM1_ARRH = (10000) >> 8;
Мелочь, конечно…
0
Гм, а в чем проблема? (10000) приводится к 8бит и получается чепуха?
0
Ровно то же самое, что и в начале ветки. Счет идет в диаппазоне [0;ARR], т.е. при коэффициенте 10000 будет деление на 10001.
По этому в комментарии ниже я и сослался на илюстрации, по ним более очевидно.
Ошибка на 2 порядка меньше, но вдруг кто решит частотомер делать :)
0
Да ладно, статья хорошая. Ну есть мелкие недочетики, но так исправим же. Да и уже изголодались по твоим статьям, ждем недождемся все когда очередная выйдет :)
0
+1
Жду с нетерпением, когда мои STM8S003 c ST-LINK-ом приедут )
0
При счете вверх, когда счетчик совпадает со значением ARR, происходит сброс, после чего таймер продолжает считать начиная с 0 (а при счете вниз — с 65535).
Для STM8S/A RM0016 17.3.5. утверждает что при счете вниз идет счет от ARR до 0. Это разное поведение S и L серий, или опечатка?

По двойной буфферизации регистров знатно помогают их илюстрации в даташите (в указанном рефмене 38-39). Понимаю что для одной статьи это уже много, но в последующем надо бы указать, что можно без буфферизации.
0
А при счете вверх от 0 до ARR или все же от ARR до переполнения? По логике названия регистра должно быть второе, но кто их знает, инженеров ST. Они немного странные.
0
от 0 до арр. Название можно подогнать под ответ, что из арр будет автоматически перегружена верхняя граница счета :)
0
Возникла такая проблема. Скажем нужно сделать временную задержку используя 16-битный таймер, но не используя прерывания от него. Для STM32 код выглядит так:
[операторы]
while(TIM3->CNT<500){};
[операторы]
В случае же stm8 необходимо по очереди сравнивать старший и младший байты счетного регистра таймера? Помнится мне, что в АВРах была возможность непосредственно обращаться к двум 8-битным регистрам как к одному 16 битному. Если у кого-то есть кусочек такого кода для stm8, большая просьба поделиться.
0
  • avatar
  • NBS
  • 04 сентября 2013, 01:08
Отвечаю на свой вопрос:
while ((TIM3->SR1 & TIM3_SR1_UIF) == 0){};
Естественно предварительно следует записать в регистры ARRH и ARRL нужные значения и настроить предделитель
0
  • avatar
  • NBS
  • 07 сентября 2013, 16:44
Ребят, подскажите, отчего флаг UG не устанавливается(на дискавери)?
инициализация таймера скопирована один в один из статьи.
я конечно понимаю что УГ оно УГ, н овсе же…
0
выглядит это примерно так
0
В рефман заглядывать не пробовал? Там четко написано:
1) Бит UG сбрасывается аппаратно
2) Бит UG доступен только на запись
Алсо, об этом можно было догадаться и без рефмана.
0
СПс)
0
Спасибо, dcoder, ты сделал мой курсовой :)
Точнее я сделал после раскуривания всех этих статей и примеров — а ведь пару месяцев назад я вообще не знал, с какой стороны подходить к микроконтроллерам)
0
Года через 2 до USART дойдет описание
0
  • avatar
  • x893
  • 15 апреля 2014, 14:46
Оптимист ты, однако.
0
жалко нет нигде продолжения, захвата на таймерах.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.