PIC Programlama – 11 – USART

Merhabalar,

Önceki yazı ile birlikte, bir mikrokontrolöre nasıl SDK geliştirebileceğimizi ve PIC16F84A’nın tüm modüllerini detaylıca incelemiştik. Her iki başlık için de gerekli mesajların verildiği, gerekli bilgilerin aktarıldığı düşüncesi ile PIC yazılarının seyrini biraz daha değiştiriyorum.

PIC programlama yazı dizisinin bu bölümünde, şu zamana kadar incelemediğimiz, ancak gömülü sistemler dünyasının adeta kolonları/kirişleri olan temel modülleri inceleyeceğiz. Bunların başında da USART modülü geliyor. PIC16F84A’da ne yazık ki USART modülü bulunmuyor. Bu sebeple PIC16F877A üzerinden devam edeceğiz 🙂 Bu defa, PIC16F877A için de ayrıca bir kütüphane yazmak yerine, mecvut kütüphaneleri kullanarak uygulama yapacağız. Kütüphane geliştirme ile ilgili bilgileri önceki yazılarda yeterince işlemiş olduğumuzdan, bu yola girmekte artık bir sakınca görmüyorum. Dileyen azimli arkadaşlar, PIC16F877A için de kütüphane yazabilirler.

Şimdi gelelim her zamanki gibi USART’a bir girizgah yapmaya. Ancak öncesinde irdelememiz gereken bir başka önemli kavram var…

Haberleşme 

Bir bilgiyi, bir kaynaktan başka bir hedefe aktarmak o bilgili veriye dönüştürüp; veriyi kaynaktan hedefe aktaracak bir haberleşme hattı kurmakla mümkündür. Çok derinden girdiğimiz bu hususu bir örnekle irdeleyelim. Ali adlı arkadaşımız, o anki şaşkınlığını ifade eden ‘A’ sesini, Veli’ye duyurmak istediğinde haberleşme süreci nasıl oluşur?

Şaşkınlık bilgisi bu şemada ‘A’ sesi ile kodlanmıştır. Bu bilgi, havayı belli frekansta titreştiren ve Ali’nin ağzından gelen ‘A’ sesi vasıtası ile havada ilerler ve Veli’nin kulağında oluşan titreşimlerin algılanması ile Veli’ye bu veri aktarılır. Ardından Veli’nin beyni bu veriyi tekrardan bilgiye dönüştürerek ‘A’ sesini algılar ve Veli; Ali’nin şaşırdığını algılamış olur. Bu şemada kaynak Ali, hedef Veli, haberleşme kanalı hava, bilgi şaşkınlık, veri ise ‘A’ sesi olmaktadır. Haberleşme ses vasıtasyla da taşınsa, elektriksel olarak da taşınsa bu şema değişmez. Bilgi, veri ve haberleşme kaynaklarını açıkladığımız bu örnek çok saf ve naif görünse de, kompleksleşen haberleşme sistemlerinde temelleri hatırlamak ve bu gerçekliklerin farkında olmak hayat kurtarmaktadır.

Bilginin veriye dönüşümü, bu yazının odak noktası olmadığından; biz verinin aktarımı üzerine yoğunlaşacağız.Peki tek bir veriyi değil de, birden fazla veriyi göndermek istediğimizde ne yapacağız?

Paralel ve Seri Haberleşme

Yukarıdaki örnekte Ali’nin biraz daha konuşkan biri olduğunu düşünelim. Ali, Veli’ye “Selam Veli” dediğinde sesler ardışıl olarak; yani peşi sıra olarak Veli’ye gider. Yani Ali sırasıyla önce ‘S’,’e’,’l’,’a’,’m’ der biraz bekler, ardından sırasıyla ‘V’,’e’,’l’,’i’ harflerini peşisıra söyler. Veli de bu sesleri -kısa bir gecikmeden sonra- aynı sıra ile duyar. İşte bu şekilde ardı-sıra veri aktarımı olan haberleşme şemaları, seri haberleşme şemaları olarak adlandırılır. Özellikle, tek haberleşme kanalının bulunduğu durumlarda, bu haberleşme yöntemi kullanılır. Seri haberleşmede yalnızca tek kanal kullanılır, ancak tüm veri sırasıyla gönderildiğinden tüm verinin aktarımı zaman alır.

Paralel haberleşmede ise veri, -adı üzerinde- paralel olarak gönderilir. 8 hoparlörü olan bir cihaz, 8 mikrofonu olan bir cihaza ‘Selamlar’ mesajını paralel olarak iletmek isterse (her bir hoparlörü sadece 1 mikrofonun duyabildiğini varsayarsak) 1. hoparlör ‘S’ sesini, 2. hoparlör ‘e’ sesini, … , 8. hoparlör ‘r’ sesini aynı anda çıkartır. Karşı tarafta da bu sesler mikrofonlardan aynı karşılıkla aynı anda algılanırsa; tüm sesler paralel olarak gönderildiğinden bu haberleşme adı üzerinde “paralel haberleşme” olur. Paralel haberleşme, birden fazla haberleşme kanalı gerektirir ancak bilgi daha kısa sürede aktarılır.

Aynı konseptler elektriksel olarak da geçerlidir. Yani ses yerine bitleri inceleyecek olursak, aşağıdaki gibi bir gösterim yapmamız yanlış olmaz. [Resim Wikipedia sayfasından alınmıştır.]

Güzel.. Şimdi dönelim konumuza. USART dediğimiz nane, bir seri haberleşme algoritması olup; sıkça kullanılmaktadır. Özellikle “tracing”,”serial logging” gibi işler için; sıklıkla USART kullanılır. Ayrıca USART ile haberleşen çok sayıda sensör vb. elektronik ekipman bulunmaktadır.

PIC16F877A ve USART

Şimdi gelelim PIC16F877A ile USART haberleşmesi yapacak kodu yazmaya… Bunun için oldukça basit bir örnek yapacağız. İlk hedefimiz, USART üzerinden “Merhaba dünya!” yazısını gönderecek bir kod yazmak. Bunu, Microchip’in en yeni kütüphanesi olan; xc kütüphanesini kullanarak yapacağız. Unutmayınız ki, xc kütüphanesini kullanabilmek için XC8 derleyicisini yüklemeniz gerekmekte.

USART haberleşmesi seri bir haberleşme olduğundan, verilerin ardı sıra gönderilimindeki zamanlama çok önemlidir. Bu sebeple, kodumuza PIC16F877A’nın kristal osilatör fraksnsını aşağıdaki gibi giriyoruz.

Öncelikli olarak UART_Intialize fonksiyonunu incelemeye başlayalım. PIC16F877A’nın C portunun 6. pini USART_TX (transmit, veri gönderme), 7. pini ise USART_RX (receive, veri alma) pini olduğundan, 6. pini çıkış, 7. pini giriş olarak konfigure ediyoruz. Buna ilişkin kod aşağıda yer alıyor:

Ardından modülümüzü konfigüre edeceğiz. Buna göre datasheet’e baktığımızda konfigüre etmemiz gereken kütüklerin TXSTA, RCSTA, SPBRG kütükleri olduğunu görüyoruz. Veri göndermek için ise TXREG kütüğüne ihtiyaç duyacağız. Veri kağıdından sırası ile bu kütüklerle ilgli bilgilere bakıp, konfigürasyonumuzu yapalım 🙂

Buna göre SYNC=0 (Asenkron mod), BRGH = 1 (Yüksek hızlı mod), TXEN=1 (Veri gönderimi aktive edildi, izin verildi), TX9=0 (8 bit veri aktarımı modu) şeklinde konfigürasyon yaptığımız gözüküyor 🙂

Şimdi hemen diğer bir önemli kütüğümüz olan RCSTA’ya bakalım.

Buna göre RX9=0 (8 bit mod), ADDEN= 0 (9. bit parity biti olarak kullanılsın), CREN=1 (Sürekli veri alımı aktive edildi, izin verildi), SPEN=1 (Seri port aktive edildi) şeklinde konfigürasyon yaptığımız gözüküyor 🙂

Seri haberleşmemizi 9600 baudrate’te yapmak istiyoruz. Buna göre SPBRG kütüğüne yazmamız gereken değeri, PIC16F877A’nın datasheet dökümanından buluyoruz.  Buna göre, veri kağıdının aşağıdaki kısmından da görülebildiği üzere BRGH bitinin durumuna göre hesaplamalar değişiyor. Biz BRGH=1 olacak şekilde konfigüre etmiştik.

Bir diğer önemli tablo da, BRGH seçim tablosu. Veri kağıdından aldığım tablolar ise aşağıdaki gibi:

Yukarıdaki tablonun alt kısmını kullancağız, çünkü BRGH=1 olarak konfigüre etmiştik. Kullanacağımız osilatör frekansı 4MHz olduğunda ve istediğimiz Baudrate (veri aktarım hızı) 9600 baud = 9.6K baud olduğundan, SPBRG değerinizin 25 olması gerektiği tablodan görülebiliyor. Biz de bu sebeple aşağıdaki konfigürasyonu yaptık:

Artık USART modülümüzü konfigüre ettiğimizden, veri gönderme kısmına geçelim. Veri göndermek için yapmamız gereken şey, göndereceğimiz 8 bitlik değeri TXREG kütüğüne yazmak. Ancak hali hazırda gönderilmekte olan bir veri var ise öncelikle onun gönderilmesini beklememiz gerekiyor. TRMT biti 1 oldğunda veri gönderimi yok demektir. Buna göre TRMT kütüğü sıfır olduğu sürece bekliyoruz.

Yukarıdaki fonksiyon ile her seferinde 8 bitlik bir veri gönderebiliriz. Bu, her seferinde en fazla 1 ASCII karakteri gönderebileceğimiz anlamına geliyor. String, yani karakter dizisi göndermek için ise aşağıdaki fonksiyonu yazdık.

Buna göre uygulamamızın Proteus ISIS simülasyonundaki çıktısı aşağıdaki gibi oluyor [Benzetimden önce PIC16F877A’ya sağ tıklayarak Processor Clock Frequency parametresini 4MHz yapmanız gerekmekte] :

Şimdilik bu kadar 🙂 Bir sonraki yazımızda USART RX kesmesini ve USART aracılığı ile veri okumayı inceleyeceğiz.

 

Yazıları beğendiyseniz, faydalanabilecek tanıdıklarınızla paylaşmayı unutmayınız.

Önceki Sayfa   Sonraki Sayfa

PIC Programlama – 10 – EEPROM

Merhabalar, doktora yeterlik sınavı dolayısıyla ara verdiğim blog yazılarıma, yeterlik aşamasını geçtiğimden dolayı devam ediyorum 😀 PIC16F84A ile ilgili, geriye kalan son bir iki konuyu da irdeleyeceğimiz bu yazıda, ana gündemimiz EEPROM olacak.

“EEPROM nedir?” sorusuna yanıt arayanlar için WIKIPEDIA sayfasında yeterli bilgi olduğunu belirtmek isterim. Kısaca özetlemek gerekirse EEPROM, elektriksel olarak yazılıp silinebilen, kalıcı bir bellek tipi; yani elektrik gitse de içerisinde kayıtlı olan veri kendiliğinden silinmiyor. Flash belleğe göre yazım ömrünün çok fazla olması, bu devre elemanını bu gün dahi popüler tutmaya yetiyor.

PIC16F84A içinde de 64 byte’lık bir EEPROM bulunuyor. 64 byte kadar küçük bir EEPROM olsa da, özellikle kalıcı konfigürasyonları saklama konusunda, bu modül hayat kurtarıyor. Bu durumda bize düşen de, PIC16FLIB adıyla geliştirdiğimiz alternatif PIC kütüphanesine bu modülü de eklemek olacak. Öyleyse hemen başlayalım!

İlk adım olarak, her zamanki gibi datasheet’i okuyup anlayarak, yazılımsal olarak modelleyeceğiz. Datasheet’te EEPROM modülü, sistemin blok diyagramında şöyle yer alıyor:

PIC16F84A veri kağıdından alınmıştır.

Buradan da görüldüğü gibi EEPROM modülümüz 64*8 bitlik veriyi saklayabiliyor. Şimdi de, EEPROM ile ilgili kütüklerin (register), hangi adreslerde olduğuna bakalım.

İlgili kütükleri fosforlu sarı ile işaretlediğimden adreslerini görebilirsiniz. Artık adreslerini ve yapılarını da bildiğimizden, bu kütükleri yazdığımız PIC kütüphanesine ekleyebiliriz. EEPROM modülünü de derlemeye dahil edip etmemeyi seçimli yapmak için işe pic16flib_conf.h dosyasına aşağıdaki işaretli satırı ekleyerek başlıyoruz.

Şimdi pic16f84a_lib.h içerisindeki kütük tanımlamalarına geçelim. Aşağıdaki işaretli satırları pic16f84a_lib.h dosyasına ekliyoruz.

Register tanımlarını nasıl yaptığımızı daha önceki yazılarda çokça açıkladığımdan bu defa tekrara düşmemek adına açıklamayacağım. Her zamanki gibi, veri kağıdındaki bilgileri modelleyerek kütük tanımlarını yaptık.

Şimdi gelelim eeprom’dan veri okuma ve eeprom’a veri yazma fonksiyonlarına. Onları da aşağıda görebilirsiniz:

Her iki fonksiyon girişinde de, girilen adresin 63 değerinden büyük olup olmadığını kontrol ettik. Toplamda 64 byte’lık bir eeprom’umuz olduğundan, en yüksek adres 63 oluyor. Ardından okuma yapmak için, okuma yapacağımız adresi seçtik. Sonrasında RD bitini 1 yaparak okumayı başlattık ve okunan değeri REG_EEDATA kütüğünden okuduk.

Yazma kısmında ise işler biraz daha karışık şekilde çözülüyor. Yine adres seçimini yaptıktan sonra ilk iş olarak yazmak istediğimiz veriyi REG_EEDATA kütüğüne yazıyoruz. Ardından WREN bitini 1 yaparak yazma izni vermiş oluyoruz. Sonrasında, veri kağıdından okuduğumuza göre EECON2 kütüğüne sırasıyla 0x55 ve 0xAA yazmak gerekiyor. Bu, koruma amaçlı konmuş ve veri kağıdına üretici tarafından yazılmış bir ek sekans 🙂 Kodu yazan ne yaptığını biliyorsa, ancak o zaman yazabilsin demişler. Saygımız sonsuz 😉 Ardından WR bitini 1 yaparak yazım sürecini başlatıyoruz. Yazım hemen bitmiyor; bu durumda yazımın bittiğini anlayabilmek için tek çaremiz EEI (eeproma veri yazıldı) kesmesini kurarak, kesme bayrağı 1 olana kadar beklemek. Burada bekleme işini asm(“nop”); satırı ile yaptık. asm(KOMUT) fonksiyonu, XC8 derleycilerinde C kodunun içerisnde assembly komutları çağırmaya yarar. Biz de “nop” yani “bir şey yapmadan 1 saat darbesi bekle” komutunu çalıştırarak bekleme sağladık. Ardından kesme bayrağı 1 olup while döngüsünden çıktığımızda kesmeyi kapatıp, kesme bayrağını temizleyip, WREN kütüğünü sıfara çekerek çıkıp gidiyoruz 🙂 Verimiz yazıldı.

Dikkat! Burada while döngüsü sonsuza kadar sürebilir. Bunu engellemek için while içinde bir değişkeni her turda 1 artırıp, döngü sayısı bir üst sınır değerini geçerse ve yazım işlemi hala bitmemişse hata döndürerek çıkmasını sağlamakta fayda vardır.

Yukarıdaki fonksiyonların eklendiği durumda pic16f84a_lib.c dosyamız şöyle oluyor:

Artık kütüphanemiz hazır olduğuna göre, basit bir test fonksiyonu yazacağız. 0x01 adresli EEPROM bellek alanına 1 byte’lık ‘X’ karakterini yazan ve doğru yazıp yazmadığını kontrol eden bir kod, aşağıda yer almakta.

Bu kodumuz, yazım ve okuma işlemleri başarılı ise B portuna 0xAA yazıyor. Eğer hata olursa B portu ilk değeri olan 0x00 değerinde kalıyor. B portuna 0xAA=10101010 değerinin yazılması, sırasıyla pinlerin yüksekte ve alçakta olmasına sebep oluyor.

Kodda bir diğer önemli kısım ise #pragma ile başlayan konfigürasyon satırları. Buna göre osilatör olarak kristal osilatör seçmiş olduk. Ayrıca, mikrokontrolörün donup kaldığı durumlarda ona reset atacak olan watchdog timer modülünü aktive ettik. Yine güç dalgalanmalarında reset atmaya yarayan power on reset’i aktive ettik ve kod korumayı kapattık. Kod koruma açık olarak mikrokontrolöre yazılım atarsanız, o kod bir daha okunamayacak ve kolaylıkla değiştirilemeyecektir. Prototip aşamasında CP (code protection, kod koruma) seçeneğini OFF yapınız (varsayılan değer de budur).

Bu kodun çalıştığı duruma ilişkin Proteus ISIS simülasyon çıktısı ise aşağıda yer alıyor.

Gördüğünüz gibi, EEPROM sürücümüz de başarıyla çalışıyor 🙂

Projenin kaynak kodlarını BURADAN indirebilirsiniz.

Bu günlük de bu kadar. İleride 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

PIC Programlama – 9 – Zamanlayıcılar (Timers) 2

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.

1.00 ms zamanlayıcı çıktısı

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.

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:

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.

Ve bu şirin mi şirin ama bir o kadar da acımasız kodun çıktısı aşağıdaki gibi oluyor.

%50 PWM

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:

Bu durumda simülasyon çıktısı da aşağıdaki gibi oluyor.

%25 PWM

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.

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

PIC Programlama – 8 – Zamanlayıcılar (Timers)

Merhabalar, geçen yazımızda kesmeler üzerine oldukça marjinal bazı çalışmalar yapmıştık. Şu ana kadar PIC mikrokontrolörün giriş çıkış ünitelerini, kesmeleri de içerecek şekilde evire çevire kullanabilecek bilgi birikimini edindik. Tabi bu bahsettiğimiz konu, işin bahanesiydi. Asıl amacımız, tüm mikrokontrolörlerde kullanabileceğimiz bir modelleme ve geliştirme altyapısını tahsis etmekti. Buna istinaden yaptığımız en orijinal iş, Microchip firmasının sunduğu kütüphanelerden performans, estetik, yazılım kalitesi, taşınabilirlik ve geliştirilebilirlik bakımından çok daha iyi bir kütüphane oluşturmak oldu. Mikrokontrolör’ün veri kağıdını nasıl modelleyeceğimizi gördüğümüzden aynı işlemleri herhangi bir başka platform için yapmak sorun olmayacaktır.

Dış dünyaya mikrokontrolörün I/O pinleri üzerinden müdahale edebildiğimizden bir sonraki adıma geçmenin sırası geldi. Bir sonraki adım şüphesiz ki periyodik işlerle nasıl başa çıkabileceğimizi bulmak olacak. Güncel durumda yazdığımız kodlar ömürleri boyunca aynı işi aralıksız olarak yaptılar. Oysa gerçek hayattaki pek çok uygulamada gerçekleşmesi gereken zamanda ve periyodik gerçekleştirilmesi gereken olaylar görürüz. Periyodik yapılan işlerden ilk akla gelen örneklemedir. Nyquist amcamızın da yıllar önce ortaya koyduğu gibi, bir işareti doğru zamanlama ile örneklemezsek o işaretin taşıdığı bilgiyi kaybetmiş olabiliyoruz.  Biraz daha net şekilde açıklamak gerekirse:

Sosyal Mesaj
Nyquist örnekleme frekansı, bir işaretin örneklenmesi sürecinde tanımlanan bir alt limittir ve bu limit işaretin içerdiği maksimum frekans bileşeninin iki katıdır. Eğer işaret maksimum frekans bileşeninin iki katından  – ki bu frekans Nyquist frekansı oluyor – daha az bir frekansla örneklenirse o işaret, ölçümlerden düzgünce tekrar üretilemez çünkü örnekleme esnasında bilgi kaybı yaşanmış olur.

Sonuç olarak, mikrokontrolörde zamanı ölçmenin bir yolunun olması gerekiyor. İşte bu noktada devreye Timer (Zamanlayıcı) modülleri giriyor. Ancak zamanlayıcılara geçmeden önce, zamanlama sorununa sunduğumuz en basit çözümü hatırlayalım. BURADAKİ yazımızda, DummyDelayMs fonksiyonun kurnazca tanımlayarak milisaniye mertebesinde bir zamanlama sağlamıştık. Hemen implementasyonu hatırlayalım.

