Her şey Doların yükselmesiyle başladı! Küçük projeler için Arduino’yu heba etmek yerine daha ucuz çözümler düşündüm. Arduino kartı yerine bu kartta kullanılan işlemci çözüm olabilirdi. ama kartta kullanılan ATmega328 de neredeyse Arduino kartlarıyla (Hatta Çin’de üretilen klonlar daha bile ucuz) aynı fiyatta. Aynı aileden olan daha küçük modellere yöneldim ben de. ATtiny85 ve en ucuz model (biraz eskimiş de olsa) ATtiny13 modeline yönlendim. Yalnız araştırdığım projelerde bizi Arduino için kullandığımız kodlardan çok farklı kodlar vardı. Kodların neden o şekilde yazıldığı şu şekilde açıklanıyordu: Arduino’nun (aslında kullanılan mikrodenetleyicinin; ATmega ) sınırlı kapasitesi. Program yazma kapasitesi ATmega için 32 kByte iken ATtiny85 için 8 kByte, ATtiny13 için ise 1 kByte. Yani Uno için yazabileceklerimizin 1/32 si! Onun için fraklı çözümlere gidilmiş.

Biz aslında Arduino’yu kullanıcı dostu olarak biliyoruz. Yani kod yazması oldukça kolay. Ama bunun sebebinin bu basit kod satırlarının arka planında C++ programının çalıştığını da biliyoruz. Yani bizim yazdığımız kodlar önce C++’a ondan da mikrodenetleyicinin bunu anlayabileceği makine dili denen bir koda dönüşüyor. Bu iki sonuç doğuruyor; Birincisi bizim yazdığımız basit kod aslında arkada başka bir program çalıştırdığından hafızda daha çok yer tutuyor. İkincisi de bu işlemler sırasında zaman kaybediyor yavaşlıyor! bu çalışmada C++ dersi vermeye çalışmayacağız, bu işi bilenlerine bırakıyoruz. ama sadece giriş çıkışlar için kullanabileceğimiz basit değişikliklerle bile hem hafızadan hem de kazanç sağlayabileceğiz.

Yine fazla uzatmadan biraz genel konuşmak gerekirse yapacaklarımız AVR tabanlı (ATmega, ATtiny’lerin ailesi) mikrodenetleyici mimarisinin getirisi. [ AVR tabanlı ne demek diyenler için : Bu mikrodenetleyici daha önce MIT tarafından oluşturulan RISC (Reduced Inroduction Set Computer: Yönergeleri azaltılmış bilgisayar setleri, yani daha basit daha spesifik kullanımlar için geliştirilmiş, daha basit) tipi mikrodenetleyicilerden. Daha sonra Nordic ( nRF24-L01 sensörünü üreten firma) firmasında çalışan iki Norveç Teknoloji Enstitüsü öğrencisinin bunu geliştirdiği (Alf-Egil Bogen  Vegard Wollan) için isminin de bu iki öğrencinin isimlerinin baş harfi eklenerek AVR olarak adlandırıldığından bahsediliyor. İşin ilginç yanı bu ne doğrulanmış, ne de yalanlanmış! Bu arada bu bilgiler çok gizli değil: wikipedia! Hazır dedikoduya girmişken şunu da ekleyelim; Nordic firmasını 1996 yılında ATMEL satın alıyor. O yüzden mikrodenetleyici isimleri ‘AT’ ile başlıyor. ama 2016 yılında da bu kez Microchip Tech Atmel’i satın alıyor. Bu kadar dedikodu yeter 🙂 ]

ATmega328 mikrodenetleyicisinin mimarisi veri kağıdında yukarıdaki gibi verilmiş. Burada asıl işi yapan ALU (Arithmetic Logic Unit). Tüm matematiksel ve mantıksal işlemler burada yapılıyor. Burada birden fazla ‘register’ görüyoruz. bunlar verilerin bir süreliğine tutulduğu sırası geldiğinde işleme alındığı kayıt yerleri. Bizi ilgilendiren giriş/çıkış modüllerindekiler. ATmega328’in bu anlamda üç (register) kayıt portu bulunuyor. bunlar bu mikrodenetleyiciye giren/çıkan bilgilerin tutulduğu yerler (gümrük gibi düşünebilirsiniz, kayıt işlemleri biten ürünler girer ya da çıkar). Bunlar PortB, PortC ve PortD. Tüm giriş/çıkışlar bu portlara bağlı.

Arduino’nun kendi sayfasından alınan yukarıdaki görüntü hangi bacağın hangi porta bağlı olduğunu gösteriyor. ATmega328 8 bitlik bir mikrodenetleyici olduğundan tüm registerların 8 bit (yani bir byte) olduğunu (0-7) hatırlayalım. buna göre Arduino’nun dijital 0-7 arası çıkışları, D portuna; 8-13 arası çıkışları B portuna, Analog girişler de C portuna bağlı. İşte giriş/çıkışlar için direk port isimlerini vererek yaptığımız bu işleme port manipulasyonu deniyor.

