Port Manipülasyonu ve Bitwise Mask

Merhaba arkadaşlar, bu yazımda arduino mikrodenetleyici kartlarının port işlemlerini basitçe nasıl manipüle edilebileceğine değineceğim. Bu konuyu ele alma sebeplerimden biri bu konu hakkında yeterli türkçe kaynağın bulunmaması ve port işlemlerinde biraz daha derine inip  arka planda dönen işleri ön plana çıkarmak istememdir. Hadi başlayalım. Umarım faydalı bir yazı olur 🙂 

Arduino kartları üzerindeki I/0 (bir/sıfır) pinlerinin daha hızlı bir şekilde ayarlama yeteneğine sahibiz. Bu yazımı Arduino Uno kartlarını baz alarak ele alıyorum. Diğer Arduino kartları da benzer bir mantıkla çalışmaktadır. Datasheet’ lerini inceleyerek gerekli bilgilere ulaşabilirsiniz.

İlk olarak Input/Output (I/O) olarak kullandığımız digital ve analog pinlerimiz bulunmaktadır. Bizler Arduino-IDE programlama dilinde normalde pinMode() fonksiyonu ile ilgili pini giriş veya çıkış olarak ayarlıyorduk. Aslında bu fonksiyonun içinde port manipülasyonu işlemleri yapılmaktadır. Port manipülasyonu olayında  register mantığı ile çalışmaktayız ve Arduino Uno kartında boyutu  maksimum 8 bit registerlar bulundurmaktadır.(Port manipülasyonu için kullandığımız registerlar için boyut maksimum 8 bittir.) Bundan dolayı elimizde 3 port kaydı bulunmaktadır. Bunlar D,B,C kayıtlarıdır.

D ---> D pinleri 0∼7 arasındaki pinler

B ---> B pinleri 8∼13 arasındaki  pinler

C ---> C pinleri ise analog pinleridir (0∼6)

D,B,C port kayıtlarına değindiğimize göre artık sıra ilgili pinlerimizi I/O (Input/Output) olarak ayarlamaya geldi. Bildiğiniz gibi normalde pinlerimizin I/O olarak pinMode(pin_no, INPUT/OUTPUT) fonksiyonu yardımıyla ayarlıyorduk. Port manipülasyonunda bu işlemi DDRx ( x değişkeni  D.B.C kayıtlarını belirtmektedir) komutuyla yapacağız. Çıkış olarak ayarlamak için ilgili pinlere 1 değerini, giriş olarak ayarlamak için 0 değerini atayacağız. Örnekle dahi anlaşılacaktır 😀

pinMode(3,OUTPUT);

pinMode(5,OUTPUT);        ———>  DDRD = 0b10101000; (binary tabanlı atama işlemi)

pinMode(7,OUTPUT);                        DDRD = 0xA8; (hexadecimal tabanlı atama işlemi)

Yukarıdaki 2 DDRD atama işlemi aynı işlem olup birisi binary tabanlı diğeri ise hexadecimal tabanlı atamadır. 2 atama işlemi de aynı işi yapmaktadır sadece farklı 2 yoldur. 0b10101000 burada 3, 5, 7. pinlerimiz çıkış(Output) olarak ayarlanmışken diğer pinlerimiz giriş(Input) olarak ayarlanmıştır.

pinMode(8,INPUT);

pinMode(9,OUTPUT);          ———->   DDRB = 0b00010010;

pinMode(10,INPUT);

pinMode(12,OUTPUT);

0b00010010  burada 9, 12. pinlerimiz Output diğer pinlerimiz Input olarak ayarlanmıştır.

Şimdi biraz düşünelim elimizde 13 tane digital pinimiz var ve her birini  tek tek ayarlamaya kalkarsak 13 tane pinMode() fonksiyonu kullanmak zorunda kalırız ama port manipülasyonu kullanarak ayarlama yaparsak tek atama işlemi ile tek satırda ayarlama işlemimizi halledebilir ve pinMode() fonksiyonuna nazaran programımız daha hızlı çalışmasını sağlayabiliriz. Arduino programlamada mili saniyelerin ne kadar önemli olduğunu hatırlatmama gerek yoktur sanırım 🙂