Gördüğünüz gibi bu kodda, mikrokontrolöre boş beleş sayı saydırıp, geçen zamandan faydalandık. Tersten giderek, 1ms beklemek için gerekli olan sayma sayısını hesaplayıp, mikrokontrolörün o kadar boş beleş (dummy) işlem yapmasını sağladık. Bu da adeta bir araçta vitesi boşa atıp gaza sonuna kadar basmaya benziyor. Bir yere gitmiyoruz ama cayır cayır benzin yakıyoruz 🙂 İşte bunu yapmamak için Timer modülü var.

Bizim bu zamana kadar çalıştığımız PIC16F84A’da ne yazık ki tek bir zamanlayıcı modülü var. Ancak diğer modellerde daha fazla sayıda zamanlayıcı modülü mevcut. Unutmamak gerekir ki zamanlayıcılar gömülü sistemlerde “elzem” olduğundan, hemen her mikrokontrolörde zamanlayıcı bulunur. Şimdi hemen bir tablo ile hangi PIC’te hangi zamanlayıcı var görelim.

Zamanlayıcı PIC16F8X PIC16F62X PIC16F877
Timer0 + + +
Timer1 + +
Timer2 + +

Bu arada hemen araya sıkıştırayım; zamanlayıcıları da bitirince artık daha gelişmiş PIC modelleri üzerinden devam etmemizde bir sakınca kalmayacak. O nedenle yavaştan diğerlerine de geçebiliriz. Şimdi Timer0’ın hakkından gelelim 😉

TIMER 0

Timer0 kardeşimiz, 8-bit’lik bir zamanlayıcı/sayıcı olarak tasarlanmış olup, doğal olarak 0’dan 255’e kadar sayabilmektedir. Yine bu efendi kardeşimizin sayacını okumak da sayacına yazmak da mümkündür; bu da bize zamanlayı istediğimiz değerden başlatma ve güncel değeri okuma imkanları sunar.

Zamanlayıcı, esasında; herhangi bir saat işaretini (clock pulse) alıp o işareti çeşitli ön işlemlerle örnekleyerek sayan bir devreciktir. Buna istinaden söz konusu saat işareti dışarıdaki bir osilatörden (Ring osilatör) ya da iç osilatörden (RC osilatör) alınabilmektedir.

Bu zamanlayıcılar 255’e kadar sayabildiğinden 255’ten sonraki sayımda taşma olur yani değer tekrar 0’a döner. Bu anda kesme kurmak mümkündür. Bu da zamanlayıcı kesmesi olarak adlandırılmaktadır.

Timer0 ile alakası olan kütükler, mikrodoentleyicinin veri kağıdında açıkça belirtilmiştir. Buna göre OPTION_REG, INTCON_REG, TMR0_REG, TMR0_REG ve inanmazsınız TRISA/PORTA kütükleri Timer0 ile ilintili imiş. Bunların her birini didik didik ederek zamanlayıcıların suyunu çıkaracağız. Tabi ki yapacağımız ilk iş, henüz modellemediğimiz kütükleri modelleyerek kütüphanemize eklemek olacak.

Tüm bunlara ek olarak, kütüphanemizi yine adım adım optimize etmeye devam edeceğiz. Burada yine tasarım trade-off”larını tartışacağız ve yeni eksikleri hep birlikte tespit edeceğiz 🙂 Öyleyse işe koyulalım ve TIMER0 ile ilk periyodik fonksiyonumuza hayat verelim!

Malumunuz her yazıda yeni kesme değerlendirme rutinleri, yeni veri yapıları ekliyoruz. Bunları her uygulamada kullanmayacağımızdan, bu uygulamalarda biraz bellek israfı yapmış oluyorduk. Şimdi onları bir config dosyası üzerinden seçimli hale getirmenin tam sırası. Yeni config dosyamız “pic16lib_conf.h” aşağıdaki gibi.

Görmüş olduğunuz PIC16FLIB_USE* etiketlerinden ikisi commentlenmiş. Eğer uygulamanızda RB0/PORTB kesmesini kullanacaksanız bunları da açabilirsiniz. Biz bu uygulamamızda yalnızca TIMER0 kesmelerini kullanacağımızdan seçimi o şekilde yaptık.

Hemen güncellediğimiz kütüphane dosyalarımızı görelim.

Timer0 adres ve kütük tanımlamalarının yapılmış olduğuna dikkatinizi çekerim 🙂 Aynı zamanda, config dosyasında kullandığımız etiketler de, kullanılmayan kesmelerin fonksiyon prototiplerini derleme zamanında kapatmak için yerli yerinde. Öyleyse hemen .c dosyamızı görelim.

Görüldüğü üzere, alışılageldik şekilde timer0 kesme rutinlerini de ekledik. Ek olarak yine config etiketlerimiz de fonksiyonları çevrelemiş durumda 🙂 Artık kütüphanemizi kullanan kimselere çok daha geniş optimizasyon imkanı sunuyoruz! Her durumda yine Microchip liblerinden çok daha yüksek performanslı bir konumda kütüphanemiz 🙂 0 byte kullanarak tüm register erişimlerini yapmak mümkün. Konfor arayan yazılımcılar için de, kütük erişimlerini çok kolaylaştıran veri yapılarını implement ettik. İsteyen istediği yönde optimizasyon yapabiliyor. Bu da kütüphanemizin esnekliğini ciddi oranda artırmış durumda. Şimdi uygulama koduna geçelim.

Bu yazılımda, B portlarını 1 milisaniyede bir eviren bir yazılım oluşturduk. Peki ama bunu nasıl yaptık? Hemen hesap kitaba değinelim. PIC mikrokontrolörümüze dışarıdan bir kristal osilatör bağlı ve frekansı 4MHz.

Aman Diyelim
Proteus simülasyonu yapmadan önce, PIC mikrokontrolöre çift tıklayıp FOSC değerini 4Mhz yaparsak, buradaki hesaplarla uyumlu çıktılar alabiliriz. Aksi durumda varsayılan değer olan 1Mhz geçerli olacaktır ve bu durumda istenen çıktılar elde edilemeyecektir.

PIC16F84A’da timer modülüne gelen saat işareti 4’e bölünerek gelir (datasheet’te yazmakta). Dolayısıyla modülün girişi 1MHz yani saniyede 1 milyon saat darbesi geliyor. Timer modülünün giriş kısmında bir frekans ölçeklendirici (prescaler) bulunmakta. Biz ölçeklendirme oranını 8’e bölecek şekilde seçtik. Bunu, aşağıdaki satır ile yaptık.

Buna göre, 1MHz’lik işaret frekansını 8’e böldük ve prescaler çıkışındaki frekans 125Khz oldu. Bu da 8 micro saniyede bir saat darbesi demek. Eğer bu saat darbelerinden 125 tane sayılınca kesme olacak şekilde bir konfigurasyon yaparsak, 8*125 us = 1ms’lik bir kesme rutini elde etmiş olacağız.  Bu sebeple aşağıdaki tanımla 125 değeri tanımlandı.

Bu değer ise timer0 kütüğüne aşağıdaki şekilde atandı.

