Merhabalar, görece uzunca bir aradan sonra yazılarımıza kaldığımız yerden devam ediyoruz. Önceki yazıda tam da bir takım soru işaretleri bırakarak ayrılmıştık. Gün, bunların olabildiğince yanıtlanacağı gündür.
Öncelikle şu 1.06ms meselesinin çözümünü ve cevabını verelim. Evet efendim, kodumuzda yaptığımız tek satırlık bir modifikasyon ile özlenen tabloyu öncelikle kamu huzuruna sunalım.
Gördüğünüz üzere tam 1.00 ms’lik çıktıyı yakaladık. Tabi burada tam’dan kasıt, mikrosaniye mertebesindeki ölçüme ithafen bir tamlık. Neyse, şimdi hemen ilk aşamada bu uygulamanın kodlarını paylaşalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* * File: main.c * Author: ozenozkaya * * Created on 24 September 2015 */ #include "pic16f84a_lib.h" /*FOSC is 4Mhz. Prescaler input it 1Mhz. Because prescaler is 8, prescaler output f=125kHz. So,the prescaler output period is 8uS. Hence, we need 125 timer pulses to reach 1mS. 8uS*125 = 1mS*/ #define TIMER0_VAL_FOR_1MS (118) static void milisecond_event_callback() { REG_TIMER0_UNION.count = TIMER0_ASSIGN_CNT(TIMER0_VAL_FOR_1MS); REG_PORTB_UNION.port^=0xFF; } void main() { REG_TRISB_UNION.port = TRIS_PORT_OUTPUT; REG_PORTB_UNION.port = PORT_ALL_LOW; /*! 125*8uS = 1000uS = 1mS*/ REG_TIMER0_UNION.count = TIMER0_ASSIGN_CNT(TIMER0_VAL_FOR_1MS); REG_INTCON_UNION.value = INT_DISABLE_ALL; REG_INTCON_UNION.bits.GIE = INT_ENABLE; REG_INTCON_UNION.bits.T0IE = INT_ENABLE; REG_OPTION_UNION.bits.T0CS = T0CS_SOURCE_INTERNAL_CLOCK; REG_OPTION_UNION.bits.PSA = PSA_ASSIGN_TO_TIMER0; REG_OPTION_UNION.bits.PS = PS_TMR0_RATE_DIVIDE_8; //8uS register_timer0_int_callback(milisecond_event_callback); WAIT_LOOP_FOREVER(); } |
Kütüphanemiz yine aynı, orada bir değişikliğe ihtiyaç yok. Şimdi burada neyin değiştiğini ve neden değiştiğini açıklayalım. Bir önceki yazıdaki koddan farklı olan tek satır şu:
1 |
#define TIMER0_VAL_FOR_1MS (118) |
Bu değerin normal şartlar altında 125 olması gerektiğini açıklamıştık. Ancak gelgelelim kesme fonksiyonu içinde yazdığımız kodlar, ister istemez bir miktar gecikmeye neden oluyor. Bu sebeple bu gecikmeyi tolere edemiyorsak, timer0’a yüklenecek değeri kalibre etmemiz elzem oluyor. Denemeler ile görüyoruz ki kesme fonksiyondaki kodlarımız 7 timer0 cycle kadar bir gecikmeyi doğuruyor. Bunu kompanze etmek için 125 cycle yerine 118 cycle saydığımızda tam 1.o0 ms’lik bir zamanlama elde ediyoruz. Tabi ki bu süreç yalnızca PIC gibi çok kısıtlı kaynaklı ve görece yavaş ortamlarda yaşanıyor. Örneğin ARM Cortex M3-M4 gibi platformlarda bu süreler ihmal edilecek kadar kısalır ve böyle bir istisnai kodlama yapmanız gerekmez. Yine uygulamanız zaman kritik değilse, böyle bir istisnai kodlamaya ihtiyaç duymazsınız. Ama yine de bu önemli konuyu akılda tutmakta fayda var 🙂
Şimdi, önceki yazıdaki defteri kapattığımıza göre bu yazının esas konusuna gelebiliriz. Bu yazımızda zamanlayıcıların bir başka önemli kullanım alanına değineceğiz: PWM (Pulse Width Modulation). Nedir efendim PWM? Çevirisini yapacak olursak dalga genişliği modülasyonudur. PWM’in ne olduğu detaylıca ŞURADA anlatıldığından daha fazla açıklama yapmaya gerek duymuyorum.
Çoğu mikrokontrolörde, zamanlayıcılar üzerinde çalışan PWM modülleri donanımsal olarak bulunur. PIC’in bazı modellerinde de bu imkan sağlanmış. Onların kullanımına da elbet değineceğiz ama daha önemlisi donanımsal PWM modülü olmasa da bu modülasyonu nasıl gerçekleyebileceğimiz. Bu yazıda zamanlayıcı kullanarak PWM gerçekleyeceğiz.
Zamanlayıcılar ve GPIO ile PWM
PWM dediğimiz nane sayısız işe yarıyor. Örneğin bir motorun dönüş hızını, bir LED’in ışık şiddetini ve daha bir çok benzer şeyi PWM ile yapabiliyoruz. PWM, işaretin etkin değerini değiştirmeye yönelik bir modülasyon olduğundan, fiziksel çıktı farketmeksizin “şiddet ayarlama” işini yapar. Ne demek istiyorum?
- Bir motoru sabit 5V ile beslediğinizde o motor 5 birim hızla dönüyorsa, o motoru sabit 2.5V ile beslediğinizde motor 2.5 birim hızla döner.
- Bir LED’i sabit 5V ile beslediğinizde o LED 5 birim ışık şiddeti ile yanıyorsa, o LED’i sabit 2.5V ile beslediğinizde LED 2.5 birim ışık şiddeti ile yanar.
- Bir buzzer’ı sabit 5V ile beslediğinizde o buzzer 5 birim şiddetle ses çıkarıyorsa, o buzzer’ı sabit 2.5V ile beslediğinizde buzzer 2.5 birim şiddetle ses çıkarır.
Yani çıktının fiziksel şekli farketmeksizin, genelde bu iş böyle yürür. Gelgelelim biz dijital dünyada öyle kolay kolay 2.5V veremiyoruz. Bunun yerine işareti zamanda modüle ederek işimizi görmek istiyoruz. Nasıl yani?
Yani elinizde olan yalnızca 5V ise, bundan 2.5V etkin gerilim değeri elde etmenin başka bir yolu var. 1ms süreyle 5V, 1ms süreyle 0V verip bunu sürekli tekrarlarsanız bu işaretin etkin gerilim değeri (5V*1ms +0V*1ms)/2ms = 2.5V olur. 5V lojik 1, 0V lojik 0 olduğundan ve işaret zamanın %50sinde lojik 1 değerini aldığından bu işarete %50 PWM denir.
Benzer şekilde 8ms süreyle 5V, 2ms süreyle 0V verip bunu sürekli tekrarlarsanız bu işaretin etkin gerilim değeri (5V*8ms + 0V*2ms) /10ms= 4V olur. Hesap açık efendim. Zamanın %80’inde işaret lojik 1 değerini aldığından bu işarete %80 PWM denir. PWM’de işaretin 1 olduğu zamana duty cycle denir. Tekrar eden toplam zaman, periyottur ve burada işaretin periyodu 10ms’dir. Frekans = (1 / Periyot[sn]) olduğundan burada frekans 1/10[ms] = 1/0.01[sn] = 100 Hz olur. Periyot ve duty cycle kontrol edilerek dalga genişliği modülasyonu (PWM) yapılabilir.
PWM’i anlatmaya gerek yok dedim ama anlatmadan da duramadım. Şimdi lafı bırakıp icraate geçelim. Peki ne yapacağız, PWM’in %1’lik çözünürlükle kontrol edilebildiği bir uygulama yazalım.
Kütüphanemizde bir değişiklik yapmaksızın main.c dosyamızı aşağıdaki gibi yazarsak olay tamam oluyor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
/* * File: main.c * Author: ozenozkaya * * Created on 09 January 2016 */ #include "pic16f84a_lib.h" /*FOSC is 4Mhz. Prescaler input it 1Mhz. Because prescaler is 8, prescaler output f=125kHz. So,the prescaler output period is 8uS. Hence, we need 125 timer pulses to reach 1mS. 8uS*125 = 1mS*/ #define TIMER0_VAL_FOR_1MS (118) #define PWM_DUTY_CYCLE_DEFAULT (50) #define PWM_OUTPUT_SET() do{REG_PORTB_UNION.port=0xFF;}while(0) #define PWM_OUTPUT_CLR() do{REG_PORTB_UNION.port=0;}while(0) static uint8_t pwm_duty_cycle=PWM_DUTY_CYCLE_DEFAULT; static void pwm_event_callback() { static uint8_t pwm_tick=0; REG_TIMER0_UNION.count = TIMER0_ASSIGN_CNT(TIMER0_VAL_FOR_1MS); if(pwm_tick<pwm_duty_cycle) { PWM_OUTPUT_SET(); } else { PWM_OUTPUT_CLR(); } if(pwm_tick>=100) { pwm_tick=0; } else { pwm_tick++; } } void pwm_set_duty_cycle(uint8_t new_pwm_duty_cycle) { if(new_pwm_duty_cycle <= 100) { pwm_duty_cycle = new_pwm_duty_cycle; } } void main() { REG_TRISB_UNION.port = TRIS_PORT_OUTPUT; REG_PORTB_UNION.port = PORT_ALL_LOW; /*! 125*8uS = 1000uS = 1mS*/ REG_TIMER0_UNION.count = TIMER0_ASSIGN_CNT(TIMER0_VAL_FOR_1MS); REG_INTCON_UNION.value = INT_DISABLE_ALL; REG_INTCON_UNION.bits.GIE = INT_ENABLE; REG_INTCON_UNION.bits.T0IE = INT_ENABLE; REG_OPTION_UNION.bits.T0CS = T0CS_SOURCE_INTERNAL_CLOCK; REG_OPTION_UNION.bits.PSA = PSA_ASSIGN_TO_TIMER0; REG_OPTION_UNION.bits.PS = PS_TMR0_RATE_DIVIDE_8; //8uS pwm_set_duty_cycle(50); register_timer0_int_callback(pwm_event_callback); WAIT_LOOP_FOREVER(); } |
Ve bu şirin mi şirin ama bir o kadar da acımasız kodun çıktısı aşağıdaki gibi oluyor.
Gördüğünüz üzere periyodu ~100ms olan %50’lik bir PWM işareti elde etmiş olduk. Gelin şimdi %25’lik PWM elde etmek için ne yapmamız gerektiğini görelim.
Yalnızca şu satırı değiştirmek yeterli:
1 |
pwm_set_duty_cycle(25); |
Bu durumda simülasyon çıktısı da aşağıdaki gibi oluyor.
Her iki çıktıda da periyodun 100ms olduğuna yani frekansın 10Hz olduğuna dikkat ediniz. PWM frekansını örneğin 20HZ yapmak için aşağıdaki değişikliği yapmanız yeterli.
1 |
#define TIMER0_VAL_FOR_1MS (59) |
Bu değişiklik ile zamanlayıcının birim zamanını 1ms’den 0.5ms’ye düşürdük. Bu da frekansı iki katına çıkardı.
Bu uygulama ile artık bir motorun hızını kontrol etmek mümkün. Robotçu arkadaşlar varsa bu kısım faydalı olacaktır diye düşünüyorum.
Projenin kaynak kodlarını BURADAN indirebilirsiniz.
Bu günlük de bu kadar. İleride yeni ibretlerle ve yeni yazılarla devam edeceğiz.
Yazıları beğendiyseniz, faydalanabilecek tanıdıklarınızla paylaşmayı unutmayınız.
Önceki Sayfa Sonraki Sayfa