Stm32 описание таймеров. STM32: базовые таймеры

Обновление

Stm32 описание таймеров. STM32: базовые таймеры

В STM32 есть множество очень удобных и гибких в настройке таймеров. Даже у самого младшего микроконтроллера (STM32F030F4P6) есть 4 таких таймера.

8. Настроим проект – добавим нужные файлы

Чтобы использовать таймер, нам потребуется подключить файл библиотеки периферии stm32f10x_tim.c. Точно так же, правой кнопкой щёлкаем в Workspace (окно слева) по группе StdPeriphLib, Add –> Add files, файл LibrariesSTM32F10x_StdPeriph_Driversrcstm32f10x_tim.c.

Ещё нужно включить использование заголовка к этому файлу. Открываем stm32f10x_conf.h (правой кнопкой по названию этого файла в коде, «Open stm32f10x_conf.h». Раскомментируем строчку #include «stm32f10x_tim.h».

9. Добавим таймер

Задержка пустым циклом – это кощунство, тем более на таком мощном кристалле как STM32, с кучей таймеров. Поэтому сделаем эту задержку с помощью таймера.

В STM32 есть разные таймеры, отличающиеся набором свойств. Самые простые – Basic timers, посложнее – General purpose timers, и самые сложные – Advanced timers. Простые таймеры ограничиваются просто отсчётом тактов. В более сложных таймерах появляется ШИМ. Самые сложные таймеры, к примеру, могут сгенерировать 3–фазный ШИМ с прямыми и инверсными выходами и дедтаймом. Нам хватит и простого таймера, под номером 6.

Немного теории

Всё, что нам требуется от таймера – досчитывать до определённого значения и генерировать прерывание (да, мы ещё и научимся использовать прерывания). Таймер TIM6 тактируется от системной шины, но не напрямую а через прескалер – простой программируемый счётчик–делитель (подумать только, в СССР выпускались специальные микросхемы–счётчики, причём программируемые были особым дефицитом – а теперь я говорю о таком счётчике просто между делом). Прескалер можно настраивать на любое значение от 1 (т.е. на счётчик попадёт полная частота шины, 24МГц) до 65536 (т.е. 366 Гц).

Тактовые сигналы в свою очередь, увеличивают внутренний счётчик таймера, начиная с нуля. Как только значение счётчика доходит до значения ARR – счётчик переполняется, и возникает соответствующее событие. По наступлению этого события таймер снова загружает 0 в счётчик, и начинает считать с нуля. Одновременно он может вызвать прерывание (если оно настроено).

На самом деле процесс немного сложнее: есть два регистра ARR – внешний и внутренний. Во время счёта текущее значение сравнивается именно со внутренним регистром, и лишь при переполнении внутренний обновляется из внешнего. Таким образом, можно безопасно менять ARR во время работы таймера – в любой момент.

Код

Код будет очень похож на предыдущий, т.к. инициализация всей периферии происходит однотипно – за тем лишь исключением, что таймер TIM6 висит на шине APB1. Поэтому включение таймера: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

Теперь заводим структуру типа TIM_TimeBaseInitTypeDef, инициализируем её (TIM_TimeBaseStructInit), настраиваем, передаём её в функцию инициализации таймера (TIM_TimeBaseInit) и наконец включаем таймер (TIM_Cmd).

TIM_TimeBaseInitTypeDef TIM_InitStructure; // Заводим структуру
TIM_TimeBaseStructInit(&TIM_InitStructure); // Инициализация структуры
TIM_InitStructure.TIM_Prescaler = 24000; // Предделитель
TIM_InitStructure.TIM_Period = 1000; // Период таймера
TIM_TimeBaseInit(TIM6, &TIM_InitStructure); // Функция настройки таймера
TIM_Cmd(TIM6, ENABLE); // Включение таймера

Что за магические числа? Как мы помним, на шине присутствует тактовая частота 24МГц (при наших настройках проекта). Настроив предделитель таймера на 24000, мы поделим эту частоту на 24 тысячи, и получим 1кГц. Именно такая частота попадёт на вход счётчика таймера.

Значение же в счётчике – 1000. Значит, счётчик переполнится за 1000 тактов, т.е. ровно за 1 секунду.

После этого у нас действительно появляется работающий таймер. Но это ещё не всё.

10. Разберёмся с прерываниями

Окей, прерывания. Для меня когда–то (во времена PIC) они были тёмным лесом, и я старался вообще их не использовать – да и не умел, на самом деле. Однако, в них заключена сила, игнорировать которую вообще недостойно. Правда, прерывания в STM32 – ещё более сложная штука, особенно механизм их вытеснения; но об этом позже.

Как мы заметили раньше, таймер генерирует прерывание в момент переполнения счётчика – если включена вообще обработка прерываний этого прибора, конкретно это прерывание включено и сброшено предыдущее такое же. Анализируя эту фразу, понимаем что нам нужно:

  1. Включить вообще прерывания таймера TIM6;
  2. Включить прерывание таймера TIM6 на переполнение счётчика;
  3. Написать процедуру–обработчик прерывания;
  4. После обработки прерывания сбросить его.

Включение прерываний

Честно говоря, тут вообще ничего сложного. Первым делом включаем прерывания TIM6: NVIC_EnableIRQ(TIM6_DAC_IRQn); Почему такое название? Потому что в ядре STM32 прерывания от TIM6 и от ЦАП имеют одинаковый номер. Не знаю, почему так сделано – экономия, нехватка номеров или просто какая–то наследная штука – в любом случае, никаких проблем это не принесёт, потому что в этом проекте не используется ЦАП. Даже если в нашем проекте использовался бы ЦАП – мы могли бы при входе в прерывание узнавать, кто конкретно его вызвал. Практически все другие таймеры имеют единоличное прерывание.

Настройка события–источника прерываний: TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE); – включаем прерывание таймера TIM6 по событию TIM_DIER_UIE, т.е. событие обновления значения ARR. Как мы помним из картинки, это происходит одновременно с переполнением счётчика – так что это именно то событие, которое нам нужно.