Burada ibretlik bir macromuz var. Bu macro TIMER0_ASSIGN_CNT. Peki neden buna ihtiyaç duyduk? Malumunuz bizim timer0 modülü yukarı doğru sayıyor ve 255(0xFF)’de taşıyor. Taşmadan önce 125 tur sayabilmesi için aslında 255-125 =130 değerinden başlamalı Timer0. Bu durumda timer 130’dan başlatılacak ve tam 125 tur sonra (1ms yapıyor) kesme gelmiş olacak. İşte bu çıkarma hesabını yapan macromuz TIMER0_ASSIGN_CNT. Kütüphanemizdeki tanımı da aşağıdaki gibi idi.

Bu sayede 1ms’lik zamanlama sağlamış oluyor. Ancak sürekli olarak 1ms’lik zamanlamayı sağlamak için TIMER0 taştığında, yani kesme oluştuğunda Timer0’ı tekrar 130 değerinden başlatmak gerekiyor. Bunun için, milisaniye callback fonskiyonumuzda aşağıdaki gibi bir tekar yükleme yaptık.

Bu sayede her şey çalışır durumda ve enerji efektif şekilde periyodik işlerimizi yapabilir hale geldik 🙂 Hemen proteus isis üzerindeki simülasyon çıktımızı da ekleyelim.

pic_timer0_0

 

Osiloskoptaki zaman ölçümünün 1.06 mS oluşuna dikkat ediniz. 1ms güzel de, bu 0.06 mS nereden geldi? Buradan da çok güzel ibretlere değineceğiz dostlar, ancak bu sorunun yanıtı ve dahasını bir sonraki yazıya bırakıyoruz 🙂

Zamanlayıcılar konusunda ne kadar çok tekrar yaparsanız, farklı uygulamalar yazarsanız o kadar iyi olur. Örneğin karaşimşek uygulamasını zamanlayıcı kullanarak yazmakta faydalar var.

Şimdi devam!

Yazıları beğendiyseniz, faydalanabilecek tanıdıklarınızla paylaşmayı unutmayınız.

Önceki Sayfa   Sonraki Sayfa

PIC Programlama – 7 – Kesmeler (Interrupts) 2

Merhabalar, geçen yazıda RB0/INT kesmesini bahane ederek kesmeler hakkında bir takım feyizli işler yapmıştık. Bu yazımızda da kesmelere olan sevgimizi ve ilgimizi pekiştirerek, ilginç bir başka örnek yapacağız ve yeni ibretlerle karşılaşacağız. PIC mikrodenetleyicilerde de bir taş üstüne bir taş daha koyalım diyerekten bu günkü örneğimizi PORTB değişme kesmesi üzerinden gerçekleyeceğiz.

Bundan seneler önce, gömülü sistemlerdeki ilk dönemlerimde; keypad (tuş takımı) sürmekten nefret ederdim çünkü mikrodenetleyicilerde kullanımı hem zordu hem de inanılmaz derecede verimsiz idi. Tuş takımının pinlerini matris gibi düşünürsek, sütünları ikili düzende her seferinde bir pin 1’de diğerleri 0’da olacak şekilde, 1 olan pini bir kaydırarak sürerdik ve o esnada da satır pinlerini input olarak ayarlayıp değerini okuyup hangi pine basıldığını anlamaya çalışırdık. Bu olay son derece can sıkıcı bir olaydı ama neyse ki bu can sıkıcı durumu uzunca bir süredir unutmuştum. Ta ki bu yazıyı kafamda planlayana kadar. Bu yazı, bu çirkin yönteme bir tepki olabilirdi, olacak da.

Bir donanımı sürmek için yazılan kodun kesmeler yerine polling (sürekli değer okuma/yazma yani bir nevi deneme yanılma) ile sürülmesi gerek performans açısından gerek güç tüketimi açısından kötü bir durum. Öte yandan tuş takımı sürülmesini anlatan resmi dökümanlar bile en iyi çözüm olarak kesme/polling hibrit kodlar veriyorlar. Bu da oldukça verimsiz bir duruma tekabül ediyor. Daha önce tuş takımı süren arkadaşlar eminim anlayacaklardır. Tuştakımı sürme işinin sadece kesme ile yapılabilmesi güzel olurdu. Öyleyse sadece kesmeyle yapalım 🙂 Arada PORTB değişme kesmesini de anlatmış olacağız.

keypad

Gördüğünüz üzere b portunun 4-7 arası pinleri pull down yaparak satır girişlerine bağladık. Yine 0-3 arası pinlerle keypadin sütun pinlerini süreceğiz. Pull down amaçlı koyduğumuz dirençler bize çok yardımcı olacak 😉 Şimdi gelelim kodlara. Öncelikle güncellediğimiz kütüphanelerimiz aşağıdaki gibi:

Ve yukarıdaki header dosyasına ilişkin kaynak kodu aşağıda:

Gördüğünüz gibi TRISA ve PORTA kütüklerini de modelledik ve ayrıca kütüphaneyi PORTB kesmesini de destekleyecek hale getirdik. Şimdi gelelim main fonksiyonunun bulunduğu main.c’ye 🙂

Gördüğünüz gibi gerekli pin ayarlamalarını yaptıktan sonra, kesmeyi aşağıdaki gibi aktive ettik:

Ardından, yazdığımız kütüphaneye eklediğimiz callback registration (fonksiyon bildirimi kaydı) mekanizmasını kullanarak kesme geldiğinde, haberdar olmak için initialize_keypad_event fonksiyonunu implement ettik. Buna göre PORTB’nin 4-7 arası pinlerinde gerilimsel bir değişme olduğunda haberdar olacağız. Yükselen kenarı ilk konfigurasyon olarak almamızın sebebi ise, keypad satır pinlerimizi donanımsal olarak pull-down’a yani lojik sıfıra çekmiş olmamız. Pull-down ne demek bilmiyorsanız buradan öğrenebilirsiniz.

Bu durumda kodumuz tamamen kesme ile çalışacak şekilde tamamlanmış oldu ve başarıyla da çalışıyor. Ancak yine içimize sinmeyen bazı noktalar var. Bunlardan birincisi şu aşağıdaki satır.

Bu satırın amacı, B portunun ilk 3 pinini 0 değerine çekmek ama bunu yaparken diğerlerini değiştirmemek. Aslında bu satır daha anlaşılır şekilde aşağıdaki gibi de implement edilebilirdi.

Peki neden yukarıdaki gibi yazmadık? Çok daha anlaşılır olurdu oysa ki. İşte burada cevap “performans”. Tek satır olan kod eğer diğerinden daha kısa sürede işletiliyorsa, o zaman zaman kaybetmemek için anlaşılırlıktan feda edilebilir, ki burada yapılan da o 🙂 Ancak bu durumda bile anlaşılırlığı kaybetmemek mümkün. Nasıl mı? Comment yani yorum satırları ekleyerek. Bu vesileyle bir kez daha yorum satırlarının ne denli önemli olduğuna değinmekte fayda var. Kodu iyileştirmek için aklımda bazı hinlikler var ama önce kodun bellek kullanımına bakalım.

Yukarıdaki kodun bellek kullanımı aşağıdaki gibi:

Şimdi iyileştirme yaptığımız main.c dosyamıza bakalım.

