[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); }