В уроке познакомимся с таймерами микроконтроллера, научимся конфигурировать их в режиме счетчика и организовывать циклические прерывания.
Предыдущий урок Список уроков Следующий урок
В предыдущем уроке я рассказывал о выполнении задач параллельными процессами. Для реализации такого способа необходимо формировать циклические прерывания с заданным периодом. Опять же в предыдущем уроке было сказано, что логичнее и проще это делать с помощью аппаратных таймеров.
В этом уроке мы будем рассматривать таймеры в качестве именно такого функционального применения. Т.е. будем использовать их для генерации циклических прерываний.
Таймеры STM32.
У нашего микроконтроллера STM32F103C8T6 есть 4 таймера:
- TIM1 – расширенный таймер, ориентированный на управление электродвигателем.
- TIM2 … TIM4 – таймеры общего назначения.
Все таймеры имеют одинаковую архитектуру. Расширенный таймер отличается наличием дополнительных аппаратных узлов для формирования противофазных сигналов ШИМ. В результате его можно конфигурировать на работу в режиме 6-канального ШИМ и управлять им тремя полумостовыми усилителями мощности.
Но сейчас нас это не интересует. Для нашей задачи - формирования циклических прерываний, все таймеры имеют одинаковую архитектуру.
Таймеры STM32 - многофункциональные устройства. С помощью каждого из них можно реализовать:
- Счетчик импульсов, а значит и времени с автоматической перезагрузкой.
- Захват входного сигнала (4 канала).
- Обнаружение фронта входного сигнала, запоминание времени, генерация события.
- Измерение временных параметров входного ШИМ-сигнала: периода и длительности импульсов.
- Интерфейс энкодера. Измерение параметров импульсов энкодера.
- Сравнение кодов таймера (4 канала).
- Генерация события по совпадению кода таймера с заданным значением.
- Формирование ШИМ-сигнала.
- Формирование одиночных импульсов, режим одновибратора.
У таймеров большой выбор источников тактирования, есть предделители, обеспечивается цифровая фильтрация входных сигналов, возможна синхронизация между собой и много еще чего.
Но сейчас нас интересует исключительно режим счетчика с перезагрузкой. Именно в этом режиме удобнее всего формировать циклические прерывания.
Функциональная схема таймера достаточно сложная. Я выделил только необходимую нам часть.
Собственно отсчет импульсов или времени происходит на 16-ти разрядном счетчике CNT. Когда код счетчика достигает значения регистра перезагрузки, счетчик сбрасывается в 0. Таким образом, счетчик считает по циклу от 0 до значения регистра перезагрузки.
Частота сигнала тактирования таймера может быть уменьшена с помощью 16-ти разрядного предделителя PSC.
Перезагрузка счетчика формирует событие (прерывание). Частота его появления также может быть уменьшена счетчиком повторов (8 разрядов). Коэффициент деления задается в регистре повторов.
Код счетчика используется другими узлами таймера, например, для формирования ШИМ. Но об этом в других уроках.
В качестве источника тактирования могут быть выбраны:
- Внутренние синхросигналы шин APB1 иAPB2, про которые мы говорили в уроке 5 (система тактирования микроконтроллера).
- Для таймера TIM1 используется синхросигнал шины APB2;
- Для таймеровTIM2- TIM4 используется синхросигнал шины APB1.
- Внешнее тактирование, режим 1. Используется выходной сигнал другого таймера или внешний сигнал входов захвата.
- Внешнее тактирование, режим 2. Используется вывод микроконтроллера ETR.
- Особый режим синхронизации – интерфейс подключения энкодера.
Сейчас мы будем использовать только внутренний источник тактирования.
Режимы счета таймера.
При использовании таймера в качестве счетчика импульсов можно выбрать один из режимов:
- прямой счет;
- обратный счет;
- двунаправленный.
При прямом счете содержимое счетчика с каждым импульсом тактирования увеличивается на 1. Когда оно достигает значения регистра перезагрузки, то счетчик сбрасывается. Таким образом,таймер считает по циклу от 0 до значения перезагрузки. В момент перезагрузки формируется прерывание.
В режиме обратного (реверсивного) счета с каждым входным импульсом содержимое счетчика уменьшается на 1. При достижении 0 в счетчик загружается значение регистра перезагрузки и реверсивный счет продолжается. Таймер считает по циклу от значения перезагрузки до 0. В момент перезагрузки формируется прерывание.
Двунаправленный режим означает, что счетчик считает в прямом направлении от 0 до значения перезагрузки, а затем переходит в реверсивный режим и счет ведется до 0. При изменении направления счета и сбросе генерируется прерывание.
Установка конфигурации таймера с помощью STM32CubeMX.
Давайте научимся конфигурировать таймеры через STM32CubeMX. Заодно в строгой форме перечислим регистры, задающие режимы таймера и выясним, что конкретно в них загружать.
Создадим проект Lesson16_1. Настроим конфигурацию системы тактирования. Обратим внимание на то, что частота тактирования таймеров на шинах APB1 и APB2 задана 72 мГц.
Настроим выводы:
- PC13 – активный выход;
- PB13 – активный выход;
- PB12 – вход с подтягивающим резистором.
Теперь будем конфигурировать таймер 1. В нашем микроконтроллере он самый многофункциональный.
Открываем вкладку Timers ->TIM1.
Выбираем в качестве источника тактирования внутреннее тактирование: Clock Source -> Internal Clock.
Ниже появилось поле Parameter Settings.
Давайте подробно разберем, что в нем.
Prescaler (PSC).
Это регистр предделителя. Предделитель делит частоту тактирования таймера, поступающую на основной счетчик. По сути, он, вместе с входной частотой, определяет разрешающую способность таймера.
Счетчик предделителя считает входные импульсы от 0 до значения этого регистра. При равенстве кода счетчика и регистра счетчик сбрасывается и начинает считать заново. В момент сброса формируется импульс тактирования основного счетчика таймера. Таким образом, значение регистра предделителя определяет коэффициент деления частоты входного сигнала.
Счетчик и регистр предделителя 16-ти разрядные. Т.е. максимальный коэффициент деления 65536.
Надо помнить, что реальный коэффициент деления на 1 больше, чем значение регистра предделителя. Например:
Значение регистра предделителя | Коэффициент деления |
0 | 1 |
999 | 1000 |
65535 (максимальное значение) | 65536 |
Регистр предделителя имеет буферный регистр. Поэтому его значение можно устанавливать в любой момент. Реальное изменение коэффициента деления произойдет при перезагрузке буферного регистра в момент перезагрузки основного счетчика таймера.
Counter mode.
Режим счетчика, определяет в какую сторону считать.
- Up – прямой счет.
- Down – реверсивный счет.
- Center Aligned mode 1 – двунаправленный счет, прерывание генерируется в момент, когда счетчик считает в обратную сторону и доходит до 0.
- Center Aligned mode 2 – двунаправленный счет, прерывание генерируется, когда счетчик считает в прямом направлении и достигает значения перезагрузки.
- Center Aligned mode 3 – двунаправленный счет, прерывание генерируется, в обоих случаях - при достижении 0 и значения перезагрузки.
Counter Period (Auto Reload Register).
Регистр перезагрузки. Его значение задает период работы таймера. Конечно, на время периода влияет еще режим счета.
Счетчик 16-ти разрядный. Значит, для однонаправленного счета период может длиться от 1 до 65536 длительностей импульсов предделителя. Реальная длительность периода на 1 больше значения регистра перезагрузки. Все как для регистра предделителя.
Internal Clock Division (CKD).
Делитель входной частоты для внутренних нужд таймера.
Частота используется при фильтрации внешних сигналов, формировании “мертвого времени” ШИМ и т.п. Сейчас это нам не интересно.
Repetition Counter (RCR).
Регистр счетчика повторов. Присутствует не во всех таймерах. Счетчик повторов считает импульсы событий на выходе таймера и при достижении значения регистра повторов сбрасывается и формирует реальное событие. Т.е. он делит частоту генерации событий (прерываний) таймера.
Счетчик 8-ми разрядный. Коэффициент деления на 1 больше значения регистра повторов и может быть в диапазоне 1 - 256. Регистр буферизирован, можно изменять его значения в любой момент.
Auto-reload preload.
Регистр перезагрузки буферизирован. Разработчики микроконтроллера предоставляют программисту выбор - при записи значения перезагрузки передавать его в регистр моментально или дождаться крайнего состояния счетчика.
Управляет режимом перезагрузки специальный бит, а в STM32CubeMX выбор делается в выпадающем меню.
Вкладка NVIC Settings позволяет выбрать нужный тип прерывания, связанного с таймером.
Пример конфигурации таймера и реализации программы.
Сделаем практическую задачу. Установим конфигурацию таймера 1, обеспечивающую циклические прерывания с периодом 0,5 секунд. В обработчике прерывания будем инвертировать состояние светодиода. В результате получим мигающий светодиод, но с использованием таймера и прерывания.
Частота тактирования у нас 72 мГц. Превратим ее с помощью предделителя в круглое значение.
Например, если задать 720 – 1 = 719, то частота после предделителя будет 72 000 000 / 720 = 100 000 Гц, или период 10 мкс.
Если в регистр перезагрузки задать значение 50 000, то получим требуемый период 0,5 секунд.
Во вкладке NVIC Settings выберем прерывание по перезагрузке счетчика.
Создаем проект и открываем его в Atollic TrueStudio.
В папке Src проекта создан файл stm32f1xx_it.c. Он существовал и во всех предыдущих проектах. Просто мы на него до времени не обращали внимания.
Это файл обработчиков прерываний. Хороший стиль размещать функции обработки прерываний в нем.
В самом конце файла появилась функция:
void TIM1_UP_IRQHandler(void) {
/* USER CODE BEGIN TIM1_UP_IRQn 0 */
/* USER CODE END TIM1_UP_IRQn 0 */
HAL_TIM_IRQHandler(&htim1);
/* USER CODE BEGIN TIM1_UP_IRQn 1 */
/* USER CODE END TIM1_UP_IRQn 1 */
}
Это и есть обработчик прерывания таймера 1. Код, который мы поместим в функцию, будет вызываться с периодом 0,5 секунд.
Вызовем в обработчике прерывания функции инверсии состояния для обоих светодиодов.
void TIM1_UP_IRQHandler(void) {
/* USER CODE BEGIN TIM1_UP_IRQn 0 */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_13);
Мы установили конфигурацию таймера, но не запустили его. Сделаем это HAL-функцией в файле main.c.
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1); // запуск таймера
Функция запускает таймер в режиме генерации прерываний.
Все. Компилируем, загружаем, проверяем. Оба светодиода мигают раз в секунду.
Полностью проект можно загрузить по ссылке:
Основной цикл у нас пустой. Программа просто крутится в нем.
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
По отношению к нему светодиоды мигают в фоновом режиме, сами по себе. Если мы будем выполнять в цикле какие-либо действия, это никак не скажется на равномерном мигании светодиодов. Единственное условие – надолго не запрещать прерывания.
В следующем уроке будем разрабатывать программу, обрабатывающую сигнал кнопки параллельным процессом. Научимся связывать переменные разных файлов одной программы. Разберемся в специфике использования функций обработки прерываний.
Большое Вам спасибо за ваш труд.
У меня уточнение, для получения периода 0,5 секунды на мой взгляд в регистр перезагрузки необходимо задать значение 49999 (50000 — 1). Прав ли я?
Здравствуйте!
Да, конечно. Я ошибся.
Здравствуйте. Небольшая непонятка возникла. В кубе для подсчета импульсов режим работы канала input capture direct mode? Или какой-то другой?
Спасибо!
Здравствуйте, Эдуард! Здоровья Вам и успехов!
Вопрос такой. Если нужно разные процессы запускать от разных таймеров, и при этом с разными периодами от одного таймера, то где и каким образом это прописывать?
Здравствуйте!
Не совсем вас понял.
Можно организовать вызов процесса с минимальным периодом от одного таймера, а остальные процессы отсчитывать от него. Можно сделать программные таймеры. Много вариантов.
Спасибо, я понял. Да, так и сделаю.
Статья очень понятная, осваивается на ходу.
Спасибо, всё понятно и доходчиво!
Я так понимаю, использовать цикл с каим-ниубдь delay не лучшая практика? А периодическое поведение программы лучше делать при помощи счётчиков, они для этого и предназанчены, так что ли?
Здравствуйте!
Да, конечно. Delay подвешивает программу.
Здравствуйте, Эдуард!
«Основной цикл у нас пустой. Программа просто крутится в нем.»
Что значит крутится, если работают только прерывания? какова необходимость в данном случае прописывать пустой цикл
while (1)?
Здравствуйте!
Должна же программа где-то работать между прерываниями. В микроконтроллере есть счетчик команд, который показывает, по какому адресу выбирается команда из памяти. Во время прерывания этот счетчик «перескакивает» на обработчик прерывания, а в остальное время крутится по циклу, где ничего не происходит.
Тогда скажите, Эдуард, возможно ли в принципе, чтобы программа состояла только из обработки прерываний? По идее, при редких внешних событиях это позволило бы вводить режим жесткой экономии энергии при отключении «кручения». Или так не получится? В AVR я так делал.
Да, конечно. Есть режим сна, из которого микроконтроллер выходит по прерыванию.
Спасибо большое. Статья помогла сделать простенький таймер для H743его. Написано очень просто и понятно (как раз для меня).
У меня вопрос.Режим сравнения на AVR в прерывани.А КАК в Stm32 реализовать ?
Эдуард, добрый день! Подскажите, как сделать следующее:
Необходимо настроить, чтобы 1 таймер вызывал 2 прерывания.
Одно прерывание допустим раз в 1 ms.
А второе прерывание зависит от переменной, и оно раньше первого (например через 100, 300, и т.п us)
До этого на avr делал без проблем, настраивал 2 прерывания на таймер. Одно по переполнению, это как раз 1 ms, а второе по совпадению. И второе прерывание генерилось в зависимости от числа.
А как на STM32 ума не приложу.
прерывание на 1 ms нормально работает, а вот со вторым проблема.
Использую HAL
Здравствуйте!
Я эту тему еще не затрагивал в уроках.
В CubeMX Выбираете для канала режим Output Compare или Output Compare No Output.
Mode Toggle On Match.
Pulse – значение для сравнения.
Разрешаете прерывание TIM1 capture compare interrupt.
Примерно так.
Спасибо! Если будет по таймерам урок, в котором более подробно будет рассказано, то это было бы очень здорово
В бесконечном цикле у меня есть конечный автомат в виде оператора switch с переключением case кнопкой.Мне нужно чтобы при каждом переключении менялось частота мигания светодиода, например на выходе PC13. Вопрос — как вызвать из любого case функцию мигания построенной по изложенному в этой статье материалу и как обеспечить изменение частоты в каждом случае. Я понимаю, что изменение частоты происходит с помощью,например,изменения Counter Period.Но как сделать это в случае с switch пока не могу придумать.
Для AVR я создавал функцию мигания с прерыванием по СТС
и вызывал ее с нужным мне значением совпадения переменной из любых case. Пробую научиться работать с
stm32 но пока идет тяжело.
Здравствуйте!
Если частота мигания светодиода невысокая, сделайте эту задачу программно. Например, в прерывании 1 мс в файле stm32f1xx_it.c.
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
// системный тик 1 мс
В каждом вызове прерывания прибавляйте 1 к счетчику, сравнивайте с переменной и сбрасывайте счетчик при превышении им значения переменной. В этот момент инвертируйте состояние вывода.
Останется только задавать значение переменной сравнения счетчика.
Здравствуйте. Большое спасибо за материал. Мой вопрос покажется глупым, но я не могу понять почему если мы каждые 0,5 секунды вызываем прерывание и меняем значение пина, то светодиод горит секунду? До меня не доходит. Спасибо.
Здравствуйте!
Наверно, речь идет о периоде мигания. Он 1 секунда.
Здравствуйте. Все равно не могу понять. Наш таймер переполняется каждые 0,5 секунд и вызывает прерывание, в обработчике которого мы меняем состояние светодиода. По этой логике светодиод должен мигать с периодом 0,5 секунд, а почему он мигает с периодом в 1 секунду. Заранее спасибо!
Вопрос исчерпан. Я все понял. Спасибо)