Всем доброго времени суток, сегодня мы продолжим исследовать возможности STM32CubeMx и рассмотрим таймеры. Вот предыдущие статьи цикла:
Без лишних слов, перейдем сразу к делу. В предыдущей статье мы разобрались как настраивать и использовать порты ввода-вывода, а сегодня рассмотрим конфигурацию таймера и прерываний. Пусть будет такая задача - задействуем таймер таким образом, чтобы он генерировал прерывания, допустим, каждые 500 мс, и в прерывании помигаем диодом для наглядной демонстрации работы. В принципе, ничего сложного, классический тестовый пример, так что приступаем.
Выполняем привычную последовательность действий - создаем и настраиваем новый проект в STM32CubeMx. И начнем с уже знакомого - активируем один из выводов для работы в качестве выхода, чтобы управлять светодиодом. Пусть будет PD12, не буду даже скриншот приводить, все это мы уже осуществляли в предыдущей статье курса 👍
Вторым этапом в данном случае будет активация и настройка непосредственно таймера, возьму TIM3 для этого примера, существенной разницы, какой именно модуль задействовать, нет:
Выбрав TIM3 на левой панели осуществляем последовательно следующее:
- собственно, активируем его, задав Clock Source
- задаем предделитель частоты таймера через поле Prescaler
- и, наконец, задаем период в поле Counter Period.
Сегодня у нас базовый пример, поэтому остальные настройки не трогаем, оставим пока на будущее. Итак, разбираемся с частотами и временными интервалами.
Таймер сидит на шине APB1, частота которой составляет 16 МГц (это настройка по умолчанию для данного контроллера и текущей версии CubeMx - как изменить или узнать тактовые частоты я расскажу в следующей статье). Выбираем делитель частоты равным 16000 (необходимо установить значение, меньшее на 1). В итоге получаем (16 МГц / 16000) = 1000 Гц. То есть один тик таймера будет соответствовать 1 мс. Установив период 500, получим прерывания по переполнению таймера каждые 500 мс - то, что нам и надо. Здесь же, во вкладке "NVIC Settings", нужно включить прерывание, и на этом процесс настройки завершен:
Генерируем код и получаем готовый проект для выбранной IDE.
Теперь осталось совсем немного - запустить наш таймер (STM32CubeMx занимается только инициализацией, все активные действия мы должны совершать сами). Кроме того, в прерывании по переполнению таймера мы будем менять состояние светодиода. Cам обработчик прерывания CubeMx создал за нас, и найти его совсем несложно - для всех прерываний генерируется отдельный файл stm32f4xx_it.c (либо для другого контроллера аналогичный, например, stm32f1xx_it.c). В принципе, мы можем поместить наш код непосредственно в обработчик. Но HAL предлагает альтернативный способ с использованием callback-функций. Пара слов об этой концепции...
В библиотеке реализованы различные функции, объявленные с атрибутом weak
, которые может использовать пользователь для добавления своего кода. Атрибут weak
используется для функций-заглушек, которые могут быть переопределены. Таким образом, при наступлении определенного события в библиотеке происходит вызов такой callback-функции, тело которой является по умолчанию пустым. Но если программист переопределил эту функцию, то будет вызван этот новый вариант.
Для наглядности и понимания процессов рассмотрим на нашем конкретном примере. Механизм выглядит следующим образом:
- Для таймера (как и для любой другой периферии) есть ряд callback-функций. Их список можно найти в описании HAL, нас же сейчас интересует вполне конкретная, а именно
HAL_TIM_PeriodElapsedCallback()
. Именно она соответствует событию переполнения таймера. - Эту функцию мы определяем, поместив в нее необходимый функционал.
- И по итогу она будет вызвана автоматически из библиотеки при наступлении соответствующего события.
Давайте все это поэтапно осуществим. Для начала идем в main()
и запускаем таймер:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM3_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim3); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Поскольку мы будем использовать прерывания, то нам нужна функция HAL_TIM_Base_Start_IT()
. А теперь в этом же файле main.c добавим callback-функцию. При этом обязательно размещаем внутри секций вида:
/* USER CODE BEGIN 4 */ /* USER CODE END 4 */
Это нужно для того, чтобы после того как файлы будут перегенерированы (при изменении проекта в CubeMx), данный код был также автоматически перенесен в новые файлы:
/* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); } /* USER CODE END 4 */
Здесь при помощи HAL_GPIO_TogglePin()
мы меняем состояние светодиода.
В итоге функция будет вызываться каждый раз при переполнении таймера (аналогично соответствующему прерыванию). И, поскольку тут в качестве аргумента наличествует структура TIM_HandleTypeDef
, которая является базовой при работе с таймерами, то мы можем ее использовать, чтобы убедиться, что прерывание вызвано именно TIM3
, а не другим модулем:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); } }
Теперь все готово, собираем проект и прошиваем контроллер. Сразу же видим мигающий диод, причем мигает он каждые 500 мс, а следовательно задача успешно реализована 👍
Как видите, STM32CubeMx оказывается довольно-таки удобным инструментом для решения своих задач, и это заметно даже в небольших проектах, поэтому в следующих статьях мы продолжим его изучение, оставайтесь на связи!