Daha önce bahsini ettiğim Ağustos'taki CRX 2018 etkinliğine kartuşu yetiştiremedim. Ancak arada geçen zamanda kartuşla ilgili epey bir çalışma imkanım oldu.
Facebook'ta paylaşmıştım ancak burada biraz daha fazla detay verip bahsedeyim. Tersten başlayayım, başlangıca doğru geri gideyim.
Videoda sırasıyla şunları yapıyorum (C64 tv'me anten hattından picture in picture özelliği ile bağlı. IRQHack64 üstündeki arduino'nun seri hattı da pc'de arduino'nun terminal programıyla bağlı)
1. Başlangıçta seri bağlantı üstünden menü programını açıyorum (Sofistike bir olay değil, bu özelliği her zaman vardı kartuşun)
2. PETG uzantılı bir dosya açıyorum. Pet ekran tasarlama programları ile yaratılabilecek bir dosya bu 1000 byte karakter bilgisi, 1000 byte renk bilgisi, 2 byte arka plan ve ekran rengi olmak üzere 2002 byte'lık bir dosya.
3. Menüye tekrar dönüp KOA uzantılı bir dosya açıyorum. Koala painter programı ile yaratılmış resim programlarının uzantısı KOA oluyor. (Bu örnekte sanırım daha önce ekran görüntülerinden İlker'in çevirdiği resimlerden biri bu)
4. Sonra bir WAV dosyası açıyorum. WAV dosyası SID üzerinden 4 bit olarak, DIGIMAX üzerinden de 8 bit olarak eş zamanlı olarak çalıyor.
5. Nihai olarak da özel bir formatta video gösteriliyor.
Şimdi wav ve video gösterimini zaten daha önce bu kartuşta yapmıştım, ancak burada farklı olan bütün yukarıda saydıklarımın kartuşun üstündeki arduino'daki tek bir firmware ile yapılabilmesi. Video oynatma ve ses çalma bu donanımda arduino tarafında sırasıyla 400 byte ve 128 byte birer buffer gerektiriyor. Eski firmware'de epey bir özellik kartuş tarafında implement edildiği için ciddi bir flash ve sram kullanımı söz konusu oluyor ve bu bahsettiğim buffer'lar da işin içine girdiğinde bütün özellikleri sunma imkanı bulunmuyordu. Bu flash ve ram kullanımını azaltmak için yükün ciddi bir kısmını c64 tarafına kaydırmaya karar verdim ve firmware'in tasarımını ciddi ölçüde değiştirdim. ESP8266'ya kartuş kodlarını port ederken zaten C++'a geçiş yapmıştım, yine aynı code altyapısını kullandım. Heap'te global olarak tutulan buffer'lar yerine çoğunlukla fonksiyonlara lokal stack'te tutulan buffer ve değişkenlere yöneldim. (Interrupt kullanmak söz konusu olduğunda da bu buffer'lara pointer kullanılıyor)
Kartuşta donanımsal olarak da ufak bir fark var, şöyle ki eskiden C64 -> Arduino haberleşmesini Expansion port üzerindeki IRQ hattı üzerinden yapıyordum. Zaten bu iletişim de menüden seçilen komut ya da dosya indis değerinin kartuşa gönderilmesinden ibaretti. EasyKernal'i yaparken burada kullandığım iletişim protokolünden faydalanmıştım. Api fonksiyonalitesini getirirken de bu sefer EasyKernal'den kopya çekip oradaki daha genel amaçlı kullanıma uygun olarak yazdığım halini kartuşa port ettim. IRQ hattını kullanmaktan vazgeçtim zira IRQ hattı benim hassas olarak tetikleyebileceğim bir hat değildi, modüle etmek için CIA yahut VIC çipini programlayıp interrupt oluşturmasını beklemek durumundaydım. Bunun yerine hali hazırda kartuş I/O için ayrılmış /IO1 veya /IO2 hatlarını kullanabilirdim. /IO2 hattını tercih ettim. Bu IO bank'ı $DF00 - $DFFF aralığında. /IO2 seçim hattını Atmega328 üstündeki external interrupt destekleyen daha önce IRQ hattını bağladığım pin'e bağladım. Yani, $DF00 - $DFFF aralığındaki I/O belleğine yapılacak her türlü erişim Atmega328 üstünde bir interrupt oluşturabilmekte. İletişimin nasıl yapıldığını aşağıdaki c64 için çoklu kernal projemde okuyabilirsiniz.
http://www.commodore.gen.tr/forum/index.php?topic=14781.0Bu arada api çalışmasıyla ilgili sonuçlanmayan daha önceki girişimimle ilgili de aşağıdaki mesajda yazmıştım. Api fonksiyonları (hatta neredeyse implementasyonları bile) aşağı yukarı aynı ancak yeni eklenen fonksiyonlar da oldu. Bunlar da streaming fonksiyonları daha çok.
http://www.commodore.gen.tr/forum/index.php?topic=11034.msg168223#msg168223Madem donanımı hafifçe değiştirdik ve api de yazdık menü programının da fonksiyonalitesini ufak ufak değiştireyim dedim. Basit bir plugin mekanizması oluşturdum menü programında. Açılan dosya uzantısını buluyor sd karttan o uzantı için yazılmış plugin'i yüklüyor. PETG için misal PETGplugin.bin dosyasını yükleyip çalıştırıyor. Seçilmiş olan dosyanın ismini de kaset buffer bölgesinden plugin programına iletiyor. Bu plugin de kartuşla konuşarak dosyayı açıp / yükleyip gösteriyor. Diğer dosya uzantıları için de olay benzer şekilde gelişiyor.
Devamında hazır fuarı da kaçırmışken biraz fantaziye vurayım dedim ve başka bir şey denemeye karar verdim. Basitçe sürücü emülasyonu yapıp kernal rutinlerini kullanan programlara disk sürücü yerine kartuş üstündeki sd kart üzerinden dosya yükletebilmek idi bu. C64'de çalışan çoğu tracker vesaire utility tarzı program standart kernal rutinlerini kullanır. Bunların hızlı çalışmasını isteyen insanlar da C64'lerine fastloader kartuşu ve bilimum donanım takarak hızlandırırlar. Bir kısım oyun için de geçerli bu kullanım şekli.
Bu videonun başlarında yukarıda bahsini ettiğim donanımsal değişikliği (eski setup'a geçebilmek için ben oraya jumper koydum) görebilirsiniz.
Yaptığım bu fantazi proof of concept tadında, pratik kullanımını engelleyecek kısıtlamalar söz konusu.
Kodlamaya geçmeden öncelikle source'ları hali hazırda yayınlanmış bir programı denek olarak kullanmayı tercih ettim. Goattracker'ı yazan Cadaver'ın Ninja tracker'ını source'ları da olması sebebiyle tercih ettim.
İlk etapta dizin almayı pas geçerek sadece dosya yükleme fonksiyonalitesini sağlamaya çalıştım. Source'u da incelediğimde bu fonksiyonalite için aşağıdaki kernal fonksiyonlarının kullanıldığını gördüm, bunların bir kısmını değiştirerek kartuşla konuşan kendi rutinlerimi yazdım.
-
setnam : bunu değiştirmeye gerek yok, yüklenecek dosya bilgilerini belirlemeye yarıyor. ancak set ettiği adresler daha sonra kullanılıyor. ($B7 : dosya ismi uzunluğu, , $BC : dosya ismi lokasyonu high , $BB : dosya ismi lokasyonu low)
-
setlfs : hangi cihazla konuşulacağı, hangi logical file kullanılacağı gibi parametrelerin set edildiği fonksiyon. setnam gibi bunda da mevcut kernal fonksiyonalitesinin değiştirilmesine gerek yok.
-
open : farklı implementasyonun sağlanması gereken rutinlerden en önemlilerinden biri bu. yeni implementasyonunda kartuşla iletişimi başlatıyor. Dosyayı açıyor ve dosya bilgilerini, en önemlisi de dosya boyutunu alıp bir yere yazıyor. Bitiminde de kartuşla iletişimi koparıyor.
-
chrin : bir cihazda açılmış bir logical file'dan okuma yapmayı sağlayan fonksiyon bu. bu fonksiyonun implementasyonunda şöyle bir sorun yaşadım. bu fonksiyonu her byte için bir api fonksiyonunu çağırarak implement edemezdim. c64 bir yandan chrin yaparken bir anda open / close gibi fonksiyonları çağırma ihtiyacı yüzünden de bu çağrıların karşılığında kartuşta çalışacak ilgili api fonksiyonlarının çalışabilmesi gerekiyordu.
Arduino üzerinde C64->Arduino iletişimi düşük seviyede bir kuyruk ve bir interrupt handler ile idare ediliyor. Ancak komutları çalıştıran yapı bir döngü içinde kuyruğu temizlemeye çalışan bir ön plan rutini. (Arduino üzerinde Realtime os kullanaydık daha farklı bir yapı kullanmak mümkün olabilirdi ) Özetle, ön plan'da her chrin'e hızlı hızlı cevap vereyim derken başka bir komut kabul edebilmem mümkün değil. Kartuşun streaming fonksiyonunu kullanan wav çalıcı, video oynatıcı gibi şeylerde bu büyük sorun oluşturmuyor zira orada arduino üstündeki c64-Arduino arasındaki iletişimi sağlayan interrupt handler devre dışı bırakılıyor ve tamamen ilgili fonksiyonaliteye yönelik bir iş yapılıyor. c64 tarafında çalışan kod da doğrudan benim yazdığım kod zaten.
Çözüm şu, 256 byte'lık bir buffer kullanıyoruz ve c64 bu buffer'ı her tükettiğinde bir 256 byte daha okuyor. Yani bazı chrin çağrıları kartuşla konuşup uzun sürerken, bazıları ise hızlıca buffer'dan karşılanıyor. Böylece aralarda verilebilecek bir close/open çağrısının da karşılanabilmesi söz konusu.
-
close : Kartuşla iletişime geçiyor, dosyayı kapatıyor ve iletişimi kesiyor.
-
chkin : İletişim yapılacak logical file'ı ve iletişimin yönünü not ediyor.
-
clrchn : Sürücüye gidilmesin diye dummy bir implementasyon ile değiştirdim.
C64 Kernal'i yaparlerken işin güzel kısmı bu fonksiyonların gerçek implementasyonlarını ram'deki bir jump table'a koymuşlar. Bu jump table rom'daki default implementasyonları içeriyor. Aşağıdaki gibi bir kodla bu jump table üzerinde kendi rutinlerimizin adreslerini verdiğimizde yüklediğimiz programlar artık rom'daki implementasyon yerine bizim implementasyonumuza erişmiş oluyorlar.
chrinVector = $0324
openVector = $031A
closeVector = $031C
chkinVector = $031E
clrchnVector = $0322
SETVECTORS
LDA #<NEW_CHRIN
STA chrinVector
LDA #>NEW_CHRIN
STA chrinVector + 1
LDA #<NEW_OPEN
STA openVector
LDA #>NEW_OPEN
STA openVector + 1
LDA #<NEW_CLOSE
STA closeVector
LDA #>NEW_CLOSE
STA closeVector + 1
LDA #<NEW_CHKIN
STA chkinVector
LDA #>NEW_CHKIN
STA chkinVector + 1
LDA #<NEW_CLRCHN
STA clrchnVector
LDA #>NEW_CLRCHN
STA clrchnVector + 1
RTS
Videodan da anlaşılacağı üzere bu değişikliklerle Ninja tracker içerisinden sd kart'ta bulunan dosyaları açıp kullanabilmesini sağlayabildik.
Gelelim kısıtlamalara ;
1. Çoğu program gibi Ninja tracker da bir cruncher ile sıkıştırılmış halde geliyor. Benim kullanmaya çalıştığım bellek bölgesi de cruncher marifeti ile uçabiliyor. Source'larının olması avantajıyla crunch edilmemiş halini kullandım. Tabii bu denemeyi yapmak için ram'de $C000-$D000 arasındaki bölgeyi help metinleri için kullanan programdan bu help text'lerini silip yer açmam da gerekti.
2. İlk kurguda, diğer dosya uzantılarına nasıl plugin hizmet veriyorsa bu işi de bir plugin marifeti ile yapayım dedim. Ancak daha sonra denemeler daha basitçe yapılabilsin diye kendi rutinlerimi ninja tracker'ın sonuna binary olarak ekleyerek derlemeyi tercih ettim.
3. Harala gürele yazılmış, optimize edilmemiş ancak fonksiyonel anlamda da eksikleri (yazma, dizin çekme, dosya sistemi komutları) bulunan bu implementasyon 1.4K yer kaplıyor. Bu rutinlerin ram'de çalıştırılma durumunda bu kadar yer bulmak çoğunlukla mümkün olmayacaktır.
4. Üstte chrin implementasyonunda bahsettiğim bir 256 byte bellek kullanımı var. Implementasyonu bir şekilde custom kernal yahut kartuş üzerinde bir rom'da tutsak bile bu ram'i bir şekilde bulmak lazım ya da firmware'i değiştirip farklı bir çözüme gitmek lazım. (Aslında kartuşa ram ve rom ilavesi yapmak fena fikir değil

)
5. C64'de birden fazla dosya açabilme desteği var ancak Atmega328 üstündeki 2k sram birden fazla dosya kullanılmaya çalışıldığında her bir dosya için yeterli buffer sağlamaya müsait değil. Bir byte bir dosyadan, bir byte başka bir dosyadan okuyan programlar için arduino'nun 512 byte okuyup daha sonra bu 512 byte'ı çöpe atıp diğer dosyadan 512 byte okuması gerekiyor, ve aynı şekilde devam etmesi. Oldukça verimsiz. Aynı anda birden fazla dosya açıp işlem yapacak programlar da herhalde c64 tarafında dosya kopyalama vesaire programları olacaktır.
Kapanışı bir soru ile yapayım,
Velev ki bu iş için bir custom kernal yaptım. Üstte saydığım rutinlerde hunharca kullanabileceğim bellek bölgeleri nerelerdir? (Rom'dan muhtemelen rs232 ve/veya teyp rutinleri koparılacak) 256 byte'lık buffer başta olmak üzere Firmware üstünde çalışıp custom rutinlerin ihtiyaç duyduğu bellek miktarını azaltabilirim hatta sıfırlayabilirim ancak bu da bize hız düşümü olarak geri dönecektir.