Temel C Programlama -47- Bit Bazlı Uygulamalar (Set, Reset, Toggle)

Daha önceki başlıkta bitwise operatörleri anlattığımıza göre bu başlıkta sadece bunun uygulamasını anlatacağız. Bit bazlı operatörlerin kullanım alanları oldukça çeşitlilik gösterebilir. Temelde basit elemanlar olsalar da aynı toplama, çıkarma işlemleri gibi temel işlemlerden biridir. O yüzden bu temel işlemler kullanılarak oldukça karmaşık uygulamalar yapılabilir. Genel kullanım alanları matematik işlemleri, performans uygulamaları ve gömülü sistemlerdeki yazmaç tabanlı işlemlerdir.

Bit biti 1 yapmak (set)

Gömülü sistemlerde çalışırken bir değişkende veya adreste yer alan bir bitin değerini 1 yapmak isteyebilirsiniz. Bunu sadece sayı değeri olarak düşünmeyin. Örneğin gömülü sistemlerde 0x20 adresinde yer alan 8 bitlik bir yazmaç (veri hücresi) içerisinde yer alan 2 numaralı bit aygıtta zamanlayıcıları etkileştirme veya devre dışı bırakma görevini yerine getirebilir. Bilgisayarlardaki donanım kontrolü de belirli bellek noktalarında belirli değerleri değiştirme vasıtasıyla yapılmaktadır. Biz tam olarak o biti 1 yapmak istersek şöyle bir yöntemi kullanmamız gerecektir.

bellek_hucresi |= (1<<bit_degeri);

Bu işleme maskeleme adı verilmektedir. Bilmeyen birine tuhaf işaretlerin bir araya gelmesi gibi görünebilir. Gömülü sistem geliştiriciliğinde de başta en çok zorluk çekeceğiniz nokta budur. Bit işlemlerini anlayan ilerler anlamayan ise sıfır noktasında kalıverir. Bunun için bunu anlamanız noktasında azami gayret sarf etmekteyim. Şimdi yukarıda gördüğünüz örnek söz dizimini gerçek bir koda çevirelim ve bunu en derinlemesine açıklayalım.

PORTD |= (1<<7);

Gömülü sistemlerde yazmaç seviyesinde çalışıyorsanız bir ledi yakmak için bile böyle bir kod yazmak zorunda kalırsınız. Bunu anlamadan en basit bir işlem olan led yakma işini bile yapamazsınız.

Burada her C programında olduğu gibi öncelikle eşittirin veya birleşik operatörün sağ tarafında gerçekleştirilen işleme bakmamız gereklidir. Zaten parantez içinde olduğu için de doğrudan parantez içlerine odaklanmamız gerekli. Parantezin içine odaklandığımızda 1<<7 işlemini görürüz. Yani 1 sabit değeri 7 adım sola kaydırılmakta. Bunun nasıl işlediğini tablo vasıtasıyla gösterelim.

İkilik formatta göstermek istersek bu işlemden önce elimizde 1 değeri yani 0b00000001 değeri yer alırken bu işlem gerçekleştikten sonra bit değeri yedi adım kayarak yedinci bite gelmiş durumdadır. Burada bitlerin sıfırdan ve sağdan başladığını unutmayın. Yedinci bit 8-bitlik bir verinin son basamağıdır. Burada değerimiz 0b10000000 olmakta. Buradan da anlıyoruz ki 1<<7 yazmak 0b10000000 sabitini yazmanın kısa yoludur. Benim için (1<<7) ifadesi daha anlamlı gelmektedir. Çünkü bunu (deger<<bitkonumu) olarak kodlamaktayım.

(1<<7) diyerek yedinci biti bir yapacağımızı öğrendik. Şimdi bunu PORTD adı verilen değişkene (adrese) uygulama zamanı. Başlangıçta bunun atama operatörü (=) ile uygulanacağını düşünebilirsiniz. Ama atama operatörü ile atadığımız değer PORTD’nin bütün bitlerini değiştirecektir. Yani eğer = operatörünü kullanırsak PORTD’nin değeri 0x10000000 olacaktır. Biz bunu istemiyoruz çünkü diğer bitlerde farklı farklı görevler için değerler yer almakta. Bu yüzden sadece PORTD’nin 7 numaralı bitinin bir yapılmasını istiyor geri kalana dokunulmamasını istiyoruz. Bunu ise mantıksal bir işlem olan OR işlemi ile yapacağız.