Port manüplasyonu yaparken üç değişken kullanılıyor. Birincisi DDR (data direction). Burada veri girişi mi çıkışı mı olduğu tanımlanıyor (Veri yönü). Bu bizim kodda yazdığımız ‘pinMode‘ fonksiyonunun karşılığı. Bu değer ‘1’ ise o portun çıkış, ‘0’ ise giriş olduğunu gösteriyor. Tüm portun 8 bit olduğunu hatırlatarak örnek verelim:

DDRD = B00010010; yazdığımızda, D portuna bağlı 4 ve 2 numaralı pinlerin çıkış, diğerlerinin giriş olduğu belirtiliyor. Arduino’nun karşılık gelen bacakları 1 ve 4 çıkış, diğerleri giriş anlamında. Baştaki B, verinin byte türünde olduğunu gösteriyor.

DDRB = B00001001; yazdığımzda da, B portuna bağlı 0 ve 3 numaralı pinlerin çıkış diğerlerinin giriş olduğu tanımlanıyor. Bu pinlerin Arduino’nun 8 ve 11 numaralı dijital pinleri olduğuna dikkat.

İkinci olarak PORT, kullanılıyor. PORT DDR’a göre değişiyor. DDR’da çıkış olarak tanımlanan (1 olan) pinler; port 1 ise ‘HIGH‘, port 0 ise ‘LOW‘ olarak belirleniyor. Yani bu durumda bizim yazdığımız ‘digitalWrite’ komutuna karşılık geliyor. Örnek?

void setup() {
DDRB = B00001001; // B portuna ait 0 ve 3 numaralı (D8 ve D11) pinler çıkış
// diğer pinler giriş
}
void loop() {
PORTB = B00000001; // B portunun 0 numaralı (D8) çıkışı “HIGH”
// B portunun 3 numaralı (D11) çıkışı “LOW”
}

Yuarıdaki kodun neyin yerine kullanıldığı mı?

void setup() {
pinMode (D8, OUTPUT);
pinMode (D11, OUTPUT);
}
void loop() {
digitalWrite (D8, HIGH);
digitalWrite (D11, LOW);
}

DDR’da giriş olarak tanımlanan (0 olan) pinler ise ; port 1 iken pullup dirençli, port 0 iken pullup direnci olmayan giriş olarak tanımlanıyor. Yukarıdaki resim yine veri kağıdından alınmış. Giriş/çıkış pinlerinin nasıl olduğu gösterilmiş. Bu arada pull up; girişin bir dirençle +Vcc hattına bağlanılması demek. Mesela buraya bir buton bağlayacaksanız, pinin ilk değerinin ‘1’ olduğunu unutmamanız ve butona basıldığında bu pinin Gnd hattına ‘0’ çekilmesi gerektiğini de hatırlatıyorum. Örnek mi?

void setup() {
DDRB = B00001001; // B portuna ait 0 ve 3 numaralı (D8 ve D11) pinler çıkış, diğerleri giriş
PORTB = B00000010 // B portuna bağlı 1 numaralı (D9) giriş pull up dirençli giriş
}

Üçüncü register; PIN. Bu sadece giriş için kullanılıyor. Yani ‘digitalRead‘ yerine. Ama burada bir sıkıntı var! ‘PIND’ dediğimizde D portu okunuyor. ama portun hepsi! ama biz bir girişin, örneğin bir buton pininin okunmasını istiyorsak (aksi ne olurki!) biraz kafa karışıyor. Port D üzerindeki o girişi izole etmek için “Bitwise” operatörlerini kullanmak zorundayız. Bu operatörler Arduino’nun kendi sayfasında da belirtilmiş. Portların 8 bitten oluştuğunu artık anladık. Bu operatörler de her bit için çalışan operatörler. Mantıksal kapılarda aslında bir giriş yapmıştık. Mecburen basitçe de olsa göz atacağız:

& (Bitwise AND): Bildiğimiz ‘ve’ kapısının bit bit çalışması. Her iki bit de ‘1’ ise sonuç ‘1’ diğer tüm durumlarda sonuç ‘0’

| (Bitwise OR): ‘veya’ kapısının yaptığı iş. Sadece iki giriş de ‘0’ ise çıkış ‘0’, girişlerden herhangi birinin ya da ikisinin de ‘1’ olması durumunda çıkış ‘1’. (‘Alt Gr’ tuşu ile birlikte ‘-‘ tuşuna basıyoruz)

^ (Bitwise XOR): ‘Özel Veya’ işlemi. Her iki giriş farklı ise çıkış ‘1’, eşit girişlerde çıkış ‘0’ olur

~ (Bitwise NOT): Bir bitin değeri ‘1’ ise ‘0’, ‘0’ ise ‘1’ e dönüştürür. (‘Alt’ + 126)

>> (Bitshift Right): Bir byte’ın içinde belirtilen sayı kadar sağa kaydırma yapılır. Soldan sadece ‘0’ eklenir. Aslında ‘1’ değerlerini kaydırmak gibi.

Örnek:
X = B01000100;
y = X >> 2; // y = B00010001

(Tüm basamaklar 2 sağa kaydırıldı, sola 2 tane ‘0’ eklendi)

<< (Bitshift Left): Yukarıdaki işlemin sola doğru olanı

Örnek:
X = B01000100;
y = X << 3; // y = B00100000

(Tüm basamaklar 3 sola kaydırıldı, sola 3’0′ eklendi)

Şimdi yukardaki işlemlerden bize ne? Cevabı şu: int a=digitalRead (butonPin); şeklinde tanımladığımız bir girişten gelen veriyi okuduğumuzu düşünelim. Örneğin butonPinden gelen veriye göre bir LED yakacağız. Bunu; int a = PIND; şeklinde yapıyoruz. Ama buradaki problem D portundaki tüm pinlerin tamamı okunur! Ama biz sadece tanımladığımız pin değerini okumak istiyorduk. O zaman yapacağımız şu: Önce sadece o pini belirlemek için ‘and’ operatörünü kullanarak iğer tüm pinleri sıfırlıyoruz. Farzedin giriş pininimiz D portundaki 2 numaralı pin. Tüm girişleri : 0000100 ile ‘and’ işlemine soktuğumuzu düşünelim:

00000100
xxxxxxxx
(PIND de okunan değerler; 1 ya da 0 olabilir)
& ————
00000×00
(PIND üzerinde sadece 2 numaralı porttan okunan değer)

Oldu, burada sadece D portunun üzerinden okunan değeri görüp diğerlerini sıfırladık. ( ‘and’ operatörüne göre sıfır yutan elemandı hatırlatırım). Daha işimiz bitmedi ama yukardaki ifadeyi koda nasıl ekleyeceğiz. En tepedeki 00000100 sayısı: 1<<2 olarak yazılabiliyordu. O zaman yukarıdaki ifade koda aşağıdaki gibi eklenebilir.

PIND & (1<<2 );

Yukardaki kod bize bir byte’lık bir verinin (PIND) sadece 2 numaralı bit değerinin okunacağını (0 ya da 1 olabileceğini), diğer tüm bitlerin 0 olacağını sağladı. Sonra okunan değerle yapacağımız işleme göre iki seçenek var. İlki; okunan değeri int (ya da boolean) olarak kaydedip ona göre işlem yapacaksak bu byte değerindeki ikinci bit değerini int gibi tek değere dönüştürmek. Bu değer tek bir değer olduğu için ikinci bitte okunan değeri yine en sağa atabiliriz.

int a = (PIND << 2)>>2;
if (a == LOW)…

sonra bildiğimiz ‘if’ ile (if (a == LOW) …) istediğimiz şartı işleyebiliyoruz. İkincisida en son sağa kaydırma yerine int değişkeni değil byte atayıp orada durumu kontrol edebiliriz:

byte a= (PIND & (1<<2 ));
if (a == B00000000) …..

Şeklinde. Neyse bu bize ne kazandıracak, kafayı yemeden ona bakalım. Aşağıdaki iki program da butona basınca LED yakan program. Altlarındaki hafızada kapladıkları yerlere DİKKAT! 892 byte yerine 466 byte!

Evet hafızadan bu kazanç fena değil. Özellikle de ATtiny gibi 1kByte program hafızası olan bir mikrodenetleyici kullanıyorsak.

Gelelim ikinci faydaya; Hafızada meydana gelen bu kazanç hıza da yansıyor. Normalde arduino üzerinde 12 MHz kristal var, bu da çalışma hızı demek. Yani bir saniyede 12 milyon işlem yapabiliyor demek. Ya da başka bir değişle (1/12000 000) bir işlem yapma süresi yaklaşık 63 ns. Ama tabi bizim yazdığımız kodlarla bu mümkün olabilecek mi? bunu deneyeceğiz. Yine ‘blink’ örneğini önce orijinal kodla, sonra da maniple edip deneyeceğiz. Aradaki ‘delay’Leri kaldırıp, LED’i hemen yakıp söndürmesini isteyip, 13 numaralı pine bağladığımız (kendi yaptığımız) frekans ölçer ile frekansları ölçüp farkı göreceğiz. Aşağıda kodlar:

Hızın nasıl değiştiğine bakmadan önce 734 byte yerine 456 byte kullanıldığına yine dikkat! Şimdi gelelim hız değişikliğine!

Bingo! Soldaki pinMode ve digitalWrite komutları ile alınan sonuç: Yaklaşık 147 kHz. Sağdaki sonuç ise DDR ve PORT ile alınan sonuç: 1.6 MHz. Sonuç 12 MHz’den uzak olsa da Arduino’nun hızı 10 kattan daha fazla arttı! Sadece birkaç komut değiştirerek Arduinoyu 10 kat hızlandırmak!

Sonraki çalışmalarda görüşmek üzere…

Kaynaklar: https://www.youtube.com/watch?v=6q1yEb_ukw8&t=1031s&ab_channel=SparkFunElectronics

https://www.microchip.com/wwwproducts/en/ATmega328p#datasheet-toggle

https://store.arduino.cc/usa/arduino-uno-rev3

https://www.arduino.cc/reference/en/#structure

https://egitimsart.org/pic-16f628-ile-frekans-metre-kristal-test-devresi/