Yapılan değişiklikleri kısaca sıralarsak:

  • Register erişimi için değişken kullanımını ortadan kaldırdık. Ve bu iş için hiç değişken kullanmadık!
  • Bellek bayram etti (mi acaba, göreceğiz 🙂 ama etti)
  • Kodun anlaşılabilirliğini çeşitli define ve yorum satırları ile boost ettik.
  • Doxygen stili dökümantasyon yaparak (hepsini yapmasak da 😉 ) o işi de kodun üzerinde aradan çıkardık.

Ve güncellenmiş main kodumuzun olduğu uygulamanın bellek kullanımı:

 

Gördüğünüz üzere program alanı kullanımını 612’den 440 worde (1 word=2 byte) düşürdük. Ayrıca veri alanı kullanımını da 37 byte’dan 29 byte’a düşürdük 🙂 İşte bu elle tutulur bir optimizasyon.

Keypad’i de tertemiz, kesme kullanarak sürmüş olduk. Arada PORTB değişme kesmesi nasıl kullanılır onu da hallettik. Sayısız ibretlere de değindiğimizden bu günlük bu kadar diyoruz 🙂

 

Yazıları beğendiyseniz, faydalanabilecek tanıdıklarınızla paylaşmayı unutmayınız.

Önceki Sayfa   Sonraki Sayfa

PIC Programlama – 6 – Kesmeler (Interrupts)

Önceki yazımızda PIC16F84A’nın pinlerini kontrol etmeyi işlemiştik. Elbette ki buraya kadarki bilgilerle çok sayıda uygulama yapmak mümkün ancak bu yazı dizisi altyapı oluşturma amacı taşıdığından şimdilik farklı uygulamaları geleceğe bırakarak altyapı çalışmalarına devam diyoruz. Mikrokontrolör’ün giriş çıkış arayüzlerini detaylıca inceledikten sonra sıra geldi kesmelere. Önce genel manada kesmelerin ne olduğuna değinip, ardından da port ve pin kesmeleri ile devam edeceğiz. Mikrokontrolör’ün çevresellerinden bazıları farklı tipte kesmeler destekler. Örneğin zamanlayıcı kesmelerine, zamanlayıcı modülünde değineceğiz. Hazır port/pin kontrolünü görmüşken bu yazıda port/pin kesmelerine değineceğiz.

Kesme nedir demeden önce, mikrokontrolör ve CPU arasındaki farkları hatırlayalım. CPU (işlemci, merkezi işlem birimi) mikrokontrolör içerisindeki bir modüldür. Mikrokontrolör içinde, CPU’ya ek olarak bellek ve çevreseller (perihperal) bulunur. CPU içerisinde genelde aritmetik/lojik işlemleri yapan bir modül (ALU) bulunur. Hemen PIC16F84A’nın yapısını inceleyelim. Bu blog diyagram PIC16F84A’nın veri kağıdından alındı.

pic16f84a_block_diag

Diyagramda ALU’ya dikkat ediniz. Onu CPU olarak düşünebilirsiniz. ALU’dan W reg’e giden yolda sağa doğru bir kanal olduğunu göreceksiniz. O kanal I/O portlarına, TMR (timer, zamanlayıcı) modüllerine ve daha nice yerlere gidiyor. Bu kaçak kanal, olası bir kesme sinyalinin ALU’ya müdahale edebildiğinin bir kanıtı. Bu yazının sonunda da resmin sağ alt kısmında yer alan RB0/INT’in ne olduğu anlatılacak.

Kesme Nedir?

Kesmeleri tanımlamak uygulamada göstermekten zor bir iştir ama şöyle bir tanım yapmak mümkün: Kesme (interrupt), CPU’nun dışındaki bir dış etken tarafından oluşturulan ve oluştuğunda CPU’nun yaptığı işi durdurup,  ayrı bir özel kod parçacığının işletilmesini (yürütülmesini) sağlayan elektriksel bir işarettir. Kesme  oluştuğunda işletilen bu koda genelde kesme servis rutini (interrupt service routine, ISR) denir. Kesme oluştuğu anda, ISR’ye gitmeden önce, CPU program işletiminde kaldığı yeri kaydeder. ISR’nin işletimi bittiği anda da CPU bu kayıtlı noktadan yani kaldığı yerden programı işletmeye devam eder.

Kesme asenkron gerçekleşen bir olaydır, yani ne zaman gerçekleşeceğini CPU önceden bilmez. Gündelik hayattan  buna benzer bir süreç örneği verecek olursak; kapı zilinin ya da telefonun çalması durumlarını örnek verebiliriz. Diyelim yemek yapıyorsunuz, zil çaldı; yemek yapmayı bırakıp kapıyı açıp yemek yapmaya devam ederiz. Zil çalınca, yahu dur sırası mıydı demeyiz. Ya da sürekli kapıyı kontrol etmeden zilin ne zaman çalacağını bilemeyiz. Bununla birlikte, kapının çalışını duymazdan gelmeniz bir seçenektir tabi ki. Aynı şeyler kesmeler için de geçerli 🙂

Kesmeler, gömülü sistemlerde çok çok önemli bir yere sahiptir. Kesmeler’in düzgün kullanımı ile tasarlanmış bir sistem daha verimli, daha duyarlı (responsive) ve daha anlaşılır şekilde tasarlanabilir. Kesmeler düzgün kullanılmazsa da “yaa böyle olmaması lazımdı ama, çok saçma, böyle çalışmaması lazımdı ama” sözleri havada uçuşur, işin içinden çıkılmaz. Halbuki dostlar, her şey deterministik 🙂 Çok güçlü ama biraz da tehlikeli olduklarından bazı yazılımcılar kesmelerden korkar ama gömülü sistem programcısı kesmeden korkmamalı, kesmelerle samimi olmalı, onları son derece işeyarar ve güçlü bir arkadaş olarak görmelidir. Zira, etrafınızda gördüğünüz gömülü sistemlerden en az bir kesme kullanmayanı bulmak sıradışı bir olaydır. Sözün özü, kesme candır.

Farklı mikrokontrolör mimarilerinde kesmeler farklı şekillerde ele alınır. Şimdi PIC’ten başlayalım. Yine ibretlerle dolu bir uygulama yapacağız.

Switch ve Led Uygulaması

Bir önceki yazımıza yaptığımız uygulamada, while döngüsünün içerisinde switch’in durumunu sürekli kontrol etmiştik. Buna polling deniyor ve ekstrem derecede verimsiz bir yöntemdir bu. Bunun yerine kesme kullansaydık, sadece anahtarın durumu değiştiğinde ledlerin durumuna müdahale edebileceğimiz bir senaryo oluşacaktı. Bu da ileride özellikle güç tüketimi anlamında da bize avantajlar sağlayacaktı. Haydi sağlasın madem. Uygulamayı bu defa kesme kullanarak yapacağız. Yine switch bir konumda iken kırmızı yanar sarı söner durumda olacak, switch diğer konumdayken sarı yanar kırmızı söner durumda olacak. Yine bu uygulamada da kod yazımımızı bir kaç adım öteye taşıyacağız ancak önce kaldığımız yerden devam edelim 🙂

