AVR семь таймеров и не снилось... |
Существует три вида таймеров: базовые, общего назначения и продвинутые таймеры.
В этой статье буду пробовать таймер общего назначения.
В мануале, в начале каждой главы, в начале идет перечисление линеек ST микроконтроллеров, а потом уточняется для какой линейки эта глава написана. Моя Maple Mini использует STM32F103CB с 128 Кб памяти и это относит ее к Medium-density devices.
Немного о тактировании.
В характеристиках платы написано что системная частота - 72 МГц. Полазив по мануалу обнаружил, что у STM32 есть гибкая система настройки системной частоты и частот различных устройств. Стало интересно, какая частота будет использоваться в таймере. Взглянув на обратную сторону видим кварц на 8 МГц.
Написанно 8 MHz. |
Из главы 7.2 мануала видно что для таймера которого я выбрал (TIM2) используется частота APB1. Которая настраивается PPRE1 битами в регистре RCC_CFGR. Допустимая максимальная частота 36 МГц. Осталось это проверить.
void main(void) {
SystemInit();
volatile uint32_t value = 0;
value = RCC->CFGR;
}
SystemInit();
volatile uint32_t value = 0;
value = RCC->CFGR;
}
В дебаге value = 1901578.
Бинарное представление value: 0000 0000 0001 1101 0000 0100 0000 1010.
Теперь смотрим в мануал и определяем что к чему.
Биты 0-4 имеют значение 1010, что говорит о том, что используется PLL.
Биты 18-21 имеет значение 0 111 - используется умножитель частоты PLL x9. 8 МГц * 9 = 72 МГц.
Биты 4-7 имеют значение 0000, что говорит о том, что системная частота не делится.
Биты 8-10 имеют значение 100, что по мануалу означает что частота APB1 = HCLK/2 = 36МГц.
Значит 36МГц является тактовой частотой используемого таймера.
Пробуем таймер общего назначения TIM2.
TIM2 относится к таймерам общего назначения.Регистр TIM2_PSC является предделителем частоты APB1, которая будет использоваться таймером.
Регистр TIM2_CNT является счетчиком, МК инкрементирует счетчик с каждым тактом APB1/TIM2_PSC частоты.
Таймер вызвает прерывание TIM2_IRQn, когда происходит переполнение TIM2_CNT или при достижении значения равное значению из TIM2_ARR.
Чтобы таймер заработал по переполнению с TIM2_ARR нужно:
- Включить бит разрешающий использовать TIM2_ARR.
- Разрешить прерывание для таймера + добавить его в NVIC.
- Включить счетчик таймера.
- Установить значения TIM2_PSC, TIM2_ARR.
- Написать обработчик прерывания от таймера.
- Сбрасываем UIF флаг прерывания регистра TIM2_SR, вручную.
В этом примере будем мигать встроенным светодиодом раз в секунду. Напомню, что частота таймера без предделителя равна 36 МГц. В примере предделитель будет равен 36000, чтобы частота таймера была равно 1кГц, где один такт эквивалентен 1 миллисекунде. TIM2_ARR будет иметь значение 999, что эвивалентно 1000 миллисекундам (999 тк отсчет начинается с 0).
#include "stm32f10x.h"
void main(void) {
SystemInit();
//Включаем тактирование для GPIO
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
//Включаем тактирование TIM2. Используем APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//настраиваем светодиод GPIO1 на выход
GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
GPIOB->CRL &=~(GPIO_CRL_CNF1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_0);
//добавляем TIM2 прерывание в NVIC
NVIC_EnableIRQ(TIM2_IRQn);
//устанавливаем предделитель
TIM2->PSC = 36000;
//устанавливаем количество тактов до прерывания
TIM2->ARR = 999;
//разрешаем использовать TIM2_ARR
TIM2->CR1 |= TIM_CR1_ARPE;
//включаем TIM прерывание
TIM2->DIER |= TIM_DIER_UIE;
//включаем сетчик
TIM2->CR1 |= TIM_CR1_CEN;
//enable global interrups
__enable_irq();
while (1) {
}
}
void TIM2_IRQHandler(void) {
//сбрасываем флаг прерывания вручную
TIM2->SR &=~ TIM_SR_UIF;
//Включаем/выключаем светодиод
GPIOB->ODR ^= GPIO_ODR_ODR1;
}
void main(void) {
SystemInit();
//Включаем тактирование для GPIO
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
//Включаем тактирование TIM2. Используем APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//настраиваем светодиод GPIO1 на выход
GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
GPIOB->CRL &=~(GPIO_CRL_CNF1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_0);
//добавляем TIM2 прерывание в NVIC
NVIC_EnableIRQ(TIM2_IRQn);
//устанавливаем предделитель
TIM2->PSC = 36000;
//устанавливаем количество тактов до прерывания
TIM2->ARR = 999;
//разрешаем использовать TIM2_ARR
TIM2->CR1 |= TIM_CR1_ARPE;
//включаем TIM прерывание
TIM2->DIER |= TIM_DIER_UIE;
//включаем сетчик
TIM2->CR1 |= TIM_CR1_CEN;
//enable global interrups
__enable_irq();
while (1) {
}
}
void TIM2_IRQHandler(void) {
//сбрасываем флаг прерывания вручную
TIM2->SR &=~ TIM_SR_UIF;
//Включаем/выключаем светодиод
GPIOB->ODR ^= GPIO_ODR_ODR1;
}
Несколько часов я потратил чтобы выяснить, что TIM2->SR &=~ TIM_SR_UIF; должно быть в начале обработчика прерывания, иначе оно либо не работает, либо странно работает. Хотя, во время дебага, в обоих случаях все ок. Почему так, я пока что не знаю..
Пробую Output Compare режим.
У каждого таймера общего назначения есть особые 4 канала, которые можно направлять на GPIO выводы. Каждый канал имеет соответсвующий TIM2_CRRx регистр. В данном режиме, при достижении счетчиком значения записанного в TIM2_CRRx регистре, будет меняться состояние GPIO вывода. По сути задается смещение по времени, когда GPIO вывод поменяет свое состояние.В этом примере будут задействованны два канала: второй будет включаться и выключаться на две секунды позже чем первый. Для этого примера понадобится к 11(PA0) и 10(PA1) выводу платы подключить светодиоды.
#include "stm32f10x.h"
void main(void) {
SystemInit();
//включаем тактирование для GPIO A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//включаем тактирование для таймера TIM2. используем APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//включаем тактирование для AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// настравиваем вывод PA0 (TIM2_CCR1) как альтернативный выход
GPIOA->CRL &=~(GPIO_CRL_CNF0_0);
//устанавливаем бит альтернативного выхода
GPIOA->CRL |= (GPIO_CRL_CNF0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_0);
// настравиваем вывод PA1 (TIM2_CCR2) как альтернативный выход
GPIOA->CRL &=~(GPIO_CRL_CNF1_0);
//устанавливаем бит альтернативного выхода
GPIOA->CRL |= (GPIO_CRL_CNF1_1);
GPIOA->CRL |= (GPIO_CRL_MODE1_1);
GPIOA->CRL |= (GPIO_CRL_MODE1_0);
//ставим предделитель. 1 такт = 1 мс.
TIM2->PSC = 35999;
//общая длина одного цикла 12 секунд.
TIM2->ARR = 12000;
//устанавливаем значение смешения для второго канала
TIM2->CCR1 = 1000;
//set output compare mode
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_1;
//signal is forced hight on channel 1 (PA0) when the TIM2 counter maches the CRR1 register value
TIM2->CCMR1 |= TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 &=~ TIM_CCMR1_OC1M_2;
//enable compare mode for the first chanel:
TIM2->CCER |= TIM_CCER_CC1E;
//устанавливаем значение смешения для второго канала
TIM2->CCR2 = 2000;
//set output compare mode
TIM2->CCMR1 &=~ TIM_CCMR1_CC2S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC2S_1;
//signal is forced hight on channel 2 (PA1) when the TIM2 counter maches the CRR1 register value
TIM2->CCMR1 |= TIM_CCMR1_OC2M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1;
TIM2->CCMR1 &=~ TIM_CCMR1_OC2M_2;
//enable compare mode for the second channel:
TIM2->CCER |= TIM_CCER_CC2E;
//включаем счетчик
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
}
}
void main(void) {
SystemInit();
//включаем тактирование для GPIO A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//включаем тактирование для таймера TIM2. используем APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//включаем тактирование для AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// настравиваем вывод PA0 (TIM2_CCR1) как альтернативный выход
GPIOA->CRL &=~(GPIO_CRL_CNF0_0);
//устанавливаем бит альтернативного выхода
GPIOA->CRL |= (GPIO_CRL_CNF0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_0);
// настравиваем вывод PA1 (TIM2_CCR2) как альтернативный выход
GPIOA->CRL &=~(GPIO_CRL_CNF1_0);
//устанавливаем бит альтернативного выхода
GPIOA->CRL |= (GPIO_CRL_CNF1_1);
GPIOA->CRL |= (GPIO_CRL_MODE1_1);
GPIOA->CRL |= (GPIO_CRL_MODE1_0);
//ставим предделитель. 1 такт = 1 мс.
TIM2->PSC = 35999;
//общая длина одного цикла 12 секунд.
TIM2->ARR = 12000;
//устанавливаем значение смешения для второго канала
TIM2->CCR1 = 1000;
//set output compare mode
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_1;
//signal is forced hight on channel 1 (PA0) when the TIM2 counter maches the CRR1 register value
TIM2->CCMR1 |= TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 &=~ TIM_CCMR1_OC1M_2;
//enable compare mode for the first chanel:
TIM2->CCER |= TIM_CCER_CC1E;
//устанавливаем значение смешения для второго канала
TIM2->CCR2 = 2000;
//set output compare mode
TIM2->CCMR1 &=~ TIM_CCMR1_CC2S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC2S_1;
//signal is forced hight on channel 2 (PA1) when the TIM2 counter maches the CRR1 register value
TIM2->CCMR1 |= TIM_CCMR1_OC2M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1;
TIM2->CCMR1 &=~ TIM_CCMR1_OC2M_2;
//enable compare mode for the second channel:
TIM2->CCER |= TIM_CCER_CC2E;
//включаем счетчик
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
}
}
Output Compare режим с прерыванием.
Немного модифицируем предыдущий пример. Уберем второй канал, но добавим прерывание когда счетчик = CCR1 значению. В прерывании будем мигать встроенным светодиодом PB1. Получится так, что встроенный светодиод будет мигать вместе со светодиодом первого канала.
#include "stm32f10x.h"
void main(void) {
SystemInit();
//enable clock for GPIO A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//enable clock for GPIO B
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
//enable clock for TIM2. Use APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//for AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
//configure PA0 (TIM2_CCR1) GPIO0 on alternative output for TIM2 first chanel
GPIOA->CRL &=~(GPIO_CRL_CNF0_0);
//set bit of the afio output
GPIOA->CRL |= (GPIO_CRL_CNF0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_0);
//configure PB1 (TIM2_CCR1) for internal LED
GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
GPIOB->CRL &=~ (GPIO_CRL_CNF1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_0);
//add TIM2 interrupt to NVIC
NVIC_EnableIRQ(TIM2_IRQn);
//set prescaler
TIM2->PSC = 35999;
//set total count tackts
TIM2->ARR = 12000;
//set value UOTPUT COMPARE for ch1
TIM2->CCR1 = 1000;
//set output compare mode
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_1;
//signal is forced hight on channel 1 (PA0) when the TIM2 counter maches the TIM2_CRR1 register value
TIM2->CCMR1 |= TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 &=~ TIM_CCMR1_OC1M_2;
//enable compare mode:
TIM2->CCER |= TIM_CCER_CC1E;
//enable interupt when TIM2 counter maches TIM2_CCR1
TIM2->DIER |= TIM_DIER_CC1IE;
//enable TIM2
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
}
}
void TIM2_IRQHandler(void) {
//reset interupt flag from CCR1
TIM2->SR &=~ TIM_SR_CC1IF;
//on/off internal LED
GPIOB->ODR ^= GPIO_ODR_ODR1;
}
void main(void) {
SystemInit();
//enable clock for GPIO A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//enable clock for GPIO B
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
//enable clock for TIM2. Use APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//for AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
//configure PA0 (TIM2_CCR1) GPIO0 on alternative output for TIM2 first chanel
GPIOA->CRL &=~(GPIO_CRL_CNF0_0);
//set bit of the afio output
GPIOA->CRL |= (GPIO_CRL_CNF0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_0);
//configure PB1 (TIM2_CCR1) for internal LED
GPIOB->CRL &=~(GPIO_CRL_CNF1_0);
GPIOB->CRL &=~ (GPIO_CRL_CNF1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_1);
GPIOB->CRL |= (GPIO_CRL_MODE1_0);
//add TIM2 interrupt to NVIC
NVIC_EnableIRQ(TIM2_IRQn);
//set prescaler
TIM2->PSC = 35999;
//set total count tackts
TIM2->ARR = 12000;
//set value UOTPUT COMPARE for ch1
TIM2->CCR1 = 1000;
//set output compare mode
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_1;
//signal is forced hight on channel 1 (PA0) when the TIM2 counter maches the TIM2_CRR1 register value
TIM2->CCMR1 |= TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 &=~ TIM_CCMR1_OC1M_2;
//enable compare mode:
TIM2->CCER |= TIM_CCER_CC1E;
//enable interupt when TIM2 counter maches TIM2_CCR1
TIM2->DIER |= TIM_DIER_CC1IE;
//enable TIM2
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
}
}
void TIM2_IRQHandler(void) {
//reset interupt flag from CCR1
TIM2->SR &=~ TIM_SR_CC1IF;
//on/off internal LED
GPIOB->ODR ^= GPIO_ODR_ODR1;
}
Пробуем ШИМ.
В этом примере запускаю PWM mode 1. В этом режиме 30 тактов сигнал будет положительным, а 70 отрицательным. Если включить PWM mode 2, то сигнал будет инвертирован. Сигнал будет на первом канале TIM2 - PA0. Так же уменьшил предделитель, чтобы частота таймера была 2кГц.
#include "stm32f10x.h"
void main(void) {
SystemInit();
//enable clock for GPIO A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//enable clock for TIM2. Use APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//for AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
//configure PA0 (TIM2_CCR1) GPIO0 on alternative output for TIM2 first chanel
GPIOA->CRL &=~(GPIO_CRL_CNF0_0);
//set bit of the afio output
GPIOA->CRL |= (GPIO_CRL_CNF0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_0);
//set prescaler
TIM2->PSC = 17999;
//set total count tackts
TIM2->ARR = 100;
//set value for ch1. 30 tacts is positive signal
TIM2->CCR1 = 30;
//set to output chanel 1
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_1;
//Enable PWM mode 1 on ch 1
TIM2->CCMR1 &=~ TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2;
//enable preload CCR1 register, coz it's need for PWM
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;
//enable compare mode:
TIM2->CCER |= TIM_CCER_CC1E;
//enable TIM2
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
}
}
void main(void) {
SystemInit();
//enable clock for GPIO A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//enable clock for TIM2. Use APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
//for AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
//configure PA0 (TIM2_CCR1) GPIO0 on alternative output for TIM2 first chanel
GPIOA->CRL &=~(GPIO_CRL_CNF0_0);
//set bit of the afio output
GPIOA->CRL |= (GPIO_CRL_CNF0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_1);
GPIOA->CRL |= (GPIO_CRL_MODE0_0);
//set prescaler
TIM2->PSC = 17999;
//set total count tackts
TIM2->ARR = 100;
//set value for ch1. 30 tacts is positive signal
TIM2->CCR1 = 30;
//set to output chanel 1
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_0;
TIM2->CCMR1 &=~ TIM_CCMR1_CC1S_1;
//Enable PWM mode 1 on ch 1
TIM2->CCMR1 &=~ TIM_CCMR1_OC1M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2;
//enable preload CCR1 register, coz it's need for PWM
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;
//enable compare mode:
TIM2->CCER |= TIM_CCER_CC1E;
//enable TIM2
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
}
}
Режим Center-Aligned PWM заработал только после того как биты CMS были установленны до OC1Mx битов. Я не нашел подобной информации в даташите.
Всегда нужно читать сноски после описания битов, тк там часто указывается информация когда бит возможно поменять, а когда уже нет. Например, бит CC1S можно изменить только, когда бит CC1E = 0.
Еще, самый безопасный путь использования режима выравнивания по центру – генерировать
обновление программно(установкой бита UG регистра TIMx_EGR) перед запуском
счетчика и не записывать в счетчик пока он работает.
обновление программно(установкой бита UG регистра TIMx_EGR) перед запуском
счетчика и не записывать в счетчик пока он работает.
Принудительное управление выходными каналами (Forced Output Mode).
Еще можно вручную настраивать состояние выходных каналов таймера (например PA0), меняя значения OCxM битов (x - номер канала). Значение 100 устанавливает сигнал низкого уровня, 101 - сигнал высокого уровня.
OCxFE бит.
В мануале написано, что если установить этот бит, то изменение выходного состояния, на соответсвующем канале, будет ускорено с 5 тактов до 3х.На этом пока все.
Очень хорошо изложен материал
ОтветитьУдалитьхотелось бы увидеть продолжение
искренне на это надеюсь
А для STM32 F4 будет этот пример работать.?
ОтветитьУдалить