Daha önce farklı ekran silme rutinleri karaladığımı yazmıştım. Bu mesajda yine farklı bir ekran silme rutinini adım adım açıklamaya çalışacağım. İlk olarak Spectrum'un[ garip ekran adreslemesinden bahsedelim. Spectrum'un ekran belleği $4000-$57FF arasında yer alır ve 6144 byte uzunluğundadır. Ancak bu adresler lineer değildir. Yani "ben $4000'den başlayıp $57FF'e kadar her şeyi rahatça ekrana basarım" diyemiyoruz. Ekran 3 bloktan oluşur. Her blok kendi içinde 8 text satırı ve her text satırı da 8 pixel satırı olarak düzenlenmiştir. Yani bir ekran bloku 64 pixel satırından oluşur. Bir blok 2048 byte uzunluğundadır.
Bir pixel satırı 32 byte uzunluğundadır. İlk pixel satırı -ekranın en üstündeki- $4000-$401F arasındadır. Lineer olmayan ekran yapısı nedeniyle bir alttaki pixel satırı $4020 yerine $4100 adresinden başlar ve 8 pixel satırının son adresi $4700'dir. Başka bir deyişle bir karakter satırını oluşturan 8 pixel satırının adresleri arasında 256 byte fark vardır. Bir sonraki karakter satırının ilk pixel satır adresi ise $4020'dir. Bunu da şöyle ifade edelim. Karakter satırlarının aralarındaki adres farkı 32'dir.
0. karakter satırı
0. pixel satırı: $4000
1. pixel satırı: $4100
...
...
7. pixel satırı: $4700
1. karakter satırı
0. pixel satırı: $4020
1. pixel satırı: $4120
...
...
7. pixel satırı: $4720
...
...
7. karakter satırı
0. pixel satırı: $40e0
1. pixel satırı: $41e0
...
...
7. pixel satırı: $47e0
Bu ön bilgiden sonra silme rutinimizi tasarlamaya başlayabiliriz. Silme rutinimiz ekranı iki karakter genişliğinde kolonlar halinde yukarıdan aşağıya pixel satırlarını silecek. Rutinin çalışma adresi $8000 olsun. İlk olarak ekranı $FF ile doldurarak silme işleminin istediğimiz gibi olup olmadığını kontrol etmiş olacağız.
ld hl,$4000
ld de,$4001
ld bc,6143
ld (hl),$ff
ldir
araya biraz gecikme koyalım
ld b,30
wait halt
djnz wait
Artık işleme başlayabiliriz. Ekranı temizlemek demek ekranın ilgili adresine 0 yazmak demektir. Bunun için xor a ile aküyü sıfırlayıp daha sonra ilgili adrese aküdeki değeri yazacağız. Ekran adresimizi tanımlayalım.
xor a ; aküyü sıfırla
ld ix,$4000 ; ekran başlangıç adresi
ld (ix+0),a ; iki karakter genişliği şartımız olduğu için
ld (ix+1),a ; bir sonraki kolonu da siliyoruz
Şimdi bir alttaki pixel satırını silmemiz gerekiyor. Bunun için ix registerinde $4100 olması lazım. Yani bir önceki değerin 256 fazlası olmalı. Kısa bir özet geçeyim. Z80 her ne kadar 8 bitlik işlemci ve registerleri de buna bağlı olarak 8 bitlik olsa da registerler çift olarak kullanılabilir ve bu sayede 16 bitlik işlemler yapılabilir. Ayrıca Z80'in IX ve IY registerleri ise gerçek 16 bitlik registerlerdir. İşin güzel tarafı bu iki register ayrı ayrı low ve high registerlermiş gibi komutlara sahiptir. Ancak bu komutlar Z80'in standart komut listesinde yer almaz ve undocumented command olarak tanımlanmıştır.
Bu kısa bilgiden sonra LD IX,$4000 komutunu LD IXH,$40 - LD IXL,$00 olarak düşünebiliriz. Yapmamız gereken IXH kısmının $41 olması. Bunun için ise INC IXH komutunu kullanıyoruz. Eğer bu işlemi bir döngü içinde 8 kez yaparsak bir karakter satırını oluşturan 8 pixel satırını temizlemiş oluruz. Bunun için temizleme kısmında bir sayaç tanımlamamız gerekir. Bunun için de b registerini kullanacağız.
Sayacın başlangıç değeri 8 olmalı ki istediğimiz sayıda pixel satırını temizlemiş olalım. Daha sonra sayacı 1 azaltıp sayaç 0 olana kadar işlemleri tekrarlamalıyız. Bunun için ise sayaç olarak B registerini ve sadece B registerine özel DJNZ komutunu kullanacağız. Programın yeni hali şu şekilde olacaktır.
xor a ; aküyü sıfırla
ld ix,$4000 ; ekran başlangıç adresi
loop00 ld b,8
ld (ix+0),a ; iki karakter genişliği şartımız olduğu için
ld (ix+1),a ; bir sonraki kolonu da siliyoruz
inc ixh ; ix=ix+256
djnz loop00
İlk karakter satırnı oluşturan 8 pixel satırını temizledik. Artık bir sonraki karakterine geçebiliriz. Ancak bir sorun var. İkinci karakter satırının ilk pixel adresi $4020. İlk aklımıza gelen ix registerine $4020 vererek temizleme kısmını tekrar yazmak. Fikir güzel ama 24 ekran satırı için aynı işlemi yapmak gereksiz yere hafıza kullanmak demektir. Ne yapabiliriz? Temizleme rutinini ortak olarak kullanıp bir CALL komutuyla çağırmak. İlk çözüme göre önemli miktarda hafızadan tasarruf edebiliriz. Başka bir çözüm deneyebiliriz. Biraz matematik yapacağız.
İlk karakter satırı temizlendiğinde IX registerindeki değer artık $4800 olmuştur. Bizim istediğimiz IX'in $4020 olması. Yapmamız gereken $4800'den $4020 elde etmek. Bunun için iki yol var. İlk olarak $4800'den $07E0 çıkartabiliriz. Ben çıkartma işlemi yerine daha büyük bir sayı ile toplayıp aynı sonucu elde ediyorum. Sebebi ise IX komutu için çıkartma komutunun olmaması.
Bu toplama işlemini DE registerini kullanarak yapıyoruz.
8 karakter satırımız olduğu için aynı işlemi 8 kez yapmamız ve bir sayaç kullanmamız lazım. Elimizdeki 8 bit registerlerden A ve B registerlerini kullandık. Yeni sayaç olarak C registerini kullanacağız. Sayaç değeri 8 olacak ve B registerinden önce kullanmak zorundayız. Daha sonra sayacı 1 azaltıp 0 olup olmadığına bakmalıyız. 0 ise işimiz bitmiş demektir. Sayaç her azaltılıp döngüye girildiğinde bir sonraki karakter satırında işlem yapan sayaç çalışmaya başlar. Bu arada HALT komutunu kullanarak hızımızı bira düşüreceğiz. Yoksa 1 sn'de ekran temizlenecektir.
halt
ld de,$f820
add ix,de
dec c
jr nz,loop01
Kodun başına LD C,8 satırını ekleyeceğiz. Kod aşağıdaki hale gelecek.
xor a ; aküyü sıfırla
ld ix,$4000 ; ekran başlangıç adresi
ld c,8 ; karakter satır sayacı
loop01 ld b,8 ; pixel satır sayacı
loop00 ld (ix+0),a ; iki karakter genişliği şartımız olduğu için
ld (ix+1),a ; bir sonraki kolonu da siliyoruz
inc ixh ; ix=ix+256
djnz loop00 ; pixel sayacını 1 azalt, 0 değilse işlemleri tekrarla
halt ; biraz bekle
ld de,$f820 ; Bir sonraki karakter satırını
add ix,de ; hesapla
dec c ; karakter satır sayacını 1 azalt
jr nz,loop01 ; 0 değilse işlemleri tekrarla
Buraya kadar olan kısımda bir ekran blokunu temizlemiş olduk. Artık bir sonraki ekran blokuna geçebiliriz. Ekran bloklarının adres farkı 2048'dir. IX registerine baktımızda $4100 olduğunu görürüz. Bu sayıya $0700 ekleyerek bir sonraki blok adresini elde ederiz. Ekran 3 bloktan oluştuğu için blok sayacı tanımlıyoruz ve başlangıç değeri 3 olmalı. Yeni blok adresini elde ettikten sonra sayacı azaltıp işlemleri tekrarlayacağız.
Kodun yeni hali.
xor a ; aküyü sıfırla
ld ix,$4000 ; ekran başlangıç adresi
ld l,3 ; blok sayacı
loop02 ld c,8 ; karakter satır sayacı
loop01 ld b,8 ; pixel satır sayacı
loop00 ld (ix+0),a ; iki karakter genişliği şartımız olduğu için
ld (ix+1),a ; bir sonraki kolonu da siliyoruz
inc ixh ; ix=ix+256
djnz loop00 ; pixel sayacını 1 azalt, 0 değilse işlemleri tekrarla
halt ; biraz bekle
ld de,$f820 ; Bir sonraki karakter satır
add ix,de ; adresini hesapla
dec c ; karakter satır sayacını 1 azalt
jr nz,loop01 ; 0 değilse işlemleri tekrarla
ld de,$0700 ; bir sonraki blok
add ix,de ; adresini hesapla
dec l ; blok sayacını 1 azalt
jr nz,loop02 ; 0 değilse işlemleri tekrarla
Buraya kadar olan kısımda ekranın soldan iki kolonunu yukarıdan aşağıya doğru pixel pixel sildik. Şimdi bu işlemi tüm kolonlar için yapmak gerekiyor. Specrum ekranı 32 kolondan oluştuğu ve biz de iki kolon kullanarak işlem yaptığımız için kolon sayacımızı 16 olarak tanımlıyoruz. IX registerine bakıp iki sonraki kolon için işlemlerimizi yapıp ve gerekli komutları ekleyip silme rutinimizi bitiriyoruz.
org $8000
ld hl,$4000
ld de,$4001
ld bc,6143
ld (hl),$ff
ldir
ld b,30
wait halt
djnz wait
cls xor a ; aküyü sıfırla
ld ix,$4000 ; ekran başlangıç adresi
ld h,16 ; kolon sayacı
loop03 ld l,3 ; blok sayacı
loop02 ld c,8 ; karakter satır sayacı
loop01 ld b,8 ; pixel satır sayacı
loop00 ld (ix+0),a ; iki karakter genişliği şartımız olduğu için
ld (ix+1),a ; bir sonraki kolonu da siliyoruz
inc ixh ; ix=ix+256
djnz loop00 ; pixel sayacını 1 azalt, 0 değilse işlemleri tekrarla
halt ; biraz bekle
ld de,$f820 ; Bir sonraki karakter satır
add ix,de ; adresini hesapla
dec c ; karakter satır sayacını 1 azalt
jr nz,loop01 ; 0 değilse işlemleri tekrarla
ld de,$0700 ; bir sonraki blok
add ix,de ; adresini hesapla
dec l ; blok sayacını 1 azalt
jr nz,loop02 ; 0 değilse işlemleri tekrarla
ld de,$e802 ; iki sonraki kolon
add ix,de ; adresini hesapla
dec h ; kolon sayacını 1 azalt
jr nz,loop03 ; 0 değilse işlemleri tekrarla
ret ; mutlu son
end $8000
Not: Eğer HALT komutunu DJNZ komutundan önce kullanırsanız ekran silme işlemi daha yavaş olur.