İlgili pinlerimizi Input/Output olarak ayarladığımıza göre artık ilgili pinlerimizi HIGH/LOW olarak ayarlamalıyız. Normalde bu işlemi digitalWrite() fonksiyonunu kullanarak yaparız. Şimdi tekrar düşünelim. 🙂 Elimizde 13 digital pinimiz var ve programın akışına göre bu pinlerin HIGH/LOW durumları değişmektedir. Bu durum bizim digitalWrite () fonksiyonunu defalarca kullanmamıza neden olacak bu durum hem zaman hem de programımızın çalışma hızını port manipülasyonuna nazaran yavaşlamasına sebep olacaktır. İşte bu sebeplerden ötürü port manipülasyonu tercih etmeliyiz.

Port manipülasyonu ile ilgili pinimizi HIGH/LOW olarak ayarlarken PORTy ( y değişkeni  D.B.C kayıtlarını belirtmektedir) komutunu kullanmaktayız. İlgili pinimizin HIGH olması için 1 değerini, LOW olması için 0 değerini atamalıyız. Örneklere geçelim 🙂

pinMode(2,INPUT_PULLUP);

pinMode(3,OUTPUT);          ———->   DDRB = 0b01101000;

pinMode(5,OUTPUT);                             DDRD = 0x68; (hexadecimal tabanlı atanmış hali)

pinMode(6,OUTPUT);

digitalWrite(2,HIGH);

digitalWrite(3,HIGH);     ————>  PORTD = 0b00001100;

digitalWrite(5,LOW);

digitalWrite(6,LOW);

3, 5,  6. pinlerimiz output olarak, 2. pinimiz Input_pullup olarak  ve 2,3. pinlerimiz HIGH 5,6. pinlerimiz LOW olarak ayarlandı. Pullup olayına kısaca değinecek olursak örneğin 2. pinimizde buton dinlemesi yapıldığını varsayalım. Push butonlara basarken ortaya çıkan basılı veya basılı olmama durumu (butona basarken oluşan 5,10 mili saniyelik kararsız durum) engellemek için Pullup işlemini kullanıyoruz. pinMode() fonksiyonu kullanarak pullup ayarlanması pinMode(2,INPUT_PULLUP) şeklinde yapılır. Port manipülasyonu ile ayarlamak için ise ilgili pinimizi INPUT ve HIGH olarak ayarlamalıyız. INPUT olarak ayarlamak için DDRx register’ında 0 (sıfır) değerini vermeli ve HIGH olarak ayarlanması için ise PORTy register’ında I (bir) değerini vermeliyiz. (Pullup olayını register’lar ile nasıl ayarlayabileceğimize değinmek istedim. Neyse konumuza dönelim 🙂 )

Bu örnekten sonra port manipülasyonunu kısaca özetlersek pinlerimizi INPUT/OUTPUT  olarak ayarlarken DDRx, HIGH/LOW olarak ayarlarken PORTy register’larını kullanıyoruz.

Şimdi tekrar düşünelim. 3,5,6 pinlerimizi çıkış olarak ayarlamak istiyoruz ama diğer pinlerimize şimdilik herhangi bir ayarlama yapmak istemiyoruz. Bu durumu sağlamak register’lara yaptığımız binary ve haxadecimal atamalarla mümkün olmuyor çünkü binary veya hexadecimal atamalar yaparsak ya ilgili pinlerimiz 0 (sıfır) ya da I (bir) oluyor buda ilgili pinlerimizin ya Input ya da output olmasına sebep oluyor. (DDRD =0b01101000; görüldüğü gibi) Peki bu durumu nasıl sağlarız? İlgili pinlerimizden geri kalan pinlerin ilk hallerini(herhangi bir ayarlanma yapılmamış halini) nasıl koruruz? işte bu durumda imdadımıza bitwise işlemleri koşuyor.

BİTWİSE İŞLEMLERİ

Bu bölümde kısaca bitwise işlemlerine değineceğim. Bildiğiniz and, or, not alma işlemleri arkadaşlar isim korkutmasın 😀

Bitwise AND (&)

          x: 10001101

          y: 01010111

x & y  --------------

             00000101

Bitwise OR  ( | ) 

          x: 10101000

          y: 01010111

x | y  --------------

             11111111

Bitwise NOT (∼)

          x: 10101001

         ∼x: 01010110

Bitwise XOR (^)

          x: 10001101

          y: 01010111

x ^ y  --------------

             11011010

Bitwise SHİFT (<< ve  >>)

Shift işlemi kaydırma işlemidir. Arkadaşlar 1, 0b00000001 register’larda bu şekilde algılanır yani buna eşittir. Şimdi bizim elimizde bir x sayısı olsun ve 1′ e eşit olsun x sayısını 5 kere sola shift edelim.

  x = 1 = 0b00000001;

  (1 << 5) = 0b00100000;

Elimizdeki x sayısı 8 eşit olsun ve x sayısını 2 kere sağa shift edelim.

  x = 8 = 0b00001000;

  (1 >> 2) = 0b00000010;

BİTMASK

Bitwise ile Pinlerin OUTPUT veya HIGH Olarak Ayarlanması

Bitwise işlemlerine kısaca değindikten sonra  bitwise işlemleri ile DDRx ve PORTy register’larımızı nasıl kullanabiliriz ona bakalım. Bitwise işlemleri ile register’ların kullanılması işlemine BitMask (maskeleme) denmektedir. Arduino kartlarımızın pinlerine herhangi INPUT/OUTPUT veya HIGH/LOW ayarlaması yapmadığımızda register durumu 0bXXXXXXXX (bilinmeyen değerler 0 veya 1) olsun. Biz bu değeri öyle bir değer ile öyle bir bitwise işlemine tabii tutalım ki istediğimiz pinler haricindeki diğer pinlere herhangi INPUT/OUTPUT veya HIGH/LOW  ayarlaması (ataması) yapmış olmayalım. Örnekle daha iyi anlaşılır olacaktır. 1=0b00000001 e eşit olduğunu unutmayalım. 🙂

Şimdi 4 pinini OUTPUT ve HIGH olarak bitwise işlemleri ile ayarlayalım.

DDRD = DDRD | (1 << 4);   (DDRD  |= (1 << 4) şeklinde kısaltıp da yazabiliriz. )

İşlemi inceleyelim.

DDRD’nin ilk durumu (herhangi ayarlama yapılmamış hali) bilinmiyor. DDRD’nin ilk hali

DDRD = 0bXXXXXXXX; olsun.

(1 << 4) = 0b00010000;  (00000001 ‘ i 4 kere shift ettik)

