Всё-таки, меня всегда умиляло название «таймер» для этих сложных штук в микроконтроллерах. Шутка ли: кроме срабатывания в строго заданный интервал, они имеют ещё до десятка дополнительных функций типа генерации ШИМ и подсчёта входящих импульсов. В микроконтроллерах STM32 таймеры настолько круты, что я посчитал нужным разбить их описание на несколько статей.
Таймеров в STM32 много, и они имеют разный набор возможностей, который делит их на 3 группы — базовые (basic timers), общего назначения (general-purpose timers) и продвинутые (advanced-control timers). Последние являются самыми навороченными и включают в себя функции остальных, и вдобавок имеют дополнительные функции для управления всякими трёхфазными двигателями — например, настройку dead-time.
Эта статья будет посвящена базовым таймерам STM32. Они значительно скромнее, согласно руководству по линейке STM32F100xx (можно скачать и тут):
- 16-битный счётчик с автоперезагрузкой.
- 16-битный программируемый делитель частоты: с 1 по 65535.
- Схема синхронизации для запуска
гравиЦАП. - Генерация прерывания и/или запроса DMA по переполнению счётчика.
Таймер действительно совсем базовый — только счётчиком умеет пощёлкивать, но и с таким можно сделать кое-что полезное. Программируемый делитель можно выбрать любой из указанного диапазона: хоть 666, если захочется. Это сильно упрощает настройку частоты счёта таймера по сравнению с теми же AVR (у них всего несколько возможных значений делителя). Например, у нас есть STM32VLDiscovery, на ней камень STM32F100RBT6B, работающий на частоте 24 МГц, и нужно инкрементировать счётчик каждую миллисекунду. Нет проблем: выставляем делитель в 24000, и всего делов. Предделитель, кстати, можно менять прямо в ходе работы таймера.
Переполнение происходит по достижении счётчиком специального числа (периода), которое настраивается в пределах с 0 по 65535. То есть, можно настроить таймер так, чтобы переполнение происходило через каждые 500 тиков, например. Период по умолчанию тоже можно менять в ходе работы таймера, но можно эту функцию отключить программно (даже не спрашивайте, зачем).
У нашего МК базовых таймеров два — TIM6 и TIM7. Задействуем первый для мигания светодиодами:
#include <stm32f10x.h> #include <stm32f10x_gpio.h> #include <stm32f10x_rcc.h> /* В этом файле - всё для работы с таймерами */ #include <stm32f10x_tim.h> /* В этом - для работы с NVIC */ #include <misc.h> enum { BLUE_LED = GPIO_Pin_8, GREEN_LED = GPIO_Pin_9 }; void init_leds(); void init_timer(); int main() { init_leds(); GPIO_SetBits(GPIOC, BLUE_LED); GPIO_ResetBits(GPIOC, GREEN_LED); init_timer(); do __NOP(); while (1); } void init_leds() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Pin = BLUE_LED | GREEN_LED; GPIO_Init(GPIOC, &gpio); } void init_timer() { /* Не забываем затактировать таймер */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); /* Инициализируем базовый таймер: делитель 24000, период 500 мс. * Другие параметры структуры TIM_TimeBaseInitTypeDef * не имеют смысла для базовых таймеров. */ TIM_TimeBaseInitTypeDef base_timer; TIM_TimeBaseStructInit(&base_timer); /* Делитель учитывается как TIM_Prescaler + 1, поэтому отнимаем 1 */ base_timer.TIM_Prescaler = 24000 - 1; base_timer.TIM_Period = 500; TIM_TimeBaseInit(TIM6, &base_timer); /* Разрешаем прерывание по обновлению (в данном случае - * по переполнению) счётчика таймера TIM6. */ TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); /* Включаем таймер */ TIM_Cmd(TIM6, ENABLE); /* Разрешаем обработку прерывания по переполнению счётчика * таймера TIM6. Так получилось, что это же прерывание * отвечает и за опустошение ЦАП. */ NVIC_EnableIRQ(TIM6_DAC_IRQn); } void TIM6_DAC_IRQHandler() { /* Так как этот обработчик вызывается и для ЦАП, нужно проверять, * произошло ли прерывание по переполнению счётчика таймера TIM6. */ if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) { /* Очищаем бит обрабатываемого прерывания */ TIM_ClearITPendingBit(TIM6, TIM_IT_Update); /* Инвертируем состояние светодиодов */ GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (BLUE_LED | GREEN_LED)); } }
Возможно, у вас возник вопрос: а где узнать конкретные константы для прерываний и имена обработчиков? Отвечу: константы смотреть нужно в stm32f10x.h, с учётом категории МК. Наш относится к Meduim density Value line (STM32F10X_MD_VL). Название обработчика обычно состоит из названия константы (без n) и слова Handler:
TIM6_DAC_IRQn → TIM6_DAC_IRQHandler
TIM7_IRQn → TIM7_IRQHandler
Ну, и так далее.
21 комментарий на «“STM32: Урок 6.1 — Базовые таймеры”»
Большое спасибо. Как раз закупил демо-плату с stm32, пока доставят, смогу её изучить работу по вашим статьям.
Огромное спасибо, ваши уроки здорово помогают в освоении платы!=)
Огромное спасибо за старания. Подробного русскоязычного курса по этой платформе еще не было. Автор можно сказать благородным просветительским делом занят. Прикупил себе плату, буду по ходу дела изучать.
В целом очень много общего с AVR-ками. А возможностей гораздо больше.
Привет. сделал все как в статье, но обработчик переполнения не вызывается((
библиотеки все есть, среда — IAR 6.21(c++ project), плата — STM32 Discovery
пробовал и на TIM6, и на TIM7 — не работает.
Код для Таймера7
вроде все по инструкции… плиз, хелп ми.
Странно, ничего подозрительного не вижу. Может, в IAR в startup-коде обработчик по-другому называется? У меня IAR’а нет, проверить не на чем. Выложите архив своего проекта на какой-нибудь файлообменник типа Народ.Диск.
МойProject
Пока качается IAR, я заглянул в ваши сорцы. Что ж вы сразу не сказали, что на C++ кодите? Компиляторы C++ всегда добавляют к именам функций разные символы для обозначения возвращаемого типа и типов аргументов, т.к. в C++ может быть несколько функций с одинаковым именем, но с разными типами и числом аргументов (т.н. «перегрузка» функций). А то, что делает компилятор C++ с именами функций называетсяname mangling .
Я заглянул в объектный файл main.o, сгенерированный из исходника main.cpp, и увидел то, что и ожидал: ваш обработчик называется в нём "_Z15TIM7_IRQHandlerv". По имени видно, что 15 — это длина оригинального имени «TIM7_IRQHandler», «v» обозначает, очевидно, void (который вместо аргументов), а _Z — void, который возвращает функция. Чтобы указать компилятору, что функция не C++’ная, а C’шная, нужно писать:
Но ещё лучше не маятся фигнёй и писать на чистом C, попутно изменив расширение имени файла на .c вместо .cpp (:
я сразу написал что IAR 6.21(c++ project)…
а можно сделать екстерн для всех функцый ST, чтоб каждую не екстернить?
в том то й дело, что мне надо с++(ради класов).
А, блин, не заметил сразу — не привык, что кто-то под МК на плюсах пишет, увидел «с project» на автомате (:
Во всех заголовочных файлах SPL и так extern «C» стоит, а TIM7_IRQHandler — это ж ваша функция, вот её и нужно экстернить. Если хотите, можно сразу несколько функций в блок засунуть — extern «C» {… }
Другое дело, что не работает… Тут я даже не знаю, в чём дело. Завтра посмотрю в IAR.
для STM32VLDISCOVERY нужны старые драйвера, так как с новыми он не совместим, по этому все проблемы(сам в шоке).
для STM32VLDISCOVERY нужны старые драйвера, так как с новыми он не совместим, по этому все проблемы(сам в шоке).
StLink Driver
Заменил DLLку, всё равно не пашет:
«Fatal error: ST-Link Connection error»
Я ж говорю — ну его нафиг. Кривая IDE + C++ — это взрывная смесь. Я лично устал с этим ковыряться, ковыряйтесь сами.
Вы лучше скажите, что вас побудило пользоваться IAR, когда есть Eclipse и куча IDE на его базе? Интересно, чем эта среда может быть лучше других хотя бы теоретически.
первый урок. который я нашел, был на IAR. да и кряк быстро нашелся)))
чистый еклипс позволяет работать с СТ?
так как перспектив в 32Кб кода не очень…
Урок по настройке Eclipse и остального добра
robocraft.ru/blog/ARM/653.html
В конце написано про уже собранную IDE, но там старая версия утилиты stlink, может медленно заливать на МК.
спасибо. еще IAR поковыряю, если будет глухо, перейду на еклисп… статья большая, а я ленивый к таким настройкам))))
но если будет глухо, то от судьбы не уйдешь)))
P.S. спасибо большое за поддержку. отличный блог, очень детально все изложено. продолжайте в таком же духе
я уже понял глупость вопроса…
и так не работает
Установил я IAR 6.21… Мдааа, такой печальки я не ожидал, конечно. Прямо привет из 90-х какой-то. Я так и не заставил эту шнягу работать с ST-Link, и даже в режиме GDBServer эта безмозглая IDE не смогла залить прошивку. Все настройки облазил, несколько раз перенастроил проект для STM32VLDISCOVERY — всё без толку. Мой вам совет — выкиньте это дерьмо мамонта и поставьте себе Eclipse. С такой IDE, как IAR, я ничем помочь не могу, увы — слишком много гемора.
В комментариях по настройке IDE спрашивал спрашивал, как настроить Eclipse, что бы работала функция sprintf(txt, "%f", float fl). После вызова данной функции контроллер зависает по неопределённому прерыванию в бесконечном цикле, при этом функция для чисел int работает. Кроме этого, после объявления числа как double, контроллер вообще не грузится. Не говоря уже необходимых «заглушках», что бы подключить stdlib. Настроить IAR понадобилось 2 часа, и всё работает и LINK и print(). Если кто объяснит как побороть грабли с float и double, останусь на современном Eclipse, а не на «прошлом веке».
Артем… что то я невнимательно отлаживал… короче, программа падает при
еще не доходя к хендлеру(что пока радует)… есть идеи почему так?
и как поймать ошибку в логи?
возможно массив NVIC->ISER не существует, или сам NVIC…
еще скриншот
screencast.com/t/d8ZBZ1SyPqy
внутренности так сказать)) именно на той функции