В любом современном контроллере есть таймеры. В этой статье речь пойдёт о простых (базовых) таймерах stm32f4 discovery.
Это обычные таймеры. Они 16 битные с автоматической перезагрузкой. Кроме того имеется 16 битный программируемый делитель частоты. Есть возможность генерирования прерывания по переполнению счётчика и/или запросу DMA.
Приступим. Как и раньше я пользуюсь Eclipse + st-util в ubuntu linux
Первым делом подключаем заголовки:
#include <stm32f4xx.h> #include <stm32f4xx_rcc.h> #include <stm32f4xx_gpio.h> #include <stm32f4xx_tim.h> #include <misc.h>
Ничего нового в этом нет. Если не ясно откуда они берутся либо читайте предыдущие статьи, либо открывайте файл и читайте.
Определим две константы. Одну для обозначения диодов, другую массив из техже диодов:
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[flag]); // зажигаем слудующий диод
}
}
Функция main:
int main(){
init_leds();
init_timer();
do {
} while(1);
}
Цикл оставляем пустым. Счётчик выполняет свою работу асинхронно, а прерывание на то и прерывание, чтобы не зависеть от выполняемой в данный момент операции.
Скачать проект.






