воскресенье, 1 октября 2017 г.

STM32. Maple Mini. Таймер.




AVR семь таймеров и не снилось...

Существует три вида таймеров: базовые, общего назначения и продвинутые таймеры.
В этой статье буду пробовать таймер общего назначения.
В мануале, в начале каждой главы, в начале идет перечисление линеек ST микроконтроллеров, а потом уточняется для какой линейки эта глава написана. Моя Maple Mini использует STM32F103CB с 128 Кб памяти и это относит ее к Medium-density devices.

Немного о тактировании.

В характеристиках платы написано что системная частота - 72 МГц. Полазив по мануалу обнаружил, что у STM32 есть гибкая система настройки системной частоты и частот различных устройств. Стало интересно, какая частота будет использоваться в таймере. Взглянув на обратную сторону видим кварц на 8 МГц.

Написанно 8 MHz.
Видимо используется PLL - умножитель частоты. 8 МГц внешнего кварца преобразовывается в 72 МГц внутренней частоты. Умножитель частоты можно настраивать меняя коэффициент умножения, тогда системная частота будет другой.
Из главы 7.2 мануала видно что для таймера которого я выбрал (TIM2) используется частота APB1. Которая настраивается PPRE1 битами в регистре RCC_CFGR. Допустимая максимальная частота 36 МГц. Осталось это проверить.

void main(void) {
  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 нужно:
  1. Включить бит разрешающий использовать TIM2_ARR.
  2. Разрешить прерывание для таймера + добавить его в NVIC.
  3. Включить счетчик таймера.
  4. Установить значения TIM2_PSCTIM2_ARR.
  5. Написать обработчик прерывания от таймера.
  6. Сбрасываем 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;
}

Несколько часов я потратил чтобы выяснить, что 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) {
  }
}

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

Пробуем ШИМ.

В этом примере запускаю 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) {
  }
}

Режим Center-Aligned PWM заработал только после того как биты CMS были установленны до OC1Mx битов. Я не нашел подобной информации в даташите.
Всегда нужно читать сноски после описания битов, тк там часто указывается информация когда бит возможно поменять, а когда уже нет. Например, бит CC1S можно изменить только, когда бит CC1E = 0.
Еще, самый безопасный путь использования режима выравнивания по центру – генерировать
обновление программно(установкой бита UG регистра TIMx_EGR) перед запуском
счетчика и не записывать в счетчик пока он работает.

Принудительное управление выходными каналами (Forced Output Mode).

Еще можно вручную настраивать состояние выходных каналов таймера (например PA0), меняя значения OCxM битов (x - номер канала). Значение 100 устанавливает сигнал низкого уровня, 101 - сигнал высокого уровня.

OCxFE бит.

В мануале написано, что если установить этот бит, то изменение выходного состояния, на соответсвующем канале, будет ускорено с 5 тактов до 3х.

На этом пока все.

2 комментария:

  1. Очень хорошо изложен материал
    хотелось бы увидеть продолжение
    искренне на это надеюсь

    ОтветитьУдалить
  2. А для STM32 F4 будет этот пример работать.?

    ОтветитьУдалить