- AVR 101
Bu bölümde C programlama dili ile AVR mikrokontrolcü programlama hakkındaki notlarım bulunmaktadır. Burada prototipleme için ATmega328p çipine sahip Arduino UNO veya ATmega328pb çipine sahip Arduino Nano kullanılmıştır.
Aşağıdaki bölümlerde MacOS X ve Windows için kurulumlar aşamalarıyla anlatılacaktır.
MacOS'ta kurulum yapabilmek için bir paket yöneticisi olan Homebrew'e ihtiyacımız var. Homebrew sisteminizde yüklü değilse önce Homebrew'i yüklemeniz gerekir. Ayrıca Homebrew, Xcode komut satırı araçlarının yüklenmesini de gerektirir. Bunun için öncelikle terminalimizi açıyoruz. Ardından sırasıyla ve tek tek olmak üzere aşağıdaki satırları terminalimize yazıyoruz:
Bu komut Xcode komut satırı araçlarını indirir:
xcode-select --install
Bu komut bir paket yöneticisi olan Homebrew'i indirir:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Bu komut Homebrew güncellemeleri alır:
brew update
brew tap osx-cross/awr
Bu komut paketleri güncellemeden önce düzgün kurulum için siler, eğer sisteminizde hiç kurulmamışsa
No such keg
hatası alırsınız, bunun için endişelenmenize gerek yok, sonraki komuta devam edebilirsiniz:
brew remove avr-gcc avr-binutils avr-libc
Bu komut avr-gcc'yi indirir:
brew install avr-gcc avr-binutils
Bu komut ISS kullanarak AVR mikroişlemcilerinin ROM ve EEPROM içeriğini işlemek için bir açık kaynak aracı olan AvrDude'u indirir:
brew install avrdude
Bu komut bir programın yürütülebilir dosyalarının ve diğer kaynak olmayan dosyalarının, programın kaynak dosyalarından üretilmesini kontrol eden bir araç olan GNU Make'i indirir:
brew install make
Bu komut git'i indirir:
brew install git
Aşağıdaki komutlar ile indirdiğimiz araçların doğru şekilde kurulup kurulmadığını kontrol edebilirsiniz:
avr-gcc --version
make --version
git --version
avrdude
Terminal çıktınız yaklaşık bu şekilde olmalıdır:
Derlenen kodların, avrdude aracılığıyla mikrokontrolcüye doğru bir şekilde iletilebilmesi için öncelikle kartımızı bağladığımız usb portunu öğrenmemiz gerekiyor. Bunu yapabilmek için Arduino IDE'yi indirip kurmanız gerekiyor. Arduino ideyi kurduktan sonra, aşağıdaki adımları takip edin:
-
Arduino IDE'de yüklü gelen örnek kodlardan bir tanesini açın:
-
Arduino'yu usb konnektörü ile bilgisayarınıza bağlayın.
-
Arduino'yu bağladığınız portu ide üzerinden seçin.
-
Ardunio IDE'nin tercihler menüsünü açın.
-
Tercihler menüsünde "Yükleme sırasında ayrıntılı çıktı göster" seçeneğini aktif edin ve tercihler menüsünü kaydederek kapatın.
-
Kodu derleyin ve kartınıza yükleyin.
-
Ardından output kısmını genişletin ve çıktının en üzerinde belirtilen yerdeki port adınızı kopyalayıp not defterinize kaydedin.
-
Artık Arduino IDE'yi kapatabilirsiniz.
-
Herhangi bir editör aracılığıyla aşağıdaki kodu main.c isimli bir dosyaya kaydedin.
#include <avr/io.h> #include <util/delay.h> #define BLINK_DELAY_MS 1000 int main (void) { /* set pin 5 of PORTB for output*/ DDRB |= _BV(DDB5); while(1) { /* set pin 5 high to turn led on */ PORTB |= _BV(PORTB5); _delay_ms(BLINK_DELAY_MS); /* set pin 5 low to turn led off */ PORTB &= ~_BV(PORTB5); _delay_ms(BLINK_DELAY_MS); } }
-
Terminalden dosyayı oluşturduğunuz klasöre
cd klasör_ismi
komutuyla girin. -
Ardından derleme işlemlerini yapmanız gerekiyor. Sırasıyla aşağıdaki komutları girin:
avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o main.o main.c avr-gcc -mmcu=atmega328p main.o -o main avr-objcopy -O ihex -R .eeprom main main.hex
-
Not defterine kaydettiğiniz port adınızı aşağıdaki komutta port_name ile belirtilen kısma yazın ve komutu çalıştırın.
avrdude -F -V -c arduino -p ATMEGA328P -P /dev/port_name -b 115200 -U flash:w:main.hex
-
Test kodu, arduinonuz üzerindeki dahili ledi 1 saniye aralıklarla yanıp söndürmek içindir. Başarı ile çalışıyorsa, artık test kodu çalıştırma adımlarını tekrarlayarak c kodlarınızı arduino üzerinde çalıştırabilirsiniz.
Sırasıyla aşağıdaki işlemler takip edilmelidir.
-
Öncelikle bilgisayarınıza uygun git kurulum dosyasını indirin.
-
Dosyayı indirdiğiniz konuma gidin ve dosyayı başlatın.
-
Dosya başlatıldığında özel izni kabul edin ve devam edin.
-
Install'a basarak devam edin.
-
Kurulumun bitmesini bekleyin.
-
Finish'e basarak kurulumu tamamlayın.
-
Zip dosyasını indirin.
-
Zip dosyasını kaydedeceğiniz konuma ayıklayın.
-
avr-gcc'yi Ortam Değişkenlerine ekleyin.
-
Windows Arama Menüsü'ne Ortam Değişkenleri yazarak "Sistem Ortam Değişkenlerini Düzenleyin" seçeneğine tıklayın.
-
Sistem Özellikleri Menüsünde bulunan "Ortam Değişkenleri..." butonuna tıklayın.
-
Ortam Değişkenleri menüsünün kullanıcı değişkenleri kısmından "Path"i seçerek "Düzenle..." butonuna tıklayın.
-
Öncelikle "Yeni" butonuna tıklayarak yeni bir satır oluşturun, ardından ortam değişkenlerine eklemek istediğiniz programın "bin" klasörünün yolunu kopyalayıp bu satıra yapıştırın ve "Tamam" butonuna basarak yaptıklarınızı kaydedin.
-
Tekrar "Tamam" butonlarına basarak Ortam Değişkenleri ve Sistem Özellikleri menülerini kapatın.
-
CMD'yi açın.
-
Sırasıyla
avr-gcc --version
make --version
git --version
avrdude
komutlarını çalıştırın. Terminal çıktınız aşağıdaki gibi gözükmelidir:
Derlenen kodların, avrdude aracılığıyla mikrokontrolcüye doğru bir şekilde iletilebilmesi için öncelikle kartımızı bağladığımız usb portunu öğrenmemiz gerekiyor. Bunu yapabilmek için Arduino IDE'yi indirip kurmanız gerekiyor. Ardunio ideyi kurduktan sonra, aşağıdaki adımları takip edin:
-
Arduino IDE'yi açın.
-
Arduino Kartınızı bilgisayarınıza bağlayın.
-
Arduino IDE üzerinden "Tools" menüsünü açın ardından "Port" seçeneğinin üzerine gelin ve açılan yerden port adınızı not defterinize kaydedin.
-
Artık Arduino IDE'yi kapatabilirsiniz.
-
Herhangi bir editör aracılığıyla aşağıdaki kodu main.c isimli bir dosyaya kaydedin.
#include <avr/io.h> #include <util/delay.h> #define BLINK_DELAY_MS 1000 int main (void) { /* set pin 5 of PORTB for output*/ DDRB |= _BV(DDB5); while(1) { /* set pin 5 high to turn led on */ PORTB |= _BV(PORTB5); _delay_ms(BLINK_DELAY_MS); /* set pin 5 low to turn led off */ PORTB &= ~_BV(PORTB5); _delay_ms(BLINK_DELAY_MS); } }
-
Terminalden dosyayı oluşturduğunuz klasöre
cd klasör_ismi
komutuyla girin. -
Ardından derleme işlemlerini yapmanız gerekiyor. Sırasıyla aşağıdaki komutları girin:
avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o main.o main.c avr-gcc -mmcu=atmega328p main.o -o main avr-objcopy -O ihex -R .eeprom main main.hex
-
Not defterine kaydettiğiniz port adınızı aşağıdaki komutta port_name ile belirtilen kısma yazın ve komutu çalıştırın.
avrdude -F -V -c arduino -p ATMEGA328P -P port_name -b 115200 -U flash:w:main.hex
-
Test kodu, Arduinonuz üzerindeki dahili ledi 1 saniye aralıklarla yanıp söndürmek içindir. Başarı ile çalışıyorsa, artık test kodu çalıştırma adımlarını tekrarlayarak c kodlarınızı Ardunio üzerinde çalıştırabilirsiniz.
Arduino UNO Pinout Diyagramı:
- Register; 8 ile 64 bit arasında, 2'nin kuvvetleri biçiminde, veri tutabilen bir bellek öbeğidir. Her bite 1 veya 0 değeri atanır. Microcontroller'daki bir çok farklı yazmaçtaki her bitin değeri sistemin geri kalanına ne zaman, ne yapacağını söyler. Arduino üzerinde bulundan ATmega328p çipindeki registerlerin çoğu 8 ya da 16 bittir.
-
Programcı bitwise operatörler yardımıyla registerin 0-7 arası bitlerine müdahale eder ve böylelikle yürütülecek programı yönetir.
-
ATmega328p mikrodenetleyicisinin data sheete yardımı ile, bu mikrodeneyleyicinin çevre birimlerini manipüle ederek programlama yapacağız. Burada I/O (Input/Output) ile alakalı üç ana register ve I/O ile ilişkili bir ayar biti bulunduran bir register ile başlayalım.
-
Mikrodenetleyicinin denetimi ile ilgili 5 biti bulunur. Bu bitleri 0 ya da 1 yaparak ayarlamaları yaparız.
-
Programlama yaparken bu registerin bitlerini değiştirmek istediğimizde, örneğin "PUD" isimli 4. bitini değiştirmek istediğimizde
MCUCR |= 0x10
ya daMCUCR |= 0b00010000
şeklindeMCUCR
adını kullanarak manipülasyon yaparız. -
Bu registerin bazı bitleri sadece okunabilirken bazıları hem okunabilir hem de yazılabilirdir. 0, 1 ve 4 numaralı bitler hem okunabilir hem de yazılabilirken 2, 3, 5, 6 ve 7. bitler read only yani sadece okunabilir bitlerdir.
-
MCUCR'nin 4. biti PUD'dur.
-
Bu bit 1 olduğunda tüm I/O portlarındaki internal Pull-Up dirençleri devre dışı kalır.
-
Bu bit 1 olduğunda, başka registerlerde tanımlanan Pull-Up enable'ın bir anlamı olmaz.
-
Belirtilen x, pinout diyagramda görülebilen portların temsilidir. x yerine yazılacak port adı, program akışında, komponentlerin bağlandığı yerlere göre değişkenlik gösterir.
-
Bu register, I/O portunun input için mi yoksa output için mi kullanılacağını belirler.
-
Bu registerin bitlerini 1 yapmak, o bitin output olduğunu, 0 yapmak ise o bitin output olduğunu söyler. Örneğin B portunun 0. ayağına (Arduino kartının 8. pini) bir led bağladıysak burada bu ayağı output olarak işretlemeliyiz yani
DDRB |= 0b00000001
veyaDDRB |= 0x01
şeklinde bir tanımlama yapmalıyız.
-
AVR mikrodenetleyicilerinde G/Ç yapılan bitler 8 bitlik port olarak bir araya toplanmıştır.
-
Ayakların her birini yazılımsal olarak kontrol etmek mümkünse de ayakların her biri porttan bağımsız değildir.
-
Ayakların 8'li gruplara ayrılmasının bir nedeni mikrodenetleyicinin 8 bit olmasıdır. Ancak en önemli nedeni tek ayaktan alınan giriş ve çıkışın tek başına bir şey ifade etmeyişidir.
-
Portlar 0 ile 255 arasında değer alabilir ve bu değerlerin 2'lik sistemdeki karşılığını ayaklara yansıtabilir. Aynı zamanda bu 0 ve 255 arasında yani bayt büyüklüğünde değeri de porttan okuyabilir.
-
Portların 8'li ayak grubu olması tek bir ayak üzerinden işlem yapılamayacağı anlamına gelmez. Ancak doğrudan değil dolaylı olarak bu işlemi gerçekleştiririz.
-
Dijital giriş için kullanılan yazmaçtır.
-
Ayaklardaki elektriksel durumu okumayı sağlar. Bir butonun açık veya kapalı oluşundan 8-bitlik bir paralel iletişim bağlantısını okumaya kadar bir çok örnek bu duruma verilebilir.
-
DDRx ile giriş olarak tanımlanan portlardan/pinlerden dığrudan port veya pin okuma yöntemi ile registerden elde edilen değer sonrasında mikrodenetleyicinin hafıza birimlerine kaydedilir ve bu değer üzerinde işlem yapılarak çıkış birimlerine iletilir. Burada verinin okunduktan sorna nasıl kaydedileceği, işleneceği ve çıkış olarak verileceği programcının yazdığı programa bağlıdır.
-
Portların ayakları Input, Input Pull-Up, Sink, Source ve Tri-State konumlarında olabilir.
-
Input: Input konumda harici olarak pull up veya pull down dirençleri ile beslemeye ya da şaseye bağlıdır.
-
Input Pull-Up: Input konumda, dahili pull-up direncine bağlıdır.
-
Sink: Output konumunda 0/LOW/FALSE durumudur. 0V, 20mA akım çeker. Bu yüzden sink(akmak) olarak adlandırılır.
-
Source: Output konumunda 1/HIGH/TRUE durumudur. 5V, 20mA civarında akım verir.
-
Tri-state: Ne mantıksal HIGH ne de mantıksal LOW demektir. Hükmü olmayan bir durumu temsil eder.
-
- Yukarıda, data sheetten alınan tabloda, portlarda oluşan durumların registerlerdeki hangi değerlerle oluştuğu verilmiştir.
Resim 3.2 | Resim 3.3 |
-
Yukarıda Atmega328p mikrokontrolcüsünün iki farklı kılıfı gösterilmiştir. Kılıflar yalnızca devre kartına montaj biçimini belirler. Bu fiziksel değişim mikrokontrolcünün iç mekanizmasında ya da işlenen komutlarda bir farklılık meydana getirmez.
-
DIP/DIL Kılıf (Dual In-Line Package): Bu kılıf biçiminde montaj komponent ayakları devre üzerindeki deliklerden geçirilerek kartın diğer yüzeyinden lehimleme yapılır.
-
SMT Kılıf (Surface-Mount Technology): Bu kılıf biçiminde montaj komponentin bulunacağı yüzeyde gerçekleşir ve lehim bu yüzey üzerinde yapılır.
-
-
Yukarıdaki pinout diyagramlarından görüleceği gibi mikrokontrolcünün ayakları port, iletişim, analog veya kesme gibi farklı amaçlar için kullanılabilir durumdadır.
-
Atmega328p mikrodenetleyicisinin güç ayakları dışındaki tüm ayakları portlardan oluşur. Bu portlar PORTB, PORTC ve PORTD olmak üzere üç adettir.
-
PORTD 8 ayaktan oluşurken PORTB ve PORTC mikrokontrolcüdeki kısıtlı ayak sayısı dolayısıyla 8 ayağa sahip değildir. Gelişmiş uygulamalarda bu portların yetersiz gelmesi muhtemeldir.
- x portunun n'inci ayağı Pxn yada PORTxn biçiminde ifade edilir.
#include <avr/io.h>
int main(void){
DDRD = 0xFF; //0b11111111 şeklinde de ifade edilebilir.
PORTD = OxFF; //0b11111111 şeklinde de ifade edilebilir.
while(1){}
}
-
Yukarıdaki kod Arduino UNO kartının 0,1, 2, 3, 4, 5, 6 ve 7 numaralı pinlerine bağlı ledleri yakar. Koda ait devre şemasına ve simülasyona tinkercad üzerinden erişebilirsiniz. Kodların açıklaması aşağıda yapılmaktadır.
-
#include <avr/io.h>
: Bu komut ile avr input/output kütüphanesini kodumuza ekliyoruz. -
int main(void){}
: Main fonksiyonu c dilinde ana program fonksiyonudur. Yazılan kodların main fonksiyonu içerisinde bulunmaması ya da çağrılmaması durumunda programda bir etki yaratmaz. Bu fonksiyon içindeki komutlar aşağıdan yukarıya, aksi belirtilmedikçe, bir defa yürütülür. -
DDRB = 0xFF;
: DDRB registerinin değerini 0xFF yani 8 bitinin de 1 yapılmasını sağlayan ifadedir. Bu kod satırı kullanılarak Data Direct Registerinin B kısmının 8 bitinin de çıkış olduğu belirtilmiştir. Burada, değerlerin 1 yapılması 16'lık tabanda belirtilmiştir. Ancak bu değerler 2'lik taban kullanılarak da değiştirilebilir. -
PORTB = 0xFF
: PORTB yazmacındaki tüm bitlerere 1 değeri verilerek bu portun hepsinden 5 volt çıkış sağlanmıştır. Burada, değerlerin 1 yapılması 16'lık tabanda belirtilmiştir. Ancak bu değerler 2'lik taban kullanılarak da değiştirilebilir. -
while(1){}
: Main fonksiyonu bir defa çalıştırıldığından main fonksiyonu içerisine direkt yazılan kodlar başlangıçta bir kere işletilecektir. Ancak mikrokontrolcü kullanımında bir işlevin birden fazla yapılması istenebilmektedir. Bu yüzden main fonksiyonu içerisine yazılan ve sürekli çalışan bir while döngüsü tanımlanarak süreklilik sağlanır.
-
Elektronik mantık devrelerinde bir pull-up ya da pull down direnci gelecek sinyalin parazitini önlemek amacıyla kullanılır.
-
Genellikle button, switch, sensör gibi komponentlerin daha düzgün çalışması için kullanılırlar.
Aşağıda Pull-up ve Pull-down dirençlerininin nasıl bağlanacağı devre şeması ile anlatılmaktadır.
Resim 5.1 |
-
Pull-Up direnci ile bağlanan komponentten alınan sinyal sürekli 1 olur ve durum değiştirme halinde 0 sağlanır.
-
Pull-Down direnci ile bağlanan komponentten alınan sinyal sürekli 0 olur ve durum değiştirme halinde 1 sağlanır.
- Lojik kararsızlığın önlenmesi için ayağı boşta bırakmak yerine bir direnç ile beslemeye ya da şaseye bağlanıp HIGH veya LOW yapmak gerekir. Böylelikle ayağın sabit değeri belirlenmiş olur ve farklı değerler, bu değere göre saptanabilir hale gelir. Aşağıda bir örnek ile input ve output alma işlemi yaparak bu durumu daha iyi anlayalım:
#include <avr/io.h>
int main(void){
DDRD = 0x00; //D portunun tüm ayakları giriş olarak tanımlandı
DDRB = 0xFF; //B portunun tüm ayakları çıkış olarak tanımlandı
while(1){
PORTB = PIND;
/* PORTB yazmacının değerini doğrudan PIND yazmacının değerine eşitleyerek PIND yazmacında bir değişiklik olduğunda bu değişikliğin PORTB yazmacına yansıtılması sağlandı. */
}
}
Tinkercad üzerinden simüle edebilirsiniz.
-
Bir işlem için bekleme yapılması istendiğinde util/delay.h kütüphanesi içerisinden _delay_ms(int) fonksiyonu veya türevleri kullanılabilir.
-
Duraklatma ya da bekleme işlemi sadece işlemleri bekletmek için değil, işlemlerin daha doğru bir biçimde yapılması için de kullanılmaktadır.
-
Aşağıda ledi 1 saniye aralıkla yakıp söndüren bir kod örneği verilmiştir. Bu kodu tinkercad üzerinden simüle edebilirsiniz.
#include <avr/io.h>
#include <util/delay.h>
#ifndef F_CPU
#define F_CPU 16000000UL
/*
F_CPU değeri delay kütüphanesinin kullanacağı bir değerdir.
Kütüphanelerin içerisinde bulunup bulunmadığını bilmediğimiz
için ifndef önişlemci komutu ile eğer tanımlanmamışsa
tanımlama yaptığımızı belirttik.
Bu değer kullandığımız mikrodenetleyicinin saat frekansıdır.
Frekans sayısının sonunda belirtilen UL ise sayının unsigned
long olduğunu belirtir.
*/
#endif
int main(){
DDRB = 0xFF;
while(1){
PORTB = 0xFF;
_delay_ms(1000);
/*
1 saniye bekleme süresi için fonksiyon çağırılırken
1000 milisaniye biçiminde parametre gönderilmiştir.
*/
PORTB = 0x00;
_delay_ms(1000);
}
}
- Portlar manipüle edilirken her zaman porta değer atayarak işlem yapmak efektif olmaz. Bu nedenle bitwise operatörler yardımıyla portlara ait bacakların değerleri tek tek ya da bütünüyle değiştirilebilir. aşağıda bu işlemlerin nasıl yapılacağı örneklerle anlatılacaktır.
-
Değer Atama Yoluyla Port Denetimi:
-
=
operatörü ile port yazmacına istediğimiz sabit bir değeri veya değişkeni atayabiliriz. -
Değer atama yöntemiyle port manipülasyonu gerçekleştiğinde 8 bitlik portun bütün değerleri atanan değer ya da değişkene göre güncellenecektir. Yani port bacakları tek tek değil bütün bir halde değerlendirilecektir.
-
Temel yazımı aşağıdaki gibidir.
DDRD = 0xFF; // veya DDRB = 0b00000000; //PIN veya PORT yazmacı ile de aynı biçimde kullanılabilir. PORTD = 0xFF; PINB = 0x00;
-
-
Bitwise Operatörler İle Port Denetimi:
- Port yazmaçlarına bağlı bacaklar tek tek manipüle edilmek istendiğinde bitwise operatörler ile manipülasyon yapılmalıdır.
Operatör | Operatörün İşlevi |
---|---|
& | Bitwise AND |
| | Bitwise OR |
^ | Bitwise XOR |
~ | Bitwise Tersleyen ((r-1)'e göre ters alır) |
<< | Bitwise Sola Kaydırma |
>> | Bitwise Sağa Kaydırma |
|= | OR Eşittir |
&= | AND Eşittir |
^= | XOR Eşittir |
~= | Tersleyen Eşittir ((r-1)'e göre ters alır ve atama yapar.) |
- Yukarıdaki tabloda bitwise operatörler gösterilmiştir.
- Temel kullanımlara örnekler aşağıda verilmiştir.
DDRD |= 0xFF;
// DDRD registerinin bitleri 1 ile veyalanarak atama yapıldı.
PORTD |= 0xFF;
// PORTD registerinin bitleri 1 ile veyalanarak atama yapıldı.
PORTD &= 0x00;
// PORTD registerinin bitleri 0 ile ve'lenerek atama yapıldı. Eğer veya kullanılsaydı değerler 0 yapılamazdı.
PORTD |= (1 << PORTD0);
// PORTD registerinin 0. biti 1 yapıldı.
PORTD |= (1 << 0);
// PORTD registerinin 0. biti 1 yapıldı.
PORTD ^= 0xE2;
// PORTD registerinin değerleri E2 on altılık sayısı ile xorlanarak atama yapıldı.
-
https://www.lojikprob.com/avr/c-ile-avr-programlama-60-butun-derslerin-listesi/
-
https://www.instructables.com/Microcontroller-Register-Manipulation/
-
Resim 0.1: https://commons.wikimedia.org/wiki/File:Arduino-uno-pinout.png
-
Resim 3.2: https://doc.riot-os.org/group__boards__atmega328p.html
-
Resim 3.3: https://www.reddit.com/r/arduino/comments/gyrdii/atmega328p_tqfp32_pinout/
-
Resim 5.1: https://circuitdigest.com/tutorial/pull-up-and-pull-down-resistor