środa, 18 listopada 2015

[6] STM32 M3 - Nucleo - F103RB - Konfiguracja timerów

Ten post będzie dotyczył konfiguracji timerów dla STM32.

Wstęp


Mikrokontroler F103RB pozwala na obsługę czterech wbudowanych timerów. Poniżej przedstawiam dokładne informacje zaczerpnięte z dokumentacji:

Timer
Rozdzielczość
Zliczanie
Dzielnik
Kanały
Tim1
16 bit
Góra, dół, góra/dół
1 do 65536
4
Tim2..4
16 bit
Góra, dół, góra/dół
1 do 65536
4

Oprócz informacji wymienionych w tabeli timery oferują także:
  • porównywanie stanu licznika z zadaną wartością;
  • generacja sygnału PWM;
  • pomiar parametrów PWM;
  • zliczanie impulsów zewnętrznych;
  • kaskadowe połączenie liczników;
  • współpraca z takimi układami jak czujnik Halla, DMA, enkoder;
Taktowanie liczników odbywa się poprzez szynę APB1 dla TIM2, TIM3 oraz TIM4. Natomiast TIM1 taktowany poprzez szynę APB2. W obu przypadkach przy wykorzystywaniu ustawień 64MHz z wewnętrznego oscylatora otrzymuje się taką samą częstotliwość na obu szynach, oczywiście dla timerów.

Wartość dzielnika znajduje się w zakresie od 1 do 65536. Pozwala to na osiągnięcie częstotliwości taktowania. Co przy wykorzystywaniu maksymalnej częstotliwości pozwala na osiągnięcie taktowania od około 1000Hz (976Hz) do 64MHz.  

Każdy z dostępnych Timerów posiada 4 kanały służące do porównywania wartości. Dodatkowo Tim1 jako najbardziej zaawansowany ma funkcję multipleksacji PWM na 6 kanałów. Sam PWM zaprogramowany na 16-bit pozwoli na pełną regulację w zakresie od 0 do 100%. 

Programowanie


Inicjalizacja timerów opiera się o następujące ustawienia:
  • TIM_Prescaler - określa wartości dzielnika częstotliwości sygnału taktującego;
  • TIM_Period - okres licznika;
  • TIM_ClockDivision - wybór dzielnika zegara dla układu generującego czas martwy oraz dla filtra;
  • TIM_RepetitionCounter - tryb pracy licznika;
Dla timerów TIM2 do TIM3 można ustawić standardowe funkcje obsługi przerwania tzn. TIMx_IRQHandler. Natomiast dla TIM1 do wyboru sa następujące możliwości:

  •   TIM1_BRK_IRQn
  •   TIM1_UP_IRQn   
  •   TIM1_TRG_COM_IRQn  
  •   TIM1_CC_IRQn 

 Program - przerwania od licznika


Ten program będzie przedstawiał inicjalizację przerwania poprzez TIM2. W przerwaniu będzie wykonywane cykliczne zapalenie i zgaszenie diody.

#include "stm32f10x.h"
 
#define Pin GPIO_Pin_5
#define Line GPIOA
#define Clock RCC_APB2Periph_GPIOA
 
void TIM2_IRQHandler(void);
void GPIOInit(void);
void TIM2Init(void);
void NVICInit(void);
 
int main(void)
{
 GPIOInit();
 TIM2Init();
 NVICInit();
 
 while (1) {
 }
}
 
