Как то, мне пришлось работать с процессором STM32F407. Задача состояла в том, чтобы с заданной периодичностью выполнять некоторое действие. При подходе в решении данной задачи мой взор пал на таймера. Написать модуль по работе с таймером не составило особого труда, но к моему удивлению новый модуль работал неправильно. Частота срабатываний таймера не соответствовала моим ожиданиям. После поисков просторами большого и могучего поискового гиганта я нашел ответ. Как выяснилось позже, с данным «феноменом» сталкиваются многие. Кто-то докапывается до истины, кто-то просто делает «чтобы как-нибудь работало» подставляя эмпирически-магические числа, что есть нехорошо. Дабы читающему не сталкиваться с проблемой в будущем мною был создан данный пост…

Прежде чем начать работу с таймером необходимо уточнить частоту его тактирования, то есть  определить, на какой шине (которая выбранный таймер тактирует) находится интересующий нас таймер. Где это можно узнать? Это можно узнать в документе с названием «AN3988 — Clock configuration tool for STM32F40x_41x microcontrollers  (DM00039457).pdf». Например, таймер TIM4 находится под тактированием APB1. Узнать частоту её тактирования можно вызвав функцию RCC_GetClocksFreq(&RCC_Clocks). Интересующее нас значение находится в полученой структуре в переменной PCLK1_Frequency.

Для настройки таймера необходимо настроить:

  • TIM_Prescaler (делитель частоты для шины, на которой сидит наш таймер);
  • TIM_Period (значение, до которого должен досчитать таймер, после чего должно сработать прерывание);
  • TIM_CounterMode_Up (в какую сторону считать, например, вверх, вниз, туда-обратно…);
1
2
3
4
5
6
7
8
9
// setting up the timer clock frequency to 1000000 Hz
APB1_PrescalerValue = (RCC_Clocks.PCLK1_Frequency/1000000) - 1;
// setting up the frequency of timer interrupts to 1000 Hz (according to the APB1 clock scheme (AN3988))
TIM_PeriodValue = 1000 * ((APB1_PrescalerValue == 1) ? 1 : 2);
 
TIM_TimeBaseStructure.TIM_Prescaler = APB1_PrescalerValue;
TIM_TimeBaseStructure.TIM_Period = TIM_PeriodValue;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

К странностям можно отнести только механизм расчета значения для периода таймера. Дело в том, что при работе с таймерами, которые «сидят» на шине APBx (APB1, APB2…) существует некое правило, описанное в документации от производителя. Его можно сформулировать следующим образом: «Если значение TIM_Prescaler не равно 1 — частота конечного тактирования будет в 2 раза больше, чем частота шины APBx!». Следовательно, для правильной работы мы вынуждены умножить значение периода для таймера на число 2, тем самым понизив частоту срабатывания прерываний в 2 раза.

После этого необходимо настроить приоритет для выбранного таймера.

1
2
3
4
5
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

В общем, считаю детализацию дальнейших действий по настройке таймера (включая создание обработчика прерывания от таймера) избыточной. Неожиданные аспекты в работе я показал, так что разрешите откланяться!:)

Загрузка

По приведенной ниже ссылке можно скачать готовый пример проекта по работе с таймером. Пример был разработан в среде разработки Keil.

Скачать пример