STM32F4 Discovery Maceraları – 8 : TIM10 İle PWM ve Pin Yönlendirme

Bir önceki yazımda timer kullanımını göstermiştik. Bu yazımızda bir adım daha ileri gidelim ve TIM10 kullanarak PWM üretelim. Ayrıca bu işlemi gerçekleştirirken yine güzel bir özellik olan pin yönlendirme işlemini de yapalım. O halde elimize bir kağıt kalem önce hesap yapıyoruz.

1 – Hesap Kitap

PWM işleminden önce elimizde olanlara bakıyoruz. Buna göre isteklerimizi gözden geçirip en uygun çözümü bulacağız. Çünkü aynı işi yapmanın birden fazla yolu var. Öncelikle elimizdekiler:

  • Çalışma Frekansımız : 168 MHz.
  • APB2 Frekansımız : 168 Mhz (Nedeni Açıklanacak)
  • Hedef PWM Frekansı : 32KHz (Tamamen kendi seçimim. Özel bir nedeni yok)
  • PWM Doluluğu : %50 (Kendi seçimim)

Şimdi APB2 neden önemli? DM00037051 rehberinin 18. sayfasını incelerseniz TIM10 doğrudan APB2 veri yoluna bağlı. Bir önceki yazıyı okumuşsanız eğer , ön bölücü değerlerinin 1 değerinden farklı olması sebebiyle TIM bileşenleri bağlı oldukları veri yolunun 2 katı bir hızla tetikleniyor. APB2 normalde 84MHz’de çalışmaktadır. Fakat bu püf noktası sebebi ile buna baplı olan TIM10 168MHz’lik bir kaynakla beslenmektedir.

Sıra geldi 168MHz’de çalışan bir kaynaktan %50 doluluk oranına sahip 32KHz’lik bir PWM işareti üretmeye. İlk yapmamız gerekn biraz yavaşlamak. Bunu sağlayan ise TIM10’a ait “prescaler” değeri. Bu değer sayesinde TIM10’u besleyen frekansı bölmüş olacağız.

Aslında PWM hızımızı belirleyen iki bileşen mevcut. Biri daha önce bahsettiğim “prescaler” -ön bölücü- değeri. Diğeri ise TIM10 ARR saklayıcısına yüklediğimiz hedef değer. Bu iki bileşenin değeri ile oynayarak istediğimiz hızı elde edebiliyoruz. TIM10’a ait genel şema şu şekilde :

Ben TIM10 çalışma hızı olarak 320KHz beliriyorum kendime. Bunu sağlamak için gerekli ön bölücü değerimizi ise şu şekilde hesaplıyoruz:

PSC_Değeri = (SystemCoreClock / 320000) – 1

Bu hesabın sonucunda ön bölücü değerimiz 524 oluyor. TIM10 bu durumda 320KHz hızda çalışmaya başladı. Biz ise 32KHz elde etmek istiyoruz. Yani 10’da birini. O halde her 10 sayımda bir çıkış üretirsek isteğimiz elde etmiş oluruz. Bu yüzden ARR saklayıcısına yüklememiz gerekn değeri şu şekilde buluyoruz:

ARR_Değeri = (320KHz / 32KHz) – 1

Bu işlemin sonucunda ARR için geçerli olan değer 9 değeri elde ediliyor. Nihayet 32KHz’lik bir işaret elde edebildik. Sıra geldi işaretin doluluk oranına. Bunun içinse TIM10_CCR1 saklayıcısına vereceğimiz değer önem kazanıyor. Düşünce basit. Sayıcı CCR1 içinde belirlenen değere ulaştığında çıkış terslenecek, sayıcı sıfırlandığında tekrar terslenecek. Sayma işlemi 0 dan 9’a kadar gerçekleşiyor. Biz CCR1 değeri olarak 5 değerini verirsek tam ortası olacağı için %50 oranını yakalamış oluruz. Bu değerin üst limiti ARR_Değeri olan 9 dur. Eğer 9 verirseniz %100 doluluk oranına ulaşırsınız.

Şu anda bize lazım olan tüm değerlere sahibiz. Fakat incelemeye devam edelim. Şimdi aynı hesaplamaları bu sefer TIM10’un 1.6MHz‘de çalıştığı duruma göre hesaplayalım. Ön bölücü PSC_Değeri = 104 olur. ARR_Değeri = 49 ve son olarak %50 doluluk oranı için CCR1 = 25 olarak bulunur.

TIM10’u 8MHz‘de çalıştığını varsayarsak değerler ne olur? PSC_Değeri = 20, ARR_Değeri = 249, CCR1 = 125 olur. Bunları aşağıdali gibi özetleyelim:

  1. F_TIM10 = 320KHz, PSC_Değeri = 524, ARR_Değeri = 9, CCR1 = 5
  2. F_TIM10 = 1.6MHz, PSC_Değeri = 104, ARR_Değeri = 49, CCR1 = 25
  3. F_TIM10 = 8.0MHz, PSC_Değeri = 20, ARR_Değeri = 249, CCR1 = 125

Bu konunun üzerinde durmamın sebebi şu. eğer dikkat ettiyseniz TIM10’un çalışma frekansı düştükçe PWM doluluk oranı ayarlama hassasiyetimiz giderek azalıyor.

1. durumda kabaca 10 basamak halinde (%10’luk hassasiyetle) ayarlayabiliyoruz. Yuvarlarsak yaklaşık 4 bit (aslında 3.5 🙂 diyelim azami değer 9 çünkü).

2. durumda 50 basamak halinde (%2’lik hassasiyetle) ayarlayabiliyoruz. Yuvarlarsak yaklaşık 6 bit hassasiyet.

3. durumda 250 basamak halinde (%0,4 hassasiyet). Yaklaşık 8 bit çözünürlük.

Bu örnekleri ilerletmek mümkün. Bu durumda önemli olan ne kadarlık bir hassasiyet istediğiniz. Ona göre gerekli gerekli değerli verilen formüllerle hesaplayabilirsiniz.

2 – Yazılım Zamanı

Hesabı kitabı yaptık sıra geldi yazılıma. Kod yazmadan önce bir noktaya dikkat çekmek istiyorum. PWM çıkışımızı hangi pinden alacağız? Bu noktada devreye DM00037051 rehberinin 58. sayfasında bulunan tablo giriyor. Hatırlarsanız pinlerimizi ayarlarken giriş, çıkış, analog ve ek özellikler şeklinde yapılandırabiliyoruz. Fakat her pine her özelliği atayabiliriz gibi bir serbestliğimiz de yok. O yüzden ilgili belgenin 58. sayfasındaki tabloda TIM10’un AF3 kanalına bağlı olduğunu ve TIM10 için PORTB de bulunan 8. pini kullanabileceğimiz yazıyor. O halde TIM10 çıkışını PB8’e yönlendireceğiz. Bunu iki şekilde yapabiliriz, ilk olarak StdPeriph kütüphanesi yardımıyla :

GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_TIM10);

Ya da ilgili saklayıcıları el ile değiştirerek :

GPIOB->AFR[1] |= 0x00000003;

Hangi saklayıcının bizim işimize yaradığını ise DM00031020 belgesinin 6.4.9 ve 6.4.10 başlıklarını inceleyerek görebilirsiniz. Bu ayrıntıya dikkat ettikten sonra kodumuzu yazabiliriz. İlk olarak StdPeriph kullanarak yazılan kodu paylaşıyorum:

/* TIM10_PWM
 * main.c
 *
 *  Created on: 12 Agu 2012
 *      Author: m2k
 */
#include "stm32f4xx.h"
 
GPIO_InitTypeDef  GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef	TIM_OCInitStructure;
uint16_t PrescalerValue = 0;
 
