Урок 5
Библиотека HAL. STM32 CUBE MX. Таймеры
Сегодня мы попробуем поработать с таймерами также с помощью библиотеки HAL.
У нашего контроллера STM32F407 таймеров много.
Открываем reference manual и перейдём в главу 17 — Advanced-control timers (TIM1&TIM8).
Данные таймеры — это таймеры с расширенным управлением. Таких таймеров всего два
Далее открываем следующий пункт 19 — General-purpose timers (TIM9 to TIM14).
Это таймеры общего назначения. У них функционал очень ограничен, зато их у нас целых шесть
Далее идем в пункт 20.2 TIM6&TIM7 main features
Данные таймеры — базовые. Функционал немного расширен
Также надо отметить особо таймеры 2 и 5, которые являются 32-битными, что не может не заинтересовать разработчика.
Во-первых в силу своего высокого разрешения они могут считать до более 4 миллиардов, а также у них очень широкий функционал.
Теперь мы научимся делать из предыдущего проекта следующий, чтобы заново не настраивать все в Cube и не потерять наш рукописный код в проекте Keil.
Для этого создадим папку TEST02.
Скопируем туда файлы .mxproject и TEST001.ioc
Исправим имя файла TEST001.ioc на TEST002.ioc
Внутри файлов также везде меняем TEST001 на TEST002 (получится в 5 местах).
Также копируем со старого проекта папки src и inc с содержимым. Всё!
Проект готов. Запускаем TEST002.ioc
Давайте пока воспользуемся базовыми таймерами, а вернее одним из них – таймером 6
В Cube MX ставим TIM6 -> Activated
Пройдем в мануал и посмотрим (стр. 65-66), на какой шине какой таймер находится — это APB1 и настроим ее в Clock Configuration. Здесь главное не превысить граничную частоту (42 МГц).
Давайте поставим делитель APB1 prescaler равным 8,частота получится 21 мГц, а для таймера будет 42. Нам этого вполне хватит.
Затем идем в закладку Configuration и настраиваем там таймер.
Жмем на TIM6
В закладке Parameter Settings выставляем следующие значения
Prescaler (PSC – 16 bit value) – 20999
Counter Period – 499
Trigger Event Selection – Update Event
В закладке NVIC Settings включаем глобальные прерывания таймера 6
Жмем Apply
Генерируем проект Keil5 таким же образом как и на прошлом занятии
Собираем проект в кейле. Открываем main.c – как видим ничего не пропало никуда.
Попробуем прошить и запустить. Удивительно. Все работает.
Далее подключаем таймер.
Открываем STM32F4HAL_User_manual.pdf
Откроем там раздел 61.3.3 Time Base functions
Найдем фукцию, запускающую таймер HAL_TIM_Base_Start
Вставляем код в main.c
MX_GPIO_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim6);
/* USER CODE END 2 */
Также запускаем прерывания от таймера
Для этого есть аналогичная функция HAL_TIM_Base_Start_IT
Вставляем ее сюда же
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
В файле stm32f4xx_it.c находим функцию прерывания от таймера и добавляем туда код
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
/* USER CODE END TIM6_DAC_IRQn 1 */
}
Из бесконечного цикла уберем вот эту строку, чтобы не мешала работать таймеру
else HAL_GPIO_WritePin(GPIOD, PIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
Прошиваем, смотрим.
Теперь попробуем разнообразить
Файл main.h из старого проекта положим в папку inc и подключим его в главном модуле main.c
/* Includes ——————————————————————*/
#include «stm32f4xx_hal.h»
/* USER CODE BEGIN Includes */
#include «main.h»
/* USER CODE END Includes */
Очистим его, оставив только директивы и добавим туда глобальную переменную
#ifndef MAIN_H_
#define MAIN_H_
uint8_t tim6_counter;
#endif /* MAIN_H_ */
Проинициализируем ее в функции main
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Start_IT(&htim6);
tim6_counter=0;
/* USER CODE END 2 */
Проэкстерналим в файле stm32f4xx_it.c
#include «stm32f4xx_hal.h»
#include «stm32f4xx.h»
#include «stm32f4xx_it.h»
/* USER CODE BEGIN 0 */
extern uint8_t tim6_counter;
/* USER CODE END 0 */
Изменим код в функции прерывания в этом же файле
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
switch(tim6_counter)
{
case 0:
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
break;
case 1:
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
break;
case 2:
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);
break;
case 3:
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);
break;
}
if(tim6_counter<3) tim6_counter++;
else tim6_counter=0;
/* USER CODE END TIM6_DAC_IRQn 1 */
В файле main.c уберем запуск таймера
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Start_IT(&htim6);
А в бесконечном цикле код изменим следующим образом
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_0)==GPIO_PIN_SET)
{
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Start_IT(&htim6);
}
else
{
tim6_counter=0;
HAL_TIM_Base_Stop(&htim6);
HAL_TIM_Base_Stop_IT(&htim6);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
}
}
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F4-DISCOVERY
Смотреть ВИДЕОУРОК в RuTube
Смотреть ВИДЕОУРОК в YouTube
Как сделать так, что бы прерывание возникало при нажатии на кнопку?
Можно будет использовать внешние прерывания. Очень скоро будет урок.
Спасибо огромное за изложенный материал , но в данном посте не могу найти stm32f4xx_it.c в исходниках .
А разве в папке Src его нет?
Нету , его вообще нигде нету .
а имеет ли смысл вызывать два раза функцию
HAL_TIM_Base_Start(&htim6);
HAL_TIM_Base_Start_IT(&htim6); ?
кажется вполне достаточно одного вызова
HAL_TIM_Base_Start_IT(&htim6);
Не даны пояснения, в чем разница между двумя этими функциями
Ну да, достаточно. Давно писал урок. Разница в том, что если прерывания используются, то _IT, если нет — то без _IT
У меня как раз если обе строки, то светодиод не мигает совсем, а если оставить только строку с вызовом прерывания — то все супер
Спасибо. Вы мне очень помогли. Тоже заработало только после того как инициализацию произвёл через _IT не используя другую
Тоже часа 2 убил пока допёрло что надо одной строкой с IT делать .
У меня прерывание (использую таймер 6) срабатывает сразу как только запускается таймер.Как сделать так чтобы прерывание срабатывало только по периоду(То есть запускается таймер, проходит период и запускается прерывание)?
Здравствуйте! Спасибо за Ваши уроки, очень помогают в освоении STM32!
Вопрос следующий- нужно ли очищать флаги прерываний или HAL делает это сам? У меня возникает проблема с ложным вызовом прерывания от кнопки, хотя кнопка подтянута и программно и резистором к + питания, возможно прерывания от других модулей дают такой эфект? Спасибо!
Здравствуйте! Флаги очищать не надо. Если Вы откроете автообработчик включенных в Cube глобавльных прерываний, то увидите там сброс флага.
Спасибо, понятно! А вот что вызывает ложное срабатывание на внешнем прерывании я так и не разобрался, у меня 2 USARTA 1 I2C, SDIO и EXTI. Обработчик написан только для EXTI и раз в час на нем происходит ложный вызов. При двойной проверки внутри обработчика на низкий уровень пина проблема вроде-как решилась. Может у кого то была подобная проблема? И Как грамотно распределить приоритеты прерываний? я их не трогал — везде 0.
делаю так как написано.
выбивает ошибку:
../Src/main.c(247): error: #159: declaration is incompatible with previous "_Error_Handler" (declared at line 150)
что не так?
хотя прикреплённый архив с исходниками запускается нормально
Обновите полностью репозиторий, Cube и Keil. Что-то не сходится у Вас
сразу же отвечу:
_Error_Handler(__FILE__, __LINE__); переименовал в Error_Handler(); —
ещё небыло декларации, сделал:
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_TIM6_Init(void);
А также сыпало ошибки в других файлах, … Error: L6200E: Symbol tim6_counter multiply defined …
решение — в файле main.h делаем extern uint8_t tim6_counter; , а в файле stm32f4xx_it.c — просто uint8_t tim6_counter;
Источник — http://forum.easyelectronics.ru/viewtopic.php?f=35&t=24247
Спасибо за решение ошибки Error: L6200E: Symbol tim6_counter multiply defined. Помогло.
Thank you much, I'm was sitting since 4 hours on this problem…
Спасибо! Теперь все ок)
Если мы откроем reference manual и перейдём в главу 17, а именно на страницу 519, то увидим диаграммы. Из которых следует что если мы хотим разделить частоту шины на 2, то prescaler нужно установить 2, если на 4, то 4. а если 21000, то 21000. И единицу отнимать не нужно.
А вот в Counter Period единицу отнимать нужно. Это видно на диаграмме и написано в reference manual. Цитата: "If the repetition counter is used, the update event (UEV) is generated after upcounting is repeated for the number of times programmed in the repetition counter register plus one (TIMx_RCR+1)."
Только знакомлюсь с микроконтроллерами, не подскажите, где именно в reference manual такое написано, просмотрел с 518 страницы, что-то не увидел. А в диаграммах пока не очень разбираюсь.
С Counter Period понятно, что надо -1, т.к. счет с 0 начинается. А вот с предделителем действительно интересно, почему везде его на -1 уменьшают.
В документе AN4776 «STM32 Timer cookbook» можно найти истину:
/* Set the Timer prescaler to get 8MHz as counter clock */
Prescaler = (uint16_t) (SystemCoreClock / 8000000) — 1;
в HAL таким же образом нужно отнимать единицу, он сам нигде это не делает.
Здравствуйте. Подскажите, пожалуйста, в каких случаях срабатывают прерывания TIM2_IRQHandler? Т.е. если я использую данный таймер с прерываниями (как у Вас в статье), необходимо ли мне производить доп. проверку в функции TIM2_IRQHandler или у таймеров только по одному событию формируется прерывание?
Например, как в АЦП, есть общие прерывания (HAL_ADC_IRQHandler), а есть отдельная функция HAL_ADC_ConvCpltCallback(), которую более предпочтительнее использовать. Есть ли аналогично в таймерах? Спасибо.
Здравствуйте!
Не нужно никаких проверок. Урок уже сравнительно не новый, поэтому используется такая обработка. Лучше использовать коллбэки и ничего в этом файле не проверять. Если Вы посмотрите дальнейшие уроки, то мы давно уже так и делаем.
Подскажите, а какая функция коллбэк для таймеров? Для АЦП HAL_ADC_ConvCpltCallback(), а какая для таймера?
Нашёл у Вас в 21-м уроке функцию HAL_TIM_PeriodElapsedCallback(). Подскажите, а в чём преимущества перед обычным обработчиком прерывания? И как определять какой таймер вызвал данную колбэк функцию, сравнивать в данной функции указатели?
Большое спасибо на уроки!
Скажите пожалуйста, никак не могу написать свой ГУИ для управления светодиодами и опроса кнопок.
Юзаю libUSB для C#
Какие ID должны быть в посылках со стороны ПК на МК ?
Здравствуйте, nordstream, спасибо большое за ваши видео, которые я пробовал пару раз, чтобы сделать это упражнение, без успеха дает мне ряд ошибок, когда я создаю переменную uint8_t, вы можете мне помочь. спасибо
Здравствуйте
подключите библиотеку stdint.h
Та же ошибка с unit8_t. Искал в ваших исходниках, где подключается данная библиотека — не нашел ни в одном из файлов — stdint.h нет нигде. Команда подключения библиотеки #include «stdint.h» тоже не исправляет ошибку. Хотя, видно, что файл stdint.h присутствует в Keil в раскрывающимся списке файла main.c. Видимо, вы что-то недорассказали в этом уроке.
Все застопорилось на ошибке с unit8_t. Может, из-за 5-го куба?
это не урок, а лишь представление о том, что нужно тыкнуть, чтобы что-то работало. Урок состоит в объяснении принципов работы. А тут если в Кубе лень нажать пару портов на выход и написании дергании ножек и из-за этого заниматься ******** с перекидыванием файлов из одной папки в другу и переименовыванием названий…это сильно!
Я старался по максимуму на первых порах объяснять что и почём. Если Вы считаете, что это для Вас «низко», то переходите сразу к последним урокам.
И в следующий раз прошу воздерживаться от слов-паразитов в комментариях, глобальная публичная сеть всё-таки, а не базар.
Здравствуйте! Извините, только начал изучать stm,
можно ли изменить sounter period во время работы программы?
Для глобального счётчика надо подписывать модификатор volatile, у меня без него оптимизатор ломал остановку таймера в цикле по условию. (F103/CubeIDE)
Надо в main.h добавить директиву #include «stdint.h»
C новым годом !
Спасибо за великолепные уроки
повторил на Stm32f103c8t6
В качестве таймера TIM2 HAL_TIM_Base_Start_IT(&htim2);