Kit STM32F4 Discovery- Bài 6: Tín hiệu điều chế độ rộng xung PWM

0
2673

Giới thiệu

PWM – Pulse Width Modulation (điều chế độ rộng xung) – là phương pháp điều khiển giá trị điện áp trung bình trên tải bằng cách thay đổi chu kỳ của xung. Về cơ bản các vi điều khiển đều cho phép khởi tạo tín hiệu số PWM với các tần số khác nhau.

Đơn giản nhất để hiểu PWM có thể xem ví dụ sau: cấu hình output cho chân GPIO điều khiển bóng LED, nếu ta muốn làm sáng bóng LED theo hiệu ứng mờ hay tỏ,  giải pháp đó là cho bóng LED sáng tắt ở tần số cao sao cho mắt mất khả năng phân tích sự sáng tắt của một bóng LED. Muốn bóng LED sáng tỏ thì thời gian sáng sẽ nhiều hơn thời gian tắt, và ngược lại. Hình dưới đây biểu diễn các tín hiệu dạng xung PWM.

– Duty cycle là tỷ lệ phần trăm mức cao.

– Period là chu kỳ xung.

– Pulse width là giá trị mức cao so với period.

Dựa trên nguyên lý bên trên mà STM32 hay các loại vi điều khiển khác đều hỗ trợ bộ tạo độ rộng xung tự động mà ta không phải tốn thời gian bật tắt chân IO, có thể thiết lập chu kỳ, tần số, độ rộng xung và một số chức năng đặc biệt. PWM thuộc khối timer.
Vì việc xuất tín hiệu PWM không phải là chức năng chính của các cổng, do vậy để làm được việc này trước hết cần thiết lập cấu hình cho cổng làm việc theo chức năng lựa chọn (Aternative Function – AFIO). Khởi tạo xung liên quan đến việc sử dụng chế độ phụ của timer.
Trước khi kết nối thiết bị, cần xác định rõ các tham số của tín hiệu PWM như tần số và hệ số prescalar.

Trong đó:
PSC – hệ số Prescalar,
TIMxCLK – tần số dao động đầu vào của timer (clock timer),
TIMxCNT – tần số bộ đếm (counter timer)

Để nhận được tần số đầu ra mong muốn cần ghi giá trị vào thanh ghi ARR theo công thức:

Trong đó:
ARR_VAL – giá trị ghi vào thanh ghi ARR,
TIMxCNT – tần số bộ đếm,
TIMx_out_freq – tần số đầu ra PWM mong muốn.

Bước cuối cùng là xác định hệ số để cung cấp giá trị điện áp trung bình đầu ra. Bước thiết lập này được thực hiện nhờ thanh ghi capture/compare (CCRx) theo công thức:

Trong đó:
D – hệ số cần điền, đơn vị %
CCRx _VAL – giá trị của thanh ghi CCRx (х – số hiệu thanh ghi, tương ứng với đường ngắt cụ thể),
ARR_VAL – giá trị để ghi vào thanh ghi ARR.

Ví dụ

Ví dụ 1

#include <stm32f4xx_gpio.h>
#include <stm32f4xx_rcc.h>
#include <stm32f4xx_tim.h>

void InitializeGPIO(GPIO_TypeDef* GPIOx, uint32_t Pin);
void InitializeTimer(TIM_TypeDef* TIMx, uint16_t PrescaleDiv, uint16_t Period);
void InitializePWMChannel4(TIM_TypeDef* TIMx, uint32_t ValueToOn);

void main( )
{
	// Initialize SystemFrequency
	SystemInit();

	// Initialize GPIO
	RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOD, ENABLE );
	InitializeGPIO( GPIOD,  GPIO_Pin_15);

	// Initialize TIM4
	// prescaler*period = 60000*2800 (for SysClk = 168 MHz clock of PWM will be 1 Hz)
	RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM4, ENABLE ); 
	InitializeTimer( TIM4, 60000-1, 2800-1 );

	// Initialize PWM Chanel4
	// 50% duty factor = 1400
	InitializePWMChannel4( TIM4, 1400 );
	GPIO_PinAFConfig( GPIOD, GPIO_PinSource15, GPIO_AF_TIM4 );
}

/**
  * @brief	Setup the GPIOx port as out of PWM on selected Pin.
  * @param	GPIOx: where x can be (A..I) to select the GPIO peripheral.
  * @param	Pin: specifies the port bit to be initialized for out of PWM signal.
  * 	Refer STM32F407xx Datasheet for Alternate function mapping (Table 9)
  * @retval None
  */
void InitializeGPIO(GPIO_TypeDef* GPIOx, uint32_t Pin)
{
	GPIO_InitTypeDef sg;

	sg.GPIO_Pin   = Pin;
	sg.GPIO_Mode  = GPIO_Mode_AF;		// PWM is Alternate function Mode
	sg.GPIO_Speed = GPIO_Speed_100MHz;
	sg.GPIO_OType = GPIO_OType_PP;
	sg.GPIO_PuPd  = GPIO_PuPd_DOWN;

	GPIO_Init( GPIOx, &sg );
}


/**
  * @brief  Initialize timer TIMx as counter with the specified Period and Prescaler.
  * @param	TIMx: where x can be  1 to 14 to select the TIM peripheral.
  * @param  Period: the length of period will be n+1 because the counting begin from 0
  * @param  PrescaleDiv: Divides the clock at PrescaleDiv+1.
  * 			If PrescaleDiv=0 then SrcClock not changed.
  * @retval None
  */
void InitializeTimer(TIM_TypeDef* TIMx, uint16_t PrescaleDiv, uint16_t Period)
{
	TIM_TimeBaseInitTypeDef sT;

	sT.TIM_Prescaler   = PrescaleDiv;
	sT.TIM_CounterMode = TIM_CounterMode_Up;// Count to up
	sT.TIM_Period      = Period;
	sT.TIM_ClockDivision = TIM_CKD_DIV1;	// No pre-division
	sT.TIM_RepetitionCounter = 0;

	TIM_TimeBaseInit( TIMx, &sT );

	TIM_Cmd( TIMx, ENABLE );
}

/**
  * @brief  Initialize PWM channel 4 of TIMx
  * @param	TIMx: where x can be  1 to 14 to select the TIM peripheral.
  * @param  ValueToOn: the value of TIMx starting from which PWM out will be 1
  * @retval None
  */
void InitializePWMChannel4 ( TIM_TypeDef* TIMx, uint32_t ValueToOn )
{
	TIM_OCInitTypeDef sOC;
	sOC.TIM_OCMode = TIM_OCMode_PWM1;
	sOC.TIM_Pulse = ValueToOn;
	sOC.TIM_OutputState = TIM_OutputState_Enable;
	sOC.TIM_OCPolarity = TIM_OCPolarity_High;

	TIM_OC4Init( TIMx, &sOC);
	TIM_OC4PreloadConfig( TIMx, TIM_OCPreload_Enable);
}

Ví dụ 2

Tạo xung trên các chân PD12, PD13, PD14, PD15 với các độ rộng xung khác nhau

#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"

void TM_LEDS_Init(void) {
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/* Clock for GPIOD */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

	/* Alternating functions for pins */
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
	
	/* Set pins */
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct);
}

void TM_TIMER_Init(void) {
	TIM_TimeBaseInitTypeDef TIM_BaseStruct;
	
	/* Enable clock for TIM4 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/*	
	TIM4 is connected to APB1 bus, which has on F407 device 42MHz clock 				
	But, timer has internal PLL, which double this frequency for timer, up to 84MHz 	
	Remember: Not each timer is connected to APB1, there are also timers connected 	
	on APB2, which works at 84MHz by default, and internal PLL increase 				
	this to up to 168MHz 															
	
	Set timer prescaller 
	Timer count frequency is set with 
	
	timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1)		
	
	In our case, we want a max frequency for timer, so we set prescaller to 0 		
	And our timer will have tick frequency		
	
	timer_tick_frequency = 84000000 / (0 + 1) = 84000000 
*/	
	TIM_BaseStruct.TIM_Prescaler = 0;
	/* Count up */
	TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
/*
	Set timer period when it have reset
	First you have to know max value for timer
	In our case it is 16bit = 65535
	To get your frequency for PWM, equation is simple
	
	PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
	
	If you know your PWM frequency you want to have timer period set correct
	
	TIM_Period = timer_tick_frequency / PWM_frequency - 1
	
	In our case, for 10Khz PWM_frequency, set Period to
	
	TIM_Period = 84000000 / 10000 - 1 = 8399
	
	If you get TIM_Period larger than max timer value (in our case 65535),
	you have to choose larger prescaler and slow down timer tick frequency
*/
TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */
TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_BaseStruct.TIM_RepetitionCounter = 0;
	/* Initialize TIM4 */
TIM_TimeBaseInit(TIM4, &TIM_BaseStruct);
	/* Start count on TIM4 */
TIM_Cmd(TIM4, ENABLE);
}

void TM_PWM_Init(void) {
	TIM_OCInitTypeDef TIM_OCStruct;
	
	/* Common settings */
	
	/* PWM mode 2 = Clear on compare match */
	/* PWM mode 1 = Set on compare match */
	TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
	
/*
	To get proper duty cycle, you have simple equation
	
	pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
	
	where DutyCycle is in percent, between 0 and 100%
	
	25% duty cycle: 	pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099
	50% duty cycle: 	pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199
	75% duty cycle: 	pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299
	100% duty cycle:	pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399
	
	Remember: if pulse_length is larger than TIM_Period, you will have output HIGH all the time
*/
	TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */
	TIM_OC1Init(TIM4, &TIM_OCStruct);
	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
	
	TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */
	TIM_OC2Init(TIM4, &TIM_OCStruct);
	TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
	
	TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */
	TIM_OC3Init(TIM4, &TIM_OCStruct);
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
	
	TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */
	TIM_OC4Init(TIM4, &TIM_OCStruct);
	TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
}

int main(void) {
	/* Initialize system */
	SystemInit();
	/* Init leds */
	TM_LEDS_Init();
	/* Init timer */
	TM_TIMER_Init();
	/* Init PWM */
	TM_PWM_Init();
	
	while (1) {

	}
}

Ví dụ 3

Ví dụ dưới đây cho phép thiết lập độ sáng của 4 đèn LED giảm dần trên các chân PD12, PD13, PD14, PD15.

#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"
#include "misc.h"

void GPIOinit();
void TimerInit();

int main(void)
{
	SystemInit();
GPIOinit();
	TimerInit();

	int brightness = 0;
	int i;
	while(1)
	{
		brightness++;
		TIM4->CCR3 = 333 - (brightness + 0) % 333;
		TIM4->CCR4 = 333 - (brightness + 166 / 2) % 333;
		TIM4->CCR1 = 333 - (brightness + 333 / 2) % 333;
		TIM4->CCR2 = 333 - (brightness + 499 / 2) % 333;
		for(i=0;i < 50000; ++i);
	}
}
void GPIOinit()
{
	GPIO_InitTypeDef gpio_init;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
	gpio_init.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	gpio_init.GPIO_Mode = GPIO_Mode_AF;
	gpio_init.GPIO_Speed = GPIO_Speed_100MHz;
	gpio_init.GPIO_OType = GPIO_OType_PP;
	gpio_init.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOD, &gpio_init);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
	GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
}
void TimerInit()
{
	TIM_TimeBaseInitTypeDef time_init;
	TIM_OCInitTypeDef oc_init;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	uint16_t PrescalerValue = (uint16_t)((SystemCoreClock / 2) / 21000000);
	time_init.TIM_Period = 665;
	time_init.TIM_Prescaler = PrescalerValue;
	time_init.TIM_ClockDivision = 0;
	time_init.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &time_init);
	oc_init.TIM_OCMode = TIM_OCMode_PWM1;
	oc_init.TIM_OutputState = TIM_OutputState_Enable;
	oc_init.TIM_Pulse = 0;
	oc_init.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &oc_init);
	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
	oc_init.TIM_OutputState = TIM_OutputState_Enable;
	oc_init.TIM_Pulse = 0;
	TIM_OC2Init(TIM4, &oc_init);
	TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
	oc_init.TIM_OutputState = TIM_OutputState_Enable;
	oc_init.TIM_Pulse = 0;
	TIM_OC3Init(TIM4, &oc_init);
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
	oc_init.TIM_OutputState = TIM_OutputState_Enable;
	oc_init.TIM_Pulse = 0;
	TIM_OC4Init(TIM4, &oc_init);
	TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM4, ENABLE);
	TIM_Cmd(TIM4, ENABLE);
}

LEAVE A REPLY

Please enter your comment!
Please enter your name here