Янв 282013
 

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

Таймеры общего назначения разделены на несколько групп. Например, в составе серии STM32F100 выделены три группы таймеров общего назначения: TIM2-TIM5, TIM12-TIM14 и TIM15-TIM17 (Таймер TIM1 здесь стоит особняком, он уже не относится к таймерам общего назначения и называется Advanced-control timer. Способен выполнять все те же задачи, что и таймеры общего назначения и, кроме того, еще и другие. Это самый “мощный” таймер). Эти группы таймеров имеют небольшие различия по функциональным возможностям. Устройство и реализуемые функции этих универсальных таймеров начнем рассматривать на примере таймеров TIM2-TIM5. Эта группа таймеров имеет наибольший набор возможностей из всех таймеров общего назначения. Для разных микроконтроллеров количество этих таймеров может варьироваться. Например, у STM32F100RBT6, установленного на плате STM32VL-DISCOVERY, 6 таймеров общего назначения: TIM2-TIM4 и TIM15-TIM17. Итак, состав и возможности таймеров TIM2-TIM5:

  • 16-разрядный счетный регистр CNT с режимами счета на увеличение (up), уменьшение (down) и режимом счета в двух направлениях последовательно (сначала up, затем down)
  • 16-разрядный предварительный делитель PSC частоты входного сигнала таймера. Коэффициент деления входной частоты варьируется в пределах от 1 до 65535.
  • До 4 независимых каналов, выполняющих функции: захват входного сигнала (Input capture), сравнение содержимого счетного регистра с определенным значением (Output compare), формирование выходного сигнала с ШИМ, формирование одиночного импульса
  • Схема синхронизации для управления внешними сигналами и каскадного соединения нескольких таймеров
  • Генерация прерывания и запроса DMA по событиям переполнения/обнуления таймера, операциям сброса регистров таймера в исходное состояние; от внешних команд старта/останова счета, сброса регистров; при выполнении функций захвата/сравнения
  • Работа с инкрементальным энкодером и датчиком Холла
  • Внешний управляющий вход для режима поциклового токового управления (cycle-by-cycle current management)

На структурной схеме можно ознакомиться с внутренним устройством таймеров общего назначения:

Универсальные таймеры - блок схема

Time-base unit — базовый блок формирования временных интервалов состоит из счетного регистра TIMx_CNT, регистра предварительного делителя TIMx_PSC и регистра автоматической перезагрузки TIMx_ARR.

Trigger controller формирует сигнал запуска для АЦП, ЦАП и других таймеров (когда этот таймер работает “ведущим”). Кроме того, формирует управляющие сигналы для таймера, когда он находится в режиме “ведомого” (slave). А также содержит интерфейс для работы с энкодером. На вход этого модуля поступают тактовые сигналы от внутренних источников CK_INT или других таймеров ITR0-ITR3, либо с внешних входов TIMx_ETR или TIMx_CH1-TIMx_CH3.

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

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

TIMx_CNT – счетный регистр, содержит 16 разрядов. Изменяет свое содержимое на +1/-1 с приходом каждого нового тактового импульса. Направление счета задается состоянием разряда DIR в управляющем регистре TIMx_CR1.

TIMx_PSC – входной предварительный делитель тактовой частоты. Частота входного сигнала таймера, прошедшего через делитель PSC, рассчитывается по формуле:

F=Fck_psc/(PSC[15:0]+1),

где F-входная частота таймера (после делителя), Fck_psc-тактовая частота (до делителя), PSC[15:0] – содержимое регистра TIMx_PSC таймера, определяющее коэффициент деления.

TIMx_ARR – регистр автоматической перезагрузки. Счетчик таймера считает от 0 до значения регистра TIMx_ARR. Либо наоборот, все зависит от заданного направления счета. Этот регистр содержит регистр предварительной загрузки, обновление содержимого производится либо сразу после записи нового значения, либо только после формирования события в результате переполнения или обнуления таймера.

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

Управляющий регистр TIMx_CR1:

TIMx_CR1

ARPE – использование буферного регистра для регистра автоматической перезагрузки TIMx_ARR

  • 0 – содержимое TIMx_ARR будет обновлено сразу же после записи нового значения.
  • 1 – счетчик досчитает до предыдущего значения и, только потом, после генерации события, содержимое TIMx_ARR обновится.

DIR – направление счета

  • 0 – увеличение
  • 1 – уменьшение

UDIS – разрешает генерацию события при переполнении/обнулении счетчика (а также в некоторых других случаях). По умолчанию бит сброшен (содержит “0”), это состояние разрешает генерацию событий, что нам и необходимо. Установка бита в “1” запретит генерацию событий.

CEN – запуск счета производится записью “1” в этот разряд.

В регистре статуса TIMx_SR

TIMx_SR

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

