Merhaba arkadaşlar.
RGB P10 paneller için Driver yazmaya alt yapı hazırlamakla uğraşıyorum. İşin temelinde burada anlattıklarım olacak. Bu yüzden bunu ayrı bir yazıda STM32 – 74HC595 ile nasıl pwm çoğullanır bundan bahsedeceğim.
74HC595’i mutlaka bilirsiniz. Oldukça sık kullanılan 8 bit Shift Register enteğresidir. Bu enteğre genellikle port çoğullama işlerinde kullanılır. Kullandığınız mikrodenetleyicinin sadece 3 pinini kullanarak 8 adet çıkış elde edebiliyorsunuz.
Bu enteğrenin kullandıldığı birçok devre görmüşsünüzdür. Dediğim gibi genellikle port çoğullama gibi işlemlerde kullanılıyor. PWM amaçlı pek kullanan yoktur. Bunun genel sebeblerinden biri pwm üretmek için yapılan işlemin biraz hız gerektirmesidir. Özellikle PWM frekansı arttıkça mikrodenetleyicinin işlem hızıda artması gerekiyor. Alabileceğinizi max. PWM frekansı kullandığınız mikrodenetleyicinin çalışma hızıyla doğru orantılı diyebiliriz sanırım. 🙂
Hızla ilgili sıkıntı yaşamamak için ben en baştan STM32F103 kullanayım dedim. Fritzing ile temel prensip şemasını çizdim.
İşin teorisine gelirsek olay şöyle gerçekleşiyor.
74HC595 ile PWM tamamen hız ile alakalı bir durum. Normalde Shift Registerin bir pinini lojik 1 veya 0 yapmak için tek seferlik seri olarak veri göndermek yeterlidir. Enerji olduğu sürece konum değişmez. PWM frekansı çıkarmak ise sürekli çıkışı güncellemek ile ilgili bir durum.
Örnekle gidersek sanırım daha iyi anlaşılır.
Örneğin; 100hz 8 bit pwm sinyaline ihtiyacınız var. PWM çözünürlüğünüz 8 bit olduğu için 0-255 arası bir değer alacaktır.
PWM nasıl oluşuyor?
Bu sorunun cevabı basit aslında. Diyelimki 74HC595’in 0. çıkışından 8 Bit %50Duty değerine sahip pwm sinyali almak istiyoruz. İlk başta 0-255 arası sayacak bir döngü kuruyoruz. Buna Duty döngüsü diyelim Bu döngünün her çevriminde 74HC595’e veri göndermemiz gerekiyor.
Duty döngüsü içerisindeyken sayacın değerini sürekli ilgili çıkışın duty değeriyle karşılaştırıyoruz. Diyelim Duty değerimiz 127 olsun. O halde Döngü değerimiz 127 olana kadar Shift registerın 0. bitine lojik 1 127’den sonrada lojik 0 gönderirsek %50 duty değerine sahip bir puls’ elde ederiz. %75 duty için Duty döngüsü 191 olana kadar 0. bite lojik1, 191’den büyük olursa lojik 0 göndeririz. Bu sayede %75’lik bir puls elde ederiz. Tabi ben bu işlemleri sadece Shift registerin 0. biti için söylüyorum. Shift Registerin 8 kanalından birden sinyal almak için yukarıda bahsettiğim Duty döngüsü içerisinde Shift Registerin bütün bitleri için Duty sayacıyla karşılaştırma yapılmalı ve bu karşılaştırmaya göre shift registere gönderilecek olan 8 bitlik verinin ilgili biti lojik1 veya lojik 0 yapılmalı.
Yukarıda bahsettiğim döngü 1 seferlik çalışırsa sadece 1 puls çıkar. Ama hala PWM frekansı yok. PWM frekansı için bu puls’ların sürekli çıkması lazım. Bunu nasıl yapacağız peki? Elbette timer kesmesi ile. Çünkü bize sabit zamanlama lazım.
PWM frekansımızın periyodu 1/F’den 10ms olarak bulunur. Bu bizim periyodumuz. Dolayısıyla yukarıda bahsettiğim işlemler tam olarak 10ms içerisinde gerçekleşmesi gerekir.. Bunu ben timer ile ayarladım. Yukarıda anlattığım yöntemdeki gibi harici bir duty döngüsü kurmak yerine timer kesmesini kullandım. Şimdi 100Hz için periyodumuz 10ms olarak bulmuştuk. Yani yukarıda kurduğumuz 0-255 arası döngü içerisinde yapılan işlemlerin 10ms içerisinde gerçekleşmesi gerekiyor. O halde 10/255 bağıntısından 0,039ms veya 39us gibi bir değer buluruz.
O halde biz kesme oluşma süresini 39uS olarak ayarlamamız gerekiyor. Timer kesmesi her oluştuğunda Duty_Count adında bir değişkenimizin değerini 1 arttıracağız. Bu değer 255’den büyük olursada Duty_Count değerini 1 yapacağız. Ardından Duty_Count değerini 8 kanal için ayrı ayrı tanımladığımız duty değişkenleri ile karşılaştırıp 74HC595’e gidecek verinin ilgili bitlerini lojik1 veya lojik 0 yapacağız.
Bunun için ilk başta STM32’de Timer’i ayarlamamız gerekir. Timer ayarlarını aşağıdaki gibi yapın.
void Tim_Configuration(void){ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; /*Timer1 Clock Kaynagi Aktif edildi*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseStructure.TIM_Period = 120; TIM_TimeBaseStructure.TIM_Prescaler = 2; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); /*Timer1 Kesmesi Aktif edildi*/ TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); /* Enable the TIM1 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM1, ENABLE);
Bu sayede timer’i yaklaşık 39-40’us ye kurmuş oluyoruz. Ayrıca Timer_Update kesmesini aktif etmiş oluyoruz.
Şimdi timer alt programına bakalım.
void Timer1_interrupt(void){ static int Duty_Count, Bit_Count, temp=0; for(Bit_Count=0;Bit_Count<8;Bit_Count++) { if(Duty_Count<=Duty[Bit_Count]) { temp |= ((uint8_t)1<<Bit_Count); } else { temp &= ~((uint8_t)1<<Bit_Count); } } for(Bit_Count=0;Bit_Count<8;Bit_Count++) { if(temp & 0x80) { GPIOB->BSRR=GPIO_Pin_0; //DATA=1 } else { GPIOB->BRR=GPIO_Pin_0; //DATA=0 } temp=(uint8_t)temp<<1; GPIOB->BSRR=GPIO_Pin_1; //CLK=1 __asm {nop} GPIOB->BRR=GPIO_Pin_1; //CLK=0 } GPIOB->BSRR=GPIO_Pin_2; //LATCH=1 __asm {nop} GPIOB->BRR=GPIO_Pin_2; //LATCH=1 Duty_Count++;if(Duty_Count>255)Duty_Count=1;//Duty Döngü sayacimiz. Her kesme olustugunda bu sayaci 1 arttiriyoruz. // Sayac degeri 255 'den büyük olursa sayaci 1 yapiyoruz.
yukarıdaki kodlar içerisinde Bit_Count adında iki adet döngü göreceksiniz. Bu döngülerden ilki Duty_Count değeri ile Duty değerleriminiz karşılaştırılmasının yapıldığı döngüdür. Hangi bitin lojik 1, hangi bitin lojik 0 olacağına burada karar verilir. Alttaki döngü ise Soft SPI döngüsüdür. Şimdi diyeceksiniz madem hız gerekli neden Soft spi kullanıyorsunuz? Sebebi ise RGB paneller ile yaptığım çalışmada soft spi zorunluluğu olduğu için artık bu uygulama içinde donanımsal spi ile uğraşmak istemedim. İsteyen uygulayabilir.
Şimdi ufak bir uygulama yapalım. Duty adında 8 bit 8 elemanlı bir dizimiz var. Bu dizinin 0 elemanı 74HC595’in 0. bit’inin duty değeridir. 7. elemanı ise 7. bitin duty değeridir.
mainde şöyle bir kod yazıp basitçe bir animasyon yapmaya çalıştım.
int main(void) { int i=0; Hardware_Configuration(); //Sistem ve Donanim Ayarlarini yap.. while(1) { for(i=0;i<256;i++) { Duty[0]=i; Duty[1]=abs(i-32); Duty[2]=abs(i-64); Duty[3]=abs(i-96); Duty[4]=abs(i-128); Duty[5]=abs(i-160); Duty[6]=abs(i-192); Duty[7]=abs(i-224); delay_ms(5); } for(i=255;i>0;i--) { Duty[0]=i; Duty[1]=abs(i-32); Duty[2]=abs(i-64); Duty[3]=abs(i-96); Duty[4]=abs(i-128); Duty[5]=abs(i-160); Duty[6]=abs(i-192); Duty[7]=abs(i-224); delay_ms(5); } } }
Yukarıdaki kodun çalışma videosu:
Görsel açıdan pek zevkli bir uygulama olmadı elbet. Fakat benim asıl hedefim RGB paneller olduğu için fazla üzerinde durmadım. İşin mantığını öğrenen RGB ledlerle çok güzel uygulamalar yapabilir.
İşin temel mantığı budur arkadaşlar. Burada Duty_Count değerimiz PWM çözünürlüğünü (0-255 için 8 bit) Kesme oluşma süresi ise PWM frekansını belirler. Mesela PWM Frekansını 50Hz’e düşürüp çözünürlüğü 10 bit yaparak servo kullanabilirsiniz. Aynı anda 8 kanal 10 bit pwm elde edersiniz. Bu yöntemle doğrudan motor felan sürmeyecekseniz 100Hz gibi frekanslar led sürmek için çok uygundur. Frekans duty arasında buna benzer birçok olasılık mevcuttur. Konu hakkında sormak istedikleriniz varsa sorabilirsiniz.
İyi çalışmalar.
Yayım tarihi: 2014/10/17 Etiketler: 74HC595, arm projeleri, shift pwm, stm32, stm32 pwm
merhabalar. öncelikle paylaşımınız için çok teşekkür ederim. Benim buna ek bir sorum olacak; PWM ile bir SMPS çıkışını kontrol edip SPI ile (3x)74HC595 gibi bir entegreyi kontrol ederek 24 farklı LED’i kontrol edebilir miyim? Amacım ortam ışığına göre LED’lerin ışık şiddetini düzenlemek. Ek olarak ses frekansına göre vs. ışık şiddetini ayarlamaya çalışıcam.