Kodun çalıştırılacağı devre bir önceki yazıdakilerle aynı. Yani çıktı aşağıdaki gibi olacak.

switch2

switch1

Şimdi gelelim fasülyenin faydalarına. Kesmeyi nasıl kullanacağımızı nereden bildik? PIC16F84A’nın veri kağıdının 6.8 Interrupts başlıklı kısmında hangi registerları nasıl konfigüre etmemiz gerektiği açıkça yazıyor. Bildiğiniz gibi bu yazı dizisinde kendi yazdıklarımızdan başka bir kütüphane kullanmıyoruz. Bunun amacı her şeyi temelinden anlamaktı. Doğal olarak kullanacağımız yeni registerlar için de tanımlamaları yapıyoruz. Aşağıda OPTION ve INTCON kütüklerinin (registerlarının) adres tanımlarını ve yardımcı etiketleri görebilirsiniz. Buna ek olarak bir de BIT_TOGGLE makrosu tanımladık. Bu makronun amacı bir biti birse sıfır, sıfırsa bir yapmak. Bunu ileride kullanacağız.

Registerların modellenmesini ise aşağıdaki gibi yaptık.

Bu tanımlamaları datasheetten bakıp yaptıktan sonra INTCON ve OPTION kütüklerini gösterecek işaretçileri aşağıdaki gibi tanımladık.

Ardından gelelim ana fonksiyon(main) içinde yaptığımız ek konfigürasyonlara.

Öncelikle kesme bayrağının temiz olduğundan emin olmak için onu temizledik. Ardından INTE ile RB0/INT kesmesine izin verdik. Ardından da global kesme aktivasyonu için GIE bitini etkinleştirdik. GIE ve INTE bitlerinin ikisi de 1 değerini almadan kesme gerçekleşemez. Bunu da verikağıdındaki aşağıdaki şemadan anlayabiliyoruz.

int_logic

Konfigürasyonda kesmeyi de yükselen kenarda olacak şekilde ayarladık. Buna göre anahtar sıfırdan bire çekildiğinde yükselen kenar kesmesi gelecek. Ama burada bir sorun var, bize hem yükselen kenar hem düşen kenarda kesme gerekiyor. Çünkü anahtarın konumu sıfırdan bire değiştiğinde de birden sıfıra değiştiğinde de kesme almak ve LED’lerin durumunu değiştirmek istiyoruz. PIC’te böyle doğrudan böyle bir seçenek yok; ya yükselen kenar kesmesi seçilebiliyor ya da düşen kenar. Burada beyin bedava diyoruz ve kafayı çalıştırarak soruna çözüm sunuyoruz. Önce yükselen kenar kesmesini aktive ettiğimizi düşünelim, bu durumda ilk gelen yükselen kenar kesmesinin hemen ardından düşen kenar kesmesini aktive edersek, switchin durumu tekrar değiştiğinde kesme alabiliriz. Yani yükselen/düşen kenar konfigürasyonunu, kesme oluştuğunda toggle etmemiz gerekiyor. Kesme tamamlandığında kesme bayrağını yani INTF bitini de sıfıra çekip temizlemek gerekiyor. Kodda da bunları yaptık. PIC’te kesme fonksiyonu fonksiyon adının başına gelen “interrupt” kelimesi ile derleyiciye anlatılır. Buna göre kesme fonksiyonumuz aşağıdaki gibi oluyor.

PIC’te tüm kesmeler tek bir fonksiyon içine düşer, bu da interrupt ön ekli fonksiyondur. Bu sebeple bu fonksiyonun adını global_isr_handler koyduk. Daha sonra gelen kesmenin RB0/INT olup olmadığını kontrol ettik ve eğer o ise switch değerlendirmesi işlemini yapıp yükselen/düşen kenar seçimini güncelleştirip bayrağı temizledik. INTEDG’nin toggle edilmesi ile hem düşen hem yükselen kenardaki değişimleri güzel bir oyun ile algılayabilir duruma geldik. Çiçek oldu.

Şimdi gelelim bu kodun sorunlarına. Birincisi register tanımları giderek kabarıyor ve onların ayrı bir *.h dosyası içinde yer alması gerekiyor artık. İkincisi, ilk durumda switch’in durumuna bakmaksızın kesme konfigurasyonu yaptığımız için ilk açılışta ledlerin ikisi de yanmıyor. Üçüncüsü de şu, madem her kesme aynı fonksiyona düşecek, ileride kod daha da büyüdüğünde kesme fonksiyonu sürekli değiştirilecek ve eğer birisi orada yanlış bayrakları temizlerse ya da kurarsa, önceden yazılmış kısımlar da bundan etkilenecek ve kodun her yeri bozulacak. Bu çok büyük bir risk. Burada SDK tasarımı devreye giriyor. Akıllıca bir seçenek, kesme fonksiyonunu kullanıcı programından ayırmak olacaktır. Burada kesme bayraklarının yönetiminin de otomatik yapılması da isabetli olacaktır. Bu olay gerçekten bir ileri tasarım öğesidir ancak adım adım oraya gideceğiz. Kodun bir diğer önemli sorunu da kütüphane kullanmayacağız inadımızla kullanmadığımız stdint kütüphanesidir. “unsigned char” gibi boyutu “hayırlısı” olan bir veri tipi kullanmak yerine “uint8_t” ile modelleme yapmamız daha temiz olurdu. Nitekim amacımız PIC kütüphanelerini kullanmadan, daha iyi şekilde temelden olayı yazarak olayın dinamiklerini anlamaktı. Bu da stdint’e geçişimizi engellemiyor. Bir diğer eksik nokta da register tanımlarıyla ilgili. Önceki yazıda, sebeplerini sıraladığımız üzere hem bit hem de byte erişimi sağlayabilmek için kütük modellemelerinde union kullanmıştık. Yeni modellediğimiz kütüklerde de bunu uygulamakta fayda var. Bunlara göre projemizi yine düzenleyelim 🙂

Switch ve LED Uygulaması v2

Bu projede 3 adet dosyamız olacak. Birincisi pic16f84a kütüphanemizin header dosyası. Bu dosya (pic16f84a_lib.h) aşağıdaki gibidir.

Yine pic16f84a kütüphanemizin kaynak dosyası (pic16f84a_lib.c) aşağıdaki gibidir.

Ve nihayetinde ana programımız (main.c) aşağıdaki gibidir:

Kodda, vadettiğimiz tüm değişiklikleri yapmış olduk. En en en en en önemli yer, kullanıcının yani main dosyasının kesme fonksiyonunu hiç bir şekilde görmeden kesme işlemlerini haber alması. Bunun için SDK geliştiricisi olarak çıkardığımız fonksiyonu kullancı aşağıdaki şekilde çağırmış oldu:

Bu fonksiyonun implementasyonu da tam anlamıyla ibretlik. Maksimum 5 kaynak RB0/INT kesmesine abone olabiliyor. Bu genişletilebilirlik açısından çok çok önemli. Biz switch kodunu yarın öbürgün “switch geldiğinde bir de düdük çalsın” isteriyle genişletmek istediğimizde düdüğü çalacak fonksiyonu da register edecersek o düdük çalınacaktır ve bizim önceki işlerimiz asla bundan etkilenmeyecektir. Bu olaya lütfen çok dikkat ediniz. Listeye kaydolmuş fonksiyonların yani callback’lerin çağırıldığı satır ise aşağıdaki satır.

