Найти тему
Разумный мир

Микроконтроллеры для начинающих. Часть 55. Таймеры STM8

Оглавление

Пришло время познакомиться с самой сложной реализацией таймеров среди всех рассматриваемых в рамках цикла статей микроконтроллеров. Речь пойдет о STM8.

Я сразу хочу предупредить, что так как это цикл статей для начинающих, я не буду подробно рассматривать абсолютно все аспекты работы таймеров. Ровно так же, как это было и для других микроконтроллеров. Не будут сегодня рассматриваться и режимы захвата/сравнения, формирования ШИМ сигналов. И совершенно точно не будет рассматриваться возможность объединения таймеров.

Часть вопросов, которые сегодня останутся "за кадром", будет рассмотрена в других статьях. Часть рассматриваться не будет вообще, но некоторые моменты могут быть затронуты статьями вне данного цикла.

Итак, в STM8 есть три основных типа таймеров:

  • расширенный (TIM1)
  • общего назначения (TIM2, TIM3, TIM5)
  • базовый (TIM4, TIM6)

TIM6 нет в STM8L. Обратите внимание, что не обязательно имеются все 6 (или 5) таймеров.

В документации говорится, что структура всех таймеров одна. И это действительно почти так. Но документация ST, к сожалению, далеко от идеала. Поэтому нюансы построения и работы таймеров, даже одного типа, не всегда видна с первого взгляда. Что не добавляет радости новичкам.

Общая структура таймеров STM8

Что ST подразумевает под "одинаковостью" структуры? В отличии от уже рассмотренных семейств, в STM8 выделены структурные блоки, из которых и состоят таймеры. Таких крупных структурных блоков 3:

  • Модуль времени (Time base unit). Собственно говоря, это и есть таймер в своем изначальном виде.
  • Массив захвата/сравнения (Capture compare array).
  • Контроллер управляющей импульсами счета и триггерами (Clock/trigger controller)

Основным блоком, который мы сегодня будет изучать, является модуль счета времени/импульсов. То есть, модуль времени. Блок захвата/сравнения, который обеспечивает еще и формирование ШИМ, сегодня останется "за кадром". Но мы к нему обязательно еще вернемся. Контроллер управляющий импульсами счета мы сегодня рассмотрим кратко. Только с точки зрения выбора источника тактовых импульсов.

Но есть одна сложность. Дело в том, что эти структурные блоки, уже внутри себя, имеют разные структурные схемы. Именно поэтому я и сказал ранее "почти".

Базовый таймер (TIM4, TIM6)

В статье я буду говорить о TIM4, При все сказанное будет абсолютно верным и для TIM6, если таковой есть в вашем микроконтроллере.

Базовые таймеры 8-битные. И работают только в сторону увеличения. В общем и целом, базовый таймер это простейший вид модуля времени в чистом виде. Никаких других блоков эти таймеры просто не имеют. Поэтому и изучать таймеры мы начнем "с конца", а не в том порядке, в котором они описаны в документации.

Давайте взглянем на функциональную схему самого простого модуля времени

Функциональная схема базового таймера TIM4 в STM8. По клику раскрывается в полном размере. Из документации
Функциональная схема базового таймера TIM4 в STM8. По клику раскрывается в полном размере. Из документации

Итак, основой таймера является 8 разрядный инкрементирующий счетчик UP-COUNTER (TIM4_CNTR). Источник счетных импульсов здесь возможен только один - Fmaster. Что это за источник, если вы уже забыли, можно посмотреть в статье "Микроконтроллеры для начинающих. Часть 44. Тактовые генераторы".

Счетные импульсы поступают на вход счетчика таймера через предделитель (Prescaler). Предделитель является 7-битным счетчиком и управляющим регистром TIM4_PSCR, который позволяет выбрать один из 8 возможных коэффициентов деления (1, 2, 4, 8, 16, 32, 64, 128).

По сути, регистр TIM4_PSCR просто управляет выбором одного из 7 выходов счетных триггеров, которые и образуют 7-битный счетчик. Восьмым вариантом (коэффициент деления 1) является выбор входа счетчика, а не одного их его выходов. Другими словами, предделитель это комбинация счетчика и мультиплексора, что очень похоже на предделитель в AVR.

Но необходимо сделать еще одно уточнение. Документация утверждает, что TIM4_PSCR имеет и теневой регистр, что делает безопасной загрузку нового коэффициента деления безопасным. И что записываемое значение попадает в теневой регистр, откуда и переносится в основной "во время записи младшего байта". Однако это утверждение ошибочно. Предделитель, как 16 битное, двухбайтовое, число можно загрузить только в TIM1. Во всех остальных таймерах регистр предделителя 8-битный.