На текущий момент код таймерных дел таков:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_TimeBaseStructInit(&TIM_InitStructure);
TIM_InitStructure.TIM_Prescaler = 24000;
TIM_InitStructure.TIM_Period = 1000;
TIM_TimeBaseInit(TIM6, &TIM_InitStructure);
TIM_Cmd(TIM6, ENABLE);
NVIC_EnableIRQ(TIM6_DAC_IRQn);
TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE);

Обработка прерываний

Сейчас запускать проект нельзя – первое же прерывание от таймера не найдёт свой обработчик, и контроллер повиснет (точнее, попадёт в обработчик HARD_FAULT, что по сути одно и то же). Нужно его написать.

Немного теории

Он должен иметь совершенно определённое имя, void TIM6_DAC_IRQHandler(void). Это имя, так называемый вектор прерывания, описано в файле startup (в нашем проекте это startup_stm32f10x_md_vl.s – можете сами увидеть, 126 строка). На самом деле вектор – это адрес обработчика прерывания, и при возникновении прерывания ядро ARM лезет в начальную область (в которую транслирован файл startup – т.е. его местоположение задано совершенно жёстко, в самом начале флеш–памяти), ищет там вектор и переходит в нужное место кода.

Проверка события

Первое что мы должны сделать при входе в такой обработчик – проверить, какое событие вызвало прерывание. Сейчас у нас всего одно событие, а в реальном проекте на одном таймере вполне могут быть несколько событий. Поэтому проверяем событие, и выполняем соответствующий код.

В нашей программе эта проверка будет выглядеть так: if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) – всё понятно, функция TIM_GetITStatus проверяет наличие указанного события у таймера, и возвращает 0 или 1.

Очистка флага UIF

Второй шаг – очистка флага прерывания. Вернитесь к картинке: самый последний график UIF это и есть флаг прерывания. Если его не очистить, следующее прерывание не сможет вызваться, и контроллер опять упадёт в HARD_FAULT (да что же такое!).

Выполнение действий в прерывании

Будем просто переключать состояние светодиода, как и в первой программе. Разница в том, что теперь наша программа делает это более сложно! На самом деле, так писать гораздо правильнее.

If(state)
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
else
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
state = 1 – state;

Используем глобальную переменную int state=0;

11. Весь код проекта с таймером

#include “stm32f10x_conf.h”
int state=0;
void TIM6_DAC_IRQHandler(void)
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
if(state)
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
else
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
state = 1 – state;
}
}
void main()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_TimeBaseStructInit(&TIM_InitStructure);
TIM_InitStructure.TIM_Prescaler = 24000;
TIM_InitStructure.TIM_Period = 1000;
TIM_TimeBaseInit(TIM6, &TIM_InitStructure);
TIM_Cmd(TIM6, ENABLE);
NVIC_EnableIRQ(TIM6_DAC_IRQn);
TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE);
while(1)
{
}
}

Архив с проектом таймера.

Ну и к слову, таймер умеет переключать ногу и сам, без прерываний и ручной обработки. Это будет наш третий проект.

Весь цикл:

1. Порты ввода–вывода

2. Таймер и прерывания

3. Выходы таймера

4. Внешние прерывания и NVIC

5. Ставим FreeRTOS

Post Views:

235

В любом современном контроллере есть таймеры
. В этой статье речь пойдёт о простых (базовых) таймерах stm32f4 discovery
.
Это обычные таймеры. Они 16 битные с автоматической перезагрузкой. Кроме того имеется 16 битный программируемый делитель частоты
. Есть возможность генерирования прерывания по переполнению счётчика
и/или запросу DMA.

Приступим. Как и раньше я пользуюсь Eclipse + st-util в ubuntu linux

Первым делом подключаем заголовки:

#include
#include
#include
#include
#include

Ничего нового в этом нет. Если не ясно откуда они берутся либо читайте предыдущие статьи, либо открывайте файл и читайте.

Определим две константы. Одну для обозначения диодов, другую массив из техже диодов:

Const uint16_t LEDS = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; // все диоды
const uint16_t LED = {GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_14, GPIO_Pin_15}; // массив с диодами

Скорее всего уже знакомая вам функция-инициализации периферии (то есть диодов) :

Void init_leds(){
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // разрешаем тактирование
GPIO_InitTypeDef gpio; // структура
GPIO_StructInit(&gpio); // заполняем стандартными значениями
gpio.GPIO_OType = GPIO_OType_PP; // подтяжка резисторами
gpio.GPIO_Mode = GPIO_Mode_OUT; // работаем как выход
gpio.GPIO_Pin = LEDS; // все пины диодов
GPIO_Init(GPIOD, &gpio);

Функция инициализатор таймера:

Void init_timer(){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // включаем тактирование таймера
/* Другие параметры структуры TIM_TimeBaseInitTypeDef
* не имеют смысла для базовых таймеров.
*/
TIM_TimeBaseInitTypeDef base_timer;
TIM_TimeBaseStructInit(&base_timer);
/* Делитель учитывается как TIM_Prescaler + 1, поэтому отнимаем 1 */
base_timer.TIM_Prescaler = 24000 – 1; // делитель 24000
base_timer.TIM_Period = 1000; //период 1000 импульсов
TIM_TimeBaseInit(TIM6, &base_timer);
/* Разрешаем прерывание по обновлению (в данном случае –
* по переполнению) счётчика таймера TIM6.
*/
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM6, ENABLE); // Включаем таймер
/* Разрешаем обработку прерывания по переполнению счётчика
* таймера TIM6. это же прерывание
* отвечает и за опустошение ЦАП.
*/
NVIC_EnableIRQ(TIM6_DAC_IRQn);
}

Я прокомментировал код, так-что думаю всё ясно.
Ключевыми параметрами тут являются делитель (TIM_Prescaler) и период (TIM_Period) таймера. Это параметры, которые собственно и настраивают работу таймера.

К примеру, если у вас на STM32F4 DISCOVERY тактовая частота установлена в 48МГц, то на таймерах общего назначения частота 24МГц. Если установить делитель (TIM_Prescaler) в 24000 (частота счёта = 24МГц/24000 = 1КГц), а период (TIM_Period) в 1000, то таймер будет отсчитывать интервал в 1с.

Обратите внимание, что всё зависит от тактовой частоты. Её вы должны выяснить точно.

Так же отмечу, что на высоких частотах переключение светодиода по прерыванию существенно искажает значение частоты. При значении в 1МГц на выходе я получал примерно 250КГц, т.е. разница не приемлима. Такой результат видимо получается из-за затрат времени на выполнение прерывания.

Глобальная переменная – флаг горящего диода:

U16 flag = 0;

Обработчик прерывания, которое генерирует таймер. Т.к. этоже прерывание генерируется и при работе ЦАП, сначала проверяем, что сработало оно именно от таймера:

Void TIM6_DAC_IRQHandler(){
/* Так как этот обработчик вызывается и для ЦАП, нужно проверять,
* произошло ли прерывание по переполнению счётчика таймера TIM6.
*/
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) {
flag++;
if (flag>3) flag = 0;
/* Очищаем бит обрабатываемого прерывания */
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
GPIO_Write(GPIOD, LED); // зажигаем слудующий диод
}
}

Функция main:

Int main(){
init_leds();
init_timer();
do {
} while(1);
}

Цикл оставляем пустым. Счётчик выполняет свою работу асинхронно, а прерывание на то и прерывание, чтобы не зависеть от выполняемой в данный момент операции.

Режим захвата – это особый режим работы таймера, суть которого в следующем, при изменении логического уровня на определённом выводе микроконтроллера, значение счётного регистра записывается в другой регистр, который именуют регистром захвата.

Для чего это надо?
С помощью этого режима можно измерить длительность импульса или период сигнала.

Режим захвата у STM32 обладает некоторыми особенностями:

  • возможность выбрать какой фронт будет активным
  • возможность изменить частоту входного сигнала с помощью предделителя (1,2,4,8)
  • каждый канал захвата оснащён встроенным входным фильтром
  • источником сигнала захвата может служить другой таймер
  • для каждого канала предусмотрено по два флага, первый выставляется если произошёл захват, второй если произошёл захват при установленном первом флаге

Для настройки режима захвата предназначены регистры CCMR1
(для 1 и 2 канала) и CCMR2
(для 3 и 4), а также регистры CCER
, DIER
.

Давайте рассмотрим подробнее битовые поля регистра CCMR2
, отвечающие за настройку 4 канала таймера, именно его мы будем настраивать в примере. Ещё хотелось бы отметить, что в этом же регистре находятся битовые поля, которые используются при настройке таймера в режиме сравнения.

CC4S
– определяет направление работы четвёртого канала(вход или выход). При настройке канала как вход сопоставляет ему сигнал захвата

  • 00 – канал работает как выход
  • 01 – канал работает как вход, сигнал захвата – TI4
  • 10 – канал работает как вход, сигнал захвата – TI3
  • 11 – канал работает как вход, сигнал захвата – TRC

IC4PSC
– определяют коэффициент деления, для сигнала захвата

  • 00 – делитель не используется, сигнал захвата IC1PS формируется по каждому событию
  • 01 – сигнал захвата формируется по каждому второму событию
  • 10 – сигнал захвата формируется по каждому четвёртому событию
  • 11 – сигнал захвата формируется по каждому восьмому событию

IC4F
– предназначен для настройки входного фильтра, кроме количества выборок, в течение которых микроконтроллер не будет реагировать на входные сигналы, также можно настроить частоту выборок. По сути мы настраиваем время задержки с момента прихода фронта до “подтверждающей” выборки.

Теперь давайте рассмотрим регистр CCER
.

CC4E
– включает/выключает режим захвата.
CC4P
– определяет фронт по которому будет производиться захват, 0 – передний, 1 – задний.

И регистр DIER
.

CC4DE
– разрешает формировать запрос к DMA.
CC4IE
– разрешает прерывание по захвату.

После того как произошёл захват формируется событие захвата, которое устанавливает соответствующий флаг. Это может привести к генерации прерывания и запросу DMA
, если они разрешены в регистре DIER
. Кроме того, событие захвата может быть сформировано программно, установкой битового поля в регистре генерации событий EGR
:

Битовые поля CC1G, CC2G, CC3G и CC4G
позволяют генерировать событие в соответствующем канале захвата/сравнения.

Кстати, CCR1, CCR2, CCR3 и CCR4
– регистры захвата, в которых сохраняется значение таймера по сигналу захвата.

Для того чтобы контролировать формирование сигнала захвата, в регистре SR
для каждого канала выделено по два флага.

CC4IF
– устанавливается когда формируется сигнал захвата, сбрасываются эти флаги программно или чтением соответствующего регистра захвата/сравнения.
CC4OF
– устанавливается если флаг CC4IF не был очищен, а пришёл очередной сигнал захвата. Этот флаг очищается программно записью нуля.

Теперь давайте применим полученные знания на практике, с генератора сигналов на вход TIM5_CH4 подадим синусоиду с частотой 50Гц и попробуем измерить её период. Для того чтобы ускорить процесс предлагаю использовать DMA. Какой вывод МК соответствует 4 каналу TIM5 можно найти в даташите на МК в разделе Pinouts and pin description
.

Для DMA
необходим адрес регистра CCR4
, вот как его найти. Открываем RM0008
и в таблице Register boundary addresses
находим начальный адрес TIM5.

смещение для регистра CCR4
можно найти в том же документе в разделе register map
.

#include “stm32f10x.h”
#define TIM5_CCR4_Address ((u32)0x40000C00+0x40)
#define DMA_BUFF_SIZE 2
uint16_t buff;//Буфер
uint16_t volatile T;
void DMA2_Channel1_IRQHandler (void)
{
T = (buff > buff) ? (buff – buff) : (65535+ buff – buff);
DMA2->IFCR |= DMA_IFCR_CGIF1;
}
void Init_DMA(void)
{
RCC->AHBENR |= RCC_AHBENR_DMA2EN; //Разрешаем тактирование первого DMA модуля
DMA2_Channel1->CPAR = TIM5_CCR4_Address; //Указываем адрес периферии – регистр результата преобразования АЦП для регулярных каналов
DMA2_Channel1->CMAR = (uint32_t)buff; //Задаем адрес памяти – базовый адрес массива в RAM
DMA2_Channel1->CCR &= ~DMA_CCR1_DIR; //Указываем направление передачи данных, из периферии в память
DMA2_Channel1->CNDTR = DMA_BUFF_SIZE; //Количество пересылаемых значений
DMA2_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируем после каждой пересылки
DMA2_Channel1->CCR |= DMA_CCR1_MINC; //Адрес памяти инкрементируем после каждой пересылки.
DMA2_Channel1->CCR |= DMA_CCR1_PSIZE_0; //Размерность данных периферии – 16 бит
DMA2_Channel1->CCR |= DMA_CCR1_MSIZE_0; //Размерность данных памяти – 16 бит
DMA2_Channel1->CCR |= DMA_CCR1_PL; //Приоритет – очень высокий
DMA2_Channel1->CCR |= DMA_CCR1_CIRC; //Разрешаем работу DMA в циклическом режиме
DMA2_Channel1->CCR |= DMA_CCR1_TCIE;//Разрешаем прерывание по окончанию передачи
DMA2_Channel1->CCR |= DMA_CCR1_EN; //Разрешаем работу 1-го канала DMA
}
int main(void)
{
Init_DMA();
//включаем тактирование порта А, альтернативных функций и таймера
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
TIM5->PSC = 56000-1;//новая частота 1Khz
TIM5->CCMR2 |= TIM_CCMR2_CC4S_0;//выбираем TI4 для TIM5_CH4
TIM5->CCMR2 &= ~(TIM_CCMR2_IC4F | TIM_CCMR2_IC4PSC);//не фильтруем и делитель не используем
TIM5->CCER &= ~TIM_CCER_CC4P;//выбираем захват по переднему фронту
TIM5->CCER |= TIM_CCER_CC4E;//включаем режим захвата для 4-го канала
TIM5->DIER |= TIM_DIER_CC4DE;//разрешаем формировать запрос к DMA
//TIM5->DIER |= TIM_DIER_CC4IE; //разрешаем прерывание по захвату
TIM5->CR1 |= TIM_CR1_CEN; //включаем счётчик
//NVIC->ISER |= NVIC_ISER_SETENA_18; //TIM5 Interrupt
NVIC->ISER |= NVIC_ISER_SETENA_24; //DMA Interrupt
while(1)
{
}
}

В статье приведено описание таймеров 32-разрядных ARM-микроконтроллеров серии STM32 от компании STMicroelectronics. Рассмотрена архитектура и состав регистров базовых таймеров, а также приведены практические примеры программ.

Для любого микроконтроллера тай-мер является одним из важнейших узлов, который позволяет очень точно отсчитывать интервалы времени, счи-тать импульсы, поступающие на вхо-ды, генерировать внутренние преры-вания, формировать сигналы с широт-но-импульсной модуляцией (ШИМ) и поддерживать процессы прямого доступа к памяти (ПДП).

Микроконтроллер STM32 имеет в своём составе несколько типов тай-меров, отличающихся друг от друга по функциональному назначению. Первый тип таймеров является са-мым простым и представляет собой базовые таймеры (Basic Timers). К дан-ному типу принадлежат таймеры TIM6 и TIM7. Эти таймеры очень про-сто настраиваются и управляются при помощи минимума регистров. Они способны отсчитывать интерва-лы времени и генерировать прерыва-ния при достижении таймером задан-ного значения.
Второй тип представляет собой тай-меры общего назначения (General-Purpose Timers). К нему относятся тай-меры с TIM2 по TIM5 и таймеры с TIM12 по TIM17. Они могут генерировать ШИМ, считать импульсы, поступаю-щие на определённые выводы микро- контроллера, обрабатывать сигналы от энкодера и т.п.

Третий тип определяет таймеры с развитым управлением (Advanced-Control Timer). К этому типу относит-ся таймер TIM1, который способен выполнять все перечисленные выше операции. Кроме того, на основе дан-ного таймера можно построить устрой-ство, способное управлять трёхфазным электроприводом.

Устройство базового таймера

Рассмотрим устройство и работу базового таймера, структурная схема которого представлена на рисунке. Базовый таймер построен на осно-ве 16-битных регистров. Его основой является счётный регистр TIMx_CNT. (Здесь и далее символ «х» заменяет номер 6 или 7 для базовых таймеров TIM6 и TIM7 соответственно.) Предва-рительный делитель TIMx_PSC позво-ляет регулировать частоту тактовых импульсов для счётного регистра, а регистр автозагрузки TIMx_ARR даёт возможность задавать диапазон отсчё-та таймера. Контроллер запуска и синхронизации вместе с регистрами управления и состояния служат для организации режима работы тайме-ра и позволяют контролировать его функционирование.

Благодаря своей организации счёт-чик таймера может считать в прямом и в обратном направлении, а также до середины заданного диапазона в прямом, а затем в обратном направлении. На вход базового таймера может подаваться сигнал от нескольких источников, в том числе тактовый сигнал синхронизации от шины APB1, внешний сигнал или выходной сигнал других таймеров, подаваемый на выводы захвата и сравнения. Таймеры TIM6 и TIM7 тактируются от шины APB1. Если использовать кварцевый резонатор с частотой 8 МГц и заводские настройки тактирования по умолчанию, то тактовая частота с шины синхронизации APB1 составит 24 МГц.

Регистры базового таймера

В таблице приведена карта регистров для базовых таймеров TIM6 и TIM7. Базовые таймеры включают в свой состав следующие 8 регистров:

●● TIMx_CNT – Counter (счётный ре-гистр);
●● TIMx_PSC – Prescaler (предваритель-ный делитель);
●● TIMx_ARR – Auto Reload Register (регистр автоматической загрузки);
●● TIMx_CR1 – Control Register 1 (регистр управления 1);
●● TIMx_CR2 – Control Register 2 (ре-гистр управления 2);
●● TIMx_DIER – DMA Interrupt Enable Register (регистр разрешения ПДП и прерываний);
●● TIMx_SR – Status Register (статусный регистр);
●● TIMx_EGR – Event Generation Register (регистр генерации событий).

Регистры TIMx_CNT, TIMx_PSC и TIMx_ARR используют 16 информа-ционных разрядов и позволяют запи-сывать значения от 0 до 65535. Частота тактовых импульсов для счётного регистра TIMx_CNT, прошед-ших через делитель TIMx_PSC, рассчи-тывается по формуле: Fcnt = Fin/(PSC + 1), где Fcnt – частота импульсов счётно-го регистра таймера; Fin – тактовая частота; PSC – содержимое регистра TIMx_PSC таймера, определяющее коэффициент деления. Если записать в регистр TIMx_PSC значение 23999, то счётный регистр TIMx_CNT при тактовой частоте 24 МГц будет изменять своё значение 1000 раз в секунду. Регистр автоматической загрузки хранит значение для загрузки счёт-ного регистра TIMx_CNT. Обновление содержимого регистра TIMx_CNT про-изводится после его переполнения или обнуления, в зависимости от заданно-го для него направления счёта. Регистр управления TIMх_CR1 име-ет несколько управляющих разрядов. Разряд ARPE разрешает и запрещает буферирование записи в регистр авто-матической загрузки TIMx_ARR. Если этот бит равен нулю, то при записи нового значения в TIMx_ARR оно будет загружено в него сразу. Если бит ARPE равен единице, то загрузка в регистр произойдёт после события достиже-ния счётным регистром предельного значения. Разряд OPM включает режим «одно-го импульса». Если он установлен, после переполнения счётного регистра счёт останавливается и происходит сброс разряда CEN. Разряд UDIS разрешает и запрещает генерирование события от таймера. Если он обнулён, то событие будет гене-рироваться при наступлении условия генерирования события, то есть при переполнении таймера или при про-граммной установке в регистре TIMx_ EGR разряда UG. Разряд CEN включает и отключает таймер. Если обнулить этот разряд, то будет остановлен счёт, а при его уста-новке счёт будет продолжен. Входной делитель при этом начнёт счёт с нуля. Регистр управления TIMх_CR2 име-ет три управляющих разряда MMS2… MMS0, которые определяют режим мастера для таймера. В регистре TIMx_DIER использует-ся два разряда. Разряд UDE разреша-ет и запрещает выдавать запрос DMA (ПДП) при возникновении события. Разряд UIE разрешает и запрещает пре-рывание от таймера. В регистре TIMx_SR задействован только один разряд UIF в качестве фла-га прерывания. Он устанавливается аппаратно, при возникновении собы-тия от таймера. Сбрасывать его нужно программно. Регистр TIMx_EGR содержит разряд UG, который позволяет программно генерировать событие «переполне-ние счётного регистра». При установ-ке этого разряда, происходит генера-ция события и сброс счётного регистра и предварительного делителя. Обнуля-ется этот разряд аппаратно. Благода-ря этому разряду можно программно генерировать событие от таймера, и тем самым принудительно вызывать функ-цию обработчика прерывания таймера.

Рассмотрим назначение регистров управления и состояния таймера на конкретных примерах программ.

Примеры программ

Для запуска таймера необходи-мо выполнить несколько операций, таких как подача тактирования на тай-мер и инициализация его регистров. Рассмотрим эти операции на основе примеров программ для работы с тай мерами. Довольно часто в процессе програм-мирования возникает задача реализа-ции временных задержек. Для реше-ния данной задачи необходима функ-ция формирования задержки. Пример такой функции на основе базово-го таймера TIM7 для STM32 приведён в листинге 1.

Листинг 1

#define FAPB1 24000000 // Тактовая частота шины APB1
// Функция задержки в миллисекундах и микросекундах
void delay(unsigned char t, unsigned int n){
// Загрузить регистр предварительного делителя PSC
If(t = = 0) TIM7->PSC = FAPB1/1000000-1; // для отсчёта микросекунд
If(t = = 1) TIM7->PSC = FAPB1/1000-1; // для отсчёта миллисекунд
TIM7->ARR = n; // Загрузить число отсчётов в регистр автозагрузки
ARR TIM7->EGR |= TIM_EGR_UG; // Сгенерировать событие обновления
// для записи данных в регистры PSC и ARR
TIM7->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; // Пуск таймера
//путём записи бита разрешения счёта CEN
//и бита режима одного прохода OPM в регистр управления CR1
while (TIM7->CR1&TIM_CR1_CEN != 0); // Ожидание окончания счёта
}

Эта функция может формировать задержки в микросекундах или мил-лисекундах в зависимости от парамет ра «t». Длительность задержки задаётся параметром «n». В данной программе задействован режим одного прохода таймера TIM7, при котором счётный регистр CNT выполняет счёт до значения переполне-ния, записанного в регистре ARR. Когда эти значения сравняются, таймер оста-новится. Факт остановки таймера ожи-дается в цикле while, путём проверки бита CEN статусного регистра CR1. Включение тактирования таймеров производится однократно в главном модуле программы при их инициали-зации. Базовые таймеры подключены к шине APB1, поэтому подача такто-вых импульсов выглядит следующим образом:

RCC->APB1ENR |= RCC_APB1ENR_ TIM6EN; // Включить тактирование на TIM6
RCC->APB1ENR |= RCC_APB1ENR_ TIM7EN; // Включить тактирование на TIM7

Описанный выше программный спо-соб формирования задержки имеет существенный недостаток, связанный с тем, что процессор вынужден зани-маться опросом флага на протяжении всего времени задержки и поэтому не имеет возможности в это время выпол-нять другие задачи. Устранить такой недостаток можно с помощью использо-вания режима прерываний от таймера. Функции обработки прерывания для базовых таймеров обычно выгля-дят следующим образом:

Void TIM7_IRQHandler(){
TIM7->SR = ~TIM_SR_UIF; // Обнулить флаг
//Выполнить операции
}
void TIM6_DAC_IRQHandler(){
//Если событие от TIM6
if(TIM6->SR & TIM_SR_UIF){
TIM6->SR =~ TIM_SR_UIF; // Обнулить флаг
//Выполнить операции
}
}

Рассмотрим пример программы для организации задержки на базовом тай-мере TIM6, которая использует преры-вания от таймера. Для контроля выпол-нения программы задействуем один из выводов микроконтроллера для управ-ления светодиодными индикаторами, которые должны будут переключаться с периодичностью, определяемой про-граммной задержкой, организованной на таймере TIM6. Пример такой программы приведён в листинге 2.

Листинг 2

// Подключение библиотек
#include
#include
#include
#include
#include
// Назначение выводов для светодиодных индикаторов
enum { LED1 = GPIO_Pin_8, LED2 = GPIO_Pin_9 };
// Функция инициализации портов управления светодиодными индикаторами
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 = LED1 | LED2;
GPIO_Init(GPIOC, &gpio);
}
//Функция инициализации таймера TIM6
void init_timer_TIM6() {
// Включить тактирование таймера
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_TimeBaseInitTypeDef base_timer;
TIM_TimeBaseStructInit(&base_timer);
// Задать делитель равным 23999
base_timer.TIM_Prescaler = 24000 – 1;
// Задать период равным 500 мс
base_timer.TIM_Period = 500;
TIM_TimeBaseInit(TIM6, &base_timer);
// Разрешить прерывание по переполнению счётчика таймера
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
//Включить таймер
TIM_Cmd(TIM6, ENABLE);
//Разрешить обработку прерывания по переполнению счётчика таймера
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) ^ (LED1 | LED2));
}
}
// Главный модуль программы
int main() {
init_leds();
GPIO_SetBits(GPIOC, LED1);
GPIO_ResetBits(GPIOC, LED2);
init_timer_TIM6();
while (1)
{
// Место для других команд
}
}

В данной программе функция задержки вызывается один раз, после чего процессор может выполнять дру-гие операции, а таймер будет регуляр-но формировать прерывания с задан-ным интервалом задержки. Аналогичную программу можно напи-сать и для таймера TIM7. Отличие такой программы будет состоять в именах реги-стров и названии обработчика прерыва-ния. Обработчик прерывания таймера TIM6 имеет одну особенность, связанную с тем, что вектор обработки прерывания этого таймера объединён с прерывани-ем от цифро-аналогового преобразова-теля (ЦАП). Поэтому в функции обработ-чика прерывания выполняется проверка источника прерывания. Подробнее озна-комиться с таймерами микроконтролле-ра STM32 можно на сайте St.com . Для таймера существует множество других задач, описанных выше, кото-рые он может успешно решить. Поэто-му его применение в программе значи-тельно облегчает нагрузку на процес-сор и делает программу эффективнее.

Собственно, поэтому давайте сразу же переходить к программированию. Возьмем любой из базовых таймеров микроконтроллера STM32F3
, произведем его минимальную настройку и попытаемся сгенерировать прерывания через равные промежутки времени. Максимально простой пример 😉

Итак, из Standard Peripheral Library
нам понадобятся парочка файлов, в которых реализовано взаимодействие с регистрами таймеров:

#include “stm32f30x_gpio.h”

#include “stm32f30x_rcc.h”

#include “stm32f30x_tim.h”

#include “stm32f30x.h”

/*******************************************************************/

TIM_TimeBaseInitTypeDef timer;

/*******************************************************************/

Минимальная инициализация таймера выглядит следующим образом. Кстати заодно настроим одну из ножек контроллера на работу в режиме выхода. Это нужно всего лишь для того, чтобы мигать светодиодиком 😉

/*******************************************************************/

void
initAll()

{

// Тактирование – куда ж без него

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE,
ENABLE)
;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,
ENABLE)
;

// На этом выводе у нас синий светодиод (STM32F3Discovery)

gpio.GPIO_Mode
=
GPIO_Mode_OUT;

gpio.GPIO_Pin
=
GPIO_Pin_8;

gpio.GPIO_OType
=
GPIO_OType_PP;

gpio.GPIO_Speed
=
GPIO_Speed_50MHz;

GPIO_Init(GPIOE,
&
gpio)
;

// А вот и долгожданная настройка таймера TIM2

TIM_TimeBaseStructInit(&
timer)
;

timer.TIM_Prescaler
=
7200
;

timer.TIM_Period
=
20000
;

TIM_TimeBaseInit(TIM2,
&
timer)
;

/*******************************************************************/

Тут стоит уделить внимание двум непонятно откуда взявшимся числам – 7200
и 20000
. Сейчас разберемся что это 😉 Таймер у меня тактируется частотой 72 МГц
. Prescaler, он же предделитель, нужен для того, чтобы эту частоту делить) Таким образом, получаем 72 МГц / 7200 = 10 КГц
. Значит один “тик” таймера соответствует (1 / 10000) секунд
, что равняется 100
микросекундам. Период таймера – это величина, досчитав до которой программа улетит на обработчик прерывания по переполнению таймера. В нашем случае таймер дотикает до 20000
, что соотвествует (100 * 20000) мкс
или 2 секундам. То есть светодиод (который мы зажигаем и гасим в обработчике прерывания) будет мигать с периодом 4
секунды (2 секунды горит, 2
секунды не горит =)). Теперь с этим все понятно, продолжаем…

В функции main()
вызываем функцию инициализации, а также включаем прерывания и таймер. В цикле while(1)
кода и того меньше – он просто пуст 😉

/*******************************************************************/

int
main()

{

__enable_irq()
;

initAll()
;

TIM_ITConfig(TIM2,
TIM_IT_Update,
ENABLE)
;

TIM_Cmd(TIM2,
ENABLE)
;

NVIC_EnableIRQ(TIM2_IRQn)
;

while
(1
)

{

}

}

/*******************************************************************/

Все, осталось написать пару строк для обработчика прерываний, и дело сделано:

/*******************************************************************/

void
TIM2_IRQHandler()

{

TIM_ClearITPendingBit(TIM2,
TIM_IT_Update)
;

if
(GPIO_ReadInputDataBit(GPIOE,
GPIO_Pin_8)
==
1
)

{

GPIO_ResetBits(GPIOE,
GPIO_Pin_8)
;

}

else

{

GPIO_SetBits(GPIOE,
GPIO_Pin_8)
;

}

}

/*******************************************************************/

Прошив программу в контроллер, наблюдаем мигающий синий светодиод, следовательно программа функционирует верно! В принципе на этом все на сегодня, такая вот получилась краткая статейка)

Оцените статью
Добавить комментарий

3 + шестнадцать =