AVR C Kütüphanesi

AVRLibc, GCC için geliştirilmiş Atmel AVR kütüphanesidir. Bu kütüphane ile ilgili olarak baş vurulacak ilk kaynak Download kısmında bulunan AVR Libc El Kitabıdır (Maalesef bu el kitabının şimdilik Türkçe sürümü yok). Şimdi yanıp sönen Led programımızda neler olup bittiğine daha yakından bakalım.

Program Akışı


#include <avr/io.h>
#include <util/delay.h>
#define sure 500

void delay_ms(uint16_t ms) {
for(uint16_t x=0;x<ms;x++){_delay_ms(1); }
}

void mcu_init(void){ //Giris/Cikis ayarlari
PORTB=0x00;
DDRB=0x01; }

//Ana Program Fonksiyonu
int main(void){
mcu_init(); //islemciyi hazirla
while(1){ //sonsuz dongu
delay_ms(sure);
PORTB&=~_BV(0);
delay_ms(sure);
PORTB|=_BV(0); }
}

Standard C direktifi olan #include komutuyla eklenen <avr/io.h> hedef mikro işlemcinin yazmaçları (register) ile ilgili tanımlamaları içerir. İkinci dosya olan <util/delay.h> ise void _delay_ms (double ms) fonksiyonunu içerir, bu fonksiyon program akışında milisaniye cinsinden duraklama yaratmak için kullanılır. AVR Libc el kitabına baktığınızda, bu fonksiyon ile elde edilebilecek azami bekleme süresi 262.14 / F_CPU (Mhz) şeklinde hesaplanır. F_CPU mikro işlemcinin megahertz cinsinden çalışma frekansı anlamına gelir, bu örnekte F_CPU 1 Mhz olduğundan _delay_ms fonksiyonu ile elde edilebilecek en uzun bekleme süresi 262 ms, yani yaklaşık saniyenin dörtte biridir. Bu yüzden _delay_ms fonksiyonunu kullanarak daha uzun süreler için bekleme yaratabileceğimiz kendi üst fonksiyonumuzu yaratmamız gerekecek.

void delay_ms(uint16_t ms) fonksiyonu işte bu işi yapar, AVR Libc kütüphanesi ile gelen _delay_ms fonksiyonunu kullanarak daha uzun bekleme süreleri elde etmemizi sağlar. uint16_t temel veri tiplerinden biridir ve adından anlaşılacağı üzere "16 bit Unsigned Integer" - işaretsiz 16 bitlik integer - tipi değişken tanımlamak için kullanılır. Bu fonksiyon bir milisaniyelik bekleme yaratan _delay_ms(1) fonksiyonunu ms kere çağırarak 16 bitlik işaretsiz bir değişkenin alabileceği en yüksek değer olan 65.535 milisaniyeye kadar bekleme yaratabilir.

void mcu_init (void) fonksiyonu mikro işlemcinin ilk ayarlarının yapıldığı fonksiyon olarak tanımlanmıştır, programı basit tutmak için sadece ledin bağlanacağı portun ayarlarını yapmak yeterli olacaktır. PORTB aslında 0x18 sayılı yazmaca yapılmış olan bir tanımlamadır, bu tanımlama mikro işlemciye ait io.h dosyasında yapılmıştır. Mikro işlemcinin portlarını giriş çıkış olarak ayarlamak için DDR tanımı kullanılır; DDR ilgili portun yönünü belirtmek için kullanılır. DDR'a bir porttu giriş yapmak için 0; çıkış yapmak için 1 atamak yeterlidir. Örneğin, DDRC=0x00; C portunun tüm pinlerini giriş yapar. (Buradaki 0x00, sıfırın hexadecimal gösterimidir, iyi bir programcılık tekniği açısından 0x00 şeklinde yazmak tercih edilir.) DDRB=0x0F; ise PB0, PB1, PB2 ve PB3 pinlerini çıkış, PB7,PB6,PB5 ve PB4 pinlerini ise giriş yapar. (Onaltı tabanına göre 0x0F = On tabanına göre 15 = İki tabanına göre = 0b00001111)

PB7
PB6
PB5
PB4
PB3
PB2
PB1
PB0
0
0
0
0
1
1
1
1

Bu noktada yeni başlayanların karıştırdığı bir kuralı hatırlatmak isterim. Bir portun tamamı yada bazı pinleri DDR komutuyla çıkış olarak ayarlanmışsa, PORT yazmacına verdiğiniz değer portun durumunu belirleyecektir, yani PORT'un herhangi bir pinini 1 (high) yaparsanız, o portun çıkışı 5V olur (örneğin bağlı bir led varsa led yanar); 0 (low) yaparsanız çıkış 0V olur (led söner). Diğer taraftan eğer DDR komutuyla bir portun tamamı ya da bazı pinleri giriş olarak ayarlandıysa, PORT yazmacına yazacağınız değer dahili pull-up dirençlerinin aktif hale getirilip getirilmeyeceğini belirler.

DDxn
PORTxn
I/O
Pull-Up
Durum
0
0
Input
No
Tri-State (kararsız hal)
0
1
Input
Yes
Boşta iken High
1
0
Output
No
Çıkış Low
1
1
Output
No
Çıkış High

Bu bilgiler ışığında tekrar mcu_init fonksiyonuna dönelim; burada iki komut var; DDRB=0x01komutu işlemcinin 14 numaralı bacağında bulunan PB0'ı çıkış, geri kalanları giriş yapar. PORTB=0x00 komutu ise PORTB nin tamamının (dolayısıyla PB0'ın da) çıkışını low yapar, program ilk çalıştığında led sönük olacaktır (diğer pinler şu anda bizi ilgilendirmiyor).

int main (void) fonksiyonu bütün C programlarındaki ana fonksiyondur, program buradan çalışmaya başlar. Program çalışırken ilk önce main fonksiyonu çağrılacaktır. Main fonksiyonunun ilk satırında mcu_init fonksiyonu çağrılacak ve daha sonra While(1) ifadesiyle program sonsuz döngüye girecektir (parantez içindeki koşul her zaman 1 yani true'dur). Döngü içindeki ilk komut bizim tanımladığımız delay_ms fonksiyonudur ve 500 parametresiyle çalıştığından 500 milisaniye (yarım saniye) beklemeye sebep olacaktır.

Tek Biti Değiştirmek

PORTB &= ~_BV(0); komutu üzerinde biraz durmamız gerekli. Bir portun tek bir pinini değiştirmek için PORTB=1; gibi komut yazamayız, çünkü 1 sayısı binary 0b00000001'e eşittir ve PB0 pini 1 ve diğer tüm pinler 0 olacaktır. Oysa bize lazım olan bir portun bizim istediğimiz pinlerini değiştirirken, diğer pinleri oldukları gibi bırakmaktır. Bunu yapmak için mantıksal işlemleri kullanırız. PORTB'nin sadece sıfırıncı bitini sıfır (low) yapmak için şunu yapabiliriz: PORTB &= 0b11111110 . Bu komut PORTB'nin mevcut değeri ile eşitliğin sağ tarafındaki değeri AND işlemine tabi tutacak ve sonucu PORTB'ye yazacaktır. Diyelim ki PORTB'nin bu işlemden önceki değeri şöyleydi:

PB7
PB6
PB5
PB4
PB3
PB2
PB1
PB0
1
1
1
1
0
0
1
1

AND

PB7
PB6
PB5
PB4
PB3
PB2
PB1
PB0
1
1
1
1
1
1
1
0

=

PB7
PB6
PB5
PB4
PB3
PB2
PB1
PB0
1
1
1
1
0
0
1
0

Görüldüğü gibi sadece sıfırıncı bit 0 (low) oldu. Burada _BV (x) bit value) makrosu aslında (1<<x) ifadesinin yaptığı işi yapar, yani 1 değerini x kadar sola kaydırır. Bizim örneğimizde _BV(0) olduğu için 1 sayısı sıfır kez sola kayacak (yani yerinde kalacak) ve 0b00000001 sayısına eşit olacaktır. Eğer PORTB'nin en son bitini (7inci bit) sıfır (low) yapmak isteseydik o zaman PORTB &= ~_BV(7); komutunu verecektik. Bu durumda 1 sayısı 7 kez sola kaydırılacak ve 0b10000000 sayısı elde edilecek, bu terslenecek ve 0b01111111 haline gelecek, bu sayıyla PORTB'nin mevcut değeri AND işlemine tabi tutulacak ve sonuçta sadece PORTB'nin yedinci biti sıfırlanmış olacaktır.

PORTB |= _BV(0); komutu ise aynı mantıkla çalışıp bu kez OR işlemine tabi tutularak PORTB'nin sadece sıfırıncı bitini 1(high) yapar. Karışık gibi görünse de C dilinin gücünü anlamak ve yazdığınız kodun başka derleyiciler tarafından da derlenebilmesi açısından temel C ifadelerinin kullanılması daha avantajlıdır. Çok kullanılacak bu iki ifadeyi sindirerek öğrenmek ileride çok işinize yarayacaktır.

Özet

Bir A değişkeninin X'inci bitini 0(sıfır) yapmak için: A &= ~_BV(X);
Bir A değişkeninin X'inci bitini 1yapmak için: A |= _BV(X);

Proje Dosyaları

Proje Dosyaları