AVR ile DS1307 Kullanımı
Bir önceki makalede DS1307 üzerinde yer alan 24C32 EEPROM entegresini kullanmayı anlatmıştım. Burada ise DS1307 entegresini kullanmayı anlatacağım. Modülü aldığınızda iki entegre ile beraber geldiği için bir önceki makalede öğrendiklerinizi burada kullanma imkanına sahipsiniz. Tek bir DS1307 modülü ile de iki uygulamayı yapma imkanınız vardır.
DS1307 entegresi RTC yani gerçek zamanlı saat entegreleri arasında en yaygın ve en bilineni olsa gerektir. Oldukça uygun fiyatı ve Çin üretimi modüller sayesinde hemen hemen her robotik dükkanında veya satış sitesinde kolayca bulabilirsiniz. Yalnız burada dikkat etmeniz gereken bir nokta da bu entegrelerin eğer Çin kaynaklı yerden alıyorsanız klon olma ihtimalinin çok yüksek olduğudur. Çünkü üreticinin kendi sitesinde yer alan tek bir entegrenin fiyatından daha aşağısına bir modül alma imkanınız oluyor. Ben çok yüksek teknoloji olmadıktan ve hassas iş yapılmadıktan sonra basit seviyede klon entegre kullanmaya çok olumsuz bakmıyorum. Yalnız mesela DS3231 gibi içinde hassas osilatör devresi bulunan ve teknoloji bakımından daha ileri entegrelerin orijinalini almak gerekebilir. Piyasada yer alan DS3231 modüllerinin klon olduğunu söylememize gerek yoktur sanırım. Biz burada eğitim uygulamaları yaparken bunu çok düşünmesek de yarın birgün aynı parçaları profesyonel işlerde kullanmaya kalkarsanız başınız ağrıyabilir. Bunu sadece DS1307 bağlamında değil genel olarak klon-orjinal bağlamında anlamanız gereklidir.
DS1307 entegresinin nasıl kullanılacağını yine datasheetten öğreniyoruz. Yalnız Maxim firmasının takdir ettiğim bir yanı da datasheet yanında bolca uygulama notlarını yayınlamalarıdır. Bu yüzden sadece datasheet değil tasarımda uygulama notlarından da yararlanabilirsiniz.
Datasheeti açıp incelediğimizde baş kısımda pek çok elektrik karakteristiği ve teknik özellik yer almakta. Bizim elimizde hazır modül yer aldığı için devre tasarımı ile çok işimiz olmayacak. Aslında koca datasheette en önemli ve odaklanmamız gereken nokta şu tablo olacaktır.
Dijital entegrelerle çalışırken özellikle SPI ve I2C protokolleri üzerinden iletişime geçiyorsak mikrodenetleyicinin çevre birimlerini kullanırken olduğu gibi adres ve değer tabanlı çalışmaktayız. Burada gördüğünüz gibi en başta bu dijital devrenin yazmaçlarının adresi 0x00, 0x01, 0x02… diye devam etmekte ve bu yazmaçların belirli bitleri özel verileri tutmaktadır. Bu durumda bizim yapacağımız iş yazma anında doğru bitlere veri yazmak, okuma anında ise doğru bitlerden veri okumaktır.
Buradan her ne kadar basit bir veri okuma ve yazma işlemi gibi görünse de uygulamada bizi biraz uğraştıran tarafı olmaktadır. Fark ettiyseniz burada değerler bizim yazılımda kullandığımız tipte olmayıp BCD formatında yer almaktadır. Mesela 0x00 adresinde BIT 6, BIT 5 ve BIT4 “10 Seconds”, BIT3, BIT2, BIT1 ve BIT0 ise “Seconds” olarak belirtilmiştir. Yani saniye verisinin her bir basamağı ayrı ayrı olarak ele alınmaktadır. Aynı durum bütün tarih verileri için geçerli olmakla beraber 0x07 yazmacının da kontrol yazmacı olduğunu görmekteyiz. 0x08–0x3F arası ise RAM bellek olarak görev yapıp şu anlık bir işimize yaramamaktadır.
Okuma ve yazma işleminin nasıl yapıldığına dair datasheetin devamında bir diyagram yer almaktadır.
Okuma modunda herhangi bir adres yazılmadığını görsek de belli bir adresten itibaren okuma yapmak için öncelikle hangi adresten itibaren okuma yapacağımızı belirtmek gereklidir. Bunun diyagramı da şu şekildedir.
Burada gördüğümüz üzere öncelikle S ile START durumu oluşturulup I2C aygıtının adresi ile R/W bitine WRITE durumunu yazmaktayız. Sonrasında ise okunacak baytın adresini yazdıktan sonra tekrar okuma durumunda iletişimi başlatıyoruz. Burada R/W üzerinde çizgi olduğunu, yani tersleme durumunun olduğunu fark etmeniz gerekli. Orada 0 olarak belirtilse de biz yazılımda 1 yapacağız. Bütün bunları öğrendiğimize göre şimdi bir okuma uygulaması yapalım.
Bu uygulamada da daha öncekilerde olduğu gibi Peter Fluery’in kütüphanelerini kullandım. Öncelikle uint8_t tipinde sec, min, hour, weekDay, date, month, year adında değişkenler tanımlamaktayız. Fark ettiyseniz her bir değişken bir yazmaca denk gelmekte, her bir yazmaç da belli bir tarih seviyesinde veriyi tutmakta. Yani saniye için ayrı, yıl için ayrı yazmaç yer almakta. Daha önce i2cscanner programı ile tespit ettiğim adres verisini ise en başta RTC_ADDR olarak tanımladım.
#define RTC_ADDR (0x68 << 1)
Eğer siz DS1307 modül kullanıyorsanız EEPROM ile beraber iki tane adres verisi göreceksiniz. Bu adreslerin hangilerinin hangi aygıtlara ait olduğunu bilmenin tek yolu datasheetleri incelemektir. Ayrıca her aygıtın belli bir adres aralığı olduğu için bir veya birkaç aygıtın adresini biliyorsanız ötekileri bulmak çok zor olmayacaktır.
i2c_start(RTC_ADDR+I2C_WRITE);
i2c_write(0x07);
i2c_write(0x00);
i2c_stop();
Burada kontrol yazmacındaki ayarları sıfırladım. Okumaya doğrudan bir etkisi olmasa da şu anki uygulamada kare dalga gibi özellikleri kullanmaya gerek yok.
i2c_start(RTC_ADDR+I2C_READ);
sec = i2c_readAck();
min = i2c_readAck();
hour = i2c_readAck();
weekDay = i2c_readAck();
date = i2c_readAck();
month = i2c_readAck();
year = i2c_readNak();
i2c_stop();
Burada okuma uygulaması yapmaktayız. Değişkenleri nasıl sırasıyla okuduğuma dikkat ediniz. Aynı datasheette yazan sırayla her bir değişkeni okuduk ve karışmamasına dikkat ettik. i2c_readAck() fonksiyonu okumayı yapar ve “Daha okuyacağım var.” diyerek aygıta bildirimde bulunur. i2c_readNak() fonksiyonu ise “Bu son okumam.” diyerek bildirimde bulunur ve son okumayı yaptıktan sonra okumayı bitirir. Bu sayede sırayla okuma yapmaktayız. Okuma bittikten sonra ise i2c_stop() ile bağlantıyı kesiyoruz.
Buraya kadar okuma yapıldı ve değerler değişkenlere aktarıldı ama bunlar üzerinde işlem yapmadıkça bu haliyle pek bir anlam ifade etmeyecektir. Neyse ki C dilinin formatlı çıkışı sayesinde tek tek bitlerle uğraşmadan bunları yazdırma imkanına sahibiz.
sprintf(buf,"\n\rSaat:%2x:%2x:%2x \nTarih:%2x/%2x/%2x",(uint16_t)hour,(uint16_t)min,(uint16_t)sec,(uint16_t)date,(uint16_t)month,(uint16_t)year);
Burada %2x diyerek onaltılık biçiminde ve iki basamaklı halde her bir değişkeni yazdırıyoruz. Bu bir nevi BCD formatında yazdırmak demektir. On altılık tabanda her bir rakam 4 bitlik yer kapladığından eğer bunu onaltılık tabanda yazdırırsak bitler BCD’de olduğu gibi 4'er 4'er bölünecektir.
Okuma uygulamasında elde ettiğim saat değerleri de tecrübe açısından dikkate değerdir. Ben elimdeki RTC modülünü en son 2015 yılının sonlarında kullanmış ve bir kenara koymuştum. Üzerinde pil yer aldığı için yıllarca çalışmaya devam etti ve en son bu yıllarda elime alınca tekrar saat değerini okudum ve saatin 5 yılda yaklaşık 4 saat ileri saptığını gördüm. Modülün durduğu ortamın oda sıcaklığında olması belki de çok fazla sapmasının önüne geçmiş olabilir ama uygulama notlarında düz saat kristali ile beslenen RTC’lerin yılda bir saat kadar saptığı yazmakta.
Bu sapmanın diğer tecrübeleri de dikkate alırsak genellikle ileri sapma olduğunu görmekteyiz. Kesin çözüm olmaktan uzak olsa da bu sapma oranı sabit gibi göründüğünden programla düzeltilebilme ihtimali var. Bunun dışında eğer çok hassas uygulamalarınız varsa, saati güncelleyemiyorsanız çok daha iyi çözümler söz konusudur. Bir sonraki makalede anlatacağım DS3231 buna en iyi örneklerden biridir.
Şimdi yazma uygulamasını anlatarak makaleyi bitirelim.
Burada biraz kullanışsız bir uygulama yazsak da hem basit olmakta hem de oldukça iş görmekte. Öncelikle tanımlanan değişkenleri ve ne için kullanıldıklarını aktaralım.
uint8_t secx10=0;
uint8_t sec = 0;
uint8_t minx10=3;
uint8_t min = 3;
uint8_t hour = 9;
uint8_t hourx10 = 1;
uint8_t weekDay = 0;
uint8_t date = 1;
uint8_t datex10=0;
uint8_t month = 1;
uint8_t monthx10 = 1;
uint8_t year = 0;
uint8_t yearx10 = 2;
Burada secx10 diye belirttiğim değişken saniye değerinin ikinci basamadığır. sec değişkeni ise saniye değerinin birinci basamağıdır. Mesela saniye verimiz 56 ise secx10 5, sec 6 olmalıdır. Aynı şekilde dakikanın ikinci basamağı minx10, birinci basamağı min 3 diye belirtilmiştir. weekDay ise haftanın gününü belirlemekte ve pazar günü 1'den başlamaktadır. Bundan başka date ayın kaçında olduğumuzu, month kaçıncı ayda olduğumuzu, year ise kaçıncı yılda olduğumuzu (2020 -> 20) belirtir. Buna göre her bir DS1307'yi programlamak için bu kodu çalıştırıyor ve tarih verisini el ile giriyoruz.
Yalnız burada örneğin saniye değeri için iki adet değişken olsa da normalde saniye için tek bir 8 bitlik yazmaç yer almaktadır. Bu durumda bizim iki değişkeni tek bir adrese atamamız söz konusudur. Bunu ise aşağıdaki kodla maskeleme yöntemini kullanarak yapmaktayız.
i2c_write(((secx10<<4) | sec));
Burada datasheette belirtildiği gibi 10 saniye kısmını 4 bit sola kaydırıyor ve üzerine sec kısmını ekliyoruz. Okuma işlemi gibi yazma işleminin de sırayla yapıldığına dikkat ediniz. Burada kuralları datasheet belirlemektedir.
Bir önceki EEPROM uygulaması ve bu uygulama ile beraber çok önemli bir kazanımı elde ettiğinizi söylemeliyim. Gömülü sistemlerde öncelikle bir mikrodenetleyicinin çevre birimlerini nasıl kullanacağımızı öğreniriz. Bunu daha önceki kitap ve makalelerde fazlasıyla gördük. Sonrasında ise ilerlememiz gereken nokta harici çevre birimlerini kullanmaktır. Bu harici çevre birimleri ise genellikle SPI ve I2C protokolünü kullanan dijital entegrelerdir. Eğer gelişen teknolojiye ayak uydurmak istiyorsanız örneğin sıcaklık ölçmek için LM35 veya termistör kullanacağınıza SPI ve I2C üzerinden haberleşen ve çok daha iyi sonuçlar veren dijital entegreleri kullanmanız gereklidir. Bu dijital entegreleri kullanmak size yazılım tarafında bir yük getirecek ve her zaman kolay olmayacaktır. Ama işin temelinde burada yaptığımız gibi belli adreslerdeki yazmaçlardan verileri okuyup onlara veri yazmak vardır. Bu mantığı öğrendikten sonra diğerlerini kullanmak zor olmayacaktır.