Возьмем простейший случай, когда тактовый сигнал поступает изнутри, от системы RCC, а именно с шины APBx. Для формирования задержки, измеряемой в миллисекундах, логично было бы привести входную частоту таймера к величине в 1кГц, что дало бы нам возможность задавать длительность задержки сразу в миллисекундах. При этом, конечно, задержки в 1мс или 2мс точно сформировать не получится, для этого лучше использовать входные частоты с микросекундными периодами. Но для формирования “больших” задержек (500мс и более), используем входной тактовый сигнал с “базовым” периодом в 1мс.

Ниже приведен код программы для платы STM32VL-DISCOVERY. Для визуального наблюдения за работой программы выполняется мигание светодиодом через заданный временной интервал.

#include "stm32f10x.h"

void TIM2_IRQHandler(void) //Функция обработчика прерывания от таймера 6
  {
    TIM2->SR &= ~TIM_SR_UIF; //Сбрасываем бит вызова прерывания. 
    GPIOC->ODR ^= GPIO_ODR_ODR8;//Инвертируем состояние вывода - зажигаем/гасим светодиод
  }

int main()
{
  NVIC_SetPriority(TIM2_IRQn, 1); //Приоритет прерывания
  NVIC_EnableIRQ(TIM2_IRQn); //Разрешаем обработку прерывания от таймера 2
  
  RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; //Тактирование порта C
  GPIOC->CRH |= GPIO_CRH_MODE8_0;//Вывод PC8 порта C - выход
  GPIOC->CRH &= ~GPIO_CRH_CNF8;//Режим Push-Pull для вывода PC8 порта C
  
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;//Тактирование таймера TIM2
  TIM2->PSC = 23999;//Настройка предделителя таймера
  TIM2->ARR = 500;//Загружаем число миллисекунд в регистр автоперезагрузки
  TIM2->DIER |= TIM_DIER_UIE; //Разрешаем прерывание при переполнении счетчика
  TIM2->CR1 |= TIM_CR1_CEN;//Запускаем счет
  
  while(1);
}

В регистр предделителя таймера загружается число 23999, поскольку при использовании стандартных файлов инициализации из библиотеки STM32F10x_StdPeriph_Lib внутренняя тактовая частота будет равна 24МГц.

В регистр автоматической перезагрузки TIMx_ARR заносится число миллисекунд, в данном случае 500. При генерации прерывания в обработчике сбрасывается флаг прерывания, а затем инвертируется состояние вывода порта, к которому подключен светодиод.

Направление счета таймера можно изменить установкой или сбросом разряда DIR:

TIM2->CR1 |= TIM_CR1_DIR;

При счете на увеличение (инкремент) в счетный регистр надо загружать число миллисекунд следующим образом:

TIM2->CNT = 65535-msec;//Загружаем число миллисекунд в счетный регистр


А при счете на уменьшение (декремент) так:

TIM2->CNT = msec;//Загружаем число миллисекунд в счетный регистр

Код для платы STM32L-DISCOVERY с микроконтроллером STM32L152RB будет отличаться только инициализацией вывода порта, подключенного к светодиоду, а также числом в предварительном делителе таймера TIM2->PSC, поскольку тактовая частота здесь равна 2,097МГц.

#include "stm32l1xx.h" 

 void TIM2_IRQHandler(void) //Функция обработчика прерывания от таймера 2
  {
    TIM2->SR &= ~TIM_SR_UIF; //Сбрасываем бит вызова прерывания. 
    GPIOB->ODR ^= GPIO_OTYPER_ODR_6;//Инвертируем состояние выхода - зажигаем или гасим светодиод
  }

int main()
{
  NVIC_SetPriority(TIM2_IRQn, 1); //Приоритет прерывания
  NVIC_EnableIRQ(TIM2_IRQn); //Разрешаем обработку прерывания от таймера 2
  
  //GPIOB init
  RCC->AHBENR |= RCC_AHBENR_GPIOBEN; //Подаем тактовые импульсы в порт B
  GPIOB->MODER |= GPIO_MODER_MODER6_0; //Порт B в режиме "выход"
  GPIOB->OTYPER &= ~GPIO_OTYPER_OT_6; //Выход в режиме "push-pull"
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR6; //Подтягивающие резисторы не используются
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6; //Скорость работы порта максимальная
  
  //TIM2 init
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //Подаем тактовые импульсы на таймер 2
  TIM2->PSC = 2096; //Коэффициент деления 2096 (1 msec)
  TIM2->ARR = 500; //Перезагружаемое значение соответствует вызову прерывания через 500 msec
  TIM2->DIER |= TIM_DIER_UIE; //Разрешаем прерывание при переполнении счетчика
  TIM2->CR1 |= TIM_CR1_CEN; //Запуск счета    
  
  while(1); 
}

По ссылкам ниже можно загрузить проекты примеров в IAR:

Проект для STM32F100

Проект для STM32L152