При этом регистр предделителя действительно можно загрузить в любое время. Так как же все устроено? Довольно просто. Из теневого регистра в рабочий данные переносятся при UEV. То есть, изменение коэффициента деления предделителя сказывается только на следующем временном интервале, а не на текущем.

Регистр автоперезагрузки (Auto-reload register) является комбинацией рабочего регистра, и теневого регистра. Зачем так сделано я расскажу чуть позже, когда будем рассматривать работу таймера.

В этом регистре хранится максимальное значение счетчика таймера. Содержимое регистра непрерывно сравнивается с содержимым счетчика. И когда счетчик таймера становится равным регистру автоперезагрузки счетчик таймера сбрасывается. То есть, регистр TIM4_ARR выполняет функцию аналогичную регистру PR2 в TIMER2 микроконтроллеров PIC.

Таким образом, хорошо видно, что основные идеи и принципы работы таймеров действительно общие, в самых различных микроконтроллерах.

Теперь пришло время рассказать о специфическом принципе описания работы модулей, который использует ST. Специфичность тут не в названиях, а в том, как это показано на иллюстрациях в фирменной документации.

ST выделяет несколько событий, и несколько прерываний. Именно они изображаются ломанными стрелками на функциональных схемах. В данном случае это одно событие, и одно прерывание. Рассмотрим их.

UEV (Update event), в базовых таймерах это событие возникает обнулении счетчика таймера при достижении им максимального значения, хранящегося в TIM4_ARR. При обновлении содержимого счетчика таймера от счетных импульсов это событие не возникает. На иллюстрации выше, событие UEV показано в двух местах. И новичков это сильно сбивает с толку. Но мы с этим разберемся.

UIF (Update interrupt flag), это флаг запроса прерывания, которое поступает на вход контроллера прерываний. Мы еще не изучали прерывания, поэтому подробно рассматривать не буду. Но скажу, что одному запросу прерывания могут соответствовать несколько разных событий, в общем случае. Для базовых таймеров UIF может быть сформирован при UEV. Но неверно считать UIF тождественным UEV. Так как UIF формируется из UEV, но управляются они независимо. Скоро мы это увидим.

Режимы работы TIM4

Работа базовых таймеров может быть разрешения или запрещена битом CEN регистра TIM4_CR1. Если этот бит равен 0, то счетчик таймера останавливается. Обратите внимание, что это дополнительная возможность остановки таймера. Влияния выбора и разрешения тактовых импульсов, управления питанием, это не отменяет, а лишь дополняет.

Кроме этого, базовые таймеры могут работать в непрерывном режиме, и в режиме одиночных импульсов. Выбор режима осуществляется битом OPM регистра TIM4_CR1.

Работа таймера в обоих режимах схожа. Поэтому лишь укажу основное отличие. Если OPM=0, то таймер продолжает счет с нуля при достижении предельного значения. То есть, при наступлении UEV. Если OPM=1, то таймер будет остановлен автоматическим сбросом CEN при автоматическом обнулении счетчика таймера. То есть, при наступлении UEV.

Если провести схемотехническую аналогию, то OPM=0 соответствует мультивибратору. А OPM=1 одновибратору (ждущему мультивибратору), причем импульсом запуска будет момент установки CEN=1.

Подробнее о UEV

UEV штука довольно запутанная, а первый взгляд. Даже в базовых таймерах.

Итак, что мы уже знаем? Что UEV соответствует моменту обнуления счетчика таймера. Но что еще связано с этим событием? А связана с UEV работа TIM4_ARR.

В регистре TIM4_CR1 есть бит ARPE. Если ARPE=0, то записываемое в TIM4_ARR значение вступает в силу немедленно. То есть, мы можем изменить верхний предел счета таймера в любое время.

Если ARPE=1, то записываемое в TIM4_ARR значение на самом деле записывается в теневой регистр (программно не доступен). А в регистр ARR содержимое теневого регистра копируется лишь при наступлении UEV.

Если вернуться к схемотехнической аналогии, ARPE=0 позволяет влиять на длительность формируемого в данный момент импульса. А ARPE=1 позволяет изменить лишь длительность последующих импульсов, но не текущего.

Но мы можем не полагаться на автоматическое аппаратное формирование UEV. Мы можем сформировать UEV сами, в любой момент времени. Для этого нужно установить бит UG в регистре TIM4__EGR. Сбросится этот бит автоматически, аппаратно.