void TIM2Init(void)
{
 TIM_TimeBaseInitTypeDef TIMInit;
 
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
 
 //Taktowanie licznika  64MHz/64000 = 1kHz (1ms)
 TIMInit.TIM_Prescaler = 64000 - 1;
 //przepełnienie licznika 2000 taktów - 2s
 TIMInit.TIM_Period = 2000 - 2;
 //dzielnik dla zegara czasu martwego i filtru
 TIMInit.TIM_ClockDivision = TIM_CKD_DIV1;
 //licznik powtórzeń
 TIMInit.TIM_RepetitionCounter = 0;
 //tryb pracy licznika
 TIMInit.TIM_CounterMode = TIM_CounterMode_Up;
 //Inicjalizacja licznika
 TIM_TimeBaseInit(TIM2, &TIMInit);
 
 //Wlaczenie przerwan od licznikow, wlaczenie
 //przerwania kiedy nastąpi przepełnienie licznika
 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
 
 //Wlaczenie timerow
 TIM_Cmd(TIM2, ENABLE);
} 
void NVICInit()
{
 NVIC_InitTypeDef NVICInit;
 
 NVICInit.NVIC_IRQChannel = TIM2_IRQn;
 NVICInit.NVIC_IRQChannelPreemptionPriority = 0;
 NVICInit.NVIC_IRQChannelSubPriority = 0;
 NVICInit.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVICInit);
}
//Obsługa przerwania dla timera
void TIM2_IRQHandler()
{
 //Wywolanie przerwania po przepełnieniu licznika
 if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
 {
  TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
 
  if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5))
  {
   GPIO_ResetBits(GPIOA, GPIO_Pin_5);
  }
  else
  {
   GPIO_SetBits(GPIOA, GPIO_Pin_5);
  }
 }
}
 
//Deklaracja pinu z podłączoną diodą PA5
void GPIOInit(void)
{
 GPIO_InitTypeDef GpioInit;
 
    //Włączenie zegara
    RCC_APB2PeriphClockCmd(Clock, ENABLE);
 
    GpioInit.GPIO_Pin = Pin;
    GpioInit.GPIO_Speed = GPIO_Speed_50MHz;
    GpioInit.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(Line, &GpioInit);
}

Program dla timera 1 należy delikatnie zmodyfikować. Przede wszystkim będzie on taktowany z innej linii APB2 zamiast APB1. Inna będzie także deklaracja przerwania, TIM1_UP_IRQn oraz TIM1_UP_IRQHandler().

Program z ustawieniami dla timera pierwszego zamieszczam poniżej.

#include "stm32f10x.h"
 
#define Pin GPIO_Pin_5
#define Line GPIOA
#define Clock RCC_APB2Periph_GPIOA
 
void TIM1_UP_IRQHandler(void);
void GPIOInit(void);
void TIM1Init(void);
void NVICInit(void);
 
int main(void)
{
 GPIOInit();
 TIM1Init();
 NVICInit();
 
 while (1) {
 }
}
 
void TIM1Init(void)
{
 TIM_TimeBaseInitTypeDef TIMInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
 
 //Taktowanie licznika  64MHz/64000 = 1kHz (1ms)
 TIMInit.TIM_Prescaler = 64000 - 1;
 //przepełnienie licznika 2000 taktów - 2s
 TIMInit.TIM_Period = 2000 - 2;
 //dzielnik dla zegara czasu martwego i filtru
 TIMInit.TIM_ClockDivision = TIM_CKD_DIV1;
 //licznik powtórzeń
 TIMInit.TIM_RepetitionCounter = 0;
 //tryb pracy licznika
 TIMInit.TIM_CounterMode = TIM_CounterMode_Up;
 //Inicjalizacja licznika
 TIM_TimeBaseInit(TIM1, &TIMInit);
 
 //Wlaczenie przerwan od licznikow, wlaczenie
 //przerwania kiedy nastąpi przepełnienie licznika
 TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
 
 //Wlaczenie timerow
 TIM_Cmd(TIM1, ENABLE);
}
 
void NVICInit()
{
 NVIC_InitTypeDef NVICInit;
 
 NVICInit.NVIC_IRQChannel = TIM1_UP_IRQn;
 NVICInit.NVIC_IRQChannelPreemptionPriority = 0;
 NVICInit.NVIC_IRQChannelSubPriority = 0;
 NVICInit.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVICInit);
}
//Obsługa przerwania dla timera
void TIM1_UP_IRQHandler()
{
 //Wywolanie przerwania po przepełnieniu licznika
 if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET)
 {
  TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
 
  if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5))
  {
   GPIO_ResetBits(GPIOA, GPIO_Pin_5);
  }
  else
  {
   GPIO_SetBits(GPIOA, GPIO_Pin_5);
  }
 }
}
 
//Deklaracja pinu z podłączoną diodą PA5
void GPIOInit(void)
{
 GPIO_InitTypeDef GpioInit;
 
    //Włączenie zegara
    RCC_APB2PeriphClockCmd(Clock, ENABLE);
 
    GpioInit.GPIO_Pin = Pin;
    GpioInit.GPIO_Speed = GPIO_Speed_50MHz;
    GpioInit.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(Line, &GpioInit);
}

Program 2


Jak już wcześniej wspomniałem do dyspozycji są 4 kanały, wobec tego istnieje możliwość wysterowania większej liczby diod wykorzystując tylko jeden timer. Aby była możliwość takiego sterowania należy skorzystać z kanału Capture Compare. Pozwala on na porównywanie stanu licznika z zaprogramowaną wartością.

W programie zaprezentuje możliwość wysterowania dwóch diod zewnętrznych podlączonych do pinów PB15 i PB14.

#include "stm32f10x.h"
 
void GPIOInit(void);
void NVICInit(void);
void TIM2Init(void);
void TIM2_IRQHandler(void);
 
int main(void)
{
 GPIOInit();
 TIM2Init();
 NVICInit();
 
 while (1) {
 }
}
 
void TIM2_IRQHandler()
{
 //Obsługa przerwania od przepełnienia licznika
 if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
 {
  TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
 
  if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5))
   GPIO_ResetBits(GPIOA, GPIO_Pin_5);
  else
   GPIO_SetBits(GPIOA, GPIO_Pin_5);
 
  GPIO_SetBits(GPIOB, GPIO_Pin_15|GPIO_Pin_14);
 }
 
 //Porównywanie wartości
 if (TIM_GetITStatus(TIM2, TIM_IT_CC1) == SET)
 {
  TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
  GPIO_ResetBits(GPIOB, GPIO_Pin_15);
 }
 if (TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET)
 {
  TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
  GPIO_ResetBits(GPIOB, GPIO_Pin_14);
 }
}
 
void TIM2Init(void)
{
 TIM_TimeBaseInitTypeDef TIMInit;
 
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
 
 //Taktowanie licznika  64MHz/64000 = 1kHz (1ms)
 TIMInit.TIM_Prescaler = 64000 - 1;
 //przepełnienie licznika 2000 taktów - 2s
 TIMInit.TIM_Period = 2000 - 2;
 //dzielnik dla zegara czasu martwego i filtru
 TIMInit.TIM_ClockDivision = TIM_CKD_DIV1;
 //licznik powtórzeń
 TIMInit.TIM_RepetitionCounter = 0;
 //tryb pracy licznika
 TIMInit.TIM_CounterMode = TIM_CounterMode_Up;
 //Inicjalizacja licznika
 TIM_TimeBaseInit(TIM2, &TIMInit);
 
 //Wlaczenie przerwan od licznikow, wlaczenie
 //przerwania kiedy nastąpi przepełnienie licznika oraz dla porównywania kanałów
 TIM_ITConfig(TIM2, TIM_IT_Update|TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4, ENABLE);
 
 //Wlaczenie timerow
 TIM_Cmd(TIM2, ENABLE);
 
 TIM_SetCompare1(TIM2, 1100);
 TIM_SetCompare2(TIM2, 1350);
}
 
void NVICInit()
{
 NVIC_InitTypeDef NVICInit;
 
 NVICInit.NVIC_IRQChannel = TIM2_IRQn;
 NVICInit.NVIC_IRQChannelPreemptionPriority = 0;
 NVICInit.NVIC_IRQChannelSubPriority = 0;
 NVICInit.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVICInit);
}
 
void GPIOInit(void)
{
 GPIO_InitTypeDef GpioInit;
 
    //Włączenie zegara
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
 
    GpioInit.GPIO_Pin = GPIO_Pin_5;
    GpioInit.GPIO_Speed = GPIO_Speed_50MHz;
    GpioInit.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GpioInit);
 
    GpioInit.GPIO_Pin = GPIO_Pin_15|GPIO_Pin_14;
    GpioInit.GPIO_Speed = GPIO_Speed_50MHz;
    GpioInit.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GpioInit);
}

W kolejnym poście opiszę sposób obsługi PWM za pomocą liczników.