STM32F4 – PWM and Complementary Output
STM32F4 de bulunan bir çok Timer dan TIM1 ve TIM8 bize ekstra özellikler sağlıyor.Bu timerlar ile “Complementary” çıkışlar elde edebiliyorsunuz. Complement çıkış bir PWM sinyalinin tam tersidir. Şayet bir PWM çıkışı “CH1″olarak isimlendiriliyor ise Complement’i “CH1N” olarak ifade edilir. Alternatif olarak PWMH ve PWML olarak da görebiliriz.
Bu yapı oldukça basit ve bir çok uygulamada kullanılmakla beraber çeşitli mosfet devrelerinde de karşılaşabilirsiniz. Bu konuyu bir görsel üzerinden daha rahat inceleyebiliriz.
Burada önemli bir nokta ise “Dead Time” dır. Bu Dead Time Complement çıkış kullanıldığında aynı anda HIGH veya LOW olmaması içindir.Bu ölü zaman denilen bölge nanosaniyeler mertebesindedir. Bu gibi bir durumlar Full-Half Bridge Inverterlarda karşımıza çıkabilir. Aşağıdaki şekilde T1 bir PWM sinyali ise T4 bu sinyalin Complement olmalıdır.T3 PWM iken T2 bu sinyalin Complementi olur ve böylece bir motoru sağ ve sola yönlendirmiş oluruz. Ama T1-T2 ve T3-T4 aynı anda iletimde olursa bu kısa devreye yol açar.Bunu için sinyale Dead Time eklenir.
Ben bu örnekte bir çıkış ve bu çıkışın complement’ini uyguladım. Duty değeri 60% ise bunun tersi 40% olacaktır.Sürekli değişen Duty değerini 2 adet LED üzerinde gözlemleyebiliriz.
Bu uygulama TIM1_CH1 çıkışı PE9 ve TIM1_CH1N çıkışı PE8 olarak kullanılmıştır.Bu çıkışların nasıl belirlendiğine STM32F407’nin datasheetinden öğrenebilirsiniz.(Sayfa 50)
Programda yapılacak ilk iş Clock ayarlarıdır.
Öncelik stm32f4xx.h dosyasındaki
#if !defined (HSE_VALUE) #define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */ #endif /* HSE_VALUE */
Kısmı aşağıdaki gibi değiştiriyoruz.
#if !defined (HSE_VALUE) #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */ #endif /* HSE_VALUE */
Bu Harici kristal OSC değeridir.Daha sonra system_stm32f4xx.c dosyasından PLL parametrelerini aşağıdaki gibi düzenliyoruz. Burada amaç 168MHz değerini elde etmek.
/************************* PLL Parameters *************************************/ /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */ #define PLL_M 8 #define PLL_N 336 /* SYSCLK = PLL_VCO / PLL_P */ #define PLL_P 2 /* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */ #define PLL_Q 7 /******************************************************************************/
Bu işlemler tamam ise main fonksiyonunda harici OSC’yi aktif edip aktif olana kadar bekliyoruz.
int main(void) { // Enable HSE clock RCC_HSEConfig(RCC_HSE_ON); // Wait for clock to stabilize while (!RCC_WaitForHSEStartUp()); }
Kullanacağımız pimleri hazırlamak için “GPIO Init” işlemini “Alternatif Function” olarak ayarlıyoruz.
void GPIO_Config(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_PinAFConfig(GPIOE, GPIO_PinSource8, GPIO_AF_TIM1); GPIO_PinAFConfig(GPIOE, GPIO_PinSource9, GPIO_AF_TIM1); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); }
Pinleri hazırladıktan sonra PWM için kullanacağımız Timer 1’i hazırlamamız gerekiyor.Ben 10kHz lik bir PWM oluşturdum.
uint16_t PWM_Freq = 10000; //10kHz #define PWM_PERIOD ((SystemCoreClock / PWM_Freq) – 1) void TIM_Config(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseInitTypeDef TIM_BaseStruct; TIM_BaseStruct.TIM_Period = PWM_PERIOD; TIM_BaseStruct.TIM_Prescaler = 0; TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, &TIM_BaseStruct); TIM_Cmd(TIM1, ENABLE); }
PWM ayarlarını yapmak için “TIM_OCStruct” yapısına ayarları yükleyip sinyalleri başlatıyoruz.Burada önemli bir nokta “TIM_OutputNState” ile N çıkışını aktif etmemizdir.
void PWM_Config(void) { TIM_OCInitTypeDef TIM_OCStruct; TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_OCNPolarity = TIM_OCNPolarity_High; TIM_OCStruct.TIM_OutputNState = TIM_OutputNState_Enable; TIM_OCStruct.TIM_Pulse = 0; TIM_OC1Init(TIM1, &TIM_OCStruct); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM1, ENABLE); }
Buna ek olarak ben bir fonksiyon ile istediğimiz çıkışlara Duty değerini yükleyen bir yapı hazırladım.
void PWM_SetDutyCycle(TIM_TypeDef* TIMx, uint8_t Duty, uint8_t OCx) { switch(OCx) { case 1: TIMx->CCR1 = (Duty * PWM_PERIOD) / 100; break; case 2: TIMx->CCR2 = (Duty * PWM_PERIOD) / 100; break; case 3: TIMx->CCR3 = (Duty * PWM_PERIOD) / 100; break; case 4: TIMx->CCR4 = (Duty * PWM_PERIOD) / 100; break; } }
Artık çalışmaya hazır olan yazılımı SystemTick ile otomatik değişen bir hale getirdir.
Bu SysTick ile Duty değerini yüzdesel olarak değiştirerek çıkışa aktardığımı gözlemleyebilirsiniz.Duty değerini çıkışa yüklediğiniz anda o değerini terside ototmatik Complement çıkışa aktarılacaktır.
https://youtu.be/zCM0X46vPvA
Kodun tamamı
#include “stm32f4xx_conf.h” // SysTicks uint8_t TimerTick_PWM = 0; // Funtion Prototypes void GPIO_Config(void); void TIM_Config(void); void PWM_Config(void); void PWM_SetDutyCycle(TIM_TypeDef* TIMx, uint8_t Duty, uint8_t OCx); void PWM_Handler(void); // PWM Duty Values uint16_t PWM_Duty = 0; // PWM Frequncy : 10kHz uint16_t PWM_Freq = 10000; // #define PWM_PERIOD ((SystemCoreClock / PWM_Freq) – 1) // uint8_t Flag = 1; void SysTick_Handler(void) { if(TimerTick_PWM == 15) PWM_Handler(); else TimerTick_PWM++; } int main(void) { // Enable HSE clock RCC_HSEConfig(RCC_HSE_ON); // Wait for clock to stabilize while (!RCC_WaitForHSEStartUp()); // SystemTick Interrrupt – 1 miliSecond SysTick_Config(SystemCoreClock / 1000); // GPIO Configuration GPIO_Config(); // Timer 1 Configuration PWM Channels TIM_Config(); // PWM_ Configuration PWM_Config(); // main Loop for (;;) { } } void GPIO_Config(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_PinAFConfig(GPIOE, GPIO_PinSource8, GPIO_AF_TIM1); GPIO_PinAFConfig(GPIOE, GPIO_PinSource9, GPIO_AF_TIM1); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); } void TIM_Config(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseInitTypeDef TIM_BaseStruct; TIM_BaseStruct.TIM_Period = PWM_PERIOD; TIM_BaseStruct.TIM_Prescaler = 0; TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, &TIM_BaseStruct); TIM_Cmd(TIM1, ENABLE); } void PWM_Config(void) { TIM_OCInitTypeDef TIM_OCStruct; TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_OCNPolarity = TIM_OCNPolarity_High; TIM_OCStruct.TIM_OutputNState = TIM_OutputNState_Enable; TIM_OCStruct.TIM_Pulse = 0; TIM_OC1Init(TIM1, &TIM_OCStruct); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM1, ENABLE); } void PWM_SetDutyCycle(TIM_TypeDef* TIMx, uint8_t Duty, uint8_t OCx) { switch(OCx) { case 1: TIMx->CCR1 = (Duty * PWM_PERIOD) / 100; break; case 2: TIMx->CCR2 = (Duty * PWM_PERIOD) / 100; break; case 3: TIMx->CCR3 = (Duty * PWM_PERIOD) / 100; break; case 4: TIMx->CCR4 = (Duty * PWM_PERIOD) / 100; break; } } void PWM_Handler(void) { TimerTick_PWM = 0; if(Flag) PWM_Duty++; else PWM_Duty–; if(PWM_Duty == 100) Flag = 0; else if(PWM_Duty == 0) Flag = 1; PWM_SetDutyCycle(TIM1, PWM_Duty, 1); }
Son yorumlar