Şimdilik bu kadar 🙂 Bu gün epey fantastik mevzulara değindik. PIC deyip geçmemek gerek, bu basit alet için bile ileri düzey tasarım yapmak mümkün. Nitekim daha da ilerleyeceğiz. Yazılar devam edecek.

Yazıları beğendiyseniz, faydalanabilecek tanıdıklarınızla paylaşmayı unutmayınız.

Önceki Sayfa   Sonraki Sayfa

 

PIC Programlama – 5 – Giriş Çıkış (I/O, Input/Output) İşlemleri -2

Önceki yazımızın başlığını giriş çıkış işlemleri olarak koymuştuk ama açıkça görülebildiği üzere, yalnızca çıkış işlemleri üzerinde durduk 🙂 Bu yazıda, vadedileni tamamlayarak PIC mikrokontrolörlerinde giriş işlemlerini nasıl yaparız ona değineceğiz.

PIC programlama yazı dizisinin asıl amaç seti, gömülü sistemlerin programlanması hususunda temel oluşturup, sürecin iç dinamiklerini de anlatmak olduğundan yine bir kütüphane kullanmadan devam edeceğiz. Giriş işlemleri üzerinden gidebilmemiz için, mikrokontrolörün pinlerine bağlayacağımız elemanların, pinde oluşturacağı gerilimsel seviyeyi okumamız gerekir. Buna göre buton ve switch uygulamaları, mevzuyu anlatmakta kullanılabilecek iki güzel ve basit eleman oluyor. Butona daha sonra da değineceğiz. Şimdi mevzuyu anlatmak için switch üzerinden gidelim. Butonun basit kullanımı switch’den çok farklı değil ama bouncing gibi problemlere de önlem alacağımız için butonu timer’dan sonra anlatacağız. Neyse 🙂

Öncelikle daha kolay olandan başlayalım. Switch yani anahtar elemanının kullanımına değinelim. Anahtar’ın adı üzerinde, elektriksel akımı açıp kapatabilen devre elemanı. Buna göre, iki durumlu bir anahtar için; anahtar bir konumdayken açık devre, diğer konumdayken kısa devre davranışı gösterecektir.

Wikipedia sayfasında daha fazla bilgi bulabilir, çeşitli switch’lerin fotoğraflarını da görebilirsiniz.

Şimdi gelelim bu anahtarın PIC16 ile kullanılmasına. Çok basit bir uygulama olacak olsa da, anahtar ile PIC’i aracı yaparak LED sürelim. LED’i normal şartlar altında PIC’siz de sürebiliriz ama mevzunun basit yoldan anlaşılması için böyle anlamsız bir uygulama üzerinden gitmek mantıklı olacak diye düşünüyorum. Bir sarı ve bir kırmızı olmak üzere iki LED, bir anahtar ve bir de PIC olan bir devre düşünelim (Direnc, kondansatör gibi elemanları saymıyorum). Birazcık da olsa farklılık yaratmak için anahtar bir konumda iken sarı LED’i yakıp kırmızı LED’i söndüreceğiz. Diğer durumda ise kırmızı ledi yakıp sarı ledi söndüreceğiz. Tabi bu basit işi yaparken bile, kodu ibretlik şekilde yazacağız. Haydi işe koyulalım 🙂

Kodu açıklamadan önce, bir define hinliği ile, daha şeker hale getirelim diyorum. Misal aşağıdaki gibi yapsak, kodun yaptığı iş daha iyi anlaşılacak sanki.

Tabi kodu yazdık öyle ama kısa bir açıklama da yapacağız. Ayrıca birinci kod ile ikincisi arasındaki farkları da ele almakta fayda var. Ancak bundan önce kodun Proteus ISIS ortamındaki çıktılarını bir paylaşalım.

switch1 switch2

 

Gördüğünüz gibi kodumuz çiçek gibi çalışıyor. Şimdi gelelim kod hakkında konuşma faslına. Malumunuz bu kod çok farklı şekillerde yazılabilirdi ama yine özgün bir iş yapalım istedik. Bu sebeple gittik her bir pini struct bitfield olarak tanımladık. Bu sayede port üzerindeki her pine tek tek, bir değişken üzerinden erişmek mümkün oldu. Tanımladığımız union ile, registerlara hem pin bazlı (tek tek) hem de port bazlı (topluca) erişim imkanı sağladık. Bu işin ekmeğini de kodu yazarken yedik; kodumuz hem çok anlaşılır hem de çok genişletilebilir oldu.

İkinci kodda yaptığımız trick ise, kodun okunurluğunu çok artırdı. Switch ve ledlerin hangi pinlere bağlı olduğunu define ile tanımlayarak, kodun okunurluğunu epeyce artırmış olduk. Bu sayede ikinci kodu okuyan birisi artık kodun fiziksel olarak ne iş yaptığını kolayca anlayabilir durumu geldi. Her kod bir hikaye anlatır malum. Birinci kod hikayeyi mikrokontrolörün perspektifinden anlatırken, ikinci kod hikayeyi insan gözü perspektifinden anlatmış oldu.

Bu yazı bu kadar olsun 🙂 Bir başka yazıda yine devam edeceğiz. Yazıları beğendiyseniz, faydalanabilecek tanıdıklarınızla paylaşmayı unutmayınız.

Önceki Sayfa   Sonraki Sayfa

PIC Programlama – 4 – Giriş Çıkış (I/O, Input/Output) İşlemleri

Merhabalar, bir önceki yazımızda  PIC için yazdığımız bir merhaba dünya kodunun içini dışını hallaç pamuğuna çevirmiş ve sayısız ibretlerle keyiflenmiştik. Bu yazımızda da PIC16’nın bir pinini nasıl giriş yaparız, nasıl çıkış yaparız, bir giriş pininden nasıl değer okuruz, bir çıkış pinine nasıl değer yazarız gibi işlemlere değineceğiz 🙂 Sözün özü, hayatı bir dizi gerilimsel işaretle mıncıklayacağız 🙂

Sosyal Mesaj
Malumunuz bu yazım öncekilere nazaran epey gecikti. Ciddi bir ara vermiş oldum ancak gerçekten yoğun bir dönemi geride bırakmış olduğumu belirtip, affınıza sığınarak yazı dizilerine devam ediyorum.

Şimdi, yine bir kütüphane kullanmadan (ya da sadece kendi yazdığımız kütüphaneleri kullanarak) bir karaşimşek kodu yazalım =) Karaşimşek kodu meşhurdur dostlar =) es geçmeyelim!

Tıpkı PIC mikrodentleyicilerinin bizler için “araç” olması gibi, karaşimşek kodu da anlatmak istediklerimizi anlatmak üzere kullanacağımız bir araç olacak. Amacımız ibretleri birer birer kapmak. Sözü sakız edip uzatmadan hemen kodumuzu yazalım.