[Home] [< Prev: Базовые таймеры TIM6 и TIM7] [Next: DMA (прямой доступ к памяти) >]

Использование базовых таймеров (TIM6, TIM7)

В статье рассказывается о подготовительных действиях, которые необходимо выполнить перед использованием таймеров; описываются некоторые опыты с таймерами; приводится пример программы с использованием базового таймера.



Включение таймера
Обработка прерываний от таймера
Конфигурирование таймера
Пример программы

Включение таймера

Каждому периферийному устройству микроконтроллера для работы требуется тактовый сигнал. Для каждого из устройств тактовый сигнал может быть включён и отключён индивидуально, независимо от других устройств. По умолчанию, после сброса микроконтроллера, тактовые сигналы всех периферийных устройств отключены. Это сделано для уменьшения потребления энергии. В программе, перед тем как использовать устройство, следует включить его тактовый сигнал. Без включения тактового сигнала, даже регистры устройства будут недоступны для чтения и записи.

Базовые таймеры подключены к шине APB1, так что для управления их тактовыми сигналами используется регистр RCC->APB1ENR:

    // Включить тактовый сигнал для TIM6.
    RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;
или
    
    // Включить тактовый сигнал для TIM7.
    RCC->APB1ENR|=RCC_APB1ENR_TIM7EN;

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

    // Включить тактовый сигнал для TIM6 и TIM7.
    RCC->APB1ENR|=RCC_APB1ENR_TIM6EN|RCC_APB1ENR_TIM7EN;

Обработка прерываний от таймера

В зависимости от настроек, таймер может вообще не генерировать прерываний, генерировать их только при переполнении счётчика или генерировать прерывание как в случае переполнения, так и каждый раз при программной установке бита TIM_EGR_UG в регистре TIMx->EGR.

Если частота тактового сигнала таймера равна Fclk и прерывания разрешены, то прерывания в результате переполнения будут происходить с частотой F=Fclk/((PSC+1)*(ARR+1)). Здесь под PSC и ARR подразумевается содержимое соответствующих теневых регистров. ARR входит в формулу в виде множителя 1/(ARR+1), это объясняется тем, что счётчик таймера производит счёт от 0 до ARR включительно, т.е. всего счётчик проходит через ARR+1 состояние.

Обработчик прерывания для TIM6 имеет имя TIM6_DAC_IRQHandler, для TIM7 - TIM7_IRQHandler. Как видим, два устройства, DAC и TIM6 имеют прерывание с одним номером и общим обработчиком. Поэтому обработчик должен сначала выяснить источник прерывания, затем выполнить соответствующий код. Таймер TIM7 имеет своё собственное, отдельное прерывание.

Обработчик прерывания от таймера должен сбросить бит TIM_SR_UIF регистра TIMx->SR путём записи в бит 0. Иначе, после возврата из прерывания, будет снова вызван его обработчик.

// Обработчик прерывания от TIM6 и DAC.
// Учитываем, что обработчик общий для прерываний от двух устройств.
extern "C" void TIM6_DAC_IRQHandler()
{
    // Проверяем, было ли прерывание от таймера...
    if(TIM6->SR&TIM_SR_UIF)
    {
        // Сбрасываем бит TIM_SR_UIF.
        TIM6->SR&=~TIM_SR_UIF;

        // Выполняем свой код по обработке прерывания.
        // .....
    }
    
    // Проверяем было ли прерывание от DAC и обрабатываем его
    // (если генерация этого прерывания разрешена настройками DAC).
    // .....
}

// Обработчик прерывания от TIM7.
extern "C" void TIM7_IRQHandler()
{
    // Сбрасываем бит TIM_SR_UIF.
    TIM7->SR&=~TIM_SR_UIF;

    // Выполняем свой код по обработке прерывания.
    // .....
}

