Duyuru

Kapat
Henüz duyuru yok.

[C64] EasySD - Eski adı IRQHack64

Kapat
X
  • Filtrele
  • Zaman
  • Göster
Hepsini Sil
new posts

    [C64] EasySD - Eski adı IRQHack64

    Belki hatırlayan çıkacaktır, IRQHack64'e ses çalma, video oynatma gibi özellikleri özel firmware'ler ile eklemiştim. Sonrasında ESP8266 versiyonu gibi farklı fantastik arayışlara girip bir şeyler yapmış olsam da orjinal donanımı çok değiştirmeden daha fazla nasıl faydalanırızı düşünerek yeni bir firmware yazmaya da başlamıştım. Yakın zamanda bu işe bir geri dönüş yaptım.

    Geçmişten gelen bug'ları fix etmek acı verdiği için öncelikle yeni bir kaç plugin yazmaya çalışarak geçmişte yediğim hurmaların etrafında dolanayım dedim.

    Bilmeyenler için ;

    EasySD, eski adıyla IRQHack64 bir program yükleme kartuşu. C64'e genişleme yuvası (expansion port) üzerinden takılıyor. SD kart'a koyduğunuz tek parça oyun / program gibi prg uzantılı dosyaları hızlı bir şekilde C64'ün RAM'ine aktararak çalıştırmaya yarıyor.

    Yeni firmware ve yeni adı olan EasySD ile beraber gelen yenilikler de şunlar :

    1. Artık C64 tarafında koşan herhangi bir program tarafından kullanılabilecek bir API'si var kartuşun. Bu API'deki rutinler iki kısma ayrılıyor, bir kısmı kartuşla olan iletişimi düşük seviyede uygulayan rutinler, bir kısmı da kullanıcı programları tarafından kolayca kullanılabilecek olan rutinler.

    2. Menü programı artık C64 ve Kartuş arasındaki interaksiyon üzerinde daha fazla söz sahibi. Kartuş tarafından sunulan API'yi kullanabiliyor.

    3. API'lerin de sayesinde menü yazılımı belli uzantılar için yazılmış plugin'leri kullanabiliyor.
    Mevcut Plugin'lerin işlem yapabildiği dosya türleri şunlar :

    PETG : Petscii grafikleri
    KOA : Koala Paint resimleri
    WAV : 8 bit 11khz mono ses
    CVID : 160x80 5FPS multicolor video
    PRG : Gelecekte kullanılacak bir plugin, kartuşa ekstra RAM/ROM eklenmesi durumunda Kernal vektörlerini kartuş üstündeki implementasyonlarına yönlendirerek basit sıklıkla kullanılan Tracker / Grafik programı gibi şeylerde load/save desteği sağlamak.
    *FLI : FLI formatında resim
    *D64 : D64 imajlarının bir 1541 sürücüne aktarılabilmesini sağlıyor.

    Son iki plugin henüz yeni geliştirdiğim ve daha github üzerine aktarmadığım geliştirmeleri içeriyor.

    Adres şurası : https://github.com/nejat76/IRQHack64

    Hazır D64'ü diskete aktarmayı yapmışken TAP dosyalarını da Kasetlere aktarmak için bir şey yapayım, bu iş için de elimde varolan imkanları kullanayım dedim. Her bir plugin veriyi kullanma ihtiyacının farklı olmasıyla da firmware'in düzeltilmesi / geliştirilmesi için kapı aralıyor.

    Henüz D64 ve FLI pluginlerini bitirmeden önce çektiğim şu video ile yukarıda bahsettiklerimi görmeniz mümkün. Menü açılışında gelen splash screen Fero'ya ait. Teşekkürler Fero



    Muhtemelen soru işareti şu olacak : Bendeki IRQHack64 kartuşu ile bu bahsettiğin firmware'i çalıştırabilir miyim?
    Cevap da şu şekilde : Elinizde ekstra kartuş yoksa önermiyorum ancak kartuşu mıncırmak, plugin yazarak destek olmak gibi amaçlarla çok basit bir şekilde mevcut kartuşu EasySD firmware'inin çalışabileceği hale getirebilirsiniz.

    1- Genişleme yuvasındaki IRQ hattından Arduino 2 no'lu pin'e giden bağlantıyı kesmek
    2- Genişleme yuvasında 10. sırada olan /IO2 sinyaline bir kablo lehimlemek.
    3- /IO2 sinyaline bağlanan kabloyu Arduino 2 no'lu pin'e lehimlemek.

    Bu bağlantıyı dilerseniz kartuş eski firmware'i ile de çalışabilsin diye switch kullanarak da yapabilirsiniz. Temelde kartuşun eski hali C64->Arduino iletişimi için IRQ hattını kullanırken artık bu hat yerine /IO2 hattını kullanıyor.

    Resmi gerçek boyutunda görmek için tıklayın.