Кроме того, мы можем вообще запретить автоматическое формирование UEV. За это отвечает бит UDIS регистра TIM4_CR1. Если UDIS=0, то UEV формируется как обычно. Но если UDIS=1, то автоматическое формирование события не выполняется. Как не выполняются и все связанные с ним действия. Но мы сохраняем возможность сформировать UEV вручную, как я написал выше.

Важно отметить, что UEV эту внутреннее явление таймера. Его напрямую невозможно зафиксировать. Все это и приводит к тому, что на функциональной схеме UEV показано дважды. И как входящий фактор (влияет на ARR), и как исходящий фактор (используется при генерации прерываний).

А теперь разберемся с тем, как таймер может сформировать запрос прерывания. Но кратко, так как прерывания еще не изучали.

Напрямую UEV зафиксировать невозможно. Но возможно зафиксировать косвенно, при соблюдении определенных условий. Даже в том случае, когда прерывания полностью запрещены.

Запрос прерывания представлен битом UIF регистра TIM4_SR. И мы можем прочитать этот бит не зависимо от того, разрешены прерывания или нет. Бит устанавливается аппаратно, но вот сбрасывать его нужно программно.

Установка UIF может быть запрещена битом UIE=0 регистра TIM4_IER. Это не бить запрещения прерываний! Это бит запрета формирования запроса прерывания от соответствующего таймера. И если формирование запроса запрещено, то UIF не будет устанавливаться. А мы никогда не сможем узнать, что наступило UEV.

Но и это еще не все. Мы можем разделить случаи аппаратного UEV, и сформированного вручную через бит UG. Если бит URS регистра TIM4_CR1 сброшен, то запрос прерывания (если разрешен битом UIE) будет формироваться при любом UEV. А если установлен, то только при аппаратном (UG не будет вызывать формирование запроса).

TIM6

На самом деле, TIM6 не является точной копией TIM4. Он имеет дополнительные управляющие регистры и может использоваться для совместной работы с другими таймерами. Однако, в рамках сегодняшней статьи TIM4 и TIM6 работают одинаково.

Различаются и функциональные схемы таймеров. Так в TIM6 есть Clock/trigger controller, который и реализует возможность совместной работы с другими таймерами.

Таймер общего назначения (TIM2, TIM3, TIM5)

Как мы увидели, базовый таймер довольно функционален. Но у него есть самое главное ограничение - малая разрядность. И второе ограничение - отсутствие захвата/сравнения. Из второго ограничения вытекает и невозможность формирования ШИМ сигналов.

Оба эти ограничения устранены в таймерах общего назначения. Не смотря на то, что блок захвата/сравнения мы сегодня не рассматриваем я приведу полную функциональную схему TIM2

Функциональная схема таймера TIM2 в STM8. По клику раскрывается в полном размере. Из документации
Функциональная схема таймера TIM2 в STM8. По клику раскрывается в полном размере. Из документации

Хорошо видно, что блок (массив) захвата/сравнения действительно является отдельным блоком. В данном случае есть три канала. TIM3 отличается только тем, что у него два канала захвата/сравнения.

Хорошо видно, что TIM2 и TIM3 в качестве счетных импульсов могут использовать только Fmaster, как и TIM4. А вот TIM5можно использовать для совместной работы с другими таймерами, поэтому у него есть и Clock/trigger controller (как у TIM6, только более функциональный).

И в этот раз я покажу полную функциональную схему TIM5. Только для примера того, как это все строится

Функциональная схема таймера TIM5 в STM8. По клику раскрывается в полном размере. Из документации
Функциональная схема таймера TIM5 в STM8. По клику раскрывается в полном размере. Из документации

Собственно говоря, Clock/trigger controller действительно позволяет определять источник импульсов/событий для счета. И формировать выходные сигналы для связи с другими таймерами.

Работу этих дополнительных блоков мы будем рассматривать отдельно в последующих статьях, как я уже говорил. А сегодня объект нашего изучения

Функциональная схема модуля времени таймера TIM2 в STM8. По клику раскрывается в полном размере. Из документации
Функциональная схема модуля времени таймера TIM2 в STM8. По клику раскрывается в полном размере. Из документации

На первый взгляд, это точная копия того, что мы уже рассматривали на примере TIM4. Однако присмотритесь, теперь у нас счетчик таймера и регистр автоперезагрузки стали 16 битными.

Кроме того, регистр предделителя теперь содержит не три, а четыре бита, что позволяет выбирать один из 16 коэффициентов деления. А значит, и счетчик предделитея стал 16-битным.

Поскольку в работе модуля времени, в целом, других изменений нет (кроме изменения количества бит PSCR), я расскажу только о работе с 16 битным счетчиком таймера и регистра автоперезагрузки.

Основная проблема в том, что у нас микроконтроллер 8-битный. А значит, встает вопрос атомарности доступа. Или, более простым языком, для чтения/записи 16 битного значения нам потребуется две операции доступа к регистрам, между которыми содержимое регистров может измениться. Таймер же не останавливается и работает асинхронно к выполнению программы.

При этом регистр управления предделителем и регистр автоперезагрузки имеют теневые регистры, а вот счетчик таймера нет. А значит, точно есть какие то правила, как работать с 16 битными регистрами таймера. Давайте разберемся.

Регистр счетчик CNTR (CNTRH, CNTRL)

Запись в регистр счетчик требуется редко. В основном для его сброса. Поэтому никаких дополнительных механизмов введено не было. И записывать в регистр можно только при остановленном таймере. То есть, CEN=0. А после записи, причем порядок записи байт не важен, можно разрешить работу таймера.

Чтение регистра счетчика гораздо более востребованная операция. Поэтом был добавлен специальный механизм обеспечения атомарности. Вот как это работает:

  1. Читаем старший байт счетчика (CNTRH). При этом автоматически выполняется атомарное считывание всего 16 битного содержимого CNTR. Но как результат чтения возвращается именно старший байт, а младший временно сохраняется в специальном теневом регистре.
  2. Для сохранения прочитанного значения можно выполнить некоторое (теоретически, любое) количество машинных команд и операций.
  3. Читаем младший байт счетчика (CNTRL). При этом фактического чтения регистра CNTR не выполняется, просто возвращается сохраненной на шаге 1 содержимое CNTRL.

Для чтения регистра CNTR нельзя использовать команду LDW, так как она сначала считывает младший байт, а затем старший!

Таким образом, если говорить о программе на языке высокого уровня, нельзя считывать счетчик таймера в 16 битовую переменную!

НЕВЕРНО!

uint16_t counter;

counter=TIM2_CNTR;

При этом будет нарушена правильная последовательность доступа.

ВЕРНО

uint8_t counter_l, counter_h;

counter_h=TIM2_CNTRH;

counter_l=TIM2_CNTRL;

Теперь последовательность доступа не нарушена.

Регистр автоперезагрузки ARR (ARRH, ARRL)

Чтение регистра ARR безопасно в любом случае. Так как этот регистр не изменяется асинхронно аппаратно.

Запись регистра ARR может вызвать проблемы, если между записью двух половин нового значения произойдет UEV. Поэтому и здесь введен специальный механизм обеспечивающий атомарность операции.

  1. Записываем старший байт регистра автоперезагрузки (ARRH). При этом запись фактически выполняется в отдельный теневой регистр предзагрузки. Одновременно, блокируется (автоматически, аппаратно) изменение теневого регистра ARR. Но копирование теневого регистра ARR в рабочий регистр, по UEV, не блокируется. То есть, таймер продолжает штатно работать.
  2. Записываем младший байт регистра автоперезагрузки (ARRL). При этом блокировка изменения теневого регистра ARR снимается и в него, как единое 16 битное число, записывается содержимое регистра предзагрузки (хранит старший байт) и записываемое значение младшего байта.

Для записи регистра ARR нельзя использовать команду LDW, так как она сначала считывает младший байт, а затем старший!

Таким образом, если говорить о программе на языке высокого уровня, нельзя записывать в регистр ARR 16 битовое число или переменную!

НЕВЕРНО!

uint16_t arr;

TIM2_ARR=arr;

При этом будет нарушена правильная последовательность доступа.

ВЕРНО

uint8_t arr_l, arr_h;

TIM2_ARRH=arr_h;

TIM2_ARRL=arr_l;

Теперь последовательность доступа не нарушена.

Возникает вопрос, что будет, если одновременно с записью ARR на шаге 2 наступит UEV? Ничего страшного, просто обновление теневого регистра ARR будет задержано, пока его содержимое не будет скопировано в рабочий регистр ARR.

Расширенный таймер TIM1. Или таймер с расширенным управлением

Ну вот мы и добрались до самого сложного таймера. Но после всего, что мы уже узнали, нам будет гораздо проще.

TIM1 является 16 битным таймером, как и таймеры общего назначения. Но, в отличии от всех остальных, он может работать как на увеличение, так и на уменьшение. А еще, у него появился счетчик повторений.

Я не буду приводить полную функциональную схему TIM1. Дополнительные блоки и их подключение я ранее уже показывал (для примера) у TIM5. И нас они сегодня интересовать не будут. А вот модуль времени рассматривать будем

Функциональная схема модуля времени таймера TIM1 в STM8. По клику раскрывается в полном размере. Из документации
Функциональная схема модуля времени таймера TIM1 в STM8. По клику раскрывается в полном размере. Из документации

