7 Aralık 2025 Pazar

Arduino ve MAX7219 ile 8x8 LED Matrix Display Nasıl Kullanılır?


Merhaba. Bu yazıda MAX7219 entegresini ve bununla 8x8 LED Matrix'in nasıl sürüleceğini anlatacağım. Entegrenin, Arduino için yazılmış bir kütüphanesi olduğu için örnekleri Arduino ile yapacağım ama seri veri yolu protokolü mikrodenetleyiciden bağımsız olduğundan, başka denetleyicilere de kolayca uygulanabilir. MAX7219 da oldukça basit bir entegre, kütüphane olmadan doğrudan erişmek de kolay. Örneklerden birinin kütüphane olmadan nasıl yapılacağını da göstereceğim.


MAX7219 ve MAX7221 Entegreleri

MAX7219 ve MAX7221 entegreleri, SPI ile 7 segment display veya 8x8 LED matrix sürmek için arayüz sunan entegreler. Pin ve komut uyumlular, datasheet'leri ve yaptıkları iş hemen hemen aynıysa da MAX7221, SPI dışında başka seri protokolleri de destekliyor ve daha kararlı çalışıyor. Dolayısıyla daha pahalı.

Entegre, ondalık noktalı (decimal point) sekiz adet 7 segment display'i, bar-graph display'leri ve 8x8 LED matrixlerini destekliyor. Bunlar için farklı kod çözme (decode) seçenekleri var. 64 LED, 8 tane ortak katot üzerinden sürülebiliyor. Ben hazır kitlerden aldığım için entegre pin bağlantılarına değinmeyeceğim.

Entegrelerin güzel bir özelliği de sekiz taneye kadar kaskadlanabiliyor olmaları. Yani bu modülden sekiz tane alıp birinin DOUT bacağını diğerinin DIN bacağına zincirleme bağlayarak daha fazla sayıda LED display'i de sürebilirim. Kitin arka tarafındaki 5-pin konektör kaskadlama için. Elbette farklı display türleri de kaskadlanabiliyor.

Benzer şekilde dört tane 8x8 LED matrix display de kaskadlanmış hazır kit olarak satılıyor. Kaskadlamanın nasıl çalıştığını anlamak için bir tane de 4lü LED matrix display aldım. Bunu entegrenin yazmaçlarına değindiğim zaman ayrıntılı açıklayacağım.

Baskı devreye arkadan bakıldığı zaman kaskadlamanın nasıl ve ne kadar basitçe yapıldığı kolayca anlaşılıyor:

Modülün beş tane bacağı var. Vcc ve GND bacakları açıklama gerektirmiyor. DIN (Data In) verinin yazıldığı bacak. CS (Chip Select) bacağı active low. Bu bacak aktif olduğunda DIN'e bağlı shift register latch'ları açılıp veri okunuyor. CLK, DIN'den gelen veriye eş zamanlı saat sinyalini taşıyor.

Buradaki Türkçe'den ötürü bir parantez açma ihtiyacı hissettim. Normalde kullandığım dile özen göstermeye çalıştığımdan yukarıda cümle beni rahatsız ediyor. Diğer yandan yukarıdaki terimlerin oturmuş Türkçe karşılığını bulamıyorum. Shift register = kaydırma yazmacı, evet anlaşılabiliyor ama anlamı tam karşılamıyor gibi. Peki, Latch = Mandal(?). Türkçe Wikipedia'da Flip flop maddesinde parantez içinde "yaz-boz" verilmiş. Bazı yazarlar terimleri kendi Türkçeleştirmeye çalışıyor ama bu terimler kullanılmayıp yaygınlaşmadığında, yazılar sadece yazarının anlayabildiği anlamsız bir metinden öteye gitmiyor. O yüzden bu terimler beni rahatsız etse de olduğu gibi bırakacağım.

Yazmaçlar

Yazının başında MAX7219'un oldukça basit bir entegre olduğunu söylemiştim. Bütün işlemler toplam 14 yazmaçla yapılıyor. Bunların sekizi LED'leri kontrol eden veri yazmaçları. Yazmaçların listesini kolaylık olsun diye datasheet'ten alıp yana ekledim.

Bu adreslerin (veya başka bir bakış açısıyla komutların) hepsi 8-bit ve hepsinden sonra 8-bitlik bir veri alanı geliyor. Başka bir deyişle DIN'e (Data IN) gelen her paket 16-bit olmalı. Veri göndermek için CS' (nChipSelect) sinyalini lojik 0 yapmak ve veriyi DIN'den CLK ile senkron şekilde göndermek gerekiyor. CS'´yi tekrar lojik 1 yaparak veri gönderimi sonlandırılıyor. Datasheet'in "Serial Addressing Modes" bölümünde iletişim ayrıntılı anlatılıyor.

İlk yazmaç olan No-Op'u en son ele alacağım, çünkü No-Op aslında tam olarak No-Op işlemi yapmıyor.

Digit 0..7 yazmaçları, 8x8 LED Matrix'in bir LED satırını veya 7-segment display'in bir basamağını tutuyor. Örn. Digit 0'a 0x0F değerini göndermekle, yani veriyoluna 0x01 0x0F verisini sürmekle ilk satırın en sağdaki dört LED'in yakıp, en sol dörtlüyü söndürmüş oluyorum. Digit 1 için bu ikinci satır, Digit 2 için üçüncü satır vb.

Decode Mode (0x09) yazmacı entegrenin içindeki 7-segment decoder birimini kontrol ediyor. Buraya 0xFF değeri yazılırsa, Digit X yazmaçlarına gelen verilerin yalnızca düşük dört biti dikkate alınıp, 7-segment display için çözümleniyor, 0x00 yazılırsa herhangi bir kod çözme işlemi yapılmıyor. 8x8 LED Matrix için doğru olan mod bu.

Intensity (0x0A) yazmacı PWM ile LED'lerin parlaklığını ayarlamak için kullanılır.

Scan Limit (0x0B) yazmacı, bütün LED'ler kullanılmayacaksa (örn. ondalık noktası olmayan 7 segment display) bağlı olmayan pinleri deaktive ederek, kullanılan LED'in tarama hızını arttırmak için kullanılır. LED Matrix'te bütün LED'ler kullanılacaktır.

Shutdown (0x0C) yazmacına 0x0 değeri yazılırsa entegre kendini kapatır. Entegrenin tarama osilatörü kapanır ve LED'ler söner. Çekilen akım 150µA'e düşer. Normal çalışmada çekilen akım mA mertebesinde olup tüm LED'ler yanık olduğu zaman 300 mA kadardır. Entegreye elektrik geldiği zaman shutdown modunda başlar. Dolayısıyla ilk iş, buraya 0x1 değeri yazılmalıdır.

Display test (0x0F) yazmacına 0x1 değeri yazıldığı zaman tüm LED'ler yanar. Böylelikle bozuk LED olup olmadığı kontrol edilebilir. Test sırasında Digit yazmaçlarının değerine dokunulmaz. Buraya 0x0 değeri yazıldığında entegre normal çalışma moduna döner.

No-Op işlemi (0x0) entegrelerin kaskadlanmasında kullanılır. Bu komutun, komutu alan display üzerinde etkisi yoktur. No-Op işlemini alan entegre, bu işlemden sonra gelen işlemi DOUT bacağından gönderir. Örn. dört display'in zincirleme bağlandığı durumda, dördüncü display'e erişmek için önden üç tane No-Op ve ardından asıl işlem gönderilir. Bu durumda, birinci display ilk No-Op'u aldıktan sonra DOUT bacağından önce iki No-Op ardından asıl işlemi ikinci display'e gönderir. İkinci display aynı şekilde DOUT bacağından bir No-Op ve asıl işlemi üçüncü display'e gönderir. Üçüncü display kalan No-Op'u alır ve dördüncü display'e asıl işlem böylece gönderilmiş olur. Aşağıda bu işleme dair bir çizim var.



Datasheet'teki blok diyagramda bu adda bir yazmaç gösterilmiyor, fakat onuncu sayfada "No-Op Register" başlığı altında anlatıldığı için bu bir işlem mi yoksa bir yazmaç mı ben anlayamadım. Bu arada yukarıda görüldüğü üzere, No-Op'un anlamsız bile olsa, kendisini takip eden 8-bitlik veriyle birlikte gönderilmesi gerekmektedir.


Arduino LedControl Kütüphanesi

Aslında yazmaçları bu kadar ayrıntılı açıkladıktan sonra kütüphane kullanmak mantıksız gelebilir, ama LedControl kütüphanesi bazı işleri gerçekten kolaylaştırıyor. Örn. Digit yazmaçları LED'lere satır satır erişim sağlıyorken, kütüphanede setRow() yanısıra setColumn(), setLed() ve setChar() gibi fonksiyonlar var.

Kütüphaneyi yüklemek için Arduino IDE'de Tools -> Manage Libraries'i seçip oradan LedControl aratmak gerekiyor. Sonrasında kodlarda LedControl.h header dosyasını ekleyip bir LedControl nesnesi oluşturuluyor. Bunu oluştururken constructor fonksiyona hangi bacağın Arduino'nun hangi bacağına bağlı olduğunu ve kaç tane cihazın kaskadlandığı giriliyor. Örn.

#include "LedControl.h"
LedControl lc = LedControl(data = 12, clk = 11, chipSel = 10, 4);

Ben bütün örneklerde DIN'i Arduino'nun 12. bacağına, CLK'yı 11 ve CS'i 10. bacağına bağladım. Görseli aşağıda:

Fritzing'deki LED Matrix display görseli altı pinli. En üstte boşta duran pin, ikinci Vcc, dolayısıyla bağlı olmamasının bir önemi yok.


Kod Örnekleri

İlk örnekte basit bir karakter kaydırma işlemi var. Burada karakterler modül modül kaydırılıyor. Herhangi bir bit operasyonu yok. Kodu github hesabıma yükledim. MAX7219 entegresinin başlangıçta shutdown modunda açıldığını yazmıştım. setup() rutininin 61. satırındaki döngüde her display tek tek shutdown modundan çıkarılıyor, LED'ler en düşük parlaklığa ayarlanıyor. "dizi" dizisi display'de gösterilecek karakterlerin sıralarını tutuyor. En sonda döngüsellik için son 3 karakter tekrar ediyor.

table dizisi karakterlerin bitmap'lerini içeriyor. Bu bitmap'leri şuradan topluca indirip BIOS.F08 fontunu kullandım. Bu dosya klasik 8x8 BIOS fontunu içeriyor. Bunu GIMP'te açıp C source code veya C header olarak export ettim. Elbette tüm karakter tablosu 2 KB olduğu için (256 * 8), Arduino UNO'nun 2KB'lık RAM'ine hepsini aktarmak olanaksız, zaten gerek de yok. Yalnız kullanılacak karakterler yeterli. loop() rutinindeki for döngüsünde değerler setRow() fonksiyonuyla satır satır yazılıyor. Karakterler 20 tane oldukları halde, pointer'in değeri 16 olduğunda ekranda 16, 17, 18 ve 19. karakterler görünecekleri için, pointer'in 20-4'ü aşmaması gerekiyor.


İkinci örnek (counter) ilkinden daha da basit bir sayaç. Arduino, "a" dizisindeki değerleri sıfırıncı elemandan başlayarak işlemci saat hızında arttırıyor. 25. satırdaki for döngüsünde dizi elemanları byte büyüklüğünde bir taşmaya (overflow) karşı kontrol ediliyor. Eğer taşma varsa bu dizi elemanı sıfırlanıp, elde bir sonraki byte'a aktarılıyor (satır 29), ve bu ikili sayaç 32. satırda LED'lerle görselleştiriliyor.

Sıfırdan 256'ya bir sayma işlemi bir saniyenin altında tamamlanıyor. Onuncu bite karşılık gelen LED yaklaşık saniyede bir yanıp sönüyor. Dolayısıyla daha küçük bitleri görmezden gelirsek, geri kalan LED'lerin yanması için kabaca 2(64-10)=254 saniye gerekiyor, ki bu da 5.709 * 108 (571 milyon) yıl ediyor.


Üçüncü örnek, ikincinin aynısı ama bunu kütüphane kullanmadan yazdım. Bu kodda, önce pinler OUTPUT olarak ayarlanıyor. Ardından sırayla entegre shutdown modundan çıkarılıp (satır 32), tarama limiti 7'ye ayarlanıyor (satır 37), decoding devreden çıkarılıyor (satır 43) çünkü 8x8 LED display bağlı. 51. satırda parlaklık en düşüğe ayarlanıyor ve tüm LED'ler sıfırlanıyor (satır 60). loop() prosedüründeki mantık aynı. Tek fark, her dizi elemanı bir LED satırına karşılık gelecek şekilde Digit yazmaçlarına yazılıyor olması (satır 77).

Görüleceği gibi entegre kütüphane kullanmadan da kolayca programlanıyor. Burada amaç kütüphaneli veya kütüphanesiz hız testi yapmak değil. Zaten muhtemelen yakın sonuçlar alınacaktır. Hıza odaklanacak olsaydım zaten Assembly kullanıyor olurdum.


Dördüncü ve son örnekte yazıyı bit kaydırma işlemleri kullanarak yumuşak geçişlerle kaydırdım. Bunun için iki tane uzun kelime alıp bunu karakter dizisi haline getirdim (satır 16 veya 19). İlk örnekteki gibi bir karakter tablosu oluşturdum, elbette bu sefer daha fazla karakterle. setup() rutininde her zamanki gibi entegre başlatılıyor ve karakterler bitmap olarak "kayanyazi" dizisine aktarılıyor.

loop()'ta "kayanyazi" dizisinin ilk 4 karakteri display'e gönderiliyor. Yazıyı sola kaydıracağımdan, her satırın en soldaki bit'inin (MSB) carry_old değişkenine atıyorum (yani carry_old sıfırıncı sütunu içeriyor). Sonra tüm karakterler bir bit kaydırılıyor (satır 108) ama önce her karakterin ilk sütunu carry_new'e aktarılıyor (satır 106) ki, bu taşacak bitler sonraki karakterin en düşük anlamlı (LSB) bitine aktarılabilsin.

Hiç yorum yok:

Yorum Gönder