Мар 252012
 

Это самые простые таймеры в составе микроконтроллера. Они не способны создать сигнал ШИМ на выходе, не выполняют функций захват/сравнение и т.д. С их помощью можно лишь сформировать временной интервал, сгенерировать прерывание или запрос DMA. Поэтому базовые таймеры содержат минимум регистров и настроить их очень несложно. Используя отладочную плату STM32L-DISCOVERY, рассмотрим простейший пример создания временной задержки в программе с использованием базового таймера. Помигаем светодиодом с периодом в 1 секунду, при этом настройки будут минимально необходимыми, только конфигурация порта и таймера, остальное все будет по умолчанию (то есть тактовым источником будет MSI с F = 2,097 МГц).

Счетный регистр таймера TIMx_CNT суммирующий, с приходом каждого тактового импульса инкрементирует свое содержимое. На вычитание работать не может. Регистр 16-разрядный.

Теперь о тактовых импульсах. Изначально они приходят с шины APB1. Затем проходят через делитель. Коэффициент деления задается в 16-разрядном регистре TIMx_PSC (предделитель таймера), этот коэффициент можно задать в пределах от 1 до 65536.

Еще  один  регистр, содержащий 16-разрядную величину, это TIMx_ARR (регистр автоперезагрузки). В этот регистр записывается число, до которого будет идти счет. При достижении этого значения, содержимое счетчика TIMx_CNT обнуляется и формируются прерывание или запрос DMA (если они разрешены).

Описание остальных регистров будет далее, поскольку этих регистров немного. Программа написана в среде IAR, настройки стандартные, с процессом создания заготовки проекта можно ознакомиться в статье STM32. Создание проекта в IAR EWARM. Работа с портами ввода/вывода. Часть 1.

Первым делом подключаем к проекту файл stm32l1xx.h.

#include "stm32l1xx.h"


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

uint8_t i = 0;

Отвел под нее целых 8 бит, расточительно конечно всего для двух состояний.

А ниже заранее объявляем функцию обработчика прерывания от таймера TIM6.

void TIM6_IRQHandler(void);


Далее переходим в главную функцию программы:

int main()
 {
  //GPIOB init
  RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
  GPIOB->MODER |= GPIO_MODER_MODER6_0;
  GPIOB->OTYPER &= ~GPIO_OTYPER_OT_6;
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR6;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;
  ...
  ...

Сначала здесь конфигурируем вывод порта B, к которому подключен светодиод.

В первой строке разрешаем подачу тактовых импульсов в порт B, без тактирования работать ничего не будет. А далее по порядку:

  • Вывод PORTB_6 – выход
  • Выход двухтактный — “push-pull”
  • Подтягивающие резисторы отключены
  • Вывод порта способен работать на максимальной частоте – 40 МГц.

В принципе, если мы выполняем конфигурацию порта при “дефолтных” значения (после сброса), то нам интересны только разряды, которые нужно установить в “1”. Остальные и так нулевые. Необходимо только сконфигурировать порт в качестве выхода, режим “push-pull” задан по умолчанию, подтягивающие резисторы тоже отключены изначально. Скорость вывода в порт для зажигания светодиода тоже неважна, глазами отличий не заметить. Вся процедура конфигурации порта приведена в полном объеме для полноты информации.

Далее необходимо задать параметры NVIC (это контроллер прерываний):

NVIC_SetPriority(TIM6_IRQn, 1); 
NVIC_EnableIRQ(TIM6_IRQn);


Первой командой устанавливаем уровень приоритета прерывания от таймера TIM6. В нашем случае можно установить любой уровень, поскольку источник прерываний будет всего один. А следующей командой разрешаем обработку прерывания от таймера TIM6. Переходим к настройке самого таймера:

//TIM6 init 
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; 
TIM6->PSC = 0xFFFF; 
TIM6->ARR = 0x000F; //Перезагружаемое значение 
TIM6->DIER |= TIM_DIER_UIE; 
TIM6->CR1 |= TIM_CR1_CEN;

Рассмотрим код инициализации таймера построчно:

  1. Разрешаем тактирование таймера от шины APB1.
  2. Задаем коэффициент деления предделителя таймера. Выбираем максимальное значение. Рассчитаем частоту, подаваемую на вход счетчика. Частота, поступающая с шины APB1, равна 2,097 МГц, поскольку по умолчанию после аппаратного сброса источником тактирования является MSI. Подробней можно почитать в статье STM32L. Система формирования тактовых частот Reset and Clock Control (RCC). Тогда, на выходе делителя получим частоту 2 097 000 Гц / (65535 +1) = 32 Гц.
  3. Прерывание необходимо вызывать через 0,5 секунды, значит счетчик у нас должен считать до 16. Загружаем в регистр автоперезагрузки TIM6_ARR величину 0x000F, тогда прерывание будет вызываться с частотой 32 Гц/(15 + 1) = 2 Гц.
  4. В регистре TIM6_DIER разрешаем генерацию прерывания установкой бита UIE.
  5. И, наконец, запускаем счет таймера, установкой бита CEN в регистре управления TIM6_CR1.

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

Завершает основную функцию main() бесконечный цикл

 while(1);


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

void TIM6_IRQHandler(void)
 {
  TIM6->SR &= ~TIM_SR_UIF;
  if (i == 0) GPIOB->BSRRL |= GPIO_BSRR_BS_6;
  else GPIOB->BSRRH |= GPIO_BSRR_BS_6;
  i = ~i;
 }

Первым делом в обработчике прерывания сбросим флаг прерывания UIF. Устанавливается он аппаратно, а сбрасывается только программно. Иначе, выйдем из обработчика прерывания и… снова в него попадем! И так будет до бесконечности, пока флаг не будет сброшен. И неважно тут содержимое счетчика таймера, раз флаг установлен, контроллер будет вызывать обработку прерывания.

Далее проверяем переменную состояния светодиода, если i = 0, зажигаем светодиод, если отлично от нуля – гасим. Выводом порта управляем через BSRR – регистр поразрядной установки/сброса данных на линиях порта. В конце процедуры обработки прерывания инвертируем содержимое разрядов переменной i.

Вот и все. Если не терпится испытать работу программы в “железе”, то в конце статьи есть ссылка, по которой можно загрузить архив с проектом. А пока рассмотрим содержимое регистров базовых таймеров TIM6 и TIM7.

TIM6&TIM7 control register 1 (TIMx_CR1)

Bit 7 ARPE: Auto-reload preload enable

0: Регистр автоперезагрузки TIMx_ARR работает без буфера. Если в программе содержимое регистра изменяется, то это происходит сразу же.

1: Для TIMx_ARR используется буфер. В этом случае новое значение в регистр автоперезагрузки заносится только в момент события – одновременно с вызовом прерывания или запросом DMA. Используется промежуточный регистр — “теневой”. То есть, когда счетный регистр достигнет предыдущего значения TIMx_ARR, и сбросится в “ноль”, только тогда и произойдет запись нового значения. На рисунках ниже приведена диаграмма работы для обоих вариантов:

ARPE = 0

ARPE_0

ARPE = 1

ARPE_1

Bit 3 OPM: One-pulse mode

Установка бита включает режим формирования одиночного импульса. После первого события счет прекращается. Если бит = 0, счет продолжается.

Bit 2 URS: Update request source

Задает источник формирования прерывания и запроса DMA.

0 – переполнение счетчика, установка бита UG, поступление внешнего сигнала при работе в режиме “ведомого” таймера.

1 – только переполнение счетчика

Bit 1 UDIS: Update disable

0 – генерация прерывания и запроса DMA разрешены

1 — генерация прерывания и запроса DMA запрещены

Bit 0 CEN: Counter enable

0 – счет остановлен

1 – счет запущен

TIM6&TIM7 control register 2 (TIMx_CR2)

Bits 6:4 MMS: Master mode selection

Эти биты устанавливают значение выходного сигнала в режиме “Master – Slave”, когда один таймер управляет другим.

000 – формируется сигнал RESET

001 – формируется сигнал ENABLE

010 – формируется сигнал UPDATE

TIM6&TIM7 DMA/Interrupt enable register (TIMx_DIER)

Здесь всего два значащих бита, которые разрешают запрос DMA и генерацию прерывания – UDE (DMA) и UIE (прерывание). “1” – можно, “0” – нельзя.

TIM6&TIM7 status register (TIMx_SR) – регистр статуса. Всего один значащий бит UIF – флаг прерывания. Остальные разряды зарезервированы.

TIM6&TIM7 event generation register (TIMx_EGR). Также всего один бит используется – это бит UG. Его установка (программно) принудительно вызовет формирование события – прерывание или запрос DMA.

TIM6&TIM7 counter (TIMx_CNT) – счетный регистр.

TIM6&TIM7 prescaler (TIMx_PSC) – предделитель.

TIM6&TIM7 auto-reload register (TIMx_ARR) – регистр автоперезагрузки.

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

Здесь можно загрузить архив с примером работы таймера 6.

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

 

  15 Responses to “STM32. Базовые таймеры TIM6 и TIM7.”

  1. Здравствуйте.
    Скажите а вы дальше будете развивать этот курс?
    Тоже начал осваивать эту платформу. После AVRок с их архитектурой очень напряжно. Так и не понял как добраться до ЕЕПРОМ.
    ЗЫ Очень хорошо получается. Всё понятно. Начал «исследовать» стандартную библиотеку периферии — но таааааам….:)

    • Буду. С курсом пока застой, уже две недели в Питере, командировка. Также есть планы не только по ARM статьи писать, пока жду заказанные комплектующие, как «пощупаю», отпишусь. С библиотеками на самом деле все не так сложно, когда понимаешь принцип работы периферийного модуля, знаешь назначение его регистров. Я бы рекомендовал изучать именно в такой последовательности.

  2. Здравствуйте, подскажите пожалуйста, где объявлены и определены функции NVIC_SetPriority() и NVIC_EnableIRQ().

  3. При конфигурировании и проверки TIM6 возникли проблемы.
    1. — все равно сначало вызывается прерывание а только потом начинается счет.
    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    TIM6->PSC = 0xFFFF;
    TIM6->ARR = 0xFF;
    TIM6->CR1 |= TIM_CR1_CEN;
    TIM6->DIER |= TIM_DIER_UIE;
    2. — записывается сразу 0xF22F, без окончания счета до значения 0xFF.
    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    TIM6->PSC = 0xFFFF;
    TIM6->ARR = 0xFF;
    TIM6->CR1 |= TIM_CR1_ARPE;
    TIM6->CR1 |= TIM_CR1_CEN;
    и где нибудь в
    while(1)
    {TIM6->ARR = 0xF22F;}

    проверяю в Keil.

    • 1. Я думал, что кто-то раньше заметит :). Дело в том, что после разрешения счета таймера командой
      TIM6->CR1 |= TIM_CR1_CEN;
      сразу же устанавливается флаг вызова прерывания UIF в регистре состояния.
      Почему так происходит сказать не могу, предлагаю исправить следующим образом:
      До установки бита разрешения прерывания устанавливаем бит UG (update generation), тем самым вызываем прерывание, при этом флаг UIF должен установиться. И сразу же сбрасываем этот флаг вызова прерывания — UIF. Тогда после разрешения счета этот флаг уже не будет установлен. Вот так:

      //TIM6 init
      RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; //Подаем тактовые импульсы на таймер 6
      TIM6->PSC = 0xFFFF; //Коэффициент деления максимальный — 65535 + 1
      TIM6->ARR = 0x0F; //Перезагружаемое значение — 16
      TIM6->EGR |= TIM_EGR_UG;
      TIM6->SR &= ~TIM_SR_UIF;

      TIM6->DIER |= TIM_DIER_UIE; //Разрешаем прерывание при переполнении счетчика
      TIM6->CR1 |= TIM_CR1_CEN; //Запуск счета

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

      2. Проверил в ИАР, все работает как положено. Командой
      TIM6->ARR = 0xF22F;
      записывается значение 0xF22F, но первый раз таймер все равно считает до 0xFF.

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

  5. Возникла проблема. Есть проект и в одном из циклов есть оператор break. При настройке IAR во вкладке Language1 я указываю следующие настройки.
    Language: C
    Language Confomance: Standart with IAR extension

    Не работает оператор break. но вызываются прерывания.

    При выборе C++ оператор break успешно работает но не вызываются прерывания.

    for (i = 0; i < MaxAmountPacket; i++) {
    if (PackTurnSend[i].State == EmptyItem) {
    PackTurnSend[i].BodyType = PacketType;
    PackTurnSend[i].State = State;
    PackTurnSend[i].Channel = Channel;
    PackTurnSend[i].NumBytes = NumBytes;
    PackTurnSend[i].TimeOut = TimeOut;
    memcpy(PackTurnSend[i].Buff, buff, NumBytes);
    result = i;
    break;
    }

    Когда силы уже иссякли я взял Ваш проект с таймером TIM6 и проверил. и в самом деле не работают прерывания когда выбираю C++.

  6. Здравствуйте, подскажите пожалуйста… У меня плата STM32L Discovery, почему при отладке я не могу попасть в прерывание таймера? Программа Ваша, работает нормально, но в режиме отладки не переходит в прерывание… :(. Заранее спасибо.

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

    Использую STM32L-DISCOVERY, IDE IAR, MSI 2.097

    Хочу получить задержку:

    RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; // Включение тактирования таймера
    TIM7->ARR = 0xFFFF;
    TIM7->PSC = 0xFFFF; // Коэффициент деления предделителя
    TIM7->CNT = 0;
    TIM7->CR1 |= TIM_CR1_CEN; // Запуск таймера
    while (TIM7->CNT < 64){}

    В результате задержки нет. Впечатление такое, что не задан коэффициент деления предделителя.
    Куда копать?

    • Сделал так:

      RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; // Включение тактирования таймера
      TIM7->ARR = 64;
      TIM7->PSC = 0xFFFF; // Коэффициент деления предделителя
      TIM7->CR1 |= TIM_CR1_CEN; // Запуск таймера
      while (!(TIM7->SR & TIM_SR_UIF)){} //Ждать установки флага прерывания
      TIM7->SR &= ~TIM_SR_UIF;
      TIM7->CR1 &= ~TIM_CR1_CEN; // Остановка таймера

      Теперь работает, но все же интересно, что не так с предыдущим кодом.