Resmin ismi:  IMG_3585.JPG
Görüntüleme: 1
Büyüklüğü:  61,0 KB

    #2
    Kartuş içindeki api rutinlerinin genel bir tarifini de şuraya attıktan sonra reel hayata; TAP plugin'ini geliştirirken kendi içimde izlediğim rutini sizlere eş zamanlı aktarmaya çalışacağım.

    IRQ_StartTalking
    Kartuşu aktif hale getirmeye yarayan rutin. Kartuş normal şartlarda C64 açıldığında pasif halde. Menü tuşuna basıldığında yahut bu rutin çağrıldığında aktif hale geliyor.

    IRQ_EndTalking
    IRQ_StartTalking rutininin tam tersini yani kartuşu tekrar pasif hale getirmeye yarıyor. Menü yazılımı bir program başlattığında daima bu rutini çağırarak kartuşu pasif hale getiriyor. Kullanan diğer programlarda da artık yeni bir iletişim ihtiyacı yoksa kullanılmalı.

    Yüksek seviye rutinler

    IRQ_OpenFile
    SD kart üstünde dosya açmaya yarıyor. SDFat kütüphanesinin ve atmega328 üstündeki ram miktarının kısıtlı olmasından dolayı aynı anda sadece tek bir dosya açık olabiliyor.
    IRQ_CloseFile
    Açılan dosyayı kapamaya yarıyor.
    IRQ_DeleteFile
    Dosya silmeye yarıyor.
    IRQ_ReadFile
    Açılmış olan dosyadan istenilen miktarda blok okumaya yarıyor. Bu yöntemle yapılan okumalar NMI interrupt'ları ile yapıldığı için bu interrupt'ı o anda başka bir amaçla kullanmıyor olmanız gerekiyor.
    IRQ_SeekFile
    Açılmış olan dosya üzerinde okuma / yazma gibi operasyonlar için kullanılacak pozisyonu değiştirmeye yarıyor.
    IRQ_LongSeekFile
    IRQ_SeekFile ile fonksiyonalite açısından aynı ancak daha büyük offset'leri kullanabiliyorsunuz.
    IRQ_GetInfoForFile
    FAT sisteminde dosya ile ilgili tutulan bilgileri getiriyor. (Dosya tarihi, boyutu vesaire)
    IRQ_WriteFile
    Her seferinde 32 byte olmak üzere dosyaya yazma yapmaya yarıyor. Burada dikkat edilmesi gereken yazmaların okumalara göre kat kat yavaş olması. C64'den Kartuşa doğru olan transferler C64'ün teyp için kullandığı protokole benzer, tek bir hat kullanılıyor ve gönderilen iki pulse arasındaki uzunluğa göre gönderilen bilgi 1 ya da 0 oluyor.

    IRQ_ReadDirectory
    IRQ_ChangeDirectory
    IRQ_DeleteDirectory
    SD karttaki FAT dosya sistemindeki dizinlerle çalışmayı sağlıyor bu rutinler. Okuma, dizin değişimi ve dizin silme şeklinde.

    IRQ_ReadEeprom
    IRQ_WriteEeprom
    IRQ_SeekEeprom
    Bu rutin silsilesi Kartuş üstündeki Atmega328'in EEPROM (Elektriksel olarak silinebilir bellek) bölgesine erişim sağlıyor. Okuma, yazma ve operasyon pozisyonu değiştirme şeklinde. Henüz kullanılmıyor olsa da ileride Menü yazılımında konfigürasyon bilgisi saklamakta kullanacağım.

    IRQ_InvokeWithName
    Menü için düşünülmüş bir rutin, ancak herhangi bir program tarafından da kullanılabilir. Kontrolü kartuşun program yükleme fonksiyonuna devrediyor. Kartuş üstünde programlar tarafından kullanılabilecek ekstra bir rom yahut ram olmadığı için C64 reset ediliyor ve Kartuşun yükleme rutini istenen programı ram'e aktararak başlatıyor.

    IRQ_ExitToMenu
    Kullanacak olan programın menü programının adını bilmesine gerek duymaksızın menü'yü çağırabilmesini sağlayan rutin. Aslında IRQ_InvokeWithName'den çok büyük farkı yok.

    Streaming için kullanılan rutinlerin detayına girmeden Atmega328, SDFat ve SPI ile Sd kart erişimi konusunda bir kaç kelam etmek lazım. Kartuşun üstünde bulunan microcontroller (atmega328) SDFat kütüphanesini kullanıyor, bu kütüphane de sd kart'a olan erişimlerini SPI ile yapıyor. Kütüphane Atmega328'in toplam 2048 byte olan SRAM'inden 512 byte kullanarak bütün işlemlerini hallediyor. Okuma yapılan bir 512 byte sonrası tekrar SD kart'a gidip o veriyi alması gerekiyor. Bu da SD kart üstünde yapılan herhangi bir erişimde gecikme yaşanmasına sebep oluyor. Streaming kullanıldığında mecburen buradaki gecikmeye dikkat ederek bir kullanım yapmak durumunda kalıyoruz. Gecikmenin süresini daha önce ölçmüştüm ancak şu an elimde yok. Burstloader / Video oynatma plugin'inin uygulamasında plugin her ekran taramasında 400 byte'lık bir veriyi ekranın bad line denk gelmeyen yerlerinde kartuştan transfer ederken, ekranın açık olduğu satırlarda kartuşa bahsettiğim bu gecikmeyi telafi edecek yeni bir 400 byte okuması için zaman bırakıyor. 200 raster satırından düşük olmakla beraber tam ne kadar olduğunu şu an hatırlamıyorum.

    Gelelim rutinlerin açıklamasına ;

    IRQ_Stream
    WAV çalma plugin'inin de kullandığı double buffering ile C64'e veri aktarımına yarayan rutin. Bu rutin kullanılırken veri transferi için her seferinde kartuşa biz gidiyoruz. IO2 bölgesinden yapılan bir okuma Kartuş üstünde bir interrupt'a sebep oluyor, interrupt gecikmesi değişmekle beraber aşağı yukarı 2 ile 6 cycle arasında bir sürede Kartuşun tuttuğu buffer üzerinden bir byte veriyi Kartuşun rom bölgesindeki özel adreslerden ($80AB adresi) birinde buluyoruz. Bu aktarım yönteminde kartuş herhangi bir interrupt kullanmadığı için Kernal rutinleri vesaire çok rahat kombine edilebilecek bir aktarım yöntemi. Şu an için ses çalmaya uygun ancak daha yaygın kullanım için gerek double buffering yönteminde gerekse de sd kart gecikmesinin yönetimi için bazı değişiklikler yapılması gerekli.

    IRQ_NIStream
    IRQ_Stream'den farklı olarak her bir byte için microcontroller üstünde bir interrupt üretmek yerine 8 byte'lık bloklar için interrupt ile değil sıkı bir döngüde çalışan bir transfer rutini ile senkron oluyoruz. 8 byte'lık bir bloğun transferi için IO2 bölgesinden bir okuma yapıyoruz. Sırasıyla dizilmiş aşağıdaki şekildeki kodumuz microcontroller tarafından gönderilen 8 byte'ı yakalıyor. Bu rutin de ses çalmada olduğu gibi geliştirmeye açık, aktarılan data tam kayıpsız değil. (NI'nin açılım Non Interrupted)

    Kod:
        LDA MODULATION_ADDRESS
        LDA CARTRIDGE_BANK_VALUE
        STA $a000
    *IRQ_SBStream
    Bu henüz Github üzerinde yer almayan yeni geliştirdiğim D64 plugin'i için yazdığım kayıpsız Streaming metodu. Bu metod tek buffer kullanıyor. Her bir byte'ın transferi için microcontroller üstünde bir interrupt generate ediliyor. Kartuş tarafında bu işe ayrılmış olan buffer 256 byte. SD kart gecikmesini yönetebilmek için C64 tarafında bu rutini kullanan program bir sonraki 256 byte'ı okumak için kartuşa kendisi zaman tanıyor. (SB'nin açılımı Single Buffered)

    Yorum yap


      #3
      TAP Plugin ile ilgili geliştirme macerasına başlamadan önce D64 plugin'i ile ilgili kısa bir özet geçeyim; FLI plugin'ine hiç girmiyorum çünkü bu tarz grafik dosyalarının açılmasını sağlamak kartuşun imkanları ile oldukça basit, sadece ilgili formatı bilmek ve oturup biraz kod yazmaya bakıyor.

      D64 plugin'ini yazarken aslında gariptir onlarca senedir duyduğum ama kafamda hiç bir zaman adlandırmaya tenezzül etmediğim bir kavramı da artık beynime perçinledim. Nedir o? Sürücüden gelen o ses Track değiştirme sesiymiş yahu Hayır oturdum iki tane arduino ile dvd rom mekaniği kullanarak plotter filan da yaptım, sorsalar biraz düşünüp o sesin track değiştirme sesi olduğunu söylerdim ama insan kollarını sıvayıp gerçekten bir işe odaklanıp uğraştığında karşılaştığı şeylerle edindiği bilgileri beynine kalıcı olarak işliyormuş resmen.

      Neyse geyiği bırakayım. D64 plugin'ini yazarken daha önce bahsetmiş olduğum gecikme/latency kavramı bir sektörün yazılması esnasında bütün zamanlamayı bozacağı için tüm bir track'in datasını C64 tarafında bir buffer'a yazıp buradan disk sürücüye göndermeyi hedefledim. Sector interleave denen kavram ile tanıştım. Sürücü kafasının yaptığı okuma/yazma operasyonuna arka tarafta destek veren sistem (1541 içindeki bilgisayar) GCR decoding / Encoding / Seri iletişim gibi işlerle boğuştuğu için yetişemiyor, böyle olunca bir dosyanın parçalarını arada belli sayıda sektör boşluk bırakarak yazmak ve okumak performansı arttırıyor.

      Tüm işi parçalara bölüp ilk hedeflediğim parça olan formatlanmış boş bir diski tamamen $00 yahut $FF bilgisi ile doldurma kısmı ilk denemelerimde VICE'ta çalışırken gerçek sürücü üstünde afallıyordu. True drive emulation'ın ne derece true olduğunu varın siz düşünün, ilk defa (ya da ikinci) yazdığım rutinde daha gerçek sürücü ile emülasyon arasında fark çıkıyor. Testleri hızlandırmak için araya Pi1541 koydum, epey de faydasını gördüm. Kartuştan bir track'in buffer'a alınması noktasına geldiğimde ilerleme kaydedebilmek için sector interleave olayını daha sonra geri dönmek üzere bir kenara bıraktım. Transfer tarafında IRQ_Stream rutini beni pes ettirene kadar mıncırdıktan sonra onu da kenara atıp kartuşa daha basit yeni bir streaming yöntemi ekledim. Olay da bu noktada çözüldü. Henüz hızlandırılmış bir DOS sistemi ile test yapmadım ancak standart kernal rutinleri ile 8-10 dakikalık bir sürede kartuş üstündeki bir D64 imajı 1541 üstündeki diskin bir yüzüne aktarmak mümkün olabiliyor. Sürenin bu kadar uzun olmasının sebebi standard C64 dos'un çok yavaş olması. Optimum çözüm için sürücü üstünde çalışacak custom bir çözüm gerekiyor. Mevcut hızlandırıcı çözümleri ciddi avantaj sağlarsa girmeyi düşünmüyorum.

      Neyse ; saat te geç olmuş, TAP tantanası sonraya kaldı. Şu anda zaten tamamen kafada kurgu aşamasındayım. $01 processor port, cycle saysak da mı taplasak yoksa cycle'ları CIA timer'a saydırsak ta mı TAP'lasak diye ben bu düşünceyi biraz daha beynimde çiğnemeye devam edeyim. Ama TAP plugin'imiz de yolda, geliyor

      Yorum yap


        #4
        Geçmiş projelerden de alışık olduğum üzere buraya yazdıklarım biraz monolog gibi olacak. Bu işin biraz kaderi böyle.

        SD Karttan okuduğumuz TAP dosyasının Teybe transferi için pek tabii öncelikle TAP formatını inceledim,

        Kod:
        0000-000B: Dosya imzasi "C64-TAPE-RAW"
        000C-000C: TAP versiyonu, $00 - Orjinal 01 - Güncellenmis
        000D-000F: Gelecekteki gelistirmeler için
        0010-0013: Dosya boyutu  (Header hariç little endian formatinda) 
        0014-xxxx: Esas veri


        Şu kaynaklardan format'a ulaşıyoruz,

        http://unusedino.de/ec64/technical/formats/tap.html

        http://wav-prg.sourceforge.net/tape.html
        Temelde şu aşamada benim ilgilendiğim kısım Teybe doğru olan transfer kısmı. Bu yüzden Teypten okuma ile ilgili kısımlar benimle alakalı değil. Formatta da dikkat edilmesi gereken en önemli nokta TAP version denen byte. (Buna daha sonra geleceğim)
        Diğer kaynağa baktığımızda,karşıma şöyle bir grafik çıkıyor, bu teypten okunan datanın kabataslak bir görüntüsü. Aslında burada sinüsoidal dalgalar yerine kare dalgalar olması gerekiyor. Bizim TAP'tan teybe yazarken oluşturmaya çalışacağımız sinyal böyle bir şey olacak. TAP içinde tutulan ana veri aşağıda grafikte gösterilen kırmızı noktalar arasında geçen süreler. C64 dataset üzerinden teypten gelen sinyalde bu kırmızı ile işaretli noktaları algılayabiliyor. Bu kırmızı noktalar arasında geçen süreler kullanılan loader'a göre yorumlanarak 1'in 0'ın neyle ifade edileceği belli oluyor.

        Resmi gerçek boyutunda görmek için tıklayın.  Resmin ismi:  waveform.png Görüntüleme: 1 Büyüklüğü:  4,6 KB

        TAP formatı bu kırmızı noktaların aralarındaki süreleri tuttuğu ve her bir aralığın (byte değeri 1 iken) 8 cycle ila 2040 (255*8 ) cycle arasında olmasından sebep 1 saniyelik bir yüklemede bile ciddi bir datanın içeriye akışına ihtiyacımızın olduğu anlaşılabilir. Düz hesap C64 1 milyon cycle işletiyor, ortalama da 100 cycle'lık aralıklardan bahsediyor olsak... Dur bir saniye, bu iş sanırım olmaz'a doğru gidiyor diye şu an yazarken düşünmeye başladım. Türk'ün aklı ya yazarken ya seçerken

        Neyse biraz araştırdım ve biraz TAP dosyası kurcaladım ve biraz da rahatladım.. Baktığım bir kaç TAP dosyasında gördüğüm en küçük değer $20 şeklinde ki bu da 256 cycle'a tekabül ediyor. Gerçek zamanlı olarak teybe yazarken bir yandan 10K transferi sustain etmek zor gözükse de 5K gibi değerler nispeten kolay. Yine örnek dosyalara baktığımda bunlar çok iyi sıkıştırılan dosyalar. Çoğunda sadece 4-5 değişik değer var ve bu değerlerin dizilimi de belirli örüntüler (pattern) içeriyor. Huffman kodlamasının çok özel bir uygulaması ile bile 5-10 kat sıkıştırma çarpanlarına ulaşmak mümkün. 7Zip ve LZMA algoritması örnek baktığım bir kaç tanesini 24'te birine indirmiş örneğin.

        Çoğunlukla çalışma şeklim bir an önce mükemmel hedefe doğru yol almaktansa kendime küçük mihenk taşları, küçük hedefler belirleyerek bunları da motivasyon kaynağı haline getirmek şeklinde oluyor. Yine benzer şekilde ilerlerken bu işi bileşenlerine nasıl ayırırsam daha baştan test edebileceğim bir şeyler ortaya koyarım kısmı hep kafamın bir tarafında oluyor. D64 yazarken örneğin ilk hedefim kopyalama işini bizzat yapmak yerine bir diski tamamen belli bir değerle nasıl doldururum idi ilk hedefim.

        Benzer düşünce ile açtım VICE'ı ve yazılabilecek en basit programlardan birini yazıp teybe kaydetmeyi denedim.

        Kod:
        10 PRINT "NEJAT"
        20 GOTO 10
        VICE'ta File kısmından Attach Tape Image dedim, altta New Tape Image denen yerden DENEME.tap diye yeni bir imaj yarattım ve bunu seçtim. Sonrasında C64'de,

        Kod:
        SAVE "DENEME",1
        Komutu ile bu teyp imajına kayıt aldım. Teyp varsayılan cihaz olduğu için 1'i belirtmeye gerek yok ancak işimiz teyp ile ilgili olduğu için vurgulamak istedim.

        Resmi gerçek boyutunda görmek için tıklayın.  Resmin ismi:  TAPVICE.png Görüntüleme: 1 Büyüklüğü:  29,7 KB

        Bu bana 42KB'lık bir TAP imajı oluşturdu. İşin veri transferi noktasında elimizi kirletip ister istemez denemeleri gerçek C64 üzerinde yapacağız (çünkü kartuşumuzun emülatörü* yok) ancak emülasyonun nimetlerinden faydalanırsak işimizi hızlandırırız. Bu noktada bu projede nasıl ilerleyeceğim ile ilgili aklıma bir fikir geldi. Henüz yeni kaydettiğim bu TAP imajını donör olarak kullansam ve yazdığım plugin'in de ilk amacı bu TAP imajının aynısını yahut epey bir benzerini üretmek olsa nasıl olur? Test'i oldukça kolaylaşmaz mı?

        İşte bu noktada 64KB ram'i olan C64'ümüz ile ilgili düşünüyorum, 42K'lık imaj saklama açısından acaba beni zorlar mı zira nihai ürünüm üzerinde bu kadar bir buffer tutmayı hedeflemiyorum, sırf test yapabilmek için baştan kendimi kısıtlamam da doğru olmayabilir. Hemen bir bellek haritasını çıkaralım ;

        Öncelikle biz bu işi yaparken ne BASIC'e ne de KERNAL'e ihtiyacımız var, Teyp erişimi Processor port ($01 adresi) üzerinden yapıldığı için IO alanına bu anlamda ihtiyacımız yok. Aklımın bir köşesinde CIA interrupt'larını kullanmak yer aldığı için mecburen kullanabileceğimiz maksimum adres $BFFF oluyor. Alt kısımda da stack'in bulunduğu alanın üst kısmından itibaren kullanabiliriz. Sürekli processor port üzerinden belleği yeniden map ederek hem IO alanını hem de Kernal'in altındaki ram'i kullanabiliriz ancak daha baştan işleri çetrefilli hale getirmek istemiyorum.

        Bu arada $D000 - $0300 = $CD00 = 52480 byte... kod yazacak gani gani 10K'mız var.. bu iş olur. (Şu ana kadar hiç kod yazmadım bu arada )

        Henüz tam olarak tasarlamadığımız iki tane husus var ; Transfer rutini ve tam olarak teybe yazarken cycle'ların senkronunu nasıl tutturacağımız. Bunlarla ilgili şu an kafamda sadece kaba bir kurgu var. Tekrar kendi çalışma prensiplerime dönecek olursam ; Bir projeyi tüm detayları ile daha baştan tasarlamaya kalkarsam gereksiz detaylara boğulurum, aslında girebileceğim ancak daha baştan çok detay tasarım yaptığım için kendimi girmekten alıkoyduğum yolları daha baştan eleyebilme riskim olduğunu düşünürüm. Bazen detaylara odaklanıp bazen de detaylar olmadan genel görünüme bakarak ilerlemeyi tercih ediyorum bu yüzden. Burada da transfer rutinini şimdilik bir kenara bırakıp elimdeki mevcut test data'sını seçeceğim yöntemlerle yazmaya çalışmaya odaklanacağım.

        Tabii yine de transfer rutini ile ilgili değerlendirme yapmakta fayda var. Uğraştığımız data yüksek cycle sapmalarında hataya sebep olabilecek türden bir data. Müzik gibi frekansı yüksek ve gerçek zamanlı işlenmesi gereken bir data. Başta da bahsettiğim gibi bant genişliği açısından da bizi zorlayacak tarafları var ancak o kısma şu an girmiyorum.

        Neyse efendim, kafamdaki ilk kurgu şu şekilde... işin gerçek zamanlı tarafında senkrondan kopmamak adına ilk fikir olarak timer interrupt kullanayım diyorum. CIA çipi bizim için istediğimiz cycle sayısı geçtiğinde interrupt üretme yetisine sahip. Bu sayede ön planda zaman alacak transfer rutini / gerekirse decompression gibi şeyleri çalıştırma imkanı olacak. Interrupt'a girme, çıkma, o anda yürütülen işleme göre eklenecek jitter gibi faktörler dikkate alınmak durumunda olacak. İlk uğraşacağım TAP imajı C64'ün standart implementasyonu olduğu için burada tutturulması gereken cycle sayıları da yüksek hassasiyet gerektirmeyecek cinsten.

        Sayıları şuradan görebiliriz : http://sidpreservation.6581.org/tape-format/

        Kod:
        • “Short pulses” at 2840 Hz (period = 352 microseconds about). (S)hort : TAP value $30.
        • “Medium pulses” at 1953 Hz (period = 512 microseconds about). (M)edium : TAP value $42.
        • “Long pulses” with a frequency of 1488 Hz (period = 672 microseconds about). (L)ong : TAP value $56.
        Halletmemiz gereken üç değişik uzunluk var, $30 * 8 = 384 cycle, $42*8 = 528 cycle ve $56*8 = 688 cycle

        Üretmiş olduğum TAP dosyasında da bunlara yakın değerler olduğunu görüyorum.

        Hiç kod yazamadık ancak hazırlığın büyük bir kısmını da yaptık sanki, hadi bakalım
        Konu I.R.on tarafından (https://www.commodore.com.tr/member/3-i-r-on Saat 28-11-2019, 02:29 ) değiştirilmiştir.

        Yorum yap


          #5
          Bugün kod yazmak ile geçti. Enteresandır, bir iki debug sekansından sonra kafamda kurguladığım ilk aşama başarıyla sonuçlandı. İlk etapta oluşturduğum basit programın TAP imajını programın içine gömdüm. Program vasıtasıyla başka bir TAP imajına bunu naklettim. Oluşan yeni TAP imajından ilk yüklemede kullandığım programı tekrar elde edebildim.

          Şimdilik sadece kodları paylaşayım, açıklamaları daha sonra yapacağız artık.

          Kod:
          IRQ6502     = $FFFE
          BORDER      = $D020
          ;-- Complex Interface Adapter -------------------
          CIA_1_BASE              = $DC00
          CIA_2_BASE              = $DD00
          DATA_A                  = 0
          DATA_B                  = 1
          DDR_A                   = 2
          DDR_B                   = 3
          TIMER_A_LO              = $04
          TIMER_A_HI              = $05
          ;-- CIA Registers
          CIA_INT_MASK            = $0D
          CIA_TIMER_A_CTRL        = $0E
          CIA_TIMER_B_CTRL        = $0F
          ;-- CIA Enums
          CRA_TOD_IN_50HZ         = 128
          CRA_SP_MODE_OUTPUT      = 64
          CRA_IN_MODE_CNT         = 32
          CRA_FORCE_LOAD          = 16
          CRA_RUN_MODE_ONE_SHOT   = 8
          CRA_OUT_MODE_TOGGLE     = 4
          CRA_PB6_ON              = 2
          CRA_START               = 1
          ;-- Video Interface Chip ------------------------
          VIC_CONTROL_1           = $D011
          VIC_INT_CONTROL         = $D01A
          VIC_INT_ACK             = $D019
          VIC_BORDER_COLOR        = $D020
          ;-- VIC Enums
          VIC_DEN                 = 16    
          
          PROCESSOR_PORT          = $01
          PP_CONFIG_ALL_RAM       = $34           ; RAM visible in $A000-$BFFF, $E000-$FFFF, $D000-$DFFF
          PP_CONFIG_RAM_ON_ROM    = $35           ; RAM visible in $A000-$BFFF, $E000-$FFFF
          PP_CONFIG_RAM_ON_BASIC  = $36           ; RAM visible in $A000-$BFFF
          PP_CONFIG_DEFAULT       = $37           ; $A000-$BFFF, $E000-$FFFF is ROM, default config.
          MOTOR_ON    = $20
          DEBUG       = 1
          FREQ        = 250
          SYNC        = $40
          TAPINDEX_LO    = $FB
          TAPINDEX_HI    = $FC
          ;   *=$C000
              *=$080E 
              SEI
              LDY #$7f
              STY CIA_1_BASE + CIA_INT_MASK  ; Turn off CIAs Timer interrupts 
              STY CIA_2_BASE + CIA_INT_MASK  ; Turn off CIAs Timer interrupts 
              LDA CIA_1_BASE + CIA_INT_MASK  ; cancel all CIA-IRQs in queue/unprocessed 
              LDA CIA_2_BASE + CIA_INT_MASK  ; cancel all CIA-IRQs in queue/unprocessed 
              ; Turn off VIC interrupts
              LDA #$00
              STA $D01A       
          
              ;Enable RAM under ROM (IO kept intact)
              LDA #PP_CONFIG_RAM_ON_ROM
              STA PROCESSOR_PORT
              JSR PREPARECYCLES
              LDA #<TAPFILE
              STA TAPINDEX_LO
              LDA #>TAPFILE
              STA TAPINDEX_HI
          
              LDA #<CIAIRQHANDLER
              STA IRQ6502
              LDA #>CIAIRQHANDLER
              STA IRQ6502+1
          
              ;setup CIA #1
              ;TIMER A
              LDA #<FREQ
              STA CIA_1_BASE + TIMER_A_LO
              LDA #>FREQ
              STA CIA_1_BASE + TIMER_A_HI
          
              ;10000001 ; Release Time A
              LDA #$81    
              STA CIA_1_BASE + CIA_INT_MASK   
          
              ; Turn on motor
              LDA #PP_CONFIG_RAM_ON_ROM - MOTOR_ON
              STA PROCESSOR_PORT
          ; Wait for cassette buttons    
          -
              LDA PROCESSOR_PORT
              AND #%00010000    
              BNE -
              JSR IRQ_DisableDisplay    
              LDY #$00    
              LDA #$0
              STA SYNC
              CLI
          SYNCPOINT    
              BIT SYNC
              BVC SYNCPOINT    
              LDA #0
              STA SYNC    
              LDA (TAPINDEX_LO), Y
              INY
              BNE SYNCPOINT
              LDY TAPINDEX_HI
              INY
              CPY #>(TAPFILEEND + $100)
              BEQ +
              STY TAPINDEX_HI
              LDY #$0
              JMP SYNCPOINT
          +
              SEI
              LDY #$7f
              STY CIA_1_BASE + CIA_INT_MASK  ; Turn off CIAs Timer interrupts 
              JSR IRQ_EnableDisplay
          -
              INC BORDER
              JMP -
          
          SignalOn    .macro
              LDA $01
              ORA #%00001000
              STA $01
              .endm
          SignalOff    .macro
              LDA $01
              AND #%11110111
              STA $01
              .endm
          
          CIAIRQHANDLER   
              TAX
              LDA CYCLES_LOW, X
              STA CIA_1_BASE + TIMER_A_LO
              LDA CYCLES_HIGH, X
              STA CIA_1_BASE + TIMER_A_HI
              SignalOn
              INC BORDER
              SignalOff 
              LDA CIA_1_BASE + CIA_INT_MASK           ;Acknowledge    
              DEC BORDER
              LDA #$64
              STA SYNC
              RTI
          ; Prepare a table of X*8
          PREPARECYCLES
              LDX #$00
          -    
              TXA
              CLC
              ROL
              ROL 
              ROL   
              ROL 
              AND #$07
              STA CYCLES_HIGH, X
              TXA
              ASL
              ASL
              ASL
              STA CYCLES_LOW, X
              INX
              BNE -
              RTS
          ;-----------------------------------------
          ; Registers In : None
          ; Registers Used : A
          ;-----------------------------------------  
          IRQ_DisableDisplay
              LDA VIC_CONTROL_1
              AND #$EF
              STA VIC_CONTROL_1   
          
              RTS
          
          ;-----------------------------------------
          ; Registers In : None
          ; Registers Used : A
          ;-----------------------------------------  
          IRQ_EnableDisplay
              LDA VIC_CONTROL_1
              ORA #VIC_DEN
              STA VIC_CONTROL_1   
              RTS 
          .ALIGN 256
          CYCLES_LOW  
          .FILL 256
          CYCLES_HIGH
          .FILL 256
          .if DEBUG = 1
          TAPFILE:    .binary "DENEME.tap", $14
          TAPFILEEND:
          .else
          TAPFILE:
          .endif

          Yorum yap


            #6
            Her ne kadar burada Türkçe olarak anlatsam da işin kod yazım aşamasında gerek yorum yazma olsun gerek isimlendirme olsun İngilizceden vazgeçemiyorum. Yakın zamanda yazdığım için kod bana çok anlaşılır gelse de el kodu okumanın zor olduğunu biliyorum, kodun ne yaptığını, neyi nasıl yaptığını çok da teferruata girmeden biraz açıklayayım.

            Kodun başında ilgilendiğimiz çiplere dair tanımlar var, normal şartlar altında bunlar kartuşun api include dosyalarında zaten var ancak anlaşılabilir olsun diye tek dosya içinde göstermeye çalıştım.

            Kodun başında interruptları kapatıyoruz, kapatmaz isek kernal bölümünü devre dışı bıraktığımız anda c64'ümüzle varsayılan olarak gelen interrupt'lar ram bölgesini işaret edeceği için çakılacaktır. Sonrasında kernal ve basic bölgesindeki rom'u kapatıp bunların altındaki ram'in okunabilmesini sağlıyoruz. Varsayılan interrupt rutinlerine de çengel atabilirdik ancak zaten zaman kritik bir iş yaptığımız için en iyisi bunları doğrudan kullanmak. Interrupt dışında çalışan kodumuzda kullanacağımız register'lara hakim olduğumuz için interrupt içinde gereksiz register saklama faaliyetine de girmemiş oluyoruz. Bu iş özelinde ben interrupt rutininde Y register'ını hiç kullanmadım, benzer şekilde interrupt'ın kestiği rutinde de X register'ını hiç kullanmadım. A register'ı genel amaçlı olduğu için ondan feragat etmek zor, orada da interrupt ile kesilen kodun her zaman interrupt'ı bekler pozisyondayken kesileceği yönünde bir varsayımla ilerledim.

            Program 6526/CIA-1 A Timer'ını kullanıyor. C64 teyp portundan sense ettiği high(1) den low (0) a geçişleri tespit edebiliyor. Bir tap dosyasını wave dosyasına çevirdiğimde audacity uygulamasında low -> high -> low geçişinin impulse şeklinde olduğunu gördüm, yani oluşturduğumuz toplam kare dalganın period'u aynı olduğu sürece dutcy cycle'ının ne olduğunun muhtemelen çok bir önemi yok. Bu yüzden interrupt rutini içerisinde sinyali önce high (1) yapıyoruz kısa bir süre içinde de low (0) . Bu alanda bilgim sınırlı bu arada, standart kernal rutinlerle save edilmiş basit programlarda sıkıntı çıkarmadı ama başka tap dosyalarında sıkıntıya yok açar mı bilmiyorum. Teoride sıkıntı yaşanmaması lazım.

            Ilk interrupt'a ne kadar gecikme ile girdiğimizin çok bir önemi yok çünkü bu C64'ün süre sayacağı ilk referans noktasını oluşturuyor. Interrupt rutinine A register'ı ile sıradaki period gönderiliyor, interrupt rutini de bu periyodu bir sonraki interrupt'ın zamanı için Time A'ya set ediyor. Bir sonraki SignalOn / SignalOff çifti ile TAP içerisinden bir byte'ı yani bir periyodu göndermiş oluyoruz.

            Dikkat çekmem gereken bir diğer nokta da SYNC adresi. Bu adres interrupt rutini ile ön plandaki rutinin haberleşmesi için kullanılıyor. Bu basit uygulamada ön plandaki rutin her seferinde TAP dosyasının içeriğinden sıradan bir byte alıp interrupt rutininin tekrar tetiklenmesini ve bitmesini beklemeye geçiyor. Ön plandaki rutin $40 adresine 0 yerleştiriyor, BIT SYNC denildiğinde $40 adresinin içeriğini test ediyoruz, 6. bit'i doğrudan oVerflow flag'i üstünde görebiliyoruz. Önyüzdeki rutin bu adrese 0 yerleştirerek beklemeye geçerken interrupt rutini buraya 64 (0100 0000) değerini koyarak beklemeyi sonlandırıyor. Program kabaca bu şekilde çalışıyor. TAP dosya içeriğinin bitip bitmediğini kontrol etmek için basitçe 16 bit bitiş adresinin sadece high byte'ını kontrol ettim, beklentim tape loader'ın kendince anlamlı bulduğu değerlerden sonra teybe yazılmış değerleri yoksayacağı idi, öyle de oldu. Tabii bu durum kullanılan loader'a göre değişkenlik gösterebilir, final versiyonda bir sonlandırma mekanizması gerekecektir.

            Gelelim işin artık emülatör üstünde deneyemeyeceğimiz bölümüne ; Kartuş üstünde daha büyük bir TAP dosyası ve streaming ile transfer kısmının eklenmesine. İlk etapta ince optimizasyonlara girmeden uzun standart kernal ile kaydedilmiş program deneyeceğim. Daha önce başımıza bela olmuş şu latency / gecikme meselesinden dem vurmuştum. Transfer tarafını düşündüğümde aklıma ilk burası geldiğinden kafamda hayal meyal oluşturduğum bazı kurgular söz konusu ; Bunlar şöyle


            a. Kartuştan her seferinde bir byte alalım, buffer'ı ve gecikmeyi kartuş idare etsin (Hali hazırda kafadan bu işi çözmesi gereken double buffer yöntemiyle stream etme rutinini fix etmek ve bunu kullanmak anlamına geliyor aslında biraz)
            b. TAP datasını yeniden organize edip 12345678 şeklindeki sıralama yerine 15263748 şeklindeki bir sıralama ile saklarsak her seferinde iki byte okuyup, ilkini kullanıp, ikincisini ise bir yere cache'leyebiliriz. Kartuşun tuttuğu 256 byte buffer'ın 128'ini kullanıp 128'ini saklamak anlamına geliyor bu. İlk 256 byte'ın 128 byte'ını bu şekilde tükettikten sonra bir sonraki turun kartuştan ilk byte okuması aslında sd kart'a erişim için ayrılmış olur, cache'lediğimiz 128 byte'ı kullanana dek kartuşa bir 256 byte daha buffer'layabilmesi için zaman tanımış oluruz.
            c. b'deki yöntemin benzeriyle dosya içeriğini değiştirmeden önyüzdeki rutinin iki farklı durumu idare etmesiyle de yapılabilir. İlk çalışan kısım gelen ilk byte'ı kullanıp bir sonrakini saklar, interrupttan dönüşte çalışan ise ilkinde saklanmış olanı kullanıp ikincisini de saklar. Sonraki çalışmalarda gelen iki byte hep saklanırken kullanılan byte hep saklanmış olan olur ; Kartuştan 256 byte veri çekip de yeniden kartuştaki buffer'ı doldurma ihtiyacı oluştuğunda elimizde daha kullanmadığımız 128 byte veri olur. Bir sonraki turun ilk işlemi olarak buffer'ın yeniden doldurulmasını tetikleyip elimizdeki veriyi kullanmaya devam edebiliriz.
            d. final çözümde tap saklamayacağımız için elimizde kullanabileceğimiz 40-50k'lık bir bellek var, burayı işin başında doldururuz. Bu buffer'ın underrun olmayacağı hesabı ile önyüzde çalışan kodumuz elinden geldiğince bu buffer'ı doldurmaya çalışır, bir yandan da bu buffer'dan veri kullanarak işini halleder. C64 tarafında kullanılması gereken buffer türü tercihen circular queue yapısına sahip olmalı.

            Burada aslında uygulaması en makul ve mantıklı gözüken a seçeneği ; Bunu düzeltirsek wav çalmada yaşadığımız veri kaybından dolayı sesin bozulması sorununu da bir taşla iki kuş hesabı çözmek mümkün. Ancak kendimi bu çözüme çok da yakın hissetmiyorum zira oradaki bugfix'i sorunsuz ve uğraşsız yapabilir miyim emin değilim. b şıkkı çok şık gelmiyor, tap dosyasını değiştirmek çok da makul bir seçenek değil. Değiştirelim desek de kodsal açıdan gereksiz kompleks gibi gözüküyor. c şıkkı b'deki gibi tap değiştirme dezavantajına sahip değil ancak bu da kompleks sayılır. Bu arada bu iki alternatifi de şablon kod / speed kod kullanıp bellekten feragat ederek (çok var elimizde) basitleştirmek mümkün. d şıkkı sanki uygulamak için en istekli olduğum çözüm ; sonucunda elimde bir de circular queue çözümü olmuş olacak. Buffer'ı doldur / doldurma kartuşa kendi buffer'ını doldurmak için süre tanı kısmı benzer şekilde speed code ile karmaşık döngü yapıları olmadan halledilebilir. Tabii burada uğraştığımız zamanları dikkate alıp baktığımızda belki de 256 byte'lık bir circular buffer bile iş görebilir ; Hem 16 bit işlemlerden kurtulmuş oluruz hem de başlama ve bitme süresi belli bir operasyona çok kısa da olsa 2-3 saniye ön buffer'lamadan kurtuluruz.

            Kafa patlattık sadece ; kod yok; bugünlük de bu kadar

            Yorum yap


              #7
              64Tass'daki label kullanmadan - + diyerek geriye ve ileriye referans vermenin başımı bu kadar ağrıtacağını hiç düşünmezdim. Aradan 4 gün geçmiş, yaşadığım şeyleri uzun uzun yazmak yerine madde madde özetleyeyim. Tabii öncelikle müjdeyi de vereyim; örnek olarak kullandığım TAP dosyasını C64 dataset'e yazdırmayı başardım. Her başarılı sonuç yeni bir motivasyon kaynağıdır diyerek es geçmemek lazım



              Gelelim yaşadığım sorunlara,

              1. Yukarıda bahsettiğim gibi 64tass'ın - / + syntax'ını kullanırken geriye dallanma için bir yerde + kullanmışım, emülatörde test edemediğim için farketmem zaman aldı.. onu fix ettim deyip biraz daha ilerleyince bir başka noktada daha kullandığımı farkettim katmerli oldu. Bu syntax'ı kullanmaya devam etmek ne kadar mantıklı tartıyorum şu an (64tass'ı değiştirmeyi düşünmüyorum, bütün codebase'im onda malesef)

              2. Arduino firmware üzerinde enteresan bir sorun yaşadım. İki ayrı stream fonksiyonunda lokal yani stack'te tutulması gereken dizilerim var, birinde 256 byte (single buffer stream) diğerinde ise 400 byte (video oynatmaya yarayan non interrupted stream)... Bu iki fonksiyon hiç bir zaman eş zamanlı yahut ardarda çağrılmamasına rağmen single buffer stream'i yapan fonksiyon varken diğer fonksiyonun çalışması heap yahut stack corruption'a sebep oldu hep. Atmega328'de heap statik olarak ayarlanmış ram'in üstünden başlayıp yukarı uzarken stack ise belleğin en tepesinden başlayıp aşağıya doğru iniyor. Mantıken bu sorunu runtime'da gerçekten iki metodu da içiçe çağırdığımda yaşamalıydım. Koddaki diğer bug'ları giderdiğimde en azından single buffer çalışanın düzgün çalıştığını tespit ettim, geçici olarak video oynatma özelliğinden feragat ettim. Ancak bu sorunu yeri geldiğinde fix etmeliyim.

              3. Duvara asmam gereken bir gerçeklikle tekrar yüzyüze geldim; Şöyle ki, kartuş kendisini kullanan programlara data sunarken her zaman için $8000-$BFFF aralığındaki rom'u kullanıyor. İlk örnek uygulamayı yaptığımda interrupt'ı doğrudan $FFFE adresindeki orjinal vektör üzerinden sürebilmek için tüm ram'i açmıştım. Program kartuş rom'una erişmek zorunda olmadığı için sorun yoktu. Gelgelelim transfer kodunun da gömüldüğü final versiyonda kartuş rom'una erişmemiz lazım, bunu da CIA timer interrupt'larının her tetiklenmesinden önce yapmak gerekiyor. Neyse ki bunu keşfetmem çok da zaman almadı. Kartuştan data getiren kısmı Rom'u aç / Rom'u kapa minvalinde iki macro ile çevrelemem işi gördü.


              Kabaca yaşadığım sorunlar bunlar oldu... Geliştirmeyi yaparken daha önce yaptığım tasarım'a sadık kaldım. Daha önce hali hazırda arduino firmware'de c64'den kartuşa iletilen datayı saklamak için kullandığım kodu 6502 assembly'e port ettim.

              https://github.com/nejat76/IRQHack64...ries/ByteQueue

              Kodu şu şekilde

              Kod:
              CIRCULARBUFFER = $0400
              HEAD = $20
              TAIL = $21
              ConfigMem
              ; TODO : Reconfigure zero page addresses used
              ; Alternative method, definition of HEAD and TAIL
              ; in the actual program.
              
              QueueReset
                  LDA #$00
              MC1
                  STA HEAD
                  STA TAIL
                  RTS
              ; Enqueued byte should be in A register
              Enqueue     ; 19
                  LDX TAIL                    ; 3
                  STA CIRCULARBUFFER,X        ; 5
                  INX                         ; 2
                  STX TAIL                    ; 3
                  RTS                         ; 6
              ; Dequeued byte in A register
              Dequeue     ; 18
                  LDX HEAD                    ; 3
                  LDA CIRCULARBUFFER,X        ; 4
                  INX                         ; 2
                  STX HEAD                    ; 3
                  RTS                         ; 6
              ; Queue size in A register
              GetQueueSize
                  SEC
                  LDA TAIL
                  SBC HEAD
                  RTS
              IsQueueEmpty
                  CLC
                  LDA TAIL
                  CMP HEAD
                  BNE +
                  SEC
              +        
                  RTS
              ; Carry = 1 Queue is full, Carry = 0 Queue is not full
              IsAvailable
                  CLC
                  LDA TAIL
                  CMP HEAD
                  BEQ +
                  SEC
              +        
                  RTS
              ; Carry = 1 Queue is full, Carry = 0 Queue is not full
              IsFull
                  CLC
                  LDX TAIL
                  INX
                  CPX HEAD
                  BNE +
                  SEC
              +    
                  RTS
              Destek metodları bug'lı bile olabilir, ben Enqueue ve Dequeue rutinlerini kullandım şimdilik. Buffer'ın nasıl çalışması gerektiği ile ilgili kafa patlatırken aslında final çözümde bu kadar overhead'e gerek olmadığını gördüm. Öncelikle belli sayıda byte'ı kartuştan okuduktan sonra latency olayımız devreye girdiği için bu buffer rutinlerini deterministik olmayan bir şekilde kullanmak çok da mümkün değil (mümkün olan yöntemi var ama biraz kompleks), öyle olmayınca da Dequeue ve Enqueue rutinlerini unroll etmek hatta HEAD ve TAIL değişkenlerinden de kurtulup doğrudan unroll edilmiş kodun içinde sabit bellek adreslerini kullanmak burada harcanan zamanı rahatça yarıya indirecek.

              Yorum yap


                #8
                Buffer'ın reel uygulamasında üstte bahsettiğim c yöntemine benzer bir şey kullanmış oldum ;

                Mantık sahte kod ile şu şekilde ifade edilebilir

                Init : 256 byte kartuştan çek circular buffer'a at

                Tekrar :
                1'den 128'e kadar : Kuyruktan çek interruptın kullanacağı bilgiyi set et
                1'den 128'e kadar : Kuyruktan çek interrupt'ın kullanacağı bilgiyi set et ; Kartuştan 2 byte çekip kuyruğa at.
                Tekrar'a git

                Buffer'ın durumu şu şekilde oluyor ;
                Başta : 256 byte dolu
                Döngünün ilk parçası : 128 byte
                Döngünün ikinci parçası : 256 byte (128 byte kuyruktan sırasıyla tüketiyor, paralelinde de 256 byte tekrar kuyruğa atılıyor)

                Neyse efendim ; rafine edilmemiş, optimize edilmemiş üstünde test propları bulunan kod da şu şekilde ;

                Kod:
                ; TAP Plugin for EasySD (formerly IRQHack64)
                ; 05/12/2019 - İstanbul
                .enc "screen"
                .include "../../Common/Kernal.def"
                .include "../../Common/VIC.def"
                .include "../../Common/6510.def"
                .include "../../Common/6526.def"
                IRQ6502     = $FFFE
                MOTOR_ON    = $20
                SIGNAL_ON   = $08
                DEBUG       = 0
                EMULATOR    = 0
                FREQ        = 250
                SYNC        = $40
                ; Cartridge fills the selected filename here
                FILENAMESHADOW = $FF00
                DELAYFRAMES .macro
                    LDX #\1
                    JSR WaitFrames
                    .endm
                CheckExitCondition .macro
                    CMP#0
                    BNE +
                    JMP FINISH
                +            
                    .endm
                UseRomStart  .macro    
                    LDA #PP_CONFIG_DEFAULT-MOTOR_ON
                    STA $01
                    .endm
                
                UseRomEnd  .macro
                    LDA #PP_CONFIG_RAM_ON_ROM-MOTOR_ON
                    STA $01
                    .endm
                StandardSyncM   .macro
                -  
                    BIT SYNC            ; 3
                    BVC -               ; 3
                    LDA #0              ; 2
                    STA SYNC            ; 3
                    .endm
                ;   *=$C000
                    *=$080E 
                    JSR IRQ_DisableInterrupts    
                    DELAYFRAMES 2
                    JSR IRQ_DisableDisplay  ; Turn off screen  so that we can safely command the cartridge
                    JSR IRQ_StartTalking    
                    DELAYFRAMES 5
                    JSR IRQ_EnableDisplay   ; Turn off screen  so that we can safely command the cartridge    
                    LDX #$00 
                -   
                    LDA FILENAMESHADOW,X
                    STA $0400,X
                    INX
                    BNE -
                    DELAYFRAMES 5
                    JSR IRQ_DisableDisplay  ; Turn off screen  so that we can safely command the cartridge    
                    LDX #<TAPFILENAME
                    LDY #>TAPFILENAME
                    LDA #31
                    JSR IRQ_SetName
                    LDX #01     ; Flags=read
                    JSR IRQ_OpenFile
                    BCC OPENINGCONT
                    JMP ERROR_OPENING_FILE
                OPENINGCONT 
                    INC VIC_BORDER_COLOR
                    DELAYFRAMES 50
                    INC VIC_BORDER_COLOR
                    LDA #20         ;Skip TAP header
                    STA IRQ_SEEK_LOW
                    LDA #0
                    STA IRQ_SEEK_HIGH
                    LDX #SEEK_DIRECTION_START 
                    JSR IRQ_SeekFile
                    DELAYFRAMES 50
                    INC VIC_BORDER_COLOR       
                    ; Call Stream function, we should receive 256 bytes at constant speed
                    ; Then we should give time to the cartridge to fill this buffer for us
                    JSR IRQ_SBStream    
                    DELAYFRAMES 100
                    JMP ACTION
                ERROR_OPENING_FILE  
                    JSR IRQ_EnableDisplay
                    LDA #$01
                    STA $0400
                    JMP *
                ACTION
                    ; After calling this Cart 256 bytes will be buffered in the
                    ; Circular queue. Cart will start to fill it's 256 bytes buffer.
                    JSR FillBuffer
                    JSR IRQ_EnableDisplay        
                    DELAYFRAMES 100
                    JSR IRQ_DisableDisplay
                    JSR PREPARECYCLES   
                    LDA #<CIAIRQHANDLER
                    STA IRQ6502
                    LDA #>CIAIRQHANDLER
                    STA IRQ6502+1
                
                    ;setup CIA #1
                    ;TIMER A
                    LDA #<FREQ
                    STA CIA_1_BASE + TIMER_A_LO
                    LDA #>FREQ
                    STA CIA_1_BASE + TIMER_A_HI
                
                    ;10000001 ; Release Time A
                    LDA #$81    
                    STA CIA_1_BASE + CIA_INT_MASK   
                
                    ; Turn on motor
                    LDA #PP_CONFIG_RAM_ON_ROM - MOTOR_ON
                    STA PROCESSOR_PORT
                ; Wait for cassette buttons    
                -
                    LDA PROCESSOR_PORT
                    AND #%00010000    
                    BNE -
                    DELAYFRAMES 50    
                    JSR IRQ_DisableDisplay    
                    LDY #$00    
                    LDA #$0
                    STA SYNC
                    CLI
                NEWBLOCK:
                .for i=0, i<128, i=i+1
                    ;-- 47
                    StandardSyncM    ;   6+17
                    JSR Dequeue         ;   6+18
                    CheckExitCondition  ;   3
                .next    
                .for i=0, i<128, i=i+1
                    ;-- 135
                    StandardSyncM    ;   6+17
                    JSR Dequeue         ;   6+18
                    TAY
                    UseRomStart
                    JSR FillTwo         ;   88
                    UseRomEnd
                    TYA
                .next        
                    JMP NEWBLOCK
                FINISH:
                    SEI
                    LDY #$7f
                    STY CIA_1_BASE + CIA_INT_MASK  ; Turn off CIAs Timer interrupts 
                    JSR IRQ_EnableDisplay
                    ; Turn off motor
                    LDA #PP_CONFIG_RAM_ON_ROM
                    STA PROCESSOR_PORT    
                -
                    INC VIC_BORDER_COLOR
                    JMP -
                
                
                SignalOn    .macro
                    LDA #PP_CONFIG_RAM_ON_ROM-MOTOR_ON+SIGNAL_ON
                    STA $01
                    .endm
                SignalOff    .macro
                    LDA #PP_CONFIG_RAM_ON_ROM-MOTOR_ON
                    STA $01
                    .endm
                
                CIAIRQHANDLER   
                    TAX
                    LDA CYCLES_LOW, X
                    STA CIA_1_BASE + TIMER_A_LO
                    LDA CYCLES_HIGH, X
                    STA CIA_1_BASE + TIMER_A_HI
                    SignalOn
                    INC VIC_BORDER_COLOR
                    LDA CIA_1_BASE + CIA_INT_MASK           ;Acknowledge    
                    LDA #$64
                    STA SYNC
                    SignalOff    
                
                    RTI
                ; Prepare a table of X*8
                PREPARECYCLES
                    LDX #$00
                -    
                    TXA
                    CLC
                    ROL
                    ROL 
                    ROL   
                    ROL 
                    AND #$07
                    STA CYCLES_HIGH, X
                    TXA
                    ASL
                    ASL
                    ASL
                    STA CYCLES_LOW, X
                    INX
                    BNE -
                    RTS
                WaitFrames
                FD
                    LDY #$90
                -   
                    CPY $D012
                    BNE -
                    LDY #50
                -   
                    DEY
                    BNE - 
                
                    DEX
                    BNE FD
                    RTS
                ; Fill 128 bytes buffer...
                FillBuffer
                    LDY #00
                -   
                    LDA MODULATION_ADDRESS
                    NOP
                    NOP
                    NOP
                    NOP
                    NOP
                    NOP       
                    LDA CARTRIDGE_BANK_VALUE
                    JSR Enqueue
                    NOP
                    NOP
                    INY
                    BNE -
                    RTS
                StandardSync    ;  17
                -  
                    BIT SYNC            ; 3
                    BVC -               ; 3
                    LDA #0              ; 2
                    STA SYNC            ; 3
                    RTS                 ; 6
                FillTwo     ;   88
                    LDA MODULATION_ADDRESS      ; 3
                    NOP                         ; 2
                    NOP
                    NOP
                    NOP
                    NOP
                    NOP            
                    LDA CARTRIDGE_BANK_VALUE    ; 4
                    JSR Enqueue                 ; 6 + 19
                    NOP
                    NOP
                    NOP
                    LDA MODULATION_ADDRESS      ; 3
                    NOP ; 2
                    NOP
                    NOP
                    NOP
                    NOP
                    NOP
                    LDA CARTRIDGE_BANK_VALUE    ; 4
                    JSR Enqueue                 ; 6 + 19
                    RTS                         ; 6
                
                .ALIGN 256
                CYCLES_LOW  
                .FILL 256
                CYCLES_HIGH
                .FILL 256
                TAPCIRBUFFER:
                .FILL 256
                TAPFILENAME:
                    .TEXT "d1.tap"
                    .BYTE  0
                .if DEBUG = 1
                TAPFILE:    .binary "DENEME.tap", $14
                TAPFILEEND:
                .else
                TAPFILE:
                .endif
                .include "../../Common/CircularBuffer.s"
                .include "../../Loader/CartLib.s"
                .include "../../Loader/CartLibHi.s"

                Yukarıda saydığım tüm bug'ları fix ettim ancak hala sonuç alamıyorum, bir yandan da odadaki kaset çalardan debug yardımı alıyorum ; Deseler günün birinde kasetçalar'ı bir debug aracı olarak kullanacaksın; kibar tabirle "hadi oradan" derdim. Neyse efendim benim teybe yazdığım sesler hiç de duymaya alışık olduğum / beklediğim gibi gelmiyordu. Biraz daha kaynak inceledim. Aklımdaki soru şu idi. Bu meret iki adet 1'den 0'a geçişi yakalıyor, kullanılan dalga tipi kare dalga... e peki bunun duty cycle'ı ne olmalı?

                Göze hoş gelen sinyal şu , veri transferi için yapılan tanımı aslında buradaki t1 ve t2 süreleri etkilemiyor, kırmızı ile işaretlenen zero crossing dediğimiz final ses çıkışında sesi polarite değiştirdiği kısımlar etkiliyor. t1+t2 sabit olmak üzere değişik değerler de kullanıyor olsak C64'ün bunu algılamasını bekleyebiliriz.


                Resmi gerçek boyutunda görmek için tıklayın.

Resmin ismi:  TAPSignal.png
Görüntüleme: 1
Büyüklüğü:  2,2 KB


                Nitekim ilk ve bu post'ta paylaştığım programda üstteki gibi bir sinyal üretilmiyor; üretilen sinyal şu şekilde;

                Resmi gerçek boyutunda görmek için tıklayın.

Resmin ismi:  TAPSignalActual.png
Görüntüleme: 1
Büyüklüğü:  1,1 KB

                Benim kasetçalardan aynı tınıları alamamamın sebebi de bu tamamen, aslında frekansı tutturuyorum ama benim ürettiğim kare dalga farklı olduğu için aynı şekilde çıkmıyor ses.

                https://www.luigidifraia.com/c64/tapwav/technical.htm

                Şurada tam da bu konuyla ilgili güzel bir dokümantasyon var. Şu cümle dikkat çekici : "All these pulses are square waves with a mark space ratio of 1:1" ; Meali t1 = t2 olsun diyor adam, Commodore bu şekilde yapmış

                Bu aşamada diyorum ki ; Lan emülatör, yine bana fake attın çalışmayacak çözümü çalıştırarak... derken, programda bir bug daha tespit ediyorum. Interrupt'a gitmesi gereken A register'ındaki değeri ezmişim... Bu sefer çalışacağına dair inancım çok yüksek... çalışıyor da meret

                Tabii bu mevcut şekilde sinyal üretmeye devam etmeliyim anlamına gelmiyor. Güvenilir bir kayıt elde edebilmek için yaklaşık olarak t1 = t2 koşulunu sağlamak gerekiyor. Bu da bambaşka ve yeni bir timing problemi...

                ps: Bahsetmedim ancak TAP dosyada kodu basitleştirmek için şimdilik bir iki düzenleme yaptım. Elimdeki dosya güncellenmiş versiyondu ancak ben bunu versiyon 0'a çevirdim. Şöyle ki ; İçinde geçen 0 byte'lardan sonra gelen 3 byte'ı silerek bunları FF yani en uzun periyoda çevirdim.0 değerini de dosyanın sonunda dosya sonunu algılamak için kullandım; Yani TAP dosyasının sonuna bir 0 byte'ı ekledim.

                Yorum yap


                  #9
                  Dün kasette 60 küsür sayaç sayan bir tap denemesi yaptım, oyun kendi loader'ını yükleyebildi ancak sonrası fecaat. Yükleme esnasında grafik gösteren bir oyundu, hatalı yüklemeyi direkt grafik üstünde gözlemleme imkanım da oldu

                  Son bulgularımı teyit etmek için uzmanlara danışmak istedim ve beklediğim cevabı da aldım :

                  http://cbm-hackers.2304266.n4.nabble...td4668686.html

                  Dün aklıma başka bir şey takıldı ve TAP konusuna kısa bir süre ara vereceğim. Bugfix yahut kısıtlamalardan kurtulma zamanı diyelim buna. Mevcutta kullandığım tüm stream metodları çağrıldıktan sonra kartuşun fonksiyonuna devam edebilmesi için C64'ün bir kapanıp açılması ve kartuşa giden gücün kesilmesi gerekiyor. Neden diye soracak olursanız : Gerçek zamanlı işlem gereksinimleri sebebiyle firmware üstünde sd karttan okuma işi hep while(1) şeklinde hiç bir zaman çıkılmayacak döngüler ile yapılıyor ;

                  https://github.com/nejat76/IRQHack64...64/CartApi.cpp

                  Bakınız HandleStream ve HandleNonInterruptedStream metodları.

                  Yaptığım hiç bir projede watchdog kullanma ihtiyacım olmamıştı, dün bir şekilde aklıma geldi. Dedim watchdog acaba bizim için interrupt üretiyor mudur? Üretiyormuş

                  Watchdog'un aslında iki temel ihtiyaca cevap vermesi için tasarlanmış bir yapı. Bilmeyenler için bahsettiğim şey bir kod kütüphanesi filan değil bizzat kullandığımız microcontroller'ın bize sunduğu bir hizmet. Atmega328 için kendine özel 125khz'lik bir RC osilatörden beslenen bir zamanlayıcıdan bahsediyoruz. Bu temel ihtiyaçlar da şunlar ;

                  1. Arada sırada aktif olan ancak sürekli çalışması gerekmeyen işler için güç tasarrufu sağlamak için.
                  2. Hem güvenlik hem de sağlamlık için kilitlenip kalmış kod bloklarından sistemin tekrar en başa dönmesini sağlamak için.

                  Benim kullanmaya çalışacağım kısmı aslında ikinciye benziyor ama tam da öyle değil, çünkü ben kodun ilgili yerde kitlenip kaldığımı zaten baştan biliyorum, benim için beklenmeyen bir durum değil bu.

                  Bu işi video oynatan uygulamada kullanılan HandleNonInterruptedStream için uygulamaya çalışacağım. Metodun ilgili kısmını şuraya alayım ;

                  Kod:
                        while(1) {        
                          for (bufferIndex = 0;bufferIndex<bufferLength;bufferIndex++) {
                              /* Synchronization block for each 8 byte */
                              while (PIND & 0x04); // Wait till C64 requests a stream                                     
                              while ((PIND & 0x04)==0);  // Wait till captured low signal changes back to high
                              /* Synchronization block for each 8 byte */
                              //cartInterface.SetPage(streamingBuffer[bufferIndex]);
                              uint8_t val = streamingBuffer[bufferIndex];
                              PORTD = portDVal | (val & 0xF0);
                              PORTC = portCVal | (val & 0x0F);            
                          }   
                  
                          //Serial.println(F("Next frame")); 
                          workingFile.read(streamingBuffer, bufferLength);      
                        }
                  8 byte'lık bir senkronizasyon varmış gibi gözüküyor ancak bu eski bir durum, yorum eski kalmış. Aslında bu rutin for döngüsünün her bir iterasyonu için 1 byte transferi yapıyor. C64 tarafında bizzat bu transfer ile ilgilenen kısım da aşağıdaki gibi.


                  Kod:
                  NMI_000  
                  
                      LDA MODULATION_ADDRESS
                      LDA CARTRIDGE_BANK_VALUE
                      STA $a000    
                  
                      LDA MODULATION_ADDRESS    
                      LDA CARTRIDGE_BANK_VALUE
                      STA $a001        
                  
                      LDA MODULATION_ADDRESS    
                      LDA CARTRIDGE_BANK_VALUE
                      STA $a002
                  Burada MODULATION_ADDRESS = $DF00 yani kullandığımız IO2 alanı, 16mhz'lik bir mikrokontrolcü ile çalıştığımız için 1 mhz ile yapılan erişim ile beraber oluşan /IO2 sinyalinin (low enabled olduğu için normalde high iken low'a geçer) high'dan low'a geçişini yakalayarak C64'deki koda senkron oluyoruz. CARTRIDGE_BANK_VALUE ise rom üstünde Atmega328 kartuş üstündeki epromdan bir bank seçtiğinde transfer edilmek istenen byte'ı ihtiva eden özel adreslerden birisi, $80AB böyle bir adres.

                  Neyse konumuz aslında çok burası ile de alakalı değil. Firmware üstündeki metoda baktığımızda for döngüsünün dışında workingFile.read(streamingBuffer, bufferLength); ile buffer'ın tekrar doldurulduğunu görüyoruz. İşte bizim konumuzla alakalı olan kısım. Şöyle ki ; Watchdog için bekçi köpeğinden yola çıkılması çok doğru bir isimlendirme olmuş ; Burada bizim huysuzlaşmaya başlayan köpeği sakinleştirmemiz gerekiyor. Bunun için de watchdog içindegi timer'ı resetleyeceğiz. Normal işleyişte video oynarken for döngüsü 400 byte data transferi yapıyor. Bu C64 tarafında gösterilen her bir ekran taramasında gerçekleşiyor. Yani bu saniyede 50 defa gerçekleşiyor, video oynatımı sürerken saniyede 50 defa watchdog'un timer'ını resetleyeceğiz. Kullanıcı videoyu durdurmak istediğinde yahut kartuşun video oynatma özelliğini bir programa eklenmiş şekilde kullanmak istediğimizi düşünelim; Şöyle bir çözüm uygulayabiliriz ;

                  Stream rutini (Firmware)
                  -
                  Başlangıçta watchdog'u ayarlayacak, uygun bir zaman değeri belirleyecek. Watchdog interrupt rutinin değiştireceği bir volatile değişkeni başlangıç değerine getirecek. Buna WDFinish diyelim örneğin başlangıçta sıfır olsun.
                  - While rutini içinde ancak for döngüsünün dışında watchdog'u resetle (Daha sonra dikkate almamız gereken ince bir nokta var, C64 tarafının data çekmeye başlama noktasına gelene kadar watchdog timer'ın dolmaması gerekir)
                  - While rutini içinde WDFinish>0 ise while döngüsünden çıkılıp kontrolü kartuşun C64'den gelen komutları algılayan ana döngüsüne bırak.

                  Watchdog interrupt rutini (Firmware)
                  - WDFinish volatile değişkenini 0'dan farklı bir değere set et. (Volatile değişkenler derleyiciye : Bu değişkeni kullanılmıyor diye kafana göre optimize etme, bir interrupt rutininden filan alakasız bir şekilde dolabilir dediğimiz değişkenler, embedded programlamada hayati öneme sahiptir.)

                  C64
                  -
                  Buradaki stratejimiz de aslında Firmware tarafındaki kodun for döngüsü içinde bizden senkronizasyon için MODULATION_ADDRESS adresine erişim beklemesi üzerine kurulu. Watchdog timer'ın dolacağı uzunca bir süre bu adrese erişmez isek istediğimizi gerçekleştiririz, watchdog timer dolar ve firmware üzerinde watchdog interrupt'ını tetikler.

                  Dikkatli gözler burada ince bir sorun olduğunun farkına varmıştır muhtemelen. Farkettiniz mi? (Benim farketmediğim başka şeyler de olabilir bu arada, şu an sadece ahkam kesiyorum, kod namına henüz hiç bir şey yazmadım )

                  Sorun şu ; watchdog timer doldu, interrupt tetiklendi, WDFinish değeri de 0'dan farklı bir değerde. Kod nerede takılı kaldı peki? Kod for döngüsü içinde takılı kaldı. Kontrol halen interrupt içinde iken kodu for döngüsü içinden alıp for döngüsünün bitişine de götürmek mambo jambo ile mümkündür muhtemelen ancak ben daha basit bir yöntem seçeceğim. Koda istediğini de verebiliriz, yani normal bir sayfa taraması süresince yapılan transfer işini bir kez daha yapmak bizi while döngüsü içinde WDFinish değişkeninin kontrol ettiğimiz noktaya pekala götürür.

                  Şu da sorulabilir bu arada : Nereden geliyor bu değirmenin suyu? Dosya bitmiyor mu da istediğin kadar dosyadan okuyorsun while(1) içeren döngü içerisinde?
                  Cevabı şöyle : SdFat kütüphanesi dosyanın sonuna ulaşılsa bile herhangi bir yamuk yapmıyor, read çağrısına 0 byte okudum diyerek dönüş yapıyor, bu durumda en son okuma yapılmış kısım tekrar okunuyor olur.

                  Yazdıklarımı dönüp uygulamaya çalışayım bakalım neler ile karşılaşacağım.

                  Yorum yap


                    #10
                    Kısaca gelişmeleri aktarıp uzayacağım. Burada uzun uzadıya watchdog mekanizmasından ve onu nasıl kullanacağımdan bahsetmiştim. Gel gelelim bunu uygulamadım

                    İhtiyacımı görecek daha basit ancak hem mikrokontrolcü hem de c64 tarafında bana biraz cycle tükettirecek çözüme yöneldim. Nedir o yöntem? Video için toplam transfer edilecek buffer'ların adedini takip etmek, ses için de (double buffering kullandığımız için c64 buffer'dan haberdar değil) transfer edilen toplam byte sayısını takip etmek. Video işleminde hem mikrokontrolcü tarafında hem de c64 tarafında gani gani zamanım olduğu için dosyanın boyutunu tutan 32 bit değeri 400'e bölerek ve bunu geri saydırarak çözüme ulaştım. Ses tarafında interrupt rutini max 80 küsür cycle'da işini bitirmesi gerektiği için 24 bit bir değer kullanmak durumunda kaldım, X ve Y register'ları boşta olduğu için rutin şöyle bir şeye evrildi, sid üstünde çalmadan feragat ettim geçici olarak ve sadece digimax çıktısı için bunu yaptım.

                    Interrupt rutini içinde X 24 bitlik dosya uzunluğunun en düşük anlamlı 8 biti, Y sonraki 8 bit, NUMH sıfırıncı sayfa adresi de en anlamlı 8 bit'i. Sayaç sıfırlandığında timer interrupt kapatılıp ön plan'daki kod'a devam et deniyor. (WAITHANDLE $64 adresini işaret ediyor)

                    Kod:
                    PlayDigimaxSimple                   ; 7
                        LDA #PP_CONFIG_DEFAULT          ; 2
                        STA PROCESSOR_PORT              ; 3
                        LDA MODULATION_ADDRESS          ; 4
                        NOP                             ; 2
                        NOP
                        NOP
                        LDA CARTRIDGE_BANK_VALUE        ; 4
                        STA CIA_2_BASE + DATA_B         ; 4
                        LDA #PP_CONFIG_RAM_ON_ROM       ; 2
                        STA PROCESSOR_PORT              ; 4
                        LDA CIA_1_BASE + CIA_INT_MASK   ; 4 - Acknowledge interrupt     
                    
                        TXA     ; 2
                        BNE D1  ; 2
                        TYA     ; 2
                        BNE D2  ; 2
                        LDA NUMH    ; 3
                        BEQ FINISH  ; 2
                        DEC NUMH    ; 5
                    D2  DEY     ; 2
                    D1  DEX     ; 2
                        INC VIC_BORDER_COLOR            ; 6
                        RTI
                    
                    FINISH  
                        LDA #$7F                        ; Turn off Timer A interrupts
                        STA CIA_1_BASE + CIA_INT_MASK
                        LDA CIA_1_BASE + CIA_INT_MASK   ; 4 - Acknowledge interrupt         
                        LDA #$64
                        STA WAITHANDLE
                        RTI
                    Artık video / ses'in çalması sonlandığında kartuşun ana menüsüne dönmek mümkün olacak.

                    Yorum yap


                      #11
                      Uzun süredir bu konuda güncelleme olmadı. EasySD çalışmaları nasıl gidiyor?

                      Yorum yap


                        #12
                        Demo hazırlayana kadar epey çalıştım, demo yapıldı ve durdum

                        Proje olayının kaderi bu, her yarım kalmış proje için çalışmaya devam edebileceğin hazır bir kurulumu barındırabileceğin bir yerin olsa sonuç daha iyi olur. Malesef tesis yok. Ama en azından buraya bir kaç şeyi dokümante ettiğim için geri döndüğümde kolayca devam edebilirim.

                        Yorum yap


                          #13
                          EasySD projesine YouTube videosu çeksen? İkisi bir arada

                          Yorum yap


                            #14
                            wolfiem Nickli Üyeden Alıntı Mesajı göster
                            EasySD projesine YouTube videosu çeksen? İkisi bir arada
                            Tanıtım tarzı bir şey olması lazım, development hikayesini anlatsam rating almaz. Tanıtımı da başkaları yapsın canım

                            Yorum yap


                              #15
                              Yahu sen neler yapmissin 😊

                              Hemen benim kartuslardan birini kozmetik ameliyatla EasySD yapiyorum.

                              Yorum yap

                              Hazırlanıyor...
                              X