Что такое таймер? Работаем с простыми таймерами STM32 F4 discovery Stm32 счетчик импульсов.

Таймеры в STM32, как в принципе и вся периферия, являются очень навороченными. От обилия разных функций, которые могут выполнять таймеры может даже закружиться голова. Хотя, казалось бы, таймер он на то и таймер, чтобы просто считать. Но на деле все гораздо круче)

Мало того, что таймеры обладают такими широкими возможностями, так их еще несколько у каждого контроллера. И даже не два и не три, а больше! В общем, нахваливать все это можно бесконечно. Давайте уже разбираться, что и как работает. Итак, микроконтроллер STM32F103CB имеет:

  • 3 таймера общего назначения (TIM2, TIM3, TIM4)
  • 1 более продвинутый таймер с расширенными возможностями (TIM1)
  • 2 WDT (WatchDog Timer)
  • 1 SysTick Timer

Собственно таймеры общего назначения и таймер TIM1 не сильно отличаются друг от друга, так что ограничимся рассмотрением какого-нибудь одного таймера. К слову я остановил свой выбор на TIM4. Без особой причины, просто так захотелось =). Таймеры имеют 4 независимых канала, которые могут использоваться для:

  • Захвата сигнала
  • Сравнения
  • Генерации ШИМ
  • Генерации одиночного импульса
  • Переполнение
  • Захват сигнала
  • Сравнение
  • Событие-триггер

При наступлении любого из этих событий таймеры могут генерировать запрос к DMA (DMA – прямой доступ к памяти, уже скоро мы будем разбираться и с ним =)). Теперь немного подробнее о каждом из режимов работы таймеров.

Режим захвата сигнала. Очень удобно при работе таймера в этом режиме измерять период следования импульсов. Смотрите сами: приходит импульс, таймер кладет свое текущее значение счетчика в регистр TIM_CCR. По-быстрому забираем это значение и прячем в какую-нибудь переменную. Сидим, ждем следующий импульс. Опа! Импульс пришел, таймер снова сует значение счетчика в TIM_CCR , и нам остается только вычесть из этого значения то, которое мы предварительно сохранили. Это, наверное, самое простое использование этого режима таймера, но очень полезное. Отлавливать можно как передний фронт импульса, так и задний, так что возможности довольно велики.

Режим сравнения. Тут просто подключаем какой-нибудь канал таймера к соответствующему выводу, и как только таймер досчитает до определенного значения (оно в TIM_CCR ) состояние вывода изменится в зависимости от настройки режима (либо выставится в единицу, либо в ноль, либо изменится на противоположное).

Режим генерации ШИМ. Ну тут все скрыто в названии) В этом режиме таймер генерирует ШИМ! Наверно нет смысла что-то писать тут еще сейчас. Скоро будет примерчик как раз на ШИМ, там и поковыряем поподробнее.

Режим Dead-Time. Суть режима в том, что между сигналами на основном и комплементарном выводах таймера появляется определенная задержка. В интернете есть довольно много информации о том, где это можно и нужно применять.

Ну вот в принципе ооочень кратко об основных режимах работы таймера. Если будут вопросы про другие режимы, более специфические, пишите в Комментарии 😉

Надо бы потихоньку написать программку для работы с таймерами. Но сначала посмотрим, что есть в библиотеке Standard Peripheral Library. Итак, за таймеры несут ответственность файлы – stm32f10x_tim.h и stm32f10x_tim.c . Открываем первый и видим, что структура файла повторяет структуру файла для работы с GPIO, который мы рассматривали в предыдущей статье. Здесь описаны структуры и поля структур, которые нужны для конфигурирования таймеров. Правда здесь уже не одна, а несколько структур (режимов, а соответственно и настроек то у таймеров побольше, чем у портов ввода-вывода). Все поля структур снабжены комментариями, так что не должно тут возникать никаких проблем. Ну вот, например:

uint16_t TIM_OCMode; // Specifies the TIM mode.

Здесь будем задавать режим работы таймера. А вот еще:

uint16_t TIM_Channel; // Specifies the TIM channel.

Здесь выбираем канал таймера, ничего неожиданного) В общем все довольно прозрачно, если что спрашивайте =) С первым файлом понятно. А в файле stm32f10x_tim.c – готовые функции для работы с таймерами. Тоже все в целом ясно. Мы уже использовали библиотеку для работы с GPIO, теперь вот работаем с таймерами, и очевидно, что для разной периферии все очень похоже. Так что давайте создавать проект и писать программу.

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

Пишем код:

Необходимо отметить, что в поле TIM_Prescaler нужно записывать значение, на единицу меньшее, чем то, которое мы хотим получить.

/****************************timers.c*******************************/ #include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_tim.h" //При таком предделителе у меня получается один тик таймера на 10 мкс #define TIMER_PRESCALER 720 /*******************************************************************/ //Переменная для хранения предыдущего состояния вывода PB0 uint16_t previousState; GPIO_InitTypeDef port; TIM_TimeBaseInitTypeDef timer; /*******************************************************************/ void initAll() { //Включаем тактирование порта GPIOB и таймера TIM4 //Таймер 4 у нас висит на шине APB1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) ; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE) ; //Тут настраиваем порт PB0 на выход //Подробнее об этом в статье про GPIO GPIO_StructInit(& port) ; port.GPIO_Mode = GPIO_Mode_Out_PP; port.GPIO_Pin = GPIO_Pin_0; port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, & port) ; //А тут настройка таймера //Заполняем поля структуры дефолтными значениями TIM_TimeBaseStructInit(& timer) ; //Выставляем предделитель timer.TIM_Prescaler = TIMER_PRESCALER - 1 ; //Тут значение, досчитав до которого таймер сгенерирует прерывание //Кстати это значение мы будем менять в самом прерывании timer.TIM_Period = 50 ; //Инициализируем TIM4 нашими значениями TIM_TimeBaseInit(TIM4, & timer) ; } /*******************************************************************/ int main() { __enable_irq() ; initAll() ; //Настраиваем таймер для генерации прерывания по обновлению (переполнению) TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE) ; //Запускаем таймер TIM_Cmd(TIM4, ENABLE) ; //Разрешаем соответствующее прерывание NVIC_EnableIRQ(TIM4_IRQn) ; while (1 ) { //Бесконечно тупим) Вся полезная работа – в прерывании __NOP() ; } } /*******************************************************************/ //Если на выходе был 0.. timer.TIM_Period = 50 ; TIM_TimeBaseInit(TIM4, & timer) ; //Очищаем бит прерывания TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; } else { //Выставляем ноль на выходе timer.TIM_Period = 250 ; TIM_TimeBaseInit(TIM4, & timer) ; TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; } }

В этой программе мы смотрим, что было на выходе до момента генерации прерывания – если ноль, выставляем единицу на 0.5 мс. Если была единица – ставим ноль на 2.5 мс. Компилируем и запускаем отладку =)

Небольшое, но очень важное отступление… Наш пример, конечно, будет работать и для теста он вполне сгодится, но все-таки в “боевых” программах нужно следить за оптимальностью кода как с точки зрения его объема, так и с точки зрения производительности и расхода памяти. В данном случае нет никакого смысла использовать структуру timer, а также вызывать функцию TIM_TimeBaseInit() каждый раз при смене периода. Правильнее менять всего лишь одно значение в одном регистре, а именно в регистре TIMx->ARR (где х – это номер таймера). В данном примере код трансформируется следующим образом:

/*******************************************************************/ void TIM4_IRQHandler() { //Если на выходе был 0.. if (previousState == 0 ) { //Выставляем единицу на выходе previousState = 1 ; GPIO_SetBits(GPIOB, GPIO_Pin_0) ; //Период 50 тиков таймера, то есть 0.5 мс TIM4-> ARR = 50 ; } else { //Выставляем ноль на выходе previousState = 0 ; GPIO_ResetBits(GPIOB, GPIO_Pin_0) ; //А период теперь будет 250 тиков – 2.5 мс TIM4-> ARR = 250 ; } TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; } /****************************End of file****************************/

Итак, продолжаем, на пути у нас очередные грабли) А именно ошибка:

..\..\..\SPL\src\stm32f10x_tim.c(2870): error: #20: identifier “TIM_CCER_CC4NP” is undefined

Не так страшно как может показаться, идем в файл stm32f10x.h, находим строки

Вот теперь все собирается, можно отлаживать. Включаем логический анализатор. В командной строке пишем: la portb&0x01 и наблюдаем на выходе:

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

Не пропустите хорошую статью про таймеры в целом – .

В любом современном контроллере есть таймеры . В этой статье речь пойдёт о простых (базовых) таймерах 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); }

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

Мы уже рассматривали таймер SysTick, который является частью ядра Cortex. Однако на этом всё не заканчивается. Таймеров в stm32 много, и они бывают разные. В зависимости от цели вам придется выбрать тот или иной таймер:

  • SysTick;
  • таймеры общего назначения (англ. general purpose timer) - TIM2, TIM3, TIM4, TIM15, TIM16, TIM17;
  • продвинутый таймер (англ. advanced timer) - TIM1;
  • сторожевой таймер (англ. watchdog timer).

Про последний стоит упомянуть лишь то, что он предназначен для контроля зависания системы и представляет собой таймер, который периодически необходимо сбрасывать. Если сброса таймера не произошло в течение некоторого интервала времени, сторожевой таймер перезагрузит систему (микроконтроллер).

Таймеры бывают разной битности: например, SysTick - 24-битный, а все таймеры, что имеются у нас в камне - 16-битные (т. е. могут считать до 2 16 = 65535), кроме WatchDog. Кроме того, каждый таймер имеет некоторое количество каналов, то есть, по сути, он может работать за двоих, троих и т. д. Эти таймеры умеют работать с инкрементными энкодерами, датчиками Холла, могут генерировать ШИМ (широтно-импульсная модуляция, англ. Pulse Width Modulation - о которой мы поговорим позже) и многое другое. Кроме того, они могут генерирувать прерывания или совершать запрос к другим модулям (например, к DMA - Direct Memory Access) по разным событиям:

  • переполнение (англ. overflow);
  • захват сигнала (англ. input capture);
  • сравнение (англ. output compere);
  • событие-триггер (англ. event trigger).

Если с переполнением таймера (точнее, достижением «0») нам всё понятно - мы рассматривали SysTick - то с другими возможными режимами работы мы еще не знакомы. Давайте рассмотрим их подробнее.

Захват сигнала

Этот режим хорошо подходит, чтобы измерять период следования импульсов (или их количество, скажем, за секунду). Когда на выход МК приходит импульс, таймер генерирует прерывание, с которого мы можем снять текущее значение счетчика (из регистра TIM_CCRx , где x - номер канала) и сохранить во внешнюю переменную. Затем придет следующий импульс, и мы простой операцией вычитания получим «время» между двумя импульсами. Отлавливать можно как передний, так и задний фронт импульса, или даже сразу оба. Зачем это нужно? Предположим, у вас есть магнит, который вы приклеили к диску на колесе, а датчик Холла - к вилке велосипеда. Тогда, вращая колесо, вы будете получать импульс каждый раз, когда магнит на колесе будет в той же плоскости, что и датчик Холла. Зная расстояние которое пролетает магнит за оборот и время можно вычислить скорость движения.

Также существует режим захвата ШИМ, однако это скорее особый способ настройки таймера, нежели отдельный режим работы: один канал ловит передние фронты, а второй - задние фронты. Тогда первый канал детектирует период, а второй - заполнение.

Режим сравнения

В таком режиме выбранный канал таймера подключается к соответствующему выводу, и как только таймер достигнет определенного значения, состояние вывода изменится в зависимости от настройки режима (это может быть «1» или «0», или состояние на выходе просто инвертируется).

Режим генерации ШИМ

Как понятно из названия, таймер в таком режиме генерирует широтно-импульсную модуляцию. Подробнее о таком режиме, а также о том, где его можно / стоит применять, мы поговорим в следующем занятии после рассмотрения захвата сигнала.

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

Режим Dead-time

Эта функция есть у некоторых таймеров; она нужна для создания задержек на выходах, которые требуются, например, для исключения сквозных токов при управлении силовыми ключами.

В курсе мы будем использовать только «захват сигнала» и «генерацию ШИМ».

Собственно, поэтому давайте сразу же переходить к программированию. Возьмем любой из базовых таймеров микроконтроллера 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) ; } } /*******************************************************************/

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

В статье приведено описание таймеров 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 . Для таймера существует множество других задач, описанных выше, кото-рые он может успешно решить. Поэто-му его применение в программе значи-тельно облегчает нагрузку на процес-сор и делает программу эффективнее.