Итак, у нас здесь видно два 16-битных регистра. Это уже знакомые нам CNTR и ARR. И как с ними работать из программы мы тоже уже знаем. Но теперь у нас и регистр предделителя стал 16 битным! И теперь он задает не степень двойки, а непосредственно значение, на которое и делится входная частота. И это значение беззнаковое. И может быть любым. Например, мыы совершенно спокойно можем установить коэффициент деления 1:11. Это весьма удобная возможность.

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

А вот при записи в регистр предделителя нам нужно использовать те же самые правила, что и для регистра ARR. Я уже подробно рассмотрел их ранее, поэтому повторяться не буду.

При всем этом у нас сохраняются все управляющие биты, которые мы ранее рассматривали. Но к ним добавляются новые. А поскольку самым большим видимым отличием является работа счетчика таймера как вверх, так и вниз, сначала рассмотрим особенности работы таймера в новых для нас режимах.

Режимы счета

Из всего множества режимов счета на сегодня будут интересны только два. Счет вверх и счет вниз. Счет попеременно вверх-вниз используется в работе блока захвата/сравнения. В основном для формирования ШИМ.

Режим счета выбирается двумя битами [CMS1:CMS0] в регистре CR1. рассматриваемые нами сегодня режимы называются выровненными по фронту (Edge-aligned mode). И им соответсвует состояние бит 00. Это является и состоянием по умолчанию после аппаратного сброса микроконтроллера.

Остальные режими называются выровненными по центру (Center-aligned mode). И их мы сегодня не рассматриваем. Но рассмотрим в будущем.

Направление счета определяется состоянием бита DIR регистра CR1.

Если DIR=0 (состояние по умолчанию), то счетчик работает на увеличение. И его работа полностью аналогична работе счетчика в более простывх таймерах. Включая взаимодействие с регистром ARR, формирование UEV и запросов на прерывание.

Если DIR=1, то счетчик работает на уменьшение. При этом изменяется не только направление счета. Теперь UEV формируется при достижении счетчиком нулевого значения. А вместо сброса по UEV в счетчик заносится содержимое регистра ARR.

Обратите внимание, что если состояние битов CMS отлично от нуля, то бит DIR доступен только для чтения, так его изменение выполняется аппаратно.

Счетчик повторений

Счетчик повторений работает на вычитание. И его работа во многом похожа на работу счетчика таймера в режиме счета вниз. Если содержимое регистра RCR отлично от нуля, то копирование содержимого регистров предзагрузки в теневые регистры не выполняется при наступлении UEV. Но самое UEV формируется и обрабатывается как обычно.

При этом UEV одновременно уменьшает и содержимое RCR. Когда RCR станет равным 0, при наступлении UEV в него запишется содержимое теневого регистра. И содержимое регистров предзагрузки перепишется в теневые регистры. И это касается не только регистра предделителя и регистра ARR. Но другие регистры мы сегодня не рассматриваем.

Я напомню, что регистры предзагрузки хранят записываемое нами новое содержимое теневых регистров. Установка ненулевого значения в RCR позволяет отложить установку новых значений до тех пор, пока мы не запишем новые значения. И эта установка будет выполнена одномоментно. Кроме того, использование RCR позволяет сформировать заданное число одинаковых временных интервалов, не смотря на вносимые изменения в условия их формирования.

Большей частью это используется совместно с блоком захвата/сравнения. И позволяет проводить измерения один раз за несколько выходных импульсов.

В рамках сегодняшней статьи счетчик повторений не имеет практического смысла использовать. Поэтому более подробно я его рассматривать не буду.

Заключение

Мы закончили изучать работу таймеров STM8 применительно к задаче отсчета интервалов времени для программы. Даже такое, весьма узкое их применение, оказалось не таким простым. Так как в STM8 был использован опыт разработки и использования других микроконтроллеров. Тех же PIC и AVR. И таймеры в STM8 сложнее.

При этом эта сложность не так и велика, если как следует разобраться. И хорошо видно, что не смотря на кажущееся радикальное различие реализации таймеров в разных микроконтроллерах, общего у них больше, чем различного.

На сегодня все. В следующий раз будем разбираться, как все полученные о таймерах знания (пока весьма скромные и ограниченные), применить для написания функций задержки. Эти функции все еще будут отнимать у процессора полезное время, так как с прерываниями мы еще не знакомы. Но они уже не будут зависеть ни от последовательности команд, ни от особенностей компилятора. И вместо количества циклов будут оперировать уже параметрами времени.

До новых встреч!