Seri porta (RS232) ya da LCD ekrana bir şeyler yazmak istiyorum ama printf fonksiyonu çok fazla program hafızası harcıyor, daha kolay bir yöntem var mı?

Evet var, printf fonksiyonunun pek çok avantajı vardır ancak program hafızasında oldukça önemli bir yer kaplar. Aşağıda verilen kod Atmega8 için derlendiğinde yaklaşık olarak sadece 200 byte tutar.

#include <avr/io.h>
#include <avr/pgmspace.h>

int UartPutChar(char c){ //RS232 ye bir karakter gonderir
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
return 0;
}

void UartPutString_P(const char *progmem_s){
char c;
while ( (c = pgm_read_byte(progmem_s++)) ) {
UartPutChar(c);
}
}

void mcu_init(void){
// USART kurulumu @ 8Mhz
// 8 Data, 1 Stop, No Parity
// Baud rate: 4800
UCSRA=0x00;
UCSRB=0x18;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x67;
}

//Ana Program Fonksiyonu
int main(void){
mcu_init(); //islemciyi hazirla
while(1)UartPutString_P(PSTR("AVR Ogreniyorum!\r\n"));
}

PSTR makrosu tekstin RAM yerine program hafızasına yazılmasını sağlayarak değişkenler için ayrılmış olan kıymetli ve kısıtlı RAM alanının işgal edilmesini önler (bu konunun detayları için AVRLibC el kitabındaki "program space" konusuna bakınız.). LCD için de izlenmesi gereken yol işlemcinin USART modülüne karakter gönderen UartPutChar isimli fonksiyonun yerine LCD'ye bir karakter gönderen fonksiyonun kullanılmasıdır.

Bu _BV() meselesi bana karışık geldi, neden doğrudan PORTB=0xFF şeklinde bir atama yapmıyoruz?

Tabii ki PORTB=255; gibi bir ifade geçerli ancak bunun pek çok sakıncaları var. B portu üzerinde farklı pinlere bağlı olarak çalışan elemanlar olabilir. Örneğin, PB0 a bir buzzer ve PB7 ye de bir led bağlı olsun, PORTB=255 gibi bir ifade PORTB nin tüm bitlerini 1 yapacağıdan buzzer da devreye girecek ve LED de yanacaktır. Oysa biz buzzer a dokunmak istemiyorduk.

PORTB=1 gibi bir komutun anlamı şudur: PB0 1 olsun diğer tğm bacaklar 0. Bu genellikle istenen bir durum değildir, biz tüm portun değerini değil sadece belli bir bitin değerini değiştirmek isteriz.

PORTB &= ~_BV(7); ifadesi karışık gibi gelebilir ama C diline hakim olan biri için karışık bir durum yok aslında. Buradaki tüm özellikleri inceleyelim:

  • 1) C dilinde x+=1 ifadesi x in kendisine bir ekle demektir ve basic'deki x=x+1 ifadesi ile aynıdır. Benzer şekilde x*=2 ise basic'deki x=x*2 ifadesine denk gelir. Bizim ifademizdeki PORTB &= a; PORTB değişkeninin kendisini a değeri ile AND'le demektir. PORTB= PORTB & a; şeklinde de düşünülebilir.
  • 2) _BV (bit value) bir makrodur ve _BV(3) ifadesi (1<<3) ifadesi ile aynıdır, 1 sayısını 3 kez sola kaydır (shift) demektir bunun da sonucu 0000 1000 dır. Bunu gözümüzde canlandırmak zor değil. _BV(0) ise (1<<0) ifadesine eşittir ve 1 sayısını 0 (sıfır) kez sola kadır demektir, yani 0000 0001 e eşittir. Sıfır kez sola kaydırmak, olduğu yerde bırakmaktır.
  • 3) ~ değil ifadesi (bitwise not) önüne geldiği ifadenin tüm bitlerini ters çevirir. ~ 0000 0001 ifadesinin sonucu 1111 1110 dır.

Şimdi bu bilgi ışığında PORTB &= ~_BV(0); ifadesini birlikte inceleyelim. _BV(0) ifadesinin binary 0000 0001 e eşit olduğunu biliyoruz, bunun terslenmiş halinin de 1111 1110 olduğunu öğrendik o halde ifademizi şu şekilde yazabiliriz:

PORTB&=0b11111110; (baştaki 0b binary sayıların önüne konur, aksi halde derleyici 11 (onbir) ve 0b11 (üç) sayılarını ayırt edemez) Bu noktadan sonrasını anlamak için ise lisede mantık konusunda öğretilenleri hatırlamak yeterli.

1 VE 1= 1
1 VE 0= 0
0 VE 1= 0
0 VE 0= 0

AND operatörüne tabi tutulan iki değerden biri 1 ise sonuç değişmiyor ama 0 ise diğer değer ne olursa olsun sonuç sıfır oluyor. Diyelimki PORTB bu işlemden önce 00110011 değerindeydi, PORTB&=0b11111110; işleminden sonra sadece sıfıncı bitte değişiklik olacak diğer bitler işlemden etkilenmeyecektir:

00110011
11111110
__________ VE
00110010

Görüldüğü gibi PB0 bire eşitken sıfır oldu diğer bit olduğu gibi kaldı. Eğer _BV makrosunu kullanmak istemiyorum derseniz aynı ifadeyi doğrudan yazabilirsiniz:

PORTb&=0b11111110;

Kesmeler (interrupt) tam olarak nedir? Ne işe yararlar? Nelere dikkat ekmek gerekir?

Kesmeler işlemcinin belli bir uyarıcıya bağlı olarak ana programdan kısa bir süre ayrılıp başka işler yapmasına imkan veren donanım özlliğidir. Kullanılan işlemciye göre kesme özellikleri farklılıklar gösterse de temelde çoğu işlemcinin desteklediği kesme tiplerinden bazıları şunlardır: Harici kesmeler, Timer taşmaları, ADC, USART, SPI, TWI gibi hardware modülleri ile ilgili kesmeler, vb.

ISR (Kesme_Vektörü){
//buraya sizin kodunuz
}

Ana program Döngüsü{
...
...
}

İşlemcinizin hangi kesme vektörlerini desteklediği AVRLibC el kitabının "Interrupts" başlıklı kısmında tablo halinde bulabilirsiniz. Sitenin "Projeler" kısmında bu konuyla ilgili açıklayıcı bir örnek bulunmaktadır, bu örneği incelediğinizde TIMER1_OFV_vect isimli kesme vektörünün kullanıldığını görürsünüz, bu ifade işlemciye TIMER1 taştığında, [yani TIMER1 sayacı yukarı doğru sayarken taşıyabileceği en büyük değer olan 65535'den 0(sıfır) a geçerken bir taşma oluştuğunda - TIMER1 overflow bayrağı 1 olduğunda] ana programdan ayrılıp ISR fonksiyonuna dallanmasını söyler. Bu, evde DVD izlerken telefonun çalması, sizin DVD'yi beklemeye alıp telefona bakmanız, görüşmeniz bittikten sonra tekrar filme dönmenize benzer.

Kesmeler işlemciye sahte de olsa bir multitasking yeteneği kazandırır, örneğin bilgisayarınız şu anda pek çok işle uğraşıken diğer taraftan sürekli olarak sizin fareyi oynatıp oynatmadığınızı, klavyede bir tuşa basıp basmadığınızı kontrol etseydi hem çok zorlanır hem de bazılarını kaçırırdı. Evde DVD izleyen adam örneğine dönersek, telefonun zili olmasaydı ve bizi arayan olup olmadığını anlamak için ikide bir telefonu kaldırıp kontrol etmek zorunda kalsaydık, hayat çok zor olurdu.

Kesmelerle ilgili olarak dikkat etmeniz gereken bir kaç kural var: 1) Kesme fonksiyonlarının içinde uzun süren işler yapmaktan kaçının esas olan programın kesme oluştuğunu anlaması, bunu bir yere kaydetmesi ve kesme oluşması halinde yapılması gereken işleri ana program akışı içinde yapmasıdır. Bunu için en iyi metod bir global değişken yaratmak, kesme fonksiyonunun içinde bu değişkene bir değer atamak ve ana program içinde bu değişkenin değerine göre kesmenin oluşup oluşmadığını test ederek buna göre programı dallandırmaktır. 2) Kullanacağınız bu tür global değişkenleri mutlaka volatile olarak tanımlayın (ayrıntı için C kitabına bakın). 3) Eğer kesme oluşması sonucu yapılması gereken işlem uzunsa ya da bu esnada başka bir kesme oluşma ihtimali varsa, kesme fonksiyona girildiği anda kesmeler pasif hale getirilmeli her şey bittikten sonra tekrak aktifleştirilmelidir. Kesme içinde kesme oluşması halinde işlemci doğru çalışamaz. Bunları sembolik olarak şu şekilde gösterebiliriz.

//global degişken
volatile int kesmeolustu=0;

ISR(Kesme_Vektörü){
kesmeolustu=1;
}

int main(){
sei();
While(1){
if(kesmeolustu)yapilacakisler();
}
}

void yapilacakisler(void){
cli(); //kesmeler pasif
//uzun zaman alan işler
//uzun zaman alan işler
//uzun zaman alan işler
//uzun zaman alan işler
kesmeolustu=0; //değişkeni tekrar sıfırla
sei(); //kesmeleri aktif
}