void GPIO_Conf(void) {
 
	/* GPIOB ve TIM10 CLK Kaynağı etkinleştiriliyor */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10,ENABLE);
 
	/* PB8 Ek özellikler için ayarlanıyor */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
 
	/*Pin Yönlendirme İşlemi*/
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_TIM10);
}
 
void TIM_Conf(void) {
 
	PrescalerValue = (uint16_t)(SystemCoreClock / 8000000) - 1;
 
	/* TIM10 Ayarları Yapılıyor */
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	/* TIM10 İçin CounterMode_UP Gereksizdir. Fakat Kodu değiştirip
	 * diğer sayıcılar için kullanma ihtimaline karşı silmiyorum */
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseStructure.TIM_Period = 249;
	TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
	TIM_TimeBaseInit(TIM10, &TIM_TimeBaseStructure);
 
	/* PWM Ayarları yapılıyor. 32KHz @ %50 */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 125;
	TIM_OC1Init(TIM10, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(TIM10, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM10, ENABLE);
 
}
int main(void) {
	GPIO_Conf();
	TIM_Conf();
	/* TIM10 Başlatılıyor */
	TIM_Cmd(TIM10, ENABLE);
 
  while (1) {}
}

Bu kodun çalışması için ilgili stm32f4xx_tim.h dosyasını projeye dahil etmeyi unutmuyoruz. Aynı kodu bir de alt seviye de yazdık. Kullandığım kütüphane fonksiyonlarını silmedim. Onların yerine yazdığım kodları da görebilmeniz için bıraktım. Her satıra karşılık kullandığım kod için açıklama yazdım. Hangi biçimde kod yazmak isterseniz:

/* TIM10_PWM
 * main.c
 *
 *  Created on: 12 Agu 2012
 *      Author: m2k
 */
#include "stm32f4xx.h"
 
GPIO_InitTypeDef  GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef	TIM_OCInitStructure;
uint16_t PrescalerValue = 0;
 
void GPIO_Conf(void) {
	/* GPIOB ve TIM10 CLK Kaynağı etkinleştiriliyor */
//	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10,ENABLE);
	RCC->AHB1ENR |= 0x00000002;
	RCC->APB2ENR |= 0x00020000;
 
	/* PB8 Ayarları Yapılıyor. */
//	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
//	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
//	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
//	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
//	GPIO_Init(GPIOB, &GPIO_InitStructure);
 
	GPIOB->MODER   |= 0x00020000;
	GPIOB->OTYPER  |= 0x00000000;
	GPIOB->OSPEEDR |= 0x00030000;
	GPIOB->PUPDR   |= 0x00010000;
 
	/*Pin Yönlendirme İşlemi*/
//	GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_TIM10);
	GPIOB->AFR[1] |= 0x00000003;
}
 
void TIM_Conf(void) {
	PrescalerValue = (uint16_t)(SystemCoreClock / 8000000) - 1;
 
	/* TIM10 Ayarları Yapılıyor */
//	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	/* TIM10 İçin CounterMode_UP Gereksizdir. Fakat Kodu değiştirip
	 * diğer sayıcılar için kullanma ihtimaline karşı silmiyorum */
//	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//	TIM_TimeBaseStructure.TIM_Period = 249;
//	TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
//	TIM_TimeBaseInit(TIM10, &TIM_TimeBaseStructure);
 
	TIM10->CR1 |= 0x0000;	//ClockDivision
	TIM10->ARR  = 0x00F9;	//Period = 249
	TIM10->PSC  = PrescalerValue;
	TIM10->EGR  = 0x0001;
 
	/* PWM Ayarları yapılıyor. 32KHz @ %50 */
//	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//	TIM_OCInitStructure.TIM_Pulse = 125;
//	TIM_OC1Init(TIM10, &TIM_OCInitStructure);
//	TIM_OC1PreloadConfig(TIM10, TIM_OCPreload_Enable);
//	TIM_ARRPreloadConfig(TIM10, ENABLE);
 
	TIM10->CCMR1 |= 0x0060;	//PWM1_MODE
	TIM10->CCER  |= 0x0000;	//OCPolarity_High
	TIM10->CCER  |= 0x0001; //OutputState_Enable
	TIM10->CCR1   = 0x007D;	//Pulse = 125
	TIM10->CCMR1 |= 0x0008; //Preload_Enable
	TIM10->CR1   |= 0x0080; //ARRPreload Enable
 
}
int main(void) {
	GPIO_Conf();
	TIM_Conf();
	/* TIM10 Başlatılıyor */
	TIM10->CR1 |= 0x0001;
 
  while (1) {
 
  }
}

Son olarak yazdığımız kod neticesinde elde ettiğimiz çıkışa ait görselleri paylaşalım. İlk olarak 320KHz @32KHz @ %50

32 KHz ve %50 doluluk oranı.

Diğer resim ise 8MHz @ 32KHz @ %25 lik işarete ait.

32KHz ve %25 doluluk oranı.

Bir yazımızın daha sonuna geldik. Herkese iyi çalışmalar.

11 thoughts on “STM32F4 Discovery Maceraları – 8 : TIM10 İle PWM ve Pin Yönlendirme

  1. ömer dedi ki:

    TIM_10_PWM_Alt_Seviye kodunu derlediğimde aşağıda yazdığım satırlarda hata veriyor. sebebi nedir acaba?

    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure

  2. muuzoo dedi ki:

    “stm32f4xx_conf.h” dosyasından tim için gerekli olan #include “stm32f4xx_tim.h” satırını bir kontrol eder misiniz.

  3. fethi gezici dedi ki:

    Selam , kodda ki pwm oranını led e bağlayarak görmek istedim board un üzerinde pb8 pininden alğım gerilimi lede uyguladım.Sonucu gördüm ama pwm oranının değiştiğinin görmeye çalıştığımda göremedim

    /* PWM Ayarları yapılıyor. 32KHz @ %50 */

    for(gen=1;gen<249;gen++){

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = gen;
    TIM_OC1Init(TIM10, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM10, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM10, ENABLE);

    for(say=1;say<249;say++){}

    }

    sadece bu kısmı değiştirdim ama ledde göremedim acaba neden olabilir?

  4. muuzoo dedi ki:

    Bu yaptığınız uygun bir kullanım değil. Sizin sadece TIM_Pulse kısmını yani CCR1 saklayıcısının değerini değiştirmeniz gerekiyor. Yazdığınız kod ile siz her döngüde tüm ayarları yine yapıyorsunuz. Şu an yolculuk yaptığım için denemedim ama bir kaç güne kadar uygun bir kod yayınlamaya çalışırım.

  5. cansu yeşilsu dedi ki:

    kolay gelsin..stm32f4xx_tim.h kütüphane dosyasını nereden buluyoruz..ayrıca başka fonksiyonlar kullanmak istediğimizde hangisinin ne işe yaradığını bulabilir miyiz? bu konuda yardımcı olursanız sevinirim

  6. muuzoo dedi ki:

    Merhaba. Geç cevabım için kusura bakmayın site ile ilgili sorunlar vardı. “stm32f4x_tim.h” dosyası proje oluştururken ekledidiğimiz “stm32f4xx_conf.h” içinde mevcut. “stm32f4xx_conf.h” dosyasında kullandığımız mikrodenetleyicide var olan donanımların hepsinin kütüphaneleri için gerekli tanımlama mevcut. Kullanacağımız donanıma uygun kütüphane tanımlamasını başındaki # işaretini kaldırarak etkinleştiriyoruz. Kütüphane isimlerinden ne işe yaradıklarını kolayca anlayabiliyoruz. Bu kütüphanelerde tanımlı olan fonksiyonların kullanımına ise daha önceki yazılarda bahsettiğimiz “stm32f4xx_dsp_stdperiph_lib” klasörü içindeki “stm32f4xx_dsp_stdperiph_lib_um.chm” dosyasından ulaşabiliyoruz.Bu dosyada ayrıca örnek kodlar da mevcut. “stm32f4xx_conf.h” dosyasında tanımlı kütüphaneler şöyle :


    /* Includes ------------------------------------------------------------------*/
    /* Uncomment the line below to enable peripheral header file inclusion */
    #include "stm32f4xx_adc.h"
    #include "stm32f4xx_can.h"
    #include "stm32f4xx_crc.h"
    #include "stm32f4xx_cryp.h"
    #include "stm32f4xx_dac.h"
    #include "stm32f4xx_dbgmcu.h"
    #include "stm32f4xx_dcmi.h"
    #include "stm32f4xx_dma.h"
    #include "stm32f4xx_exti.h"
    #include "stm32f4xx_flash.h"
    #include "stm32f4xx_fsmc.h"
    #include "stm32f4xx_hash.h"
    #include "stm32f4xx_gpio.h"
    #include "stm32f4xx_i2c.h"
    #include "stm32f4xx_iwdg.h"
    #include "stm32f4xx_pwr.h"
    #include "stm32f4xx_rcc.h"
    #include "stm32f4xx_rng.h"
    #include "stm32f4xx_rtc.h"
    #include "stm32f4xx_sdio.h"
    #include "stm32f4xx_spi.h"
    #include "stm32f4xx_syscfg.h"
    #include "stm32f4xx_tim.h"
    #include "stm32f4xx_usart.h"
    #include "stm32f4xx_wwdg.h"
    #include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */

  7. alihan dedi ki:

    merhabalar;
    ben 50hz lik bir çkış elde etmek istiyorum
    F_TIM10 = 8.0 MHz => PSC_Değeri = 20 => ARR_Değeri = 159999 => CCR1=11200
    fakat böyle olunca 276 hz veriyor acaba nerde hata yapıyorum yardımcı olursanız sevinirim kolay gelsin

  8. muuzoo dedi ki:

    Öncelikle kolay gelsin. Sizin kodunuzdaki sıkıntı şundan kaynaklanıyor; DM00031020.pdf dosyasının 636. sayfasına bakarsanız ARR saklayıcısının 16 bit olduğunu görürsünüz (16-bit auto-reload counter). Bu yüzden buraya yükleyebileceğiniz değer en fazla 65535 olabilir. Siz arr değeri olarak 159999 seçmişsiniz ki bu değer en az 18 bit demek. Mantıklı olan sizin önce PSC değerini arttırarak F_TIM10 frekansını düşürüp daha sonra ARR ile bir daha bölmeniz olur.

    İyi çalışmalar dilerim.

  9. gökhan dedi ki:

    Hocam timer1 ile bir türlü pwm alamadım, yardımcı olur musunuz:
    http://www.picproje.org/index.php/topic,59741.0.html

  10. taner dedi ki:

    Merhabalar,
    Öncelikle emeğiniz için teşekkürler, gayet faydalı bir örnek oldu benim için.
    Sormak istediğim iki soru vardı:
    1-) Sayıcı değeri TIM_CCR1 değerine ulaşınca PORTB.8 in tersleneceğini ve sayıcı sıfırlandığında PORTB.8 in tekrar tersleneceğini söylediniz. Bu işlem kod da hangi satırlar ile gerçekleştiriliyor acaba?

    2-) Sizin örneğiniz ile birebir ilgili değil ancak merak ettiğim bir konu var; 1 adet timer ın bir den fazla channel a sahip olması ne anlama geliyor acaba?

    Teşekkürler.

  11. muuzoo dedi ki:

    1) Sayıcıyı PWM modunda çalıştırdığımız için o şekilde çalışıyor. PWM ayarlarını yaptığımız satırları inceleyebilirsiniz.

    2) Mesela bir motor kontrol uygulamasında birbirlerinin tam tersi olan iki adet pwm işaretine ihtiyaç duyabilirsiniz. Böylece tek bir sayıcı ile birbirinin zıttı olan iki pwm üretebilir ve kullanabilirsiniz.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir