| [Home] | [Donate!] [Контакты] |
| [<< Базовые таймеры TIM6 и TIM7] | [DMA (прямой доступ к памяти) >>] |
В статье рассказывается о подготовительных действиях, которые необходимо выполнить перед использованием таймеров; описываются некоторые опыты с таймерами; приводится пример программы с использованием базового таймера.
ОглавлениеКаждому периферийному устройству микроконтроллера для работы требуется тактовый сигнал. Для каждого из устройств тактовый сигнал может быть включён и отключён индивидуально, независимо от других устройств. По умолчанию, после сброса микроконтроллера, тактовые сигналы всех периферийных устройств отключены. Это сделано для уменьшения потребления энергии. В программе, перед тем как использовать устройство, следует включить его тактовый сигнал. Без включения тактового сигнала, даже регистры устройства будут недоступны для чтения и записи.
Базовые таймеры подключены к шине 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);
}