STM32F4 Maceraları – 8 : TIM10 İle PWM ve Pin Yönlendirme (StdPeriph ve Elle)

Başlatan muuzoo, 13 Ağustos 2012, 01:41:23

muuzoo

Bu yazının aslı http://gunluk.muuzoo.gen.tr adresinde yayınlanmıştır.
---------------------------------------------------------------------------------

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:

       
  • F_TIM10 = 320KHz => PSC_Değeri = 524 => ARR_Değeri = 9       => CCR1 = 5
  • F_TIM10 = 1.6  MHz => PSC_Değeri = 104 => ARR_Değeri = 49    => CCR1 = 25
  • F_TIM10 = 8.0 MHz => 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] |= 0×00000003;


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

Diğer resim ise 8MHz @ 32KHz @ %25 lik işarete ait.
Bir yazımızın daha sonuna geldik. Herkese iyi çalışmalar.
gunluk.muuzoo.gen.tr - Kişisel karalamalarım...

okg

KTU Elektronik Haberleşme - YTÜ Haberşelme YL - GTU Haberleşme YL