Basic таймеры в STM32

Таймеры - это такая периферия контроллера STM32 позволяющая нам очень точно отсчитывать интервалы времени. Это пожалуй одна из самых важных и наиболее используемых функций, однако есть и другие. Следует начать с того, что в контроллерах STM32 существуют таймеры разной степени крутости. Самые простые это Basic timers. Они хороши тем, что очень просто настраиваются и управляются при помощи минимума регистров. Все что они умеют это отсчитывать временные интервалы и генерировать прерывания когда таймер дотикает до заданного значения. Следующая группа (general-purpose timers) гораздо круче первой, они умеют генерировать ШИМ, умеют считать испульсы поступающие на определённые ножки, можно подключать энкодер итд. И самый крутой таймер это advanced-control timer, думаю что его я использовать не буду еще очень долго так как мне пока без надобности управлять трехфазным электродвигателем. Начать знакомство с таймерами следует с чего попроще, я решил взяться за Basic таймеры. Задача которую я себе поставил: Заставить таймер генерить прерывания каждую секунду.
Первым делом отмечу, что Basic таймеры (TIM6 и TIM7) прицеплены к шине APB1, поэтому в случае если частота тактовых импульсов на ней будет меняться, то и таймеры начнут тикать быстрее или медленнее. Если ничего не менять в настройках тактирования и оставить их по умолчанию, то частота APB1 составляет 24МГц при условии что подключен внешний кварц на частоту 8 МГц. Вообще система тактирования у STM32 очень замысловатая и я попробую про неё нормально написать отдельный пост. А пока просто воспользуемся теми настройками тактирования которые задаются кодом автоматически сгенерированым CooCox'ом. Начать стоит с самого главного регистра - TIMx_CNT (здесь и далее x - номер basic таймера 6 или 7). Это счётный 16-ти битный регистр, занимающийся непосредственно счётом времени. Каждый раз когда с шины APB1 приходит тактовый импульс, содержимое этого регистра увеличивается на едницу. Когда регистр переполняется, все начинается с нуля. При нашей дефолтной частоте шины APB1, таймер за одну секунду тикнет 24 млн раз! Это очень дофига, и поэтому у таймера есть предделитель, управлять которым мы можем при помощи регистра TIMx_PSC. Записав в него значение 24000-1 мы заставим счётный регистр TIMx_CNT увеличивать свое значение каждую милисекунду (Частоту APB1 делим на число в регистре предделителе и получаем сколько раз в секунду увеличивается счётчик). Единицу нужно вычесть потому, что если в регистре ноль то это означает, что включен делитель на единицу. Теперь, когда счётный регистр дотикает до 1000 мы можем точно заявить, что прошла ровно одна секунда! И че теперь опрашивать счётный регистр и ждать пока там появится 1000? Это не наш метод, ведь мы можем заюзать прерывания! Но вот беда, прерывание у нас всего одно, и возникает оно когда счётчик обнулится. Для того чтоб счётчик обнулялся досрочно, а не когда дотикает до 0xFFFF, служит регистр TIMx_ARR. Записываем в него то число до которого должен досчитывать регистр TIMx_CNT перед тем как обнулиться. Если мы хотим чтоб прерывание возникало раз в секунду, то нам нужно записать туда 1000. По части непосредственно отсчёта времени это все, но таймер сам по себе тикать не начнет. Его нужно включить установив бит CEN в регистре TIMx_CR1Этот бит разрешает начать отсчёт, соответственно если его сбросить то отсчет остановится (ваш К.О.). В регистре есть и другие биты но они нам не особо интересны. Зато интересен нам еще один бит, но уже в регистре TIMx_DIER. Называется он UIE, установив его мы разрешаем таймеру генерить прерывания при сбросе счётного регистра. Вот собственно и всё, даже не сложней чем в каких-нибудь AVRках. Итак небольше резюме: Чтоб заюзать basic таймер нужно:
  1. Установить предделитель чтоб таймер не тикал быстро (TIMx_PSC)
  2. Задать предел до которого таймер должен дотикать перед своим сбросом (TIMx_ARR)
  3. Включить отсчет битом CEN в регистре TIMx_CR1 
  4. Включить прерывание по переполнению битом UIE в регистре TIMx_DIER
Вот такая нехитрая последовательность. А теперь настало время достать сами знаете что и попробовать в миллионный раз мигнуть этим несчастными светодиодами, но уже с помощью таймера :-)
 

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
int main()
{
  GPIO_InitTypeDef PORT;
  //Включаем порт С и таймер 6
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);
  // Настроим ноги со светодиодами на выход
  PORT.GPIO_Pin = (GPIO_Pin_9 | GPIO_Pin_8);
  PORT.GPIO_Mode = GPIO_Mode_Out_PP;
  PORT.GPIO_Speed = GPIO_Speed_2MHz;
  GPIO_Init(GPIOC, &PORT);
  TIM6->PSC = 24000 - 1; // Настраиваем делитель что таймер тикал 1000 раз в секунду
  TIM6->ARR = 1000 ; // Чтоб прерывание случалось раз в секунду 
  TIM6->DIER |= TIM_DIER_UIE; //разрешаем прерывание от таймера
  TIM6->CR1 |= TIM_CR1_CEN; // Начать отсчёт!
  NVIC_EnableIRQ(TIM6_DAC_IRQn); //Разрешение TIM6_DAC_IRQn прерывания 
 
  while(1)
  {
    //Программа ничего не делает в пустом цикле
  }

}
// Обработчик прерывания TIM6_DAC
void TIM6_DAC_IRQHandler(void)
{
  TIM6->SR &= ~TIM_SR_UIF; //Сбрасываем флаг UIF
  GPIOC->ODR^=(GPIO_Pin_9 | GPIO_Pin_8); //Инвертируем состояние светодиодов
}


Стоит добавить небольшое примечание к обработчику прерывания. Дело в том, что он у нас используется сразу двумя блоками периферии: таймером 6 и DAC'ом. Это означает, что если вы будете писать программу которая разрешает прерывания от обоих этих периферийных устройств, то в теле обработчика необходимо проверять кто же из них вызвал прерывание. В нашем случае я не стал этого делать, так как ни каких прерываний от DAC возникнуть не может. Он не настроен, а по дефолту прерывания запрещены. В следующий раз рассмотрим general-purpose таймеры и их практическое применение.