После того, как написали реализацию обработчика, осталось настроить NVIC (задать уровень приоритета и разрешить обработку для прерывания с соответствующим номером) и настроить таймер на генерацию исключения. Это можно сделать в основной функции программы main() с помощью примерно такого кода:

    // Задаём уровень приоритета (здесь - минимальный приоритет)
    // и разрешаем обработку прерывания.
    NVIC_SetPriority(TIM6_DAC_IRQn, (1<<__NVIC_PRIO_BITS)-1);
    NVIC_EnableIRQ(TIM6_DAC_IRQn);

    // Включаем тактовый сигнал таймера, иначе не будут
    // доступны регистры таймера.
    RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;

    // Разрешаем таймеру генерацию прерываний
    // (по умолчанию, после сброса бит TIM_CR1_URS сброшен в 0
    // и прерывание генерируется как при переполнении счётчика,
    // так и при установке бита TIM_EGR_UG).
    TIM6->DIER|=TIM_DIER_UIE;

    // При выполнении следующей строки генерируется прерывание
    // (при этом сам таймер пока ещё остановлен: бит включения
    // счёта TIM_CR1_CEN сброшен в 0).
    TIM6->EGR|=TIM_EGR_UG;

Если разрешающий генерацию прерывания от таймера бит TIM_DIER_UIE регистра TIMx->DIER не установлен в 1, то, естественно, прерывания не будут возникать. Однако содержимое бита TIM_SR_UIF в регистре TIMx->SR будет обновляться при наступлении соответствующих генерации прерывания событий. Если впоследствии мы установим в 1 бит TIM_DIER_UIE и к этому моменту уже будет установлен TIM_SR_UIF, то сразу же произойдёт прерывание. Если это нежелательно, в подобных ситуациях перед разрешением генерации прерываний следует сбросить бит TIM_SR_UIF записью в него 0.

Конфигурирование таймера

Подробно назначение регистров базовых таймеров было рассмотрено в предыдущей статье "Базовые таймеры TIM6 и TIM7". Здесь же приведём несколько фрагментов кода, демонстрирующих особенности функционирования базовых таймеров.

Будем использовать приведённый ниже базовый код, в функцию main() которого будем добавлять тот или иной фрагмент кода для демонстрации особенностей функционирования таймера. Здесь производится переключение светодиода, подключённого к выходу PC9 микроконтроллера при каждой обработке прерывания от таймера TIM6. При первом выполнении обработчика прерывания светодиод включается, при следующем выключается и т.д. Кроме того, количество обработанных прерываний от таймера подсчитывается в переменной cnt, значение которой можно посмотреть в отладчике.

/**
  *  IMPORTANT NOTE!
  *  The symbol VECT_TAB_SRAM needs to be defined when building the project
  *  if code has been located to RAM and interrupts are used. 
  *  Otherwise the interrupt table located in flash will be used.
  *  See also the <system_*.c> file and how the SystemInit() function updates 
  *  SCB->VTOR register.  
  *  E.g.  SCB->VTOR = 0x20000000;  
  */

#include <stm32f10x.h>

// Счётчик прерываний от таймера TIM6.
volatile uint32_t cnt=0;

// Обработчик прерывания от таймера TIM6 (DAC не используется).
// Переключает светодиод, подключённый к PC9 (ON-->OFF, OFF-->ON).
extern "C" void TIM6_DAC_IRQHandler()
{
    if(TIM6->SR&TIM_SR_UIF)
    {
        TIM6->SR&=~TIM_SR_UIF;

        cnt++;

        // TOGGLE LED 
        if(GPIOC->ODR&GPIO_ODR_ODR9)
            GPIOC->BRR=GPIO_BRR_BR9;
        else
            GPIOC->BSRR=GPIO_BSRR_BS9;
    }
}

int main()
{
    // Включаем тактирование используемой периферии.
    RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;
    RCC->APB2ENR|=RCC_APB2ENR_IOPCEN;

    // PC9 конфигурируем как двухтактный выход
    // с основной функцией, макс. частота 2 МГц
    // (CNF9: 00, MODE9: 10)
    GPIOC->CRH=GPIOC->CRH&
            ~(GPIO_CRH_CNF9|GPIO_CRH_MODE9)|
            GPIO_CRH_MODE9_1;

    // Конфигурируем NVIC.
    NVIC_SetPriority(TIM6_DAC_IRQn, (1<<__NVIC_PRIO_BITS)-1);
    NVIC_EnableIRQ(TIM6_DAC_IRQn);

    // Разрешаем таймеру генерировать прерывания.
    TIM6->DIER|=TIM_DIER_UIE;

    // ************************************
    // Будем добавлять фрагменты кода сюда.
    // ************************************

    while(true);
}

1. Если добавить в функцию main приведённой выше программы фрагмент

    TIM6->EGR|=TIM_EGR_UG;	