OR işleminin burada bunun için kullanılması şaşırtıcı değil mi? Görüldüğü gibi bu operatörler oldukça esnek ve işimize gelen yerde kullanılabilmesi için biraz kafa yormak gerekiyor. Şimdi |= operatörünün ne iş yaptığını size anlatalım.

Aynı toplama, çıkarma, çarpma ve bölme işlemlerinde işi kısaltmak adına birleşik operatörler (+=, -=, *=, /=) kullanılıyorsa mantık işlemlerinde de kodu kısaltma adına birleşik operatörler kullanılmaktadır. Yani &=, |= ve ^= operatörleri ile bit kaydırma operatörlerini bu şekilde birleşik halde görebilirsiniz. Ama uygulamada bit kaydırma operatörleri genelde ayrı kullanılmaktadır.

Bu durumda,

A &= B operatörü A = A & B, A |= B operatörü A = A | B, A ^= B operatörü ise A = A ^ B işlemine eşittir.

Bu kurala göre yukarıdaki PORTD |= (1<<7); işlemine baktığımızda aslında şu işlemin yapıldığını görürüz.

PORTD = PORTD | (1<<7);

Görüldüğü gibi (1<<7) işleminden dönen değer PORTD üzerine OR işlemi ile uygulanıyor. Bu yüzden buna maskeleme denmekte. O halde bunu daha anlaşılır olması için şu halde yazıp sonrasında ise nasıl çalıştığını gösterebiliriz.

PORTD = PORTD | 0b10000000

Şimdi PORTD’nin değerleri ve 0b10000000 değerinin PORTD’ye OR işlemi ile uygulandıktan sonra değerlerine bakalım.

Neden bu kadar çok x var diye sorarsanız bunlar bizi ilgilendirmeyen değerleri temsil için gösterilmiştir. Bu durumda PORTD’nin 7 numaralı bitinin de x olduğunu görebilirsiniz. Bunun değeri de aslında bizi ilgilendirmemektedir. Çünkü ister 1 olsun ister 0 olsun OR işlemine tabi tutulduktan sonra muhakkak 1 olacaktır. Geri kalan değerleri ise 0 ile OR işlemine tabi tuttuğumuzdan ister 1 olsun ister 0 olsun herhangi bir şekilde değişmeyecektir. Bir biti bir yapmanın en garanti yolunun OR işleminden geçtiğini görmüş oldunuz. Harvard mimarisinde veya bazı mikrodenetleyici mimarilerinde makine komutu olarak bit biti bir yapma komutu yer alıp bizi o kadar çok uğraştırmasa da C dili von neumann mimarisine göre tasarlanmıştır. Von neumann mimarisinde ise makine komutları bu şekilde olduğundan bize bitwise operatörler olarak miras kalmıştır. Makine dilinde olmasına rağmen C dilinde bir biti bir veya sıfır yapma operatörü veya komutu bulunmayışı o dilin eksikliği olarak görünebilir. Eğer bu dil o mimariye göre tasarlansaydı hak verebilirdik. Fakat burada bu işlem mimari farklılığından dolayı farklı bir yolla da olsa yapılabilmektedir.

Bir biti 0 yapmak (reset)

Bir biti bir yapmayı öğrenseniz de aynı kodla bir biti sıfır yapmanız mümkün değildir. O yüzden bu sefer farklı operatörlerle kısmen benzer bir işlemi gerçekleştireceğiz. Aslında bir biti sıfır yapmanın bir yapmaktan biraz daha zor olduğunu göreceksiniz. Fakat bunları bir noktadan sonra ezberleyeceğiniz için sadece değerleri değiştirerek printf fonksiyonunu yazmak kadar kolay bir şekilde yazabilirsiniz. Biz yazdığınız kodları anlamanızın gerektiğini savunduğumuz için başta teorik bilgiyi sizin en iyi şekilde anlamanız için çaba sarf ediyoruz. Pratik kazanmak kişisel bir tecrübedir ve size bağlıdır. Kişisel deneyimlerimden yola çıkarak size şunu söyleyebilirim. Bir teoriyi anlamak 5 pratik yapmaya bedeldir. Çünkü teoriyi anlayarak aslında zihninizde bir pratik yapıyorsunuz ve tecrübe kazanıyorsunuz.

Bir biti sıfır yapmak için şöyle bir kod prototipi kullanılabilir.

degisken_adi &= ~(1<<bit_numarasi);

Yukarıda bir biti bir yapmak için OR işleminin anahtar eleman olduğunu burada ise AND işleminin bu görevi yerine getirdiğini görebiliriz. İki mantık işlemine baktığımızda aralarında birbirine zıt bir ilişki görebiliriz. OR işlemi dört ihtimalin üçünde 1 çıkışını vermekte AND işlemi ise dört ihtimalin üçünde 0 çıkışını vermektedir. Şimdi gerçek bir kod yazalım ve bunun nasıl çalıştığını görelim.

PORTD &= ~(1<<7);

Burada (1<<7) diyerek yine 7 numaralı bit üzerinde işlem yapacağımızı anlayabilirsiniz. Burada elimizde 0b10000000 sabiti var fakat parantezin solunda bir operatörü daha görmekteyiz. Bu operatör NOT operatörü olup tersleme görevini yerine getirmektedir. Yani (1<<7) 0b10000000 iken ~ operatörü tarafından 0b01111111 değerine çeviriliyor. Burada neden (0<<7) diye düz bir şekilde yazmak varken (1<<7) yazıp sonra da bunu ters çeviriyoruz diyebilirsiniz. Bunun iki sebebi vardır. Birincisi (0<<7) işleminden dönen değer 0 olacaktır. Çünkü sıfır nerede olursa olsun sıfırdır. İkincisi ise bizim diğer bitlerde 1 değerine ihtiyacımız vardır. Bu maskeleme için gereklidir çünkü eğer ilk bit 1 ise onun 1 kalabilmesi için 1 & 1 = 1 işlemine tabi tutulması gerekecektir.

O halde PORTD şöyle bir değer ile AND işlemine tabi tutulmaktadır.

PORTD &= 0b01111111;
// yani
PORTD = PORTD & 0b01111111;

PORTD ile 0b01111111; işleminin nasıl gerçekleştiğine bir göz atalım.

Burada bir değere 1 ile AND işlemi uygulanırsa o değerde herhangi bir değişme olmayacaktır. Bu aynı 0 ile OR işlemi uygulamak gibidir. Bu yüzden sayıları ters çevirdik. Biz OR işlemi ile bir sayıyı bir iken sıfır asla yapamayız. AND işlemi ile de sıfır iken bir yapamayız. Fakat tam tersini kolaylıkla yapmamız mümkündür. Burada da 0 değerini hangi değere AND işlemi ile beraber uygularsak o değer muhakkak sıfır olacaktır. Diğer değerlere 1 ile AND işlemi uygulandığından dolayı değerleri ne olursa olsun asla değişmeyecektir.

Bir biti değiştirmek

Bu sefer biti sıfır veya bir yapacağımızı veya bitin değerinin ne olduğunu bilmiyoruz diyelim. Tek kuralımız bu bit bir ise sıfır olacak, sıfır ise bir olacak. Başka herhangi bir işlem söz konusu olmayacak. Yani açıp kapama (toggle) işlemi yapmaktayız. Bunu bu sefer de XOR işlemi ile yaparız.

degisken_adi ^= (1 << bit_konumu);

Burada XOR işleminin “Aynı ise 1, farklı ise 0 yap” prensibi kullanılmaktadır. 1 değerini bir bite uyguladığımızda o bit 1 ise sıfır olmakta. Sıfır ise 1 ile farklı olduğu için 1 olmaktadır.

Bunu uzun uzadıya açıklamamıza gerek olmadığını düşünüyoruz çünkü mantığını anladınız. Aynı konuyu C ile AVR programlama derslerinde de anlatmıştım fakat bunu oraya bakmadan bir daha anlattım. İki yazıdan da ayrı ayrı faydalanabilirsiniz.

Last updated