LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[2] Kurs: pierwsze kroki z STM32F0DISCOVERY. Zabawy z LED-ami

 

Timer jako generator PWM

Mikrokontrolery STM32F05x są wyposażone w kilka modułów timerów. Poszczególne timery są do siebie podobne, chociaż ich bardziej zaawansowane własności są nieco zróżnicowane. Każdy timer jest wyposażony w preskaler, umożliwiający podział częstotliwości przebiegu zegarowego przez dowolną wartość 16-bitową. Każdy timer ma również rejestr długości okresu ARR. Timery, które mogą być użyte do generowania przebiegów PWM, są wyposażone w rejestry wartości wypełnienia dla każdego kanału – CCRx. Współczynnik wypełnienia jest określony przez iloraz wartości wypełnienia i długości okresu timera. Rejestry sterujące timera umożliwiają włączenie generowania przebiegów PWM, określenie ich polaryzacji oraz sposobu synchronizacji modyfikacji wypełnienia z okresem timera.

Timer zlicza od zera do wartości zadanej w rejestrze ARR, po czym następuje wyzerowanie timera i  dalsze zliczanie. Osiągnięcie przez licznik timera wartości równej wartości rejestru CCRx powoduje zmianę stanu wyjścia PWM.

 

Określenie parametrów czasowych

Przygotowanie projektu zaczynamy od określenia wartości parametrów czasowych. W celu uniknięcia zauważalnego migotania diod częstotliwość sterujących ich świeceniem przebiegów PWM będzie równa 400 Hz. Diody będą miały 80 stopni jasności, a więc okres timera będzie równy 80 cyklom zegara wejściowego. Do określenia częstotliwości zegara timera służy preskaler. Stopień podziału preskalera zostanie wyliczony przez kompilator na podstawie wyrażenia podanego w programie. Wartości wpisywane do rejestrów preskalera i okresu timera są o jeden mniejsze od stopnia podziału i długości okresu.

 

Programowanie peryferiali

Użycie timera do generowania przebiegu PWM wymaga zaprogramowania linii portów sterujących diodami jako wyjść timera. W celu zaprogramowania timera do generowania dwóch przebiegów PWM oraz zgłaszania przerwania na końcu okresu należy:

  • Ustawić linie portów używanych do sterowania diod jako wyjścia timera TIM3 (funkcja AF) poprzez zapis do rejestru MODER portu GPIOC.
  • Włączyć timer poprzez ustawienie bitu w rejestrze APB1ENR.
  • Ustawić wartość preskalera w rejestrze PSC.
  • Ustawić okres timera w rejestrze ARR.
  • Ustawić początkowe wartości wypełnień w rejestrach CCRx.
  • Włączyć tryb PWM z buforowaniem rejestrów CCRx poprzez zapis rejestru CCMR2.
  • Włączyć sterowanie wyjść PWM przez timer poprzez zapis do rejestru CCER.
  • Włączyć zgłaszanie przerwań na końcu okresu timera – rejestr DIER.
  • Włączyć automatyczne ładowanie okresu i uruchomić timer – rejestr CR1.
  • Włączyć przerwanie timera w sterowniku przerwań – rejestr ISER[0].

 

Obsługa przerwania timera

Przerwanie timera TIM3 jest zgłaszane z częstotliwością 400 Hz. Procedura obsługi przerwania musi skasować zgłoszenie przerwania poprzez zapis bitu o wartości 0 na odpowiednią pozycję rejestru SR timera, zawierającego znaczniki przerwań; w przeciwnym przypadku procedura obsługi przerwania timera byłaby wywoływana ciągle.

Ponieważ pozostałe czynności będą wykonywane z częstotliwością 100 Hz, użyjemy zmiennej tdiv zliczającej przerwania i instrukcji warunkowej z blokiem warunkowym wykonywanym co cztery przerwania – gdy dwa najmniej znaczące bity tdiv będą miały wartość 0.

Obsługa przerwania jest podobna do obsługi z poprzedniego przykładu, tym razem jednak nie będziemy zaświecać i gasić diod poprzez ustawienie stanu linii portu – zamiast tego będziemy modyfikować ich jasność poprzez ustawienie wypełnienia w rejestrach CCRx. W naszym przykładzie diody nie będą całkowicie gaszone, zmieniana będzie tylko ich jasność pomiędzy minimalną i maksymalną. Służą do tego stałe LED_MAX i LED_DIM.

/*
	STM32F0DISCOVERY tutorial
	TIM3 PWM blinker with table-driven init and blink mode switching
	gbm, 12'2012
*/

#include "stm32f0xx.h"
//========================================================================
// defs for STM32F05x chips
#define GPIO_MODER_OUT	1
#define GPIO_MODER_AF	2

#define TIM_CCMR2_OC3M_PWM1	0x0060	// OC3M[2:0] - PWM mode 1
#define TIM_CCMR2_OC4M_PWM1	0x6000	// OC3M[2:0] - PWM mode 1

typedef __IO uint32_t * __IO32p;
//========================================================================
// defs for STM32F0DISCOVERY board
#define LED_PORT		GPIOC
#define BLUE_LED_BIT	8
#define GREEN_LED_BIT	9

#define BUTTON_PORT		GPIOA
#define BUTTON_BIT		0

#define BLUE_LED_PWM	TIM3->CCR3
#define GREEN_LED_PWM	TIM3->CCR4
//========================================================================
// Timings
#define SYSCLK_FREQ	HSI_VALUE
#define BLINK_PERIOD	50	// * 10 ms

// PWM constants
#define PWM_FREQ	400	// Hz
#define PWM_STEPS	80
#define	PWM_CLK		SYSCLK_FREQ
#define	PWM_PRE		(PWM_CLK / PWM_FREQ / PWM_STEPS)
#define LED_MAX	(PWM_STEPS - 1)
#define LED_DIM	1
#define LED_OFF	0
//========================================================================
struct init_entry_ {
	volatile uint32_t *loc;
	uint32_t value;
};

static __INLINE void writeregs(const struct init_entry_ *p)
{
	for (; p->loc; p ++)
		*p->loc = p->value;
}
//========================================================================
void SystemInit(void)
{
	FLASH->ACR = FLASH_ACR_PRFTBE;	// enable prefetch
}
//========================================================================
static const struct init_entry_ init_table[] =
{
	// port setup
	{&RCC->AHBENR, RCC_AHBENR_GPIOCEN |  RCC_AHBENR_GPIOAEN},	// GPIOC, GPIOA
	{&LED_PORT->MODER, GPIO_MODER_AF <<(GREEN_LED_BIT <<1)
		| GPIO_MODER_AF <<(BLUE_LED_BIT <<1)},	// set LED pins as AF
	{&RCC->AHBENR, RCC_AHBENR_GPIOAEN},	// GPIOA
	// PWM timer setup - TIM3
	{&RCC->APB1ENR, RCC_APB1ENR_TIM3EN},	// TIM3
	{(__IO32p)&TIM3->PSC, PWM_PRE - 1},
	{(__IO32p)&TIM3->ARR, PWM_STEPS - 1},
	// blue - CH3, green - CH4
	{(__IO32p)&BLUE_LED_PWM, LED_DIM},
	{(__IO32p)&GREEN_LED_PWM, LED_DIM},
	{(__IO32p)&TIM3->CCMR2, TIM_CCMR2_OC4M_PWM1 | TIM_CCMR2_OC4PE
		| TIM_CCMR2_OC3M_PWM1 | TIM_CCMR2_OC3PE},	// PWM mode 1, buffered preload
	{(__IO32p)&TIM3->CCER, TIM_CCER_CC4E | TIM_CCER_CC3E},	// enable CH3, 4 output
	{(__IO32p)&TIM3->DIER, TIM_DIER_UIE},	// enable update interrupt
	{(__IO32p)&TIM3->CR1, TIM_CR1_ARPE | TIM_CR1_CEN},	// auto reload, enable
	// interrupts and sleep
	{&NVIC->ISER[0], 1 <SCR, SCB_SCR_SLEEPONEXIT_Msk},	// sleep while not in handler
	{0, 0}
};
//========================================================================
int main(void)
{
	writeregs(init_table);
	__WFI();	// go to sleep
}
//========================================================================
void TIM3_IRQHandler(void)
{
	static uint8_t blink_timer = BLINK_PERIOD;
	static enum {BM_SLOW, BM_FAST, BM_OFF} blink_mode = BM_SLOW;
	static uint8_t bstate = 0;
	static uint8_t blue_led_timer = 0, on_time = 0;
	static const uint8_t blink_periods[] = {100, 50, 1};
	static uint8_t tdiv;

	TIM3->SR = ~TIM_SR_UIF;	// clear interrupt flag

	if ((++ tdiv & 3) == 0)
	{
             // 100 Hz
		bstate = (bstate <<1 &0xf) | (BUTTON_PORT->IDR >>BUTTON_BIT &1);
		
		if (blue_led_timer)
		{
			if (-- blue_led_timer == 0)
				BLUE_LED_PWM = LED_DIM;
		}
		else if (bstate == 1)
		{
			// button was released, now it is pressed - change blink mode
			if (++ blink_mode >BM_OFF)
				blink_mode = BM_SLOW;
			blue_led_timer = 100;
			BLUE_LED_PWM = LED_MAX;
		}
		
		if (-- blink_timer == 0)
		{
			blink_timer = blink_periods[blink_mode];
			on_time = blink_timer >> 1;
			GREEN_LED_PWM = LED_DIM;
		}
		else if (blink_timer == on_time)
			GREEN_LED_PWM = LED_MAX;
	}
}



 

Płynna zmiana jasności diod

Kolejny projekt będzie zmodyfikowaną wersją poprzedniego. Tym razem zmiana jasności diod nie będzie następowała natychmiast, lecz płynnie. Przejście od jasności minimalnej do maksymalnej zajmie ok. 1/5 sekundy. Osiągniemy to poprzez wprowadzenie zmiennych przechowujących docelowe wartości wypełnień i powolną modyfikację wartości rejestrów wypełnień w przerwaniu timera poprzez ich inkrementację lub dekrementację, aż do uzyskania wartości zadanej. Gotowy projekt nosi nazwę PWMblink2.

Oprogramowanie ma taką samą strukturę, jak poprzednio. Różni się ono od poprzedniej wersji projektu w następujących szczegółach:

  • Wprowadzono zmienne blue_target i green_target, przechowujące zadane docelowe wartości wypełnienia dla obu diod.
  • Ustawienie docelowej wartości wypełnienia następuje poprzez zapis zmiennej (a nie, jak poprzednio, przez zapis rejestru CCRx).
  • W każdym przerwaniu timera następuje porównanie rejestrów wypełnień ze zmiennymi wypełnień zadanych, a w przypadku stwierdzenia różnicy – inkrementacja lub dekrementacja rejestru wypełnienia. Służą do tego dwie instrukcje warunkowe umieszczone na końcu procedury obsługi przerwania TIM3.

/*
	STM32F0DISCOVERY tutorial
	TIM3 PWM blinker with table-driven init and blink mode switching
	gbm, 12'2012
*/

#include "stm32f0xx.h"
//========================================================================
// defs for STM32F05x chips
#define GPIO_MODER_OUT	1
#define GPIO_MODER_AF	2

#define TIM_CCMR2_OC3M_PWM1	0x0060	// OC3M[2:0] - PWM mode 1
#define TIM_CCMR2_OC4M_PWM1	0x6000	// OC4M[2:0] - PWM mode 1

typedef __IO uint32_t * __IO32p;
//========================================================================
// defs for STM32F0DISCOVERY board
#define LED_PORT		GPIOC
#define BLUE_LED_BIT	8
#define GREEN_LED_BIT	9

#define BUTTON_PORT		GPIOA
#define BUTTON_BIT		0

#define BLUE_LED_PWM	TIM3->CCR3
#define GREEN_LED_PWM	TIM3->CCR4
//========================================================================
// Timings
#define SYSCLK_FREQ	HSI_VALUE
#define BLINK_PERIOD	50	// * 10 ms
#define BTN_ACK_PERIOD	50	// * 10 ms

// PWM constants
#define PWM_FREQ	400	// Hz
#define PWM_STEPS	80
#define	PWM_CLK		SYSCLK_FREQ
#define	PWM_PRE		(PWM_CLK / PWM_FREQ / PWM_STEPS)
#define LED_MAX	(PWM_STEPS - 1)
#define LED_DIM	1
#define LED_OFF	0
//========================================================================
struct init_entry_ {
	volatile uint32_t *loc;
	uint32_t value;
};

static __INLINE void writeregs(const struct init_entry_ *p)
{
	for (; p->loc; p ++)
		*p->loc = p->value;
}
//========================================================================
void SystemInit(void)
{
	FLASH->ACR = FLASH_ACR_PRFTBE;	// enable prefetch
}
//========================================================================
static const struct init_entry_ init_table[] =
{
	// port setup
	{&RCC->AHBENR, RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIOAEN},	// GPIOC, GPIOA
	{&LED_PORT->MODER, GPIO_MODER_AF <<(GREEN_LED_BIT <<1)
		| GPIO_MODER_AF << (BLUE_LED_BIT <<1)},	// set LED pins as AF
	{&RCC->AHBENR, RCC_AHBENR_GPIOAEN},	// GPIOA
	// PWM timer setup - TIM3
	{&RCC->APB1ENR, RCC_APB1ENR_TIM3EN},	// TIM3
	{(__IO32p)&TIM3->PSC, PWM_PRE - 1},
	{(__IO32p)&TIM3->ARR, PWM_STEPS - 1},
	// blue - CH3, green - CH4
	{(__IO32p)&TIM3->CCMR2, TIM_CCMR2_OC4M_PWM1 | TIM_CCMR2_OC4PE
		| TIM_CCMR2_OC3M_PWM1 | TIM_CCMR2_OC3PE},	// PWM mode 1, buffered preload
	{(__IO32p)&TIM3->CCER, TIM_CCER_CC4E | TIM_CCER_CC3E},	// enable CH3, 4 output
	{(__IO32p)&TIM3->DIER, TIM_DIER_UIE},	// enable update interrupt
	{(__IO32p)&TIM3->CR1, TIM_CR1_ARPE | TIM_CR1_CEN},	// auto reload, enable
	// interrupts and sleep
	{&NVIC->ISER[0], 1 <SCR, SCB_SCR_SLEEPONEXIT_Msk},	// sleep while not in handler
	{0, 0}
};

//========================================================================
int main(void)
{
	writeregs(init_table);
	__WFI();	// go to sleep
}

//========================================================================
void TIM3_IRQHandler(void)
{
	static uint8_t blink_timer = BLINK_PERIOD;
	static enum {BM_SLOW, BM_FAST, BM_OFF} blink_mode = BM_SLOW;
	static uint8_t bstate = 0;
	static uint8_t blue_led_timer = 0, on_time = 0;
	static const uint8_t blink_periods[] = {100, 50, 1};
	static uint8_t tdiv = 0;
	static uint8_t blue_target = LED_DIM, green_target = LED_DIM;

	uint32_t pwmval;

	TIM3->SR = ~TIM_SR_UIF;	// clear interrupt flag

	if ((++ tdiv &3) == 0)
	{
             // 100 Hz
		bstate = (bstate <<1 &0xf) | (BUTTON_PORT->IDR >>BUTTON_BIT &1);
		
		if (blue_led_timer)
		{
			if (-- blue_led_timer == 0)
				blue_target = LED_DIM;
		}
		else if (bstate == 1)
		{
			// button was released, now it is pressed - change blink mode
			if (++ blink_mode >BM_OFF)
				blink_mode = BM_SLOW;
			blue_led_timer = BTN_ACK_PERIOD;
			blue_target = LED_MAX;
		}
		
		if (-- blink_timer == 0)
		{
			blink_timer = blink_periods[blink_mode];
			on_time = blink_timer >> 1;
			green_target = LED_DIM;
		}
		else if (blink_timer == on_time)
			green_target = LED_MAX;
	}
	
	if ((pwmval = BLUE_LED_PWM) != blue_target)
		BLUE_LED_PWM = pwmval