Другие статьи:

  9 Responses to “STM32. General-Purpose Timers. Часть 1. Формирование временных интервалов”

  1. Здравствуйте.

    Работаю с таймером по прерываниям. Возникла проблема:
    при инициализации таймера даю разрешение на прерывание, но не включаю таймер (он включается из кода).
    Так вот, при вызове в инициализаторе NVIC_EnableIRQ(TIM2_IRQn); моментально происходит прерывание по переполнению, что не понятно. Я конечно могу предположить, что идет обратный отсчет, а в счетчике уже 0, но тем не менее таймер на данный момент не включен. Откуда прерывание? Параллельно интересует какой функцией устанавливается текущее значение счетчика?

    • Руслан, а для какой платы этот код? Какая среда разработки, чьи библиотеки. Информации мало. Если у меня такая плата есть, давай проверю программу, может получится найти проблему.

  2. PANYTA

    Здравствуйте.
    Переделал ваш код для STM32L-DISCOVERY:

    #include «stm32l1xx.h»
    #include «stm32l1xx_gpio.h»

    #define LD_PORT GPIOB
    #define LD_GREEN GPIO_Pin_7
    #define GPIO_HIGH(a, b) a -> BSRRL = b
    #define GPIO_LOW(a, b) a -> BSRRH = b

    void delay_ms(uint16_t msec)
    {
    TIM2->ARR = msec;//Загружаем число миллисекунд в регистр автоперезагрузки
    TIM2->CR1 |= TIM_CR1_CEN;//Запускаем счет
    while((TIM2->SR & TIM_SR_UIF)==0);//Ждем окончания счета
    TIM2->SR &= ~TIM_SR_UIF;//Теперь флаг события надо сбросить вручную
    }

    int main()
    {
    //RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; //Тактирование порта C
    //GPIOC->CRH |= GPIO_CRH_MODE8_0;//Вывод PC8 порта C — выход
    //GPIOC->CRH &= ~GPIO_CRH_CNF8;//Режим Push-Pull для вывода PC8 порта C

    RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
    GPIOB->MODER |= GPIO_MODER_MODER7_0;
    GPIOB->OTYPER &= ~GPIO_OTYPER_OT_7;
    GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR7;
    GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;

    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;//Тактирование таймера TIM2
    TIM2->PSC = 0;//Настройка предделителя таймера

    while(1)
    {
    //GPIOC->BSRR |= GPIO_BSRR_BS8;//Зажигаем светодиод
    GPIO_HIGH(LD_PORT, LD_GREEN);
    delay_ms(5);//Задержка полсекунды
    //GPIOC->BSRR |= GPIO_BSRR_BR8;//Гасим светодиод
    GPIO_HIGH(LD_PORT, LD_GREEN);
    delay_ms(5);//Задержка полсекунды
    }
    }

    Таймер не считает. Ни в железе, ни в симуляторе.

  3. У меня интересный вопрос — по поводу пошаговой отладки
    вот процедура она работает
    void Delay_ms(uint16_t msec)
    {
    msec = msec*10; // корректировка с учетом предделителя 3199
    TIM2->ARR = (msec-1);//Загружаем число миллисекунд в регистр автоперезагрузки
    TIM2->CR1 |= TIM_CR1_CEN;//Запускаем счет
    while((TIM2->SR & TIM_SR_UIF)==0);//Ждем окончания счета
    TIM2->CR1 &= ~TIM_CR1_CEN;//Остановить счет
    TIM2->SR &= ~TIM_SR_UIF;//Теперь флаг события надо сбросить вручную
    }

    но оболочка IAR ведет себя не совсем корректно — на участке кода «while((TIM2->SR & TIM_SR_UIF)==0);//Ждем окончания счета» отладчик должен виснуть, как бы на одном месте стоять, а в регистрах я должен видеть как у меня инкрементируется счетчик — как бы одно нажатие кнопки пошаговой отладки должно равняться одному шагу процессора, а у меня отладчик проскакивает по факту это условия за один такт — как то тупо получается, а в реале программа правильно работает т.е. реально он там виснет но я этого не могу увидеть в отладчике.
    Вопрос почему? как правильно настроить IAR что бы видеть эти процессы в реале, и еще заметил что когда происходит прерывание в пошаговой отладке курсор тоже не перескакивает, перескакивает только в том случае, если я поставил точку останова.

    • В IAR отладчик не шагает по одному такту мк, поэтому и не видно «зависания» в цикле ожидания или перехода в обработчик прерывания. Видимо, отладчик не берет мк под полный контроль, шаг идет по инструкциям программы, при этом между шагами программы может произойти обработка прерывания. И без точки останова этого не увидеть. Так что все работает корректно.

  4. Спасибо!

  5. Здравствуйте.

    А можно ли при помощи таймеров (или может есть более простой способ) в STM32F100 получить меандр порядка 500 кГц?
    Причем важно, чтобы частота не «гуляла» (если такое возможно).
    Если, например, SYSCLK = 10 МГц, то можно ли таймером (или чем-то еще) поделить ее ровно на 20?
    Я просто не знаю, одинаковое ли время требуется на обработку прерывания от таймера (если время обработки будет хотя бы слегка от раза к разу отличаться из-за чего-нибудь, то и частота на выходе будет нестабильной?).
    Нужна точность порядка 0.01%.