то обработчик прерывания сработает 1 раз (счётчик таймера остановлен, биты TIM_CR1_URS и TIM_CR1_UDIS сброшены в 0).

2. Если вставить фрагмент

    TIM6->CR1|=TIM_CR1_URS;
    TIM6->EGR|=TIM_EGR_UG;

то прерывание не сработает - при установленном бите TIM_CR1_URS прерывание генерируется только при переполнении. Но обновление теневых регистров и сброс счётчиков выполняются независимо от значения бита TIM_CR1_URS.

3. Запуск таймера в режиме одиночного импульса с оценкой длительности интервала (в тактах тактового сигнала ядра) с помощью системного таймера.

// Добавляем в код обработчик исключения от системного таймера.
extern "C" void SysTick_Handler()
{}

Вставляем в функцию main().

    // Включаем системный таймер.
    SysTick_Config(SystemCoreClock/100);
    
    uint32_t t0, t1, dt0, dt;
    uint32_t cnt0;

    // Оценка накладных расходов на вызовы.
    cnt0=cnt;
    t0=SysTick->VAL;
    TIM6->EGR|=TIM_EGR_UG;
    while(cnt0==cnt){}
    t1=SysTick->VAL;
    dt0=t0-t1;
    if(t1>t0)
        dt0+=SysTick->LOAD+1;

    // Оценка времени счёта 1000 импульсов.
    TIM6->ARR=999;
    cnt0=cnt;
    t0=SysTick->VAL;
    TIM6->CR1=TIM_CR1_OPM|TIM_CR1_CEN;
    while(cnt0==cnt){}
    t1=SysTick->VAL;
    dt=t0-t1;
    if(t1>t0)
        dt+=SysTick->LOAD+1;
    if(dt>dt0)
        dt-=dt0;
    // <-- ставим точку останова в отладчике
    // и проверяем результат.

В результате должны получить значение dt, близкое к 1000. Изменяя коэффициент деления прескалера (не забываем генерировать событие обновления), увеличиваем продолжительность импульса в соответствующее количество раз.

Пример программы

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

Скачать исходный код примера

// file: main.cpp
/**
  *  IMPORTANT NOTE!
  *  The symbol VECT_TAB_SRAM needs to be defined when building the project
  *  if code has been located to RAM and interrupts are used.
  *  Otherwise the interrupt table located in flash will be used.
  *  See also the <system_*.c> file and how the SystemInit() function updates
  *  SCB->VTOR register.
  *  E.g.  SCB->VTOR = 0x20000000;
  */

#include <stm32f10x.h>

extern "C" void TIM6_DAC_IRQHandler()
{
    if(TIM6->SR&TIM_SR_UIF)
    {
        TIM6->SR&=~TIM_SR_UIF;

        // LED TOGGLE
        if(GPIOC->ODR&GPIO_ODR_ODR9)
            GPIOC->BRR=GPIO_BRR_BR9;
        else
            GPIOC->BSRR=GPIO_BSRR_BS9;
    }
}

int main()
{
    RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;
    RCC->APB2ENR|=RCC_APB2ENR_IOPCEN;

    // PC9 конфигурируем как двухтактный выход
    // с основной функцией, макс. частота 2 МГц
    // (CNF9: 00, MODE9: 10)
    GPIOC->CRH=GPIOC->CRH&
            ~(GPIO_CRH_CNF9|GPIO_CRH_MODE9)|
            GPIO_CRH_MODE9_1;

    NVIC_SetPriority(TIM6_DAC_IRQn, (1<<__NVIC_PRIO_BITS)-1);
    NVIC_EnableIRQ(TIM6_DAC_IRQn);

    // Настраиваем таймер на генерацию прерывания
    // с частотой 4 Гц (при частоте ядра 24 МГц).
    // Тогда частота "мигания" составит 2 Гц
    // (1 период включает в себя включение и выключение).
    TIM6->ARR=0xFFFF;
    TIM6->PSC=91;
    TIM6->CR1|=TIM_CR1_URS;
    TIM6->EGR|=TIM_EGR_UG;
    TIM6->DIER|=TIM_DIER_UIE;

    TIM6->CR1|=TIM_CR1_CEN;

    while(true);
}
author: hamper; date: 2016-05-26
  @Mail.ru