DDRD = 0bXXXXXXXX;    --------->   XXXXXXXX    (DDRD)

                                   00010000    (1 << 4)

                               |------------  (OR işlemi)

                                   XXX1XXXX    (DDRD'nin yeni değeri)

İşlemin detaylarını incelediğimizde 1 ile OR işlemine hangi değer girerse girsin sonuc 1 çıkacaktır. 0 ile OR işlemine hangi değer girerse girsin sonuç işleme giren değere eşit olacaktır. Yani 1 ile  X’in OR işlemine girmesinin sonucunda X ister 0 ister 1 olsun sonuç 1’e ,0 ile  X’in OR işlemine girmesinin sonucu  ise X ister 0 ister 1 olsun sonuç X ‘e eşit olacak ve böylece pinlerimizin ilk hali korunmuş olacaktır. Sonucumuz  DDRD = 0bXXX1XXXX; eşit oldu. Yani 4. pinimiz OUTPUT olarak ayarlandı ve diğer pinlere herhangi bir ayarlanma (atama) işlemi yapılmadı. Diğer pinlerimiz ilk hallerini korudu.

PORTD |=(1 << 4); (4. pinimiz HIGH olarak ayarlandı. Yukardaki işlem adımlarının aynısı geçerlidir.)

Pekişmesi için bir örnek daha 🙂 2,3,5 pinlerimizi OUTPUT ve HIGH olarak ayarlayalım.

DDRD |= (1 << 2) | (1 << 3) | (1 << 5);

İşlemi açalım…

DDRD = 0bXXXXXXXX; ( register’ın ilk durumu)

(1 << 2)|(1 << 3)|(1 << 5) = 0b00101100; ——>  00000100  (1 << 2)

                                               00001000  (1 << 3)

                                         | -------------  (1<<2)|(1<<3)

                                               00001100 

                                               00100000  (1<<5) 

                                            | --------------

                                               00101100

DDRD = 0bXX1X11XX;   ———–>  XXXXXXXX

                          00101100

                      | --------------

                          XX1X11XX 

PORTD |=(1 << 2) | (1 << 3) | (1 << 5);

Yukardaki işlemler ile aynı adımlar izlenir. Tek fark PORTy register’ını kullanıp HIGH olarak ayarlamamızdır. İşlemler incelendiğinde istediğimiz pinler çıkış olarak ayarlanacak ve diğer pinler ilk hallerini koruyacaktır.

Bitwise ile Pinlerin INPUT veya LOW Olarak Ayarlanması

Örneğe geçelim ve bu arada 1 =0b00000001 ‘dır hatırlatayım dedim 😛

10. pinimizi INPUT ve LOW olarak ayarlayalım…

DDRB &=  ∼(1 << 3);

işlemi açıyoruz 😀

DDRB = 0bXXXXXXXX;  (DDRB register’ının ilk durumu)

(1 << 3) = 00001000; (shift işlemi)

∼(1 << 3); = 11110111; (not işlemi)

DDRB =0bXXXX0XXX;   ———–>  XXXXXXXX

                          11110111

                      & -------------

                          XXXX0XXX

İşlemi incelediğimizde 0 ile hangi değer And işlemine girerse girsin değerimiz ister 1 ister 0 olsun sonuç 0, 1 ile hangi değer And işlemine girerse girsin değerimiz ister 1 ister 0 olsun sonuç işleme giren değere eşit olcaktır. Yani 0 ile  X’in And işlemine girmesinin sonucunda X ister 0 ister 1 olsun sonuç 0’a ,1 ile  X’in And işlemine girmesinin sonucunda  ise X ister 0 ister 1 olsun sonuç X ‘e eşit olacak ve böylece pinlerimizin ilk hali korunmuş olacaktır. Sonucumuz  DDRB = 0bXXXX0XXX; eşit oldu. Yani 10. pinimiz INPUT olarak ayarlandı ve diğer pinlere herhangi bir ayarlanma (atama) işlemi yapılmadı. Diğer pinlerimiz ilk hallerini korudu.

PORTB &=  ∼(1 << 3);

Yukardaki işlemler ile aynı adımlar izlenir. Tek fark PORTy register’ını kullanıp LOW olarak ayarlamamızdır.

Şimdi sıra son örneğimize geldi. 2,3. pinlerimizi Input 4,6. pinlerimizi Output olarak ve 2,3.pinlerimizi HIGH 4,6. pinlerimizi LOW olarak ayarlayalım.

Arkadaşlar işlemleri uzun uzadıya açmıyorum bu sefer yukarıdaki örneklerde nasıl yapıldığı öğrendiniz zaten ama siz uzun uzadıya açın öğrenmek için 😛

DDRD &=  ∼(1 << 2) & ∼(1 << 3); (2 ve 3. pinlerimiz INPUT olarak ayarlandı)

DDRD |= (1 << 4) | (1 << 6);   (4 ve 6. pinlerimiz OUTPUT olarak ayarlandı)

PORTD |=(1 << 2) | (1 << 3); (2 ve 3. pinlerimiz HIGH olarak ayarlandı)

(2 ve 3. pinlerimiz Pullup olarak ayarlanabilir)

PORTD &=  ∼(1 << 4) & ∼(1 << 6); (4 ve 6. pinlerimiz LOW olarak ayarlandı)

Arkadaşlar yazıma burada son veriyorum. Yazı ile ilgi yorumlarınızı bekliyorum. Sorularınızı yorum olarak veya o.erdemir1997@gmail.com adresine göndererek sora bilirsiniz. İnşAllah faydalı bir yazı olmuştur. Sağlıcakla ve Open Source ile kalın. Ömer Ali ERDEMİR

One thought on “Port Manipülasyonu ve Bitwise Mask”

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir