Gömülü Yazılımlarda Assembly ve C Dilleri, Sürücüler, Kütüphaneler

source-code-583537_1280Gömülü sistemler programlamada çoğu zaman bazı kavramlar çok karışıyor. Onlara kısaca açıklık getirmeye çalışacağım. Aslında bu konuların birçoğunun tartışması web, mobil ve masaüstü yazılım dünyalarında yıllar önce geride kalmış olsa da gömülü sistemlerde hala tartışılan ve bir anlamda “yeni keşfedilen” durumlar.

Aslında önce “mikroişlemcileri” kısaca tanımamız gerekiyor. Aslında dijital kapılarla oluşturulan çeşitli birimleri içeren ve toplama, karşılaştırma  ve sayma işlemleri yapabilen yapılara mikroişlemci diyoruz. Diğer bütün işlemler de toplama işleminden türetilebiliyor. Mikrodenetleyicilere de içerisinde işlemci, program belleği ve geçici bellek (RAM), çeşitli çevrebirimleri içeren tek çip düzeyindeki bilgisayarlar diyebiliriz. Gömülü sistemlerde genelde mikrodenetleyicileri kullanıyoruz.

Assembly Dili

Her mikrodenetleyicinin işlerini yürüttüğü bir “komut seti” bulunuyor. Kendi komut setine “assembly dili” diyoruz. Ancak bu dil her işlemci mimarisine göre farklı olabiliyor. Tabii ki temelde yapılan işlemler aynı olduğundan her dilde benzer yapılar yer alıyor. Örnek olması açısından aşağıdaki linklerden 8 bit mikrodenetleyici mimarilerinden PIC (Microchip), AVR (Atmel) ve 8051 komut setlerine bakabilirsiniz:

PIC – Orta Sınıf Komut Seti
AVR Komut Seti
8051 Komut Seti

Kullandığımız mikrodenetleyicileri kendi assembly dilinde programlamamız gerekiyor. Ancak bunun yerine programlarımızı derleyicilerin yardımıyla farklı “üst seviye dillerde” yazabiliyoruz. Derleyiciler de bu kodları kullandığımız mikrodenetleyicinin kendi assembly komutlarına çeviriyor. “Üst seviyeden” kastettiğimiz biz insanlara daha kolay gelen ancak işlemcilerin kullanamayacağı diller demek. Gömülü sistemlerde en çok kullanılan dil şu an için C programlama dili. Hemen her mikrodenetleyicinin bir C derleyicisi bulunuyor. Bu yüzden assembly dilinde yazmak zorunda kalmıyoruz. Günümüzde derleyiciler geldiği nokta itibariyle elle yazılmış assembly koduna yakın verimlilikte çalışan kodlar üretebiliyorlar. Ancak bu işleri ilk öğrenmeye başladığımızda, işlemcilerin nasıl çalıştığını ve işlemleri nasıl gerçekleştirdiğini görmek için assembly dilini kullanmakta fayda var. Zaten en başta söylediğim gibi diğer dillerde yazdığımız kodlar da arka planda işlemcinin kendi assembly kodlarına çevriliyor.
[ad#ad-1]

Çevre birimleri ve sürücüler (drivers)

Mikrodenetleyicileri programlamayı iki ana gruba ayırabiliriz:

  1. Çevrebirimlerini programlama
  2. Kendi uygulama programımızı yazma

Gerçekleştirmek istediğimiz sistemler için mikrodenetleyicilerin çeşitli çevre birimlerini kullanırız. Örneğin zamanlama veya belirli olayların gerçekleşme sayısını ölçmek için zamanlayıcı ve sayıcılar (timer / counter),  gerilim ölçmek için analog-dijital dönüştürücüleri(analog-digital converter – ADC) ve haberleşme için seri haberleşme modülleri (UART, SPI, I2C) gibi birimleri kullanırız. Bu çevrebirimlerini de işlemcinin “bellek haritasındaki” çeşitli adreslerde yer alan kaydedicilere (register) belirli değerler atayarak yapıyoruz. Bu değerlerin ne olduğu ve hangi değerleri yazmamız gerektiği de kullandığımız mikrodenetleyicinin veri dökümanlarında (datasheet) üreticiler tarafından yazılıyor. Çevrebirimlerini istediğimiz duruma getiren fonksiyon ve kodların tümüne birden “sürücü” (driver) diyoruz. Bu sürücüleri kendimiz yazabileceğimiz gibi zaman zaman da üreticilerin sağladığı hazır kodları kullanabiliyoruz. Basit modellerde bu işlemler birkaç satırlık kod iken karmaşık özelliklere sahip mikrodenetleyicilerde sürücü yazmak daha uzun süre alabiliyor. Bu nedenle üreticiler hem müşterileri olan mühendislerin işlerini kolaylaştırmak hem de daha fazla ürün satabilmek adına hazır sürücüler ve örnek kodlar da sağlayabiliyorlar.

KÜTÜPHANELER

Kütüphane kavramı da aslında belirli işlemleri yapmak için oluşturulmuş fonksiyon gruplarına verilen bir isim. Örneğin farklı çevre birimlerine göre zamanlayıcı, seri haberleşme, analog-dijital çevrim gibi kütüphaneler bulunabilir. Kendi yazdığımız ya da hazır olarak sağlanan kütüphaneleri kullanabiliriz.

İşte aslında Arduino, mbed gibi platformlar da aslında belirli mikrodenetleyiciler üzerine yazılmış kütüphane gruplarından başka birşey değiller. Örnek olarak Arduino kurulum klasörlerinde “\hardware\arduino\avr\cores\arduino” klasörü altına baktığınızda Arduino içerisindeki temel kütüphanelerin kaynak kodlarına ulaşabilirsiniz. Arduino, AVR serisi mikrodenetleyiciler üzerinde çalışıyor. Örneğin “wiring_analog.c” dosyasına baktığınızda analog işlemler için yazılmış sürücü fonksiyonlarını görebilirsiniz. Kodlar içerisindeki ADCSRB, ADSC gibi isimler mikrodenetleyicinin kaydedicilerinin isimleri. Ayrıca Arduino farklı tip Atmega mikrodenetleyiciler üzerinde çalıştığından her birisi için farklı kaydediciler kullanılabilmesi için farklı tanımlamalar da yer alıyor. Gördüğünüz gibi tek bir satırda çağırdığımız Arduino fonksiyonları için arka planda satırlarca kod yer alıyor. Arduino fonksiyonları olmadan da kendimiz aynı sürücüleri yazabilirdik. Arduino bu işlemleri kullanıcıdan “gizleyerek” daha basit bir programlama imkanı veriyor. Hepsi bu!

Arduino analogRead() fonksiyonunun kodları (wiring_analog.c dosyası):

 
int analogRead(uint8_t pin)
{
	uint8_t low, high;

#if defined(analogPinToChannel)
#if defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#endif
	pin = analogPinToChannel(pin);
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

#if defined(ADCSRB) && defined(MUX5)
	// the MUX5 bit of ADCSRB selects whether we're reading from channels
	// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  
	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
	// to 0 (the default).
#if defined(ADMUX)
	ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif

	// without a delay, we seem to read from the wrong channel
	//delay(1);

#if defined(ADCSRA) && defined(ADCL)
	// start the conversion
	sbi(ADCSRA, ADSC);

	// ADSC is cleared when the conversion finishes
	while (bit_is_set(ADCSRA, ADSC));

	// we have to read ADCL first; doing so locks both ADCL
	// and ADCH until ADCH is read.  reading ADCL second would
	// cause the results of each conversion to be discarded,
	// as ADCL and ADCH would be locked when it completed.
	low  = ADCL;
	high = ADCH;
#else
	// we dont have an ADC, return 0
	low  = 0;
	high = 0;
#endif

	// combine the two bytes
	return (high << 8) | low;
}

[ad#ad-1]

Performans mı sağlam yazılımlar mı?

Gömülü yazılım geliştirirken genellikle iki ayrı tercih arasında gidip geliriz: Yüksek performans ya da geliştirme, bakım ve okunabilme kolaylığı. En yüksek hızda çalışan uygulamalar işlemcinin kendi assembly dilinde yazılan kodlardır. Ancak assembly’de yazmak hem zahmetli hem de zaman alıcı bir süreçtir. Daha önce de belirttiğim gibi hem derleyicilerin hem de işlemci hızlarının geldiği nokta itibariyle C gibi dillerde yazılan programlar da yüksek performanslı olarak çalışabilmektedir. Ancak yazılım geliştirirken tek derdimiz yüksek performans değil. Aynı zamanda projelerimizi hızlıca geliştirmek ve daha da önemlisi kolay bir şekilde bakımı yapılabilen, düzgün bir yapıda geliştirilmiş projeler ortaya çıkarmak gerekiyor. Bunun için de hazır kütüphaneler kullabiliyor ya da kendi kütüphanelerimizi oluşturabiliyoruz. Kütüphane kullanımı performans açısından handikap oluşturuyor gibi görünse de zaman kazandırma ve kod organizasyonu açısından avantajlar içeriyor.

Projeler için mikrodenetleyici seçerken en büyük kriterlerden birisi geliştirme araçları ve bunların kullanım kolaylığı oluyor. Bu nedenle çip üreticisi firmalar da artık sattıkları mikrodenetleyiciler için hazır sürücüler, geliştirme kütüphaneleri gibi ek hizmetler sunuyorlar. Özellikle yeni bir mikrodenetleyici ailesi ile çalışmaya başlarken hazır kodlar üzerinden gitmek zaman tasarrufu anlamına geliyor. Sonrasında yetersiz kaldığı veya hatalar bulunan durumlarda kendi sürücülerimizi ve kütüphanelerimizi de oluşturabiliyoruz.

Bu yazımda, gömülü sistemler üzerinde yazılım geliştirmede en çok duyulan ve genelde de kafa karışıklığı yaratan konulara kısaca değinmeye çalıştım. Zaman zaman diller arasında, veya hazır kütüphane kullanıp kullanmama konusunda anlamsız tartışmalara şahit oluyoruz. Daha önceki bir yazımda da belirttiğim gibi en önemlisi bütün araçların özellikleri ve avantajları hakkında bilgi sahibi olup gerektiği yerde gerekli aracı kullanabilmek. Yapılan işlere de sadece kod geliştirme olarak bakmayıp sağlam, doğru çalışan ve ileride de bakımı ve geliştirmesi kolay yapılabilen yazılımlar geliştirmeyi amaçlamalıyız. Gömülü sistemlerin geldiği durum itibariyle “yazılım kalitesi” artık hayati önem taşıyor.