ОБОРУДОВАНИЕ
ТЕХНОЛОГИИ
РАЗРАБОТКИ
Блог технической поддержки моих разработок
Урок 16. Таймеры STM32 в режиме счетчиков. Генерация циклических прерываний от таймеров.
В уроке познакомимся с таймерами микроконтроллера, научимся конфигурировать их в режиме счетчика и организовывать циклические прерывания.
В предыдущем уроке я рассказывал о выполнении задач параллельными процессами. Для реализации такого способа необходимо формировать циклические прерывания с заданным периодом. Опять же в предыдущем уроке было сказано, что логичнее и проще это делать с помощью аппаратных таймеров.
В этом уроке мы будем рассматривать таймеры в качестве именно такого функционального применения. Т.е. будем использовать их для генерации циклических прерываний.
Таймеры 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 больше, чем значение регистра предделителя. Например:
Значение регистра предделителя | Коэффициент деления |
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 */
/* 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 */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1); // запуск таймера
Функция запускает таймер в режиме генерации прерываний.
Все. Компилируем, загружаем, проверяем. Оба светодиода мигают раз в секунду.
Полностью проект можно загрузить по ссылке:
Зарегистрируйтесь и оплатите. Всего 40 руб. в месяц за доступ ко всем ресурсам сайта!
Основной цикл у нас пустой. Программа просто крутится в нем.
while (1) <
/* USER CODE END WHILE */
По отношению к нему светодиоды мигают в фоновом режиме, сами по себе. Если мы будем выполнять в цикле какие-либо действия, это никак не скажется на равномерном мигании светодиодов. Единственное условие – надолго не запрещать прерывания.
В следующем уроке будем разрабатывать программу, обрабатывающую сигнал кнопки параллельным процессом. Научимся связывать переменные разных файлов одной программы. Разберемся в специфике использования функций обработки прерываний.
Источник
STM32 с нуля. Timer. Настройка и использование таймеров.
Таймеры в STM32, как в принципе и вся периферия, являются очень навороченными. От обилия разных функций, которые могут выполнять таймеры может даже закружиться голова. Хотя, казалось бы, таймер он на то и таймер, чтобы просто считать. Но на деле все гораздо круче!
Мало того, что таймеры обладают такими широкими возможностями, так их еще несколько у каждого контроллера. И даже не два и не три, а больше! В общем, нахваливать все это можно бесконечно. Давайте уже разбираться, что и как работает. Итак, микроконтроллер STM32F103CB имеет:
- 3 таймера общего назначения (TIM2, TIM3, TIM4).
- 1 более продвинутый таймер с расширенными возможностями (TIM1).
- 2 WDT (WatchDog Timer).
- 1 SysTick Timer.
Собственно таймеры общего назначения и таймер TIM1 не сильно отличаются друг от друга, так что ограничимся рассмотрением какого-нибудь одного из них. К слову я остановил свой выбор на TIM4. Без особой причины, просто так захотелось :). Таймеры имеют 4 независимых канала, которые могут использоваться для:
- Захвата сигнала.
- Сравнения.
- Генерации ШИМ.
- Генерации одиночного импульса.
Таймеры 16 битные (то есть могут считать до 65535), умеют работать с инкрементальными энкодерами и датчиками Холла, несколько таймеров можно синхронизировать между собой. Есть прерывания на разные события, а именно:
- Переполнение.
- Захват сигнала.
- Сравнение.
- Событие-триггер.
При наступлении любого из этих событий таймеры могут генерировать запрос к DMA (DMA – прямой доступ к памяти, уже скоро мы будем разбираться и с ним). Теперь немного подробнее о каждом из режимов работы таймеров.
Режим захвата сигнала. Очень удобно при работе таймера в этом режиме измерять период следования импульсов. Смотрите сами: приходит импульс, таймер кладет свое текущее значение счетчика в регистр TIM_CCR. По-быстрому сохраняем это значение в какую-нибудь переменную. Сидим, ждем следующий импульс… Импульс пришел, таймер снова фиксирует значение счетчика в TIM_CCR, и нам остается только вычесть из этого значения то, которое мы предварительно сохранили. Это, наверное, самое простое использование этого режима таймера, но очень полезное. Отлавливать можно как передний фронт импульса, так и задний, так что возможности довольно велики.
Режим сравнения. Тут просто подключаем какой-нибудь канал таймера к соответствующему выводу, и как только таймер досчитает до определенного значения (оно в TIM_CCR) состояние вывода изменится в зависимости от настройки режима (либо выставится в единицу, либо в ноль, либо изменится на противоположное).
Режим генерации ШИМ. Ну тут все уже понятно из названия ? В этом режиме таймер генерирует ШИМ! Наверно нет смысла что-то писать тут еще сейчас. Скоро будет пример как раз на ШИМ, там и поковыряем поподробнее.
Режим Dead-Time. Суть режима в том, что между сигналами на основном и комплементарном выводах таймера появляется определенная задержка. В интернете есть довольно много информации о том, где это можно и нужно применять.
Ну вот, в принципе, кратко об основных режимах работы таймера. Если будут вопросы про другие режимы, более специфические, пишите в комментарии, буду рад помочь!
Надо бы потихоньку написать программу для работы с таймерами. Но сначала посмотрим, что есть в библиотеке Standard Peripheral Library.
Итак, за таймеры несут ответственность файлы – stm32f10x_tim.h и stm32f10x_tim.c. Открываем первый и видим, что структура файла повторяет структуру файла для работы с GPIO, который мы рассматривали в предыдущей статье. Здесь описаны структуры и поля структур, которые нужны для конфигурирования таймеров. Правда здесь уже не одна, а несколько структур (режимов, а соответственно и настроек-то у таймеров побольше, чем у портов ввода-вывода). Все поля структур снабжены комментариями, вот, например:
Здесь будем задавать режим работы таймера. А вот еще:
Здесь выбираем канал таймера, ничего неожиданного ? В общем все довольно прозрачно, если что спрашивайте!
С первым файлом понятно. А в файле stm32f10x_tim.c – готовые функции для работы с таймерами. Тоже все в целом ясно. Мы уже использовали библиотеку для работы с GPIO, теперь вот работаем с таймерами, и очевидно, что для разной периферии все очень похоже. Так что давайте создавать проект и писать программу. Итак, создаем новый проект, добавляем в него все необходимые файлы:
- необходимо отметить, что в поле TIM_Prescaler нужно записывать значение, на единицу меньшее, чем то, которое мы хотим получить.
В этой программе мы смотрим, что было на выходе до момента генерации прерывания – если ноль, выставляем единицу на 0.5 мс. Если была единица – ставим ноль на 2.5 мс. Компилируем и запускаем отладку!
Небольшое, но очень важное отступление… Наш пример, конечно, будет работать и для теста он вполне сгодится, но все-таки в “боевых” программах нужно следить за оптимальностью кода как с точки зрения его объема, так и с точки зрения производительности и расхода памяти. В данном случае нет никакого смысла использовать структуру timer, а также вызывать функцию TIM_TimeBaseInit() каждый раз при смене периода. Правильнее менять всего лишь одно значение в одном регистре, а именно в регистре TIMx->ARR (где х – это номер таймера). В данном примере код трансформируется следующим образом:
Итак, продолжаем, на пути у нас очередные грабли! А именно ошибка:
- ..\..\..\SPL\src\stm32f10x_tim.c(2870): error: #20: identifier “TIM_CCER_CC4NP” is undefined
Не так страшно как может показаться, идем в файл stm32f10x.h, находим строки:
И смело дописываем:
Вот теперь все собирается, можно отлаживать. Включаем логический анализатор. В командной строке пишем: la portb&0x01 и наблюдаем на выходе:
Видим, что все работает правильно! В следующей статье будем изучать режим генерации ШИМ, оставайтесь на связи ?
Источник