Mikroişlemciler · Ders Notu

Bölüm 10.2: Aygıt Kontrolü

Bir mikroişlemcinin dış dünyayla bağı, çevre birimlerine yazdığı ve onlardan okuduğu baytlardır. Trafik lambası yakmak, bir motoru bir adım döndürmek ya da bir sıcaklık değerini okumak, işlemcinin gözünden bakıldığında aynı iştir: belirli bir porta bir sayı yazmak ya da o porttan bir sayı okumak. Bu not, 8086'nın bu işi yapan iki komutunu, IN ve OUT'u, G/Ç adres uzayını ve port numarasının komuta nasıl yazıldığını anlatır. Ardından aygıtın hazır olmasını beklemenin üç yolu (yoklama, kesme, doğrudan bellek erişimi) karşılaştırılır. Gerçek donanım tarafında 8255 programlanabilir çevre arayüzü, unipolar step motor sürücüsü ve 7 bölütlü gösterge işlenir. emu8086'nın yedi sanal aygıtını süren programlar ise satır satır ve yazmaç düzeyinde izlenir: trafik ışıkları, step motor, LED ekran, termometre, yazıcı, robot ve test aygıtı.

Öğrenme çıktıları

Port tabanlı G/Ç
IN ve OUT komutlarının dört biçimini, port numarasının sabit ya da DX ile verilmesini ve verinin yalnız AL ya da AX üzerinden geçmesi kuralını açıklamak; port eşlemeli ve bellek eşlemeli G/Ç'yi karşılaştırmak.
Bit düzeyinde denetim
Bir porttaki her bitin bir lambaya, bir mıknatısa ya da bir durum bilgisine karşılık geldiğini çözümlemek; istenen davranışı veren bayt ya da sözcüğü hesaplamak.
Yoklama
Aygıtın hazır ya da meşgul bitini bekleyen döngüler yazmak; yoklamanın bedelini kesme ve doğrudan bellek erişimiyle karşılaştırmak.
Zamanlama
INT 15h / AH = 86h için CX:DX bekleme süresini hesaplamak; LOOP ile yazılımla gecikmenin saat turu sayısını ve süresini bulmak.
Gerçek arayüz yongaları
8255 PPI'nin kontrol sözcüğünü ve bit kur/sil sözcüğünü oluşturmak; unipolar step motoru ULN2003 üzerinden, 7 bölütlü göstergeyi çoklamalı taramayla sürmek.
emu8086 aygıtları
Trafik ışıkları, step motor, LED ekran, termometre, yazıcı, robot ve test aygıtını süren programları okumak, adım adım izlemek ve değiştirmek.
Gömülü sistem bakışı
Programın ROM'dan RAM'e yüklenip sonsuz bir döngüde çalışma ilkesini açıklamak.

10.2.1G/Ç temelleri

10.2.1.1Çevre birimi, port ve gömülü sistem

Bir mikroişlemci tek başına yalnız yazmaçlarındaki ve belleğindeki sayılarla uğraşır. Bir lambanın yanması, bir motorun dönmesi ya da bir tuşa basılması, işlemcinin dışındaki bir devrenin işidir. İşlemciye bağlanan ve onunla veri alışverişi yapan bu devrelere çevre birimi (peripheral) denir. Klavye, ekran kartı, yazıcı, zamanlayıcı yongası, analog-sayısal çevirici ve bir motor sürücüsü birer çevre birimidir.

Çevre biriminin işlemciye görünen yüzü bir ya da birkaç yazmaçtır. Bir yazmaç, aygıta gidecek komutu ya da veriyi tutar. Bir başkası aygıttan gelen veriyi, bir üçüncüsü aygıtın durumunu gösterir: hazır mı, meşgul mü, hata var mı. Bu yazmaçların her biri bir adresle seçilir. İşlemcinin bu adresler üzerinden okuyup yazdığı kapıya G/Ç portu (I/O port) denir. G/Ç, giriş/çıkış sözünün kısaltmasıdır.

Tanım 10.2.1

G/Ç portu, bir çevre birimindeki yazmacın işlemciye görünen adresidir. 8086'da porta IN komutuyla okunur, OUT komutuyla yazılır. Bir porta yazılan değerin anlamını yalnız o porta bağlı donanım belirler: aynı OUT komutu bir aygıtta lambayı yakar, başka bir aygıtta motoru döndürür.

Port numaraları işlemciye değil, sisteme aittir. 8086 yalnız bir adres üretir; o adreste hangi aygıtın duracağını anakarttaki adres çözücü belirler. Adres çözücü, adres yolundaki bitlere bakıp yalnız kendi adres aralığında bir aygıtı etkinleştiren bir kapı devresidir. Bu yüzden temel port numaraları sistemden sisteme değişebilir. Aynı aygıt başka bir adres çözücüyle başka bir porta da taşınabilir. IBM PC'de klavye denetleyicisi 60h'de, kesme denetleyicisi 20h–21h'de durur; emu8086'nın sanal trafik lambaları ise port 4'tedir. Bu numaralar bir donanım tasarımının sonucudur, 8086'nın kuralı değildir.

Çevre birimlerini süren programların en tipik yeri gömülü sistemlerdir. Çamaşır makinesi, yazıcı, modem ve trafik lambası denetleyicisi bir mikroişlemci ya da mikrodenetleyici çevresinde kurulur. Bu cihazlarda işletim sistemi ya da disk çoğu zaman yoktur. Program bir geliştirme bilgisayarında derlenir ve düz bir ikili dosyaya (.bin) dönüştürülür. Bu dosya bir ROM (salt okunur bellek) yongasına yazılır. Cihaz açılınca işlemci programı ROM'dan okur, gerekirse RAM'e kopyalar ve yürütmeye başlar. Şekil 10.2.1 bu üç adımı gösterir.

ROMprogram.binRAMçalışan kopyaCPUyürütürG/Ç portları① oku② yükle③ IN / OUT
Şekil 10.2.1: Gömülü sistemde programın yolu. (1) Açılışta işlemci ROM'daki program.bin'i okur, (2) çalışan kopyayı RAM'e yükler, (3) program aygıtlarla G/Ç portları üzerinden, IN ve OUT komutlarıyla konuşur.

Gömülü bir programın kendine özgü bir biçimi vardır. Masaüstü programı işini bitirince işletim sistemine döner. Gömülü programın döneceği bir yer yoktur. Program bu yüzden bir hazırlık kısmıyla başlar, ardından hiç bitmeyen bir döngüye girer. Döngü her turda aygıtların durumunu okur, karar verir ve aygıtlara yeni değerler yazar. Trafik ışıkları, step motor ve termostat programları tam olarak bu biçimdedir.

emu8086 bu dünyayı yazılımla taklit eder. 8086 öykünücüsüne bağlı yedi sanal aygıt, belirli port numaralarında bekler (Tablo 10.2.1). Aygıtlar virtual devices menüsünden açılır. Programın başına #start=Traffic_Lights.exe# gibi bir yönerge yazılırsa öykünücü aygıt penceresini program başlarken kendisi açar. Bu satır 8086 komutu değildir; yalnız öykünücüye verilmiş bir talimattır ve derlenen koda bayt eklemez.

Tablo 10.2.1: emu8086'ya bağlı sanal aygıtlar ve portları.
AygıtPort(lar)YönVeri
Trafik lambaları (traffic lights)4OUTsözcük: 12 lamba biti
Step motor (stepper motor)7OUT / INbit 0–2 mıknatıslar, bit 7 hazır
Robot9, 10, 11OUT / INkomut, veri, durum
Test aygıtı (simple io test)110, 112IN / OUTbayt (110), sözcük (112)
Termometre ve ısıtıcı125, 127IN / OUTsıcaklık, ısıtıcı aç/kapa
Yazıcı (printer)130OUTkarakter (INT 21h/05h ile)
LED ekran (LED display)199OUTsözcük: işaretli sayı

Tablodaki bütün port numaraları 255'ten küçüktür. Bu ayrıntı küçük görünür ama komutun yazılışını belirler: 0–255 aralığındaki bir port numarası doğrudan komutun içine yazılabilir.

10.2.1.2IN ve OUT komutları

8086'da porta erişen iki komut vardır. IN bir porttan okur ve okunan değeri biriktiriciye (AL ya da AX) yazar. OUT biriktiricideki değeri bir porta yazar. Port numarası iki yolla verilir: 0–255 arasındaysa komutun içinde 8 bitlik bir sabit olarak, aksi hâlde DX yazmacında. Tablo 10.2.2 bütün biçimleri listeler.

Tablo 10.2.2: IN ve OUT komutlarının biçimleri, makine kodları ve 8086 süreleri.
BiçimAnlamıPort aralığıMakine koduSaat turu
IN AL, portporttan 1 bayt oku0–255 (sabit)E4 pp10
IN AX, portporttan 1 sözcük oku0–255 (sabit)E5 pp10
IN AL, DX / IN AX, DXport numarası DX'te0–65 535EC / ED8
OUT port, AL / OUT port, AXporta yaz0–255 (sabit)E6 pp / E7 pp10
OUT DX, AL / OUT DX, AXport numarası DX'te0–65 535EE / EF8

Makine kodları biçimin mantığını açık eder. Sabit portlu biçim iki bayttır: ilk bayt işlem kodu, ikinci bayt port numarasıdır (pp). Örneğin IN AL, 7 komutu E4 07, OUT 4, AX komutu E7 04, IN AX, 112 komutu E5 70 olarak derlenir (112 = 70h). Port numarası için yalnız bir bayt ayrıldığından 255'ten büyük bir sayı bu biçime sığmaz. DX'li biçim ise tek bayttır; port numarası komutta değil yazmaçtadır. OUT DX, AL komutu yalnız EE'dir. İşlem kodunun en düşük biti verinin genişliğini seçer: 0 ise bayt (AL), 1 ise sözcük (AX). E4/E5, E6/E7, EC/ED ve EE/EF çiftleri bu yüzden birbirinden 1 farklıdır.

CPUAL / AXG/Ç portun ya da [DX]OUT port, AL/AXIN AL/AX, port
Şekil 10.2.2: IN ve OUT'ta veri yolu. Veri yalnız AL ya da AX üzerinden geçer; port numarası komuttaki sabit (n) ya da DX'tir.

İki komutun da en katı kuralı biriktiricidir. Veri yalnız AL (bayt) ya da AX (sözcük) üzerinden geçer. OUT 4, BX ya da IN CL, 7 yazılamaz; böyle bir işlem kodu 8086'da yoktur. BX'teki bir değeri porta göndermek için önce MOV AX, BX yazılır. İşlenenlerin sırası da sabittir: OUT'ta önce port, sonra AL/AX gelir; IN'de tersi. Sıra MOV'daki gibi “hedef, kaynak” düzenine uyar. OUT'un hedefi port, IN'in hedefi biriktiricidir.

Bu komutlar bayraklara dokunmaz. IN AL, 125 okunan sıcaklık eksi bile olsa SF'yi değiştirmez; değeri sınamak için ardından CMP ya da TEST gerekir. Okunan değer yalnız biriktiriciye gider, başka hiçbir yazmaç değişmez.

Sözcük biçimi iki ardışık portu birlikte kullanır. OUT 4, AX komutu AL'yi port 4'e, AH'yi port 5'e yazar. IN AX, 112 ise AL'yi port 112'den, AH'yi port 113'ten alır. Bellekteki sözcüklerde olduğu gibi düşük bayt düşük adrestedir (little-endian). 8086'nın 16 bitlik veri yolu, çift adresli bir sözcüğü tek yol döngüsünde aktarır. Tek adresli bir sözcük iki ayrı bayt aktarımı gerektirir ve 4 saat turu daha uzun sürer. Aygıt yazmaçlarının çift adreslere yerleştirilmesinin bir nedeni budur.

Saat turları ilginç bir ayrıntı taşır. Intel'in kılavuzuna göre DX'li biçim 8 turla, sabit portlu biçimden (10 tur) daha hızlıdır; üstelik bir bayt daha kısadır. Sık erişilen bir port için DX bir kez yüklenip döngü boyunca kullanılırsa her erişimde iki tur kazanılır.

Sık yapılan hata

OUT 7, BL, IN BX, DX ya da OUT AL, 7 yazmak. İlk ikisi biriktirici kuralını, sonuncusu işlenen sırasını çiğner. Assembler üçünü de reddeder. Doğrusu MOV AL, BL · OUT 7, AL, IN AX, DX · MOV BX, AX ve OUT 7, AL'dir.

10.2.1.3Port numarası: sabit ya da DX ile

8086'nın G/Ç adres uzayı 16 bittir: 0000h'den FFFFh'ye kadar 65 536 bayt portu. Bunların ilk 256'sına komutun içindeki sabitle ulaşılır. Geri kalanlar için port numarası önce DX'e yüklenir. Örneğin 300 numaralı porta bir bayt yazmak şöyle yapılır:

mov dx, 300     ; 300 = 012Ch > 255
mov al, 5Ah
out dx, al      ; port 012Ch ← 5Ah

İlk komut DX'e 012Ch yükler (BA 2C 01). İkinci komut gönderilecek veriyi AL'ye koyar. Üçüncü komut tek baytlık EE'dir; işlemci G/Ç yol döngüsünde adres yoluna DX'i, veri yoluna AL'yi çıkarır. OUT 300, AL yazılsaydı assembler bunu reddederdi, çünkü 300 bir bayta sığmaz.

DX'li biçim yalnız büyük port numaraları için değildir. Port numarası program çalışırken hesaplanacaksa da DX gerekir. Örneğin aynı kartın dört yazmacı 310h, 311h, 312h ve 313h ise taban adres DX'e yüklenir ve INC DX ile sıradaki yazmaca geçilir. Sabit portlu biçimde port numarası derleme anında kesinleşir, değiştirilemez.

emu8086'nın sanal aygıtlarının hepsi 255'in altında olduğundan bu aygıtları süren programlar sabit portlu biçimi kullanır: OUT 4, AX, IN AL, 7, OUT 199, AX. 8255 gibi gerçek kartlar ise PC'de çoğu zaman 300h–31Fh aralığına yerleşir ve DX'le sürülür.

10.2.1.4Port eşlemeli ve bellek eşlemeli G/Ç

Bir aygıt yazmacını işlemciye bağlamanın iki yolu vardır. Birincisinde aygıtlar bellekten ayrı bir adres uzayında durur ve yalnız özel G/Ç komutlarıyla erişilir. Buna port eşlemeli G/Ç (port-mapped ya da isolated I/O) denir. İkincisinde aygıt yazmaçları bellek adreslerinin bir bölümüne yerleştirilir ve sıradan bellek komutlarıyla okunup yazılır. Buna bellek eşlemeli G/Ç (memory-mapped I/O) denir.

Tanım 10.2.2

Port eşlemeli G/Ç'de aygıtlar 64 K'lık ayrı bir G/Ç adres uzayındadır; erişim yalnız IN ve OUT ile olur ve bellek adresi harcanmaz. Bellek eşlemeli G/Ç'de aygıt yazmaçları 1 MB'lık bellek uzayının içindedir; MOV, ADD, CMP, AND gibi bellek işlenenli her komut aygıta erişebilir.

bellek 00000h – FFFFFh (1 MB)G/Ç 0000h – FFFFhIN / OUT, M/IO̅ = 0B800hbellekaygıt bellekte: MOV, ADD, CMP … kullanılabilirport eşlemelibellek eşlemeli
Şekil 10.2.3: 8086'nın iki adres uzayı. Solda 1 MB'lık bellek (00000h–FFFFFh); B800h kesimindeki metin ekranı belleği bellek eşlemeli bir aygıttır. Sağda 64 K'lık G/Ç uzayı (0000h–FFFFh); ona yalnız IN ve OUT ulaşır.

İşlemci iki uzayı aynı adres ve veri yollarıyla kullanır. Hangisine erişildiğini bir denetim sinyali bildirir: 8086'da M/IO̅ ucu bellek döngüsünde 1, G/Ç döngüsünde 0'dır. IN ve OUT bu ucu 0 yapan tek komutlardır. Bu sinyalin, okuma ve yazma uçlarının ve yol döngüsünün saat turu düzeyindeki ayrıntısı 8086 pinlerinin konusudur. Programcı açısından sonuç basittir: port 4 ile bellek adresi 0000:0004 birbirinden tamamen bağımsız iki yerdir.

Bellek eşlemeli G/Ç'nin en tanıdık örneği PC'nin metin ekranıdır. 80×25 metin modunda ekranın her hücresi B800:0000'dan başlayan bellekte iki bayt tutar: karakter kodu ve öznitelik (renk) baytı. Ekran kartı oraya yazılan baytı hemen görüntüye çevirir. Aşağıdaki program ekranın sol üst köşesine mavi zemin üzerine sarı bir “A”, yanına bir “B” yazar:

org 100h
mov ax, 0B800h
mov es, ax              ; ES = metin ekranı kesimi
mov di, 0               ; satır 0, sütun 0
mov al, 'A'
mov ah, 1Eh             ; mavi zemin, sarı yazı
mov es:[di], ax         ; karakter + öznitelik
mov byte ptr es:[di+2], 'B'
ret

Programda tek bir G/Ç komutu yoktur, ama ekran değişir. Kesim yazmacına sabit doğrudan yüklenemediği için B800h önce AX'e, oradan ES'ye aktarılır. mov es:[di], ax tek komutla iki bayt yazar: AL'deki 41h B800:0000'a, AH'deki 1Eh B800:0001'e gider. Fiziksel adres B800h × 16 + 0 = B8000h'dir. Son komut yalnız karakter baytını yazar; ikinci hücrenin özniteliği eski değerinde (07h, siyah zemin üzerine açık gri) kalır. Tablo 10.2.3 programın izlemesidir.

Tablo 10.2.3: Bellek eşlemeli G/Ç: metin ekranına MOV ile yazmak.
AdımKomutAXESDIBelleğe yazılanAçıklama
1mov ax, 0B800hB800h0700h0000hkesim değeri önce genel yazmaca
2mov es, axB800hB800h0000hES = B800h: metin ekranı belleği
3mov di, 0B800hB800h0000hilk hücre: satır 0, sütun 0
4mov al, 'A'B841hB800h0000hAL = 41h ('A')
5mov ah, 1Eh1E41hB800h0000hAH = 1Eh: mavi zemin, sarı yazı
6mov es:[di], ax1E41hB800h0000hB800:0000 ← 41h, B800:0001 ← 1Ehtek MOV: karakter ve öznitelik; ekranda renkli A
7mov byte ptr es:[di+2], 'B'1E41hB800h0000hB800:0002 ← 42hikinci hücrenin karakter baytı; öznitelik (07h) korunur
8ret1E41hB800h0000hprogram biter

İki yöntemin artısı ve eksisi birbirinin aynası gibidir (Tablo 10.2.4). Bellek eşlemeli aygıtta bütün bellek komutları ve bütün adresleme modları kullanılabilir. Örneğin INC BYTE PTR ES:[DI] aygıt yazmacını tek komutla bir artırır, TEST doğrudan aygıtın durum bitini sınar. Buna karşılık aygıt bellek adreslerini harcar ve o adreslere bellek yerleştirilemez. Port eşlemeli aygıt bellek uzayına dokunmaz ama yalnız iki komutla, yalnız AL/AX üzerinden erişilir. Okunan değer üzerinde işlem yapmak için önce biriktiriciye alınması gerekir.

Tablo 10.2.4: Port eşlemeli ve bellek eşlemeli G/Ç'nin karşılaştırması.
ÖzellikPort eşlemeliBellek eşlemeli
Adres uzayıayrı, 64 K (0000h–FFFFh)bellekle ortak, 1 MB'ın bir bölümü
Komutlaryalnız IN, OUTMOV, ADD, CMP, TEST, AND …
Veri yazmacıyalnız AL / AXherhangi bir yazmaç ya da sabit
Adreslemesabit (0–255) ya da DXbütün bellek adresleme modları
Bellek kaybıyokaygıtın kapladığı adresler
8086 sinyaliM/IO̅ = 0M/IO̅ = 1
Örnekemu8086 aygıtları, 8255, 8259AB800h metin ekranı, A000h grafik belleği

8086 ailesi iki yöntemi de destekler ve PC ikisini birlikte kullanır. Denetim yazmaçları az sayıda ve küçük olan aygıtlar (klavye denetleyicisi, zamanlayıcı, kesme denetleyicisi) porttadır. Büyük veri alanı isteyen aygıtlar (ekran belleği) bellektedir. Ayrı G/Ç komutu olmayan işlemcilerde (ör. ARM) bütün aygıtlar bellek eşlemelidir.

Sık yapılan hata

Port numarasını bellek adresiyle karıştırmak. MOV AL, [4] komutu port 4'ü değil, DS:0004 bellek baytını okur; trafik lambalarına hiçbir şey olmaz. Port eşlemeli bir aygıta yalnız IN ve OUT ulaşır.

10.2.1.5Yoklama, kesme ve DMA

Aygıtlar işlemciden çok daha yavaştır. Bir step motor yeni konumuna milisaniyeler içinde gelir; aynı sürede 8086 binlerce komut yürütür. Yazıcı bir karakteri basarken, robot bir adım atarken işlemcinin göndereceği yeni komutu beklemesi gerekir. Aygıtın hazır olduğunu öğrenmenin üç yolu vardır.

En basit yol yoklamadır (polling). İşlemci aygıtın durum yazmacını bir döngü içinde tekrar tekrar okur. “Hazır” biti 1 olunca döngüden çıkar ve veriyi gönderir ya da alır. emu8086'nın step motoru buna iyi bir örnektir: port 7'den okunan baytın bit 7'si motor hazır olunca 1'dir.

bekle: in   al, 7          ; durum oku
       test al, 10000000b  ; bit 7: hazır mı?
       jz   bekle          ; 0 ise yeniden oku

TEST, AL ile maskenin mantıksal VE'sini hesaplar ama sonucu bir yere yazmaz; yalnız bayrakları kurar. Bit 7 sıfırsa sonuç sıfırdır ve ZF = 1 olur; JZ döngünün başına döner. Bit 7 birse ZF = 0'dır ve döngü biter. Üç komut, aygıt hazır olana kadar dönen bir bekleme döngüsü kurar.

Tanım 10.2.3

Yoklama, işlemcinin bir aygıtın durum bitini döngü içinde okuyarak aygıtın hazır olmasını beklemesidir. Aygıt ile işlemcinin hazır ve meşgul bitleri üzerinden veri aktarımını eşlemesine el sıkışma (handshaking) denir.

Yoklamanın artısı basitliğidir: ek donanım ya da kesme yordamı gerekmez, programın akışı tek bakışta izlenir. Eksisi, işlemcinin bekleme süresince başka hiçbir iş yapamamasıdır. Motor bir adımı 10 ms'de tamamlıyorsa 4,77 MHz'lik 8086 bu sürede yaklaşık 47 700 saat turunu boş döngüde harcar. Tek bir aygıt süren bir gömülü sistemde bu kabul edilebilir. Aynı anda klavye, ekran ve iletişim hattı izlenecekse kabul edilemez.

İkinci yol kesmedir. Aygıt hazır olunca işlemciye bir istek sinyali gönderir. İşlemci o anki komutu bitirir, bayrakları ve dönüş adresini yığına iter, aygıtın hizmet yordamını çalıştırır ve IRET ile kaldığı yere döner. PC'de aygıtların istekleri 8259A kesme denetleyicisinde toplanır ve önceliğe göre işlemciye iletilir. Kesmede işlemci beklemez, ama veriyi yine kendisi taşır: hizmet yordamı IN ve OUT komutlarını işlemci yürütür.

Üçüncü yol doğrudan bellek erişimidir (DMA, direct memory access). Diskten belleğe binlerce baytlık bir blok aktarılacaksa her bayt için bir kesme bile çok pahalıdır. Bu durumda 8237 gibi bir DMA denetleyicisi işlemciden yolu ister, yolu alınca veriyi aygıt ile bellek arasında kendisi taşır ve işi bitince yolu geri verir. İşlemci aktarım sırasında yalnız yolu kullanamaz; iç yazmaçlarındaki işine devam edebilir.

Tablo 10.2.5: Aygıtla eşlemenin üç yolu.
YöntemKim bekler?Veriyi kim taşır?Uygun olduğu iş
Yoklamaişlemci, döngüdeişlemci (IN/OUT)tek aygıt, basit gömülü sistem
Kesmekimse; aygıt haber veririşlemci (hizmet yordamında)klavye, seri hat, zamanlayıcı
DMAkimse; denetleyici yolu alırDMA denetleyicisi (8237)disk, ses kartı gibi blok aktarımı

emu8086'nın sanal aygıtlarının hiçbiri kesme üretmez. Bu notun bütün programları bu yüzden yoklama kullanır. Yoklama döngüsünün ortak kalıbı her aygıtta yinelenir: durum oku, ilgili biti sına, hazırsa veriyi yaz ya da oku, değilse yeniden oku.

Sık yapılan hata

Yoklama döngüsünde TEST yerine AND kullanıp sonra AL'deki veriyi kullanmaya çalışmak. AND AL, 80h durumu sınar ama AL'nin öteki bitlerini siler. Durum baytında aygıttan gelen başka bilgi de varsa (hata biti, veri biti) kaybolur. TEST aynı sınamayı AL'yi değiştirmeden yapar.

10.2.28255 programlanabilir çevre arayüzü

10.2.2.1Yapı ve adres seçimi

emu8086'nın aygıtları porta yazılan değeri kendisi yorumlar. Gerçek bir kartta ise işlemci ile lamba, anahtar ya da motor sürücüsü arasında bir arayüz yongası durur. 8086 sistemlerinde en yaygın paralel arayüz yongası Intel 8255'tir. Adı programlanabilir çevre arayüzüdür (programmable peripheral interface, PPI). “Programlanabilir” sözü, portlarının giriş mi çıkış mı olacağının yazılımla seçilmesinden gelir.

8255'in üç adet 8 bitlik portu vardır: A, B ve C. C portu iki 4 bitlik yarıya ayrılabilir: üst yarı PC7–PC4, alt yarı PC3–PC0. Bunların yanında bir de kontrol yazmacı bulunur. Dört yazmaçtan hangisine erişileceğini yonganın A1 ve A0 uçları seçer. Yonganın etkin olup olmayacağına C̅S̅ (yonga seçme) ucu karar verir; bu uç adres çözücüden gelir. Okuma ve yazma, işlemcinin G/Ç okuma ve G/Ç yazma sinyalleriyle (IOR̅, IOW̅) yapılır. Şekil 10.2.4 bağlantıyı gösterir.

8255D0–D7A0A1R̅D̅ (IOR̅)W̅R̅ (IOW̅)RESETadres çözücüC̅S̅PA0–PA7PB0–PB7PC4–PC7PC0–PC3port Aport Bport C üstport C alt
Şekil 10.2.4: 8255 PPI'nin işlemci tarafı (D0–D7, A0, A1, R̅D̅, W̅R̅, RESET, C̅S̅) ve aygıt tarafı (PA0–PA7, PB0–PB7, PC7–PC4, PC3–PC0).

Kartın taban adresi 310h ise dört yazmacın adresleri Tablo 10.2.6'deki gibidir. Adres çözücü 310h–313h aralığını tanıyıp C̅S̅'yi 0 yapar; adresin en düşük iki biti yonganın A1 ve A0 uçlarına gider.

Tablo 10.2.6: 8255'in yazmaçları (taban adres 310h, A1 ← A1, A0 ← A0 bağlantısı).
A1 A0YazmaçAdres
0 0port A310h
0 1port B311h
1 0port C312h
1 1kontrol yazmacı313h

Bu ardışık adres düzeni, 8 bitlik veri yolu olan 8088 için doğrudur. 8086'nın veri yolu 16 bittir ve iki banka ayrılır: çift adresli baytlar D0–D7'den (alt banka), tek adresli baytlar D8–D15'ten (üst banka) aktarılır. 8255'in yalnız sekiz veri ucu vardır. Yonga D0–D7'ye bağlanırsa yalnız çift adreslerdeki yazmaçlarına ulaşılabilir. Çözüm, yonganın A0 ucunu işlemcinin A1'ine, A1 ucunu A2'sine bağlamaktır. Böylece dört yazmaç 310h, 312h, 314h ve 316h'ye, yani hep çift adreslere düşer. Bu notun programları ardışık 310h–313h düzenini kullanır; 8086'ya göre kurulmuş bir kartta adresler buna göre değiştirilir.

Adreslerin hepsi 255'ten büyüktür. 8255'e her erişim bu yüzden DX üzerinden yapılır: MOV DX, 313h · OUT DX, AL. RESET'ten sonra 8255'in bütün portları giriştir. Bunun bir güvenlik nedeni vardır: açılışta henüz programlanmamış bir yonganın çıkışları bağlı devreyi rastgele sürmemelidir. Program bu yüzden yongayı kullanmadan önce kontrol yazmacına bir kontrol sözcüğü yazar.

10.2.2.2Kontrol sözcüğü: mod seçimi

Kontrol yazmacına yazılan baytın en yüksek biti (D7) baytın türünü belirler. D7 = 1 ise bayt bir mod seçme sözcüğüdür: portların modunu ve yönünü bir kerede ayarlar. D7 = 0 ise bayt bir bit kur/sil sözcüğüdür ve yalnız C portunun tek bir bitini değiştirir.

Tablo 10.2.7: 8255 mod seçme sözcüğü (D7 = 1). Yön bitlerinde 1 = giriş, 0 = çıkış.
D7D6 D5D4D3D2D1D0
1 = mod seçimiA grubu modu (00 = mod 0)port APC7–PC4B grubu modu (0 = mod 0)port BPC3–PC0

8255'in portları iki gruba ayrılır. A grubu port A ile C'nin üst yarısıdır; B grubu port B ile C'nin alt yarısıdır. Mod 0, el sıkışmasız basit G/Ç'dir: porta yazılan değer çıkış uçlarında kalır, girişten okunan değer o anki uç durumudur. Mod 1 ve mod 2 el sıkışmalı aktarım içindir. Bu modlarda C portunun bazı uçları “veri hazır” (strobe) ve “alındı” sinyalleri olarak kullanılır; mod 2 port A'yı iki yönlü yapar. Lamba, anahtar ve motor sürücüleri için mod 0 yeterlidir.

Örnek 10.2.1

İstenen: port A çıkış (LED ya da röle sürer), port B giriş (anahtarlar okunur), C'nin iki yarısı çıkış, hepsi mod 0.

  1. D7 = 1: bu bir mod seçme sözcüğüdür.
  2. D6 D5 = 00 ve D2 = 0: iki grup da mod 0.
  3. D4 = 0 (A çıkış), D3 = 0 (C üst çıkış), D1 = 1 (B giriş), D0 = 0 (C alt çıkış).
  4. Bitler yan yana yazılır: 1 00 0 0 0 1 0b = 1000 0010b = 82h.

Aynı yolla A giriş, B çıkış, C'nin tamamı giriş için 1 00 1 1 0 0 1b = 99h; A çıkış, B çıkış, C'nin tamamı giriş için 1 00 0 1 0 0 1b = 89h bulunur. Bütün portlar çıkış ise sözcük 1000 0000b = 80h olur.

Sözcüğü kurarken en sık karışan nokta yön bitlerinin anlamıdır. 8255'te 1 “giriş”, 0 “çıkış” demektir. Bu, “1 = açık” sezgisinin tersidir ve ezberlenmesi gerekir. Sunumun etkileşimli kontrol sözcüğü oluşturucusu, her portun yönüne tıklandıkça bitlerin nasıl değiştiğini gösterir; sözcük kutusuna doğrudan bir değer yazılıp portların yönü de okunabilir.

10.2.2.3Bit kur/sil (BSR)

C portu çoğu zaman tek tek denetim uçları olarak kullanılır: bir uç bir rölenin bobinini, öteki bir LED'i, bir üçüncüsü bir hane seçme transistörünü sürer. Yalnız bir ucu değiştirmek için portun tamamını yazmak, öteki uçların değerini programda ayrıca saklamayı gerektirir. 8255 bunun yerine bit kur/sil (bit set/reset, BSR) sözcüğünü sunar.

Tanım 10.2.4

Bit kur/sil sözcüğü, D7 = 0 olan kontrol baytıdır. D3–D1 değiştirilecek C bitinin numarasını (0–7), D0 o bitin yeni değerini verir; D6–D4 kullanılmaz (0 yazılır). Sözcük kontrol yazmacına yazılır, C portuna değil. Öteki C bitleri ve port yönleri değişmez.

PC7'yi 1 yapmak için bit numarası 7 = 111b ve değer 1 seçilir: 0000 1111b = 0Fh. PC7'yi 0 yapmak için yalnız D0 değişir: 0000 1110b = 0Eh. Aynı yolla PC3'ü 1 yapan sözcük 0000 0111b = 07h, PC0'ı 0 yapan sözcük 00h'dir. Sonuncusu ilginçtir: 00h bir mod sözcüğü gibi görünse de D7 = 0 olduğundan “PC0 = 0” anlamına gelir.

Sık yapılan hata

BSR sözcüğünü C portuna (312h) yazmak. Port C'ye yazılan 0Fh, PC3–PC0'ı 1, PC7–PC4'ü 0 yapar: sekiz bitin hepsi değişir. Bit kur/sil sözcüğü yalnız kontrol yazmacına (313h) yazıldığında tek bit sözcüğü olarak yorumlanır.

10.2.2.4Kod izleme: 8255 kurulumu

Aşağıdaki program dört temel işi art arda yapar: yongayı 82h ile programlar, port B'deki anahtarları okur, okunan değeri port A'daki LED'lere yazar ve BSR ile PC7'yi 1 yapar.

org 100h
mov dx, 313h   ; kontrol yazmacı
mov al, 82h    ; A,C çık; B gir
out dx, al
mov dx, 311h   ; port B
in  al, dx     ; anahtarları oku
mov dx, 310h   ; port A
out dx, al     ; LED'lere yaz
mov dx, 313h
mov al, 0Fh    ; BSR: PC7 = 1
out dx, al
ret

Programın her G/Ç erişimi aynı iki adımdan oluşur: önce DX'e adres, sonra IN ya da OUT. Port B'ye bağlı anahtarların 1001 0110b = 96h konumunda olduğu varsayılmıştır. Tablo 10.2.8 izlemeyi, son sütunda her G/Ç işlemini verir.

Tablo 10.2.8: 8255 kurulum programının izlemesi (port B'deki anahtarlar 96h konumunda; her satır komut yürütüldükten sonraki durum).
AdımKomutDXALG/ÇAçıklama
1mov dx, 313h0313h00hDX = 313h: kontrol yazmacının adresi; 255'ten büyük olduğu için DX'te tutulur
2mov al, 82h0313h82hAL = 82h = 1000 0010b: mod 0; A çıkış, C üst çıkış, B giriş, C alt çıkış
3out dx, al0313h82h313h ← 82hkontrol sözcüğü yongaya yazıldı; port yönleri artık 82h'ye göre
4mov dx, 311h0311h82hDX = 311h: port B
5in al, dx0311h96h311h → 96hanahtarlar okundu: AL = 96h = 1001 0110b
6mov dx, 310h0310h96hDX = 310h: port A
7out dx, al0310h96h310h ← 96hLED'ler anahtarları kopyalar: bit 7, 4, 2 ve 1 yanar
8mov dx, 313h0313h96hDX yeniden kontrol yazmacını gösterir
9mov al, 0Fh0313h0FhAL = 0Fh = 0000 1111b: D7 = 0, yani bit kur/sil sözcüğü
10out dx, al0313h0Fh313h ← 0FhD3–D1 = 111 (PC7), D0 = 1: yalnız PC7 = 1 olur; yönler değişmez
11ret0313h0Fhyığından 0000h → IP; PSP'deki INT 20h'ye dönülür
12INT 20h (PSP:0000)0313h0Fhprogram biter

İzlemede üç ayrıntı dikkat çeker. Birincisi, OUT DX, AL komutları hiçbir yazmacı değiştirmez; etkileri yalnız yongadadır. İkincisi, IN AL, DX AL'nin eski değerini (82h) tümüyle siler. Üçüncüsü, son OUT kontrol yazmacına yazdığı hâlde port yönlerini değiştirmez: D7 = 0 olduğu için yonga onu bir bit kur/sil sözcüğü olarak yorumlar. Kurulumdan sonra port A'nın LED'lerinde bit 7, 4, 2 ve 1 yanar; C portunda yalnız PC7 = 1'dir.

Programın makine kodu 21 bayttır. Dört MOV DX üçer, iki MOV AL ikişer bayt tutar; dört G/Ç komutu ve RET birer bayttır. Bu kısalık, DX'li biçimin bir başka yararıdır: port numarası tek bir MOV'da verilir ve G/Ç komutunun kendisi tek bayta iner.

10.2.3Trafik ışıkları

10.2.3.1Port 4 ve lamba bitleri

emu8086'nın trafik ışıkları aygıtı dört yönlü bir kavşaktır. Her köşede bir direk, her direkte üç lamba vardır: kırmızı, sarı ve yeşil. On iki lambanın her biri port 4'e yazılan 16 bitlik sözcüğün bir bitine bağlıdır. Bit 1 ise lamba yanar, 0 ise söner. Bit 12–15 kullanılmaz. Port 4'e bir sözcük yazmak, bu yüzden kavşağın tam bir fotoğrafını bir kerede çizmek demektir.

Bitlerin lambalara dağılımı düzenlidir. Direkler k = 0, 1, 2, 3 diye numaralanırsa k. direğin kırmızı lambası bit 3k, sarısı bit 3k + 1, yeşili bit 3k + 2'dir. Sağ alt direk bit 0–2'yi, sağ üst 3–5'i, sol üst 6–8'i, sol alt 9–11'i (onaltılık 9, A, B) kullanır.

0123456789ABFEDC BA98 7654 32100000 0011 0000 1100
Şekil 10.2.5: Port 4 = 030Ch = 0000 0011 0000 1100b. Yanan bitler 2 ve 8 (yeşil), 3 ve 9 (kırmızı): sağ alt ile sol üst yeşil, sağ üst ile sol alt kırmızı.

Şekil 10.2.5'teki değeri çözmek için sözcük dörtlü gruplara ayrılır: 0000 0011 0000 1100b. 1 olan bitler sağdan sayılarak bulunur: bit 2, 3, 8 ve 9. Bit 2 = 3 · 0 + 2, yani sağ alt direğin yeşilidir. Bit 3 = 3 · 1 + 0 sağ üst direğin kırmızısıdır. Bit 8 = 3 · 2 + 2 sol üst direğin yeşili, bit 9 = 3 · 3 + 0 sol alt direğin kırmızısıdır. Tersi de aynı kolaylıkla yapılır: istenen lambaların bit numaraları bulunur ve 2'nin bu kuvvetleri toplanır.

Örnek 10.2.2

Bütün yönlerde yalnız yeşil lambaları yakan sözcük nedir?

  1. Yeşil lambalar bit 3k + 2'dedir: k = 0, 1, 2, 3 için bit 2, 5, 8, 11.
  2. Sözcük 2² + 2⁵ + 2⁸ + 2¹¹ = 4 + 32 + 256 + 2048 = 2340'tır.
  3. İkili yazımı 0000 1001 0010 0100b, onaltılık yazımı 0924h'dir.

Kavşakta böyle bir durum elbette tehlikelidir; örnek yalnız bit hesabını gösterir. Bütün kırmızıları yakan sözcük aynı yolla bit 0, 3, 6, 9'dan 0000 0010 0100 1001b = 0249h çıkar. Programda bu değerin adı all_red'dir.

10.2.3.2Durum tablosu

Kavşağın işleyişi birkaç durumun belirli bir sırayla tekrarlanmasıdır. Her durum port 4'e yazılacak bir sözcükle tanımlanır. Program bu sözcükleri bellekte ardışık bir tablo olarak tutar:

;                 FEDC_BA98_7654_3210
situation  dw     0000_0011_0000_1100b
s1         dw     0000_0110_1001_1010b
s2         dw     0000_1000_0110_0001b
s3         dw     0000_1000_0110_0001b
s4         dw     0000_0100_1101_0011b
sit_end = $
all_red    equ    0000_0010_0100_1001b

Sayıların içindeki alt çizgiler yalnız okunabilirlik içindir; emu8086 onları yok sayar. Açıklama satırındaki FEDC_BA98_7654_3210 dizisi her sütunun hangi bit olduğunu gösterir. sit_end = $ satırı bellekte yer kaplamaz: $, assembler'ın o anki adres sayacıdır ve tablonun bittiği adresi verir. Program bu adresi “tablonun sonuna geldim mi?” sorusu için kullanır. all_red de EQU ile tanımlı bir sabittir, bellekte değil, komutun içinde durur.

Tablo 10.2.9: Trafik durumları ve yanan lambalar.
AdHexSağ altSağ üstSol üstSol alt
situation030Chyeşilkırmızıyeşilkırmızı
s1069Ahsarıkırmızı + sarısarıkırmızı + sarı
s20861hkırmızıyeşilkırmızıyeşil
s30861hkırmızıyeşilkırmızıyeşil
s404D3hkırmızı + sarısarıkırmızı + sarısarı
all_red0249hkırmızıkırmızıkırmızıkırmızı

Tablodaki her satır bit hesabıyla doğrulanabilir. s1 = 069Ah = 0000 0110 1001 1010b'de 1 olan bitler 1, 3, 4, 7, 9 ve 10'dur. Bit 1 sağ alt direğin sarısı, bit 3 ve 4 sağ üst direğin kırmızısı ile sarısı, bit 7 sol üst direğin sarısı, bit 9 ve 10 sol alt direğin kırmızısı ile sarısıdır. s1, yeşilden kırmızıya geçecek yönlerde sarıyı, kırmızıdan yeşile geçecek yönlerde “kırmızı + sarı” uyarısını yakar. Kırmızı + sarı, “hazırlan” anlamına gelen geçiş işaretidir. s4 aynı geçişi ters yönde yapar.

s2 ile s3 aynı sözcüktür. Program her durumda aynı süre (5 saniye) beklediği için yan yolun yeşili iki aralık, yani 10 saniye sürer. Süreyi değiştirmek için programa dokunmak gerekmez; tabloya aynı sözcüğü bir kez daha yazmak yeter. Davranışın kodda değil tabloda durması bu tasarımın en güçlü yanıdır.

0123456789ABFEDC BA98 7654 32100000 0010 0100 1001 0123456789ABFEDC BA98 7654 32100000 0011 0000 1100 0123456789ABFEDC BA98 7654 32100000 0110 1001 1010 0123456789ABFEDC BA98 7654 32100000 1000 0110 0001 0123456789ABFEDC BA98 7654 32100000 0100 1101 0011
Şekil 10.2.6: Programın kavşağa sırayla yazdığı durumlar: all_red (0249h), situation (030Ch), s1 (069Ah), s2 = s3 (0861h) ve s4 (04D3h). Altta port 4'e yazılan sözcüğün ikili yazımı vardır.

Sunumun etkileşimli trafik bit düzenleyicisi aynı hesabı ters yönden yaptırır: bir bite tıklanınca o lamba yanar ya da söner ve sözcüğün onaltılık değeri güncellenir. Tablodaki adlara tıklanınca o durumun sözcüğü yüklenir.

10.2.3.3INT 15h ile bekleme

Her durum birkaç saniye sürmelidir. Programın bu süreyi ölçmesi gerekir. PC BIOS'u bunun için hazır bir hizmet sunar: INT 15h, AH = 86h. Hizmet, CX:DX çiftindeki 32 bitlik sayı kadar mikrosaniye bekler. CX yüksek sözcüğü, DX düşük sözcüğü taşır.

Örnek 10.2.3

5 saniyelik bekleme için CX ve DX ne olmalıdır?

  1. Süre mikrosaniyeye çevrilir: 5 s = 5 × 1 000 000 = 5 000 000 µs.
  2. Onluk sayı ardışık 16'ya bölmeyle onaltılığa çevrilir: 5 000 000 ÷ 16 = 312 500 kalan 0; 312 500 ÷ 16 = 19 531 kalan 4; 19 531 ÷ 16 = 1 220 kalan 11 (B); 1 220 ÷ 16 = 76 kalan 4; 76 ÷ 16 = 4 kalan 12 (C); 4 ÷ 16 = 0 kalan 4.
  3. Kalanlar sondan başa okunur: 4C4B40h. 32 bitlik yazımı 004C4B40h'dir.
  4. Sayı ikiye bölünür: CX = 004Ch, DX = 4B40h.
5 000 000 µs = 004C4B40h (32 bit)004C4B40CX = 004ChDX = 4B40hyüksek sözcükdüşük sözcük
Şekil 10.2.7: 5 000 000 µs = 004C4B40h. Yüksek sözcük CX'e (004Ch), düşük sözcük DX'e (4B40h) yazılır.
; 5 saniye bekle (5 milyon µs)
mov cx, 4Ch     ; 004C4B40h = 5 000 000
mov dx, 4B40h
mov ah, 86h
int 15h

Aynı hesap başka süreler için de yapılır. 1 saniye 1 000 000 = 000F4240h'dir: CX = 000Fh, DX = 4240h. 0,2 saniye 200 000 = 00030D40h'dir: CX = 0003h, DX = 0D40h. İkinci örnekte DX'in onaltılık değeri harfle başlar; kaynak koda 0D40h diye yazılır, başına 0 konmazsa assembler onu bir etiket adı sanar. Hizmetin bir çağrıda bekleyebileceği en uzun süre CX = DX = FFFFh, yani 4 294 967 295 µs'dir: yaklaşık 71,6 dakika.

Başarılı bir beklemeden sonra CF = 0 döner. emu8086'da hizmet AX'e dokunmaz; bazı gerçek BIOS'lar ise AH'ye bir durum kodu yazar. Program bu yüzden çağrıdan sonra AX'teki değere güvenmez. Gerçek PC'de bu hizmet zamanlayıcı yongasının kesmesiyle ölçülür ve süresi işlemcinin hızından bağımsızdır. emu8086'da da süreli bekleme bu hizmetle yapılır; öykünücü bekleme süresini kendi saatine göre uygular.

Sık yapılan hata

Süreyi CX ve DX'e onluk olarak bölüştürmek: 5 000 000 için CX = 5, DX = 0 yazmak. CX:DX tek bir 32 bitlik ikili sayıdır; CX = 5, DX = 0 değeri 5 × 65 536 = 327 680 µs, yani yaklaşık 0,33 saniyedir. Sayı önce bütün olarak onaltılığa çevrilir, sonra 16 bitlik iki yarıya ayrılır.

10.2.3.4Yazılımla gecikme: LOOP ve saat turu

BIOS hizmeti olmayan bir gömülü sistemde bekleme, işlemcinin bilinen sayıda saat turu harcamasıyla yapılır. En basit yol boş bir LOOP döngüsüdür. Döngünün süresi, komutların saat turu sayılarından hesaplanır. 8086 kılavuzuna göre gereken süreler Tablo 10.2.10'dedir.

Tablo 10.2.10: Gecikme döngüsündeki komutların 8086 süreleri.
KomutSaat turu
MOV CX, sabit4
LOOP (CX ≠ 0, atlar)17
LOOP (CX = 0, atlamaz)5
DEC r162
JNZ (atlar / atlamaz)16 / 4
CALL yakın / RET yakın19 / 8
; ≈ 1 ms gecikme (4,77 MHz 8086)
mov  cx, 281    ; 4 tur
bekle:
loop bekle      ; 280 × 17 + 1 × 5 tur

CX = N ile başlayan döngüde LOOP N kez çalışır. İlk N − 1 seferde CX sıfıra inmemiştir ve komut atlar (17 tur). Son seferde CX = 0 olur ve komut atlamaz (5 tur). Toplam süre şöyledir:

T = 4 + 17(N − 1) + 5 = 17N − 8 saat turu.

Saat turunu süreye çevirmek için frekans gerekir. Orijinal IBM PC'nin 8088'i 4,77 MHz'de çalışır: bir saat turu 1 / 4,77 MHz ≈ 0,2096 µs'dir. 1 ms bu yüzden 4 770 saat turudur. 17N − 8 = 4 770 denkleminden N ≈ 281,06 çıkar. CX'e yalnız tam sayı yazılabildiği için en yakın değer seçilir: N = 281, 17 · 281 − 8 = 4 769 tur, yani ≈ 0,9998 ms.

Aynı döngü başka bir işlemcide başka bir süre verir. 5 MHz'de 1 ms 5 000 turdur; N ≈ 294,6 ve N = 295 seçilirse 5 007 tur ≈ 1,0014 ms elde edilir. 4,77 MHz için bulunan N = 281 ise 5 MHz'de yalnız 4 769 / 5 ≈ 954 µs sürer. Yazılımla gecikmenin en büyük kusuru budur: süre, saat hızına bağlıdır. Program daha hızlı bir işlemciye taşınınca bütün beklemeler kısalır.

Tek döngünün bir üst sınırı da vardır. CX = 0 ile başlayan LOOP önce CX'i FFFFh'ye indirir ve 65 536 kez çalışır. Bu, 17 · 65 536 − 8 = 1 114 104 tur, 4,77 MHz'de yaklaşık 233,6 ms'dir. Daha uzun bekleme için iç içe döngü gerekir:

       mov  dx, 100     ; 4 tur
dis:   mov  cx, 281     ; 4 tur
ic:    loop ic          ; 17 · 280 + 5 = 4 765 tur
       dec  dx          ; 2 tur
       jnz  dis         ; 16 tur, son seferde 4

Dış döngünün bir turu 4 + 4 765 + 2 = 4 771 tur ve ardından bir JNZ'dir. Yüz turun 99'unda JNZ atlar (16), sonuncusunda atlamaz (4). Toplam 4 + 100 · 4 771 + 99 · 16 + 4 = 478 692 tur, 4,77 MHz'de yaklaşık 100,4 ms'dir. İç sayaç CX'te, dış sayaç DX'te durur; LOOP yalnız CX'i kullandığından iki sayaç birbirine karışmaz. Gecikme bir yordam olarak yazılıp çağrılırsa her çağrıya CALL (19) ve RET (8) turu da eklenir.

Bu hesaplar yaklaşıktır. Kesmeler döngünün arasına girip süreyi uzatabilir. Yavaş belleğin eklediği bekleme durumları ve komut kuyruğunun doluluğu da tur sayısını değiştirir. emu8086 komut sürelerini modellemez: döngü, bilgisayarın hızında ve öykünücünün adım gecikmesi ayarına göre koşar. Bu yüzden emu8086'da süreli bekleme INT 15h/86h ile yapılır; yazılımla gecikme hesabı gerçek donanım içindir.

10.2.3.5Kod izleme: gecikme döngüsü

Formülün nereden geldiğini görmek için CX = 4 ile kısa bir döngü izlenir:

org 100h
mov cx, 4      ; 4 saat turu
bekle:
loop bekle     ; 17 tur, sonuncu 5
ret
Tablo 10.2.11: Gecikme döngüsünün izlemesi; açıklamadaki tur toplamları 8086 komut sürelerinden hesaplanmıştır.
AdımKomutCXIPAçıklama
1mov cx, 40004h0103h4 tur; toplam 4
2loop bekle0003h0103hCX = 3 ≠ 0: aynı komuta atlar, 17 tur; toplam 21
3loop bekle0002h0103hCX = 2: 17 tur; toplam 38
4loop bekle0001h0103hCX = 1: 17 tur; toplam 55
5loop bekle0000h0105hCX = 0: atlamaz, 5 tur; toplam 60 = 17 · 4 − 8
6ret0000h0000hdöngüden çıkıldı; 60 tur 4,77 MHz'te ≈ 12,6 µs

LOOP bekle komutu kendi adresine (0103h) atlar; makine kodu E2 FE'dir ve FEh = −2, komutun kendi uzunluğu kadar geri gitmek demektir. İlk üç çalıştırmada IP yine 0103h olur. Dördüncü çalıştırmada CX = 0'a iner, atlama olmaz ve IP 0105h'ye, RET'e geçer. Toplam 4 + 17 + 17 + 17 + 5 = 60 = 17 · 4 − 8 turdur. 60 tur, 4,77 MHz'de 60 × 0,2096 ≈ 12,6 µs sürer.

10.2.3.6Trafik programı

Program dört parçadan oluşur: bütün yönleri kırmızıya alan hazırlık, tablodaki sözcüğü porta yazan döngü gövdesi, 5 saniyelik bekleme ve tablonun sonunda başa dönüş.

#start=Traffic_Lights.exe#
mov ax, all_red
out 4, ax
mov si, offset situation
next:
mov ax, [si]
out 4, ax
mov cx, 4Ch      ; 5 s bekle
mov dx, 4B40h
mov ah, 86h
int 15h
add si, 2        ; sonraki durum
cmp si, sit_end
jb  next
mov si, offset situation
jmp next
; ardından durum tablosu (situation … all_red)

Program ilk iş olarak bütün yönleri kırmızı yapar. Bu bir güvenlik önlemidir: denetleyici açıldığında kavşağın hangi durumda kaldığı bilinmez ve bir yön yeşilken öbürüne de yeşil yakılmamalıdır. MOV AX, all_red komutu sabiti doğrudan AX'e yükler (B8 49 02); OUT 4, AX sözcüğün iki baytını port 4 ve 5'e yazar.

SI, tablodaki sıradaki sözcüğün adresini tutar. MOV AX, [SI] o adresteki sözcüğü okur ve OUT 4, AX kavşağı o duruma geçirir. Ardından 5 saniye beklenir. ADD SI, 2 bir sonraki sözcüğe geçer; tablo sözcüklerden oluştuğu için adım 2 bayttır. CMP SI, sit_end SI'yı tablonun bitiş adresiyle karşılaştırır. SI hâlâ küçükse CF = 1'dir ve JB next döngüye döner. Eşitlik olunca CF = 0'dır; SI tablonun başına alınır ve JMP next ile döngü yeniden başlar. Program hiç bitmez: gömülü sistemlerde ana program genellikle böyle sonsuz bir döngüdür.

Tablonun bellekteki yeri, kodun uzunluğuna bağlıdır. Program ORG 100h ile derlenirse kod 36 bayt tutar ve tablo 0124h'den başlar: situation 0124h, s1 0126h, s2 0128h, s3 012Ah, s4 012Ch'dedir; sit_end = 012Eh olur. Kaynakta tablonun yeri değişirse bu adresler de değişir, ama program yalnız etiketleri kullandığı için doğru çalışmayı sürdürür. emu8086 bir .COM programını yüklerken CX'e program boyutunu yükler; izlemede ilk MOV CX'ten önceki CX 0000h alınmıştır. Program bu ilk değere dayanmadığı için sonuç değişmez.

Tablo 10.2.12: Trafik ışıkları programının izlemesi (tablo 0124h'de; INT 15h bekleme dışında her satır komut yürütüldükten sonraki durum).
AdımKomutAXSICXDXCFPort 4Açıklama
1mov ax, all_red0249h0000h0000h0000h0—AX = all_red = 0249h; EQU sabiti, bellekte yer kaplamaz
2out 4, ax0249h0000h0000h0000h00249hbütün yönler kırmızı
3mov si, offset situation0249h0124h0000h0000h00249hSI = 0124h: tablonun ilk sözcüğü (situation)
4mov ax, [si]030Ch0124h0000h0000h00249hAX ← [0124h] = 030Ch
5out 4, ax030Ch0124h0000h0000h0030Chlambalar situation durumuna geçer
6mov cx, 4Ch030Ch0124h004Ch0000h0030ChCX = 004Ch: 5 000 000 µs'nin yüksek sözcüğü
7mov dx, 4B40h030Ch0124h004Ch4B40h0030ChDX = 4B40h: düşük sözcük
8mov ah, 86h860Ch0124h004Ch4B40h0030ChAH = 86h: BIOS bekleme hizmeti; AL değişmez
9int 15h860Ch0124h004Ch4B40h0030Ch5 saniye beklenir; başarıda CF = 0
10add si, 2860Ch0126h004Ch4B40h0030ChSI = 0126h: sonraki durum
11cmp si, sit_end860Ch0126h004Ch4B40h1030Ch0126h − 012Eh: borç var, CF = 1
12jb next860Ch0126h004Ch4B40h1030ChJB atlar: next
13mov ax, [si]069Ah0126h004Ch4B40h1030ChAX ← [0126h] = 069Ah (s1)
14out 4, ax069Ah0126h004Ch4B40h1069Ahsarı geçiş durumu
…bekleme, SI = 0128h, CMP ve JB
23out 4, ax0861h0128h004Ch4B40h10861hs2 = 0861h; yan yol yeşil
…bekleme, SI = 012Ah
32out 4, ax0861h012Ah004Ch4B40h10861hs3 = 0861h; aynı durum 5 saniye daha sürer
…bekleme, SI = 012Ch
41out 4, ax04D3h012Ch004Ch4B40h104D3hs4 = 04D3h; ana yola dönüş hazırlığı
…bekleme
46add si, 286D3h012Eh004Ch4B40h004D3hSI = 012Eh = sit_end
47cmp si, sit_end86D3h012Eh004Ch4B40h004D3heşit: CF = 0, ZF = 1
48jb next86D3h012Eh004Ch4B40h004D3hJB atlamaz: tablo bitti
49mov si, offset situation86D3h0124h004Ch4B40h004D3hSI tablonun başına döner
50jmp next86D3h0124h004Ch4B40h004D3hsonsuz döngü
51mov ax, [si]030Ch0124h004Ch4B40h004D3hAX ← [0124h] = 030Ch
52out 4, ax030Ch0124h004Ch4B40h0030Chsituation yeniden: döngü baştan başlar

İzleme, INT 15h çağrısının AX'te iz bıraktığını da gösterir. MOV AH, 86h yalnız üst baytı değiştirir; AL, son okunan durumun düşük baytını korur. Bu yüzden 8. adımdan sonra AX = 860Ch'dir. Döngünün başındaki MOV AX, [SI] AX'in tamamını yeniden yazdığı için bu artık bir sorun oluşturmaz.

Sık yapılan hata

ADD SI, 2 yerine INC SI yazmak. Tablo sözcüklerden oluşur; SI bir artırılırsa ikinci okuma bir sözcüğün ortasından yapılır. 0125h'den okunan sözcük, situation'ın yüksek baytı (03h) ile s1'in düşük baytının (9Ah) birleşimi olan 9A03h'dir. Bu değer bit 12–15'e de taşan anlamsız bir lamba düzeni yakar.

10.2.4Step motor

10.2.4.1emu8086 step motoru

Sıradan bir doğru akım motoru, gerilim verildiği sürece döner; ne kadar döndüğünü bilmek için ayrı bir algılayıcı gerekir. Step motor (stepper motor) ise sargılarına uygulanan her yeni uyarma düzeninde belirli bir açı kadar döner ve orada durur. Mil, sargılardan hangilerinin akım taşıdığına göre konum alır. Uyarma düzeni sırayla değiştirilirse motor adım adım döner. Bu yüzden konumu, gönderilen adım sayısından bilinir. Yazıcı kafaları, disket sürücüleri, 3B yazıcılar ve CNC tezgâhları step motorla sürülür.

emu8086'nın step motoru port 7'ye bağlıdır ve basitleştirilmiş bir modeldir. Porta yazılan baytın bit 0, 1 ve 2'si üç elektromıknatısı açar ya da kapatır. Motor hem saat yönünde (clockwise, CW) hem de ters yönde (CCW) döndürülebilir. Bir yarım adım 11,25°, bir tam adım 22,5°'dir; tam tur bu yüzden 32 yarım adım ya da 16 tam adımdır.

2107654321000000110port 7 = 00000110b
Şekil 10.2.8: emu8086 step motoru. Port 7 = 0000 0110b: bit 2 ve bit 1'e bağlı mıknatıslar açık, rotor ikisinin arasına çekilir.

Motorun bir de durum çıkışı vardır. Bir OUT komutundan sonra motor yeni konumuna gidene kadar meşguldür. Yeni komut almaya hazır olduğunda port 7'den okunan baytın bit 7'sini 1 yapar. Program her baytı göndermeden önce bu biti yoklar. Hazır olmayan motora gönderilen bayt kaybolur ya da motor adım kaçırır.

10.2.4.2Veri tabloları ve mıknatıs dizileri

Motoru döndürmek, mıknatısları belirli bir sırayla açıp kapatmaktır. Bu sıra programda dört baytlık tablolar olarak tutulur. emu8086'nın örnek programı dört tablo tanımlar (Tablo 10.2.13).

Tablo 10.2.13: Step motor veri tabloları (bit 2–0 = mıknatıs 2–0; adresler ORG 100h'ye göre).
TabloAdresAnlam1234
datcw0102hCW yarım adım110100011010
datccw0106hCCW yarım adım011001110010
datcw_fs010AhCW tam adım001011110000
datccw_fs010EhCCW tam adım100110011000

Baytın her biti bir mıknatısı gösterir: 110b, mıknatıs 2 ve 1'in açık, 0'ın kapalı olduğu anlamına gelir; 000b bütün mıknatısları kapatır. Yarım adım tablosu çift ve tek mıknatısı sırayla açar: 110 (iki), 100 (bir), 011 (iki), 010 (bir). İki mıknatıs açıkken rotor ikisinin arasında, tek mıknatıs açıkken o mıknatısın karşısında durur. Bu ara konumlar adımı yarıya böler. Tam adım tablosu ise tek, çift, çift mıknatıs ve “hepsi kapalı” baytını gönderir.

110100011010011001110010001011110000100110011000datcw · 0102hdatccw · 0106hdatcw_fs · 010Ahdatccw_fs · 010Ehadd bx, 4 → sonraki tablo
Şekil 10.2.9: Dört tablo bellekte arka arkaya durur ve her biri 4 bayttır. BX'e 4 eklemek bir sonraki tabloya geçirir.

Tabloların bellekte ardışık durması programın en kullanışlı ayrıntısıdır. datcw 0102h'de başlar; datccw 4 bayt sonra, 0106h'de; datcw_fs 010Ah'de; datccw_fs 010Eh'dedir. BX bir tablonun başını gösteriyorsa ADD BX, 4 onu bir sonrakine taşır. Program 32 (20h) tur sonra tabloyu değiştirir. Yön ve adım türü böylece CW yarım → CCW yarım → CW tam → CCW tam → başa sırasıyla döner.

Kaynak koddaki yazım biçimi de değişebilir. emu8086'nın özgün örneğinde her bayt ayrı satırda ve sekiz basamakla yazılır: datcw db 0000_0110b, alt satırda db 0000_0100b … Notun programında tablo aynı değerlerle tek satıra sığdırılmıştır: datcw db 110b,100b,011b,010b. İki yazım da aynı dört baytı üretir.

2107654321000000110datcw[0] = 110b 2107654321000000100datcw[1] = 100b 2107654321000000011datcw[2] = 011b 2107654321000000010datcw[3] = 010b
Şekil 10.2.10: datcw tablosunun dört baytı sırayla: 110b (mıknatıs 2 ve 1), 100b (2), 011b (1 ve 0), 010b (1). Her bayt rotoru bir yarım adım, 11,25° döndürür.

10.2.4.3Kontrol programı

ORG 100h
steps_before_direction_change = 20h
jmp start
datcw     db 110b,100b,011b,010b
datccw    db 011b,001b,110b,010b
datcw_fs  db 001b,011b,110b,000b
datccw_fs db 100b,110b,011b,000b
start:
mov bx, offset datcw
mov si, 0
mov cx, 0              ; tur sayacı
next_step:
wait: in   al, 7
      test al, 10000000b
      jz   wait
mov al, [bx][si]
out 7, al
inc si
cmp si, 4
jb  next_step
mov si, 0
inc cx
cmp cx, steps_before_direction_change
jb  next_step
mov cx, 0
add bx, 4              ; sonraki tablo
cmp bx, offset datccw_fs
jbe next_step
mov bx, offset datcw
jmp next_step

steps_before_direction_change = 20h bir sabittir. = yönergesi EQU gibi çalışır: bellekte yer kaplamaz, kullanıldığı yerde değeriyle değiştirilir. Tablolar kodun başında durduğu için ilk komut JMP start onların üzerinden atlar; aksi hâlde işlemci tablo baytlarını komut sanıp yürütürdü. Kısa atlamanın kodu EB 10'dur: 0102h'den 16 bayt ileri, 0112h'ye.

Programın üç sayacı vardır. BX kullanılan tablonun başını, SI tablo içindeki sırayı (0–3), CX tamamlanan tur sayısını tutar. Döngü gövdesinin ilk üç satırı yoklama döngüsüdür: bit 7 = 0 iken motor meşguldür ve port yeniden okunur. Bit 7 = 1 olunca MOV AL, [BX][SI] sıradaki baytı okur.

[BX][SI] yazımı [BX + SI] ile aynıdır: taban artı indis adreslemesi. BX tablonun başlangıç adresini, SI o tablodaki konumu verir; etkin adres ikisinin toplamıdır. BX = 0102h, SI = 2 için okunan bayt [0104h] = 011b'dir. Bu adresleme modu, iki boyutlu bir dizinin satır ve sütunu gibi düşünülebilir: BX hangi tablo, SI tablonun kaçıncı elemanı.

Bayt porta yazıldıktan sonra SI bir artar. SI 4'ten küçükse aynı tablonun sonraki baytına geçilir. SI = 4 olunca tablo bitmiştir: SI sıfırlanır ve CX bir artar. CX henüz 20h'ye ulaşmadıysa aynı tablo yeniden başlar. Ulaştıysa CX sıfırlanır ve BX 4 artırılır. Son satırlar BX'in tablolar dışına çıkmasını önler: CMP BX, offset datccw_fs · JBE next_step BX son tablonun adresine eşit ya da küçükse devam eder; değilse BX ilk tabloya döner.

Programın davranışı uzun bir koşuda görülebilir. Port 7 sürekli “hazır” döndürdüğünde porta yazılan ilk 128 bayt datcw'nin dört baytının 32 kez yinelenmesidir. 129. bayttan itibaren datccw (011, 001, 110, 010), 257. bayttan itibaren datcw_fs, 385. bayttan itibaren datccw_fs gelir; 513. baytta yeniden datcw başlar.

Sık yapılan hata

CMP BX, offset datccw_fs · JBE yerine JB yazmak. BX son tabloyu (010Eh) gösterdiğinde JB atlamaz ve BX hemen datcw'ye döner; CCW tam adım tablosu hiç kullanılmaz. Son tablonun da sayılması için eşitlik dahil edilir.

10.2.4.4Kod izleme: ilk tur

İzlemede port 7'nin ilk okumada 00h (meşgul), sonraki bütün okumalarda 80h (hazır) döndürdüğü varsayılmıştır. Tablo 10.2.14 ilk dört baytı ve turun sonunu gösterir; yinelenen yoklama adımları “…” ile kısaltılmıştır.

Tablo 10.2.14: Step motor programının ilk turu (port 7 önce 00h, sonra hep 80h döndürür).
AdımKomutALBXSICXZFCFPort 7Açıklama
1jmp start00h0000h0000h0000h00—veri tablolarının üzerinden atlanır
2mov bx, offset datcw00h0102h0000h0000h00—BX = 0102h: datcw (saat yönünde yarım adım)
3mov si, 000h0102h0000h0000h00—SI = 0: tablodaki sıra
4mov cx, 000h0102h0000h0000h00—CX = 0: tur sayacı
5wait: in al, 700h0102h0000h0000h00—AL = 00h: bit 7 = 0, motor meşgul
6test al, 10000000b00h0102h0000h0000h10—00h AND 80h = 0: ZF = 1
7jz wait00h0102h0000h0000h10—ZF = 1: yeniden oku
8wait: in al, 780h0102h0000h0000h10—AL = 80h: motor hazır
9test al, 10000000b80h0102h0000h0000h00—80h AND 80h ≠ 0: ZF = 0
10jz wait80h0102h0000h0000h00—atlamaz: bayt gönderilecek
11mov al, [bx][si]06h0102h0000h0000h00—AL ← [0102h + 0] = 06h = 110b
12out 7, al06h0102h0000h0000h0006hmıknatıs 2 ve 1 açık: yarım adım (11,25°)
13inc si06h0102h0001h0000h0006hSI = 1
14cmp si, 406h0102h0001h0000h0106h1 − 4: CF = 1
15jb next_step06h0102h0001h0000h0106hJB atlar: sonraki bayt
…hazır biti okunur (80h)
19mov al, [bx][si]04h0102h0001h0000h0006hAL ← [0103h] = 04h = 100b
20out 7, al04h0102h0001h0000h0004hyalnız mıknatıs 2 açık
…SI = 2, yoklama
27mov al, [bx][si]03h0102h0002h0000h0004hAL ← [0104h] = 03h = 011b
28out 7, al03h0102h0002h0000h0003hmıknatıs 1 ve 0 açık
…SI = 3, yoklama
35mov al, [bx][si]02h0102h0003h0000h0003hAL ← [0105h] = 02h = 010b
36out 7, al02h0102h0003h0000h0002hyalnız mıknatıs 1 açık: ilk tur bitti
37inc si02h0102h0004h0000h0002hSI = 4
38cmp si, 402h0102h0004h0000h1002h4 − 4 = 0: ZF = 1, CF = 0
39jb next_step02h0102h0004h0000h1002hJB atlamaz
40mov si, 002h0102h0000h0000h1002hSI = 0: tablonun başına
41inc cx02h0102h0000h0001h0002hCX = 1: bir tur (4 bayt) tamam
42cmp cx, steps_before_direction_change02h0102h0000h0001h0102h1 − 20h: CF = 1
43jb next_step02h0102h0000h0001h0102hatlar: 32 tur dolmadı, aynı tabloyla devam

İzlemenin ilk kısmı yoklamanın işleyişini gösterir. 5. adımda okunan 00h ile TEST sonucu sıfırdır ve ZF = 1 olur; JZ geri döner. 8. adımda okunan 80h'de bit 7 birdir; TEST sonucu sıfırdan farklıdır, ZF = 0 olur ve program ilerler. TEST AL, 80h komutu AL'yi değiştirmez; ama AL'nin değeri bir sonraki MOV AL, [BX][SI] ile zaten silinir. Durum baytına artık ihtiyaç kalmamıştır.

İkinci kısım tablonun taranmasıdır. SI = 0, 1, 2, 3 için port 7'ye sırasıyla 06h, 04h, 03h ve 02h yazılır. SI = 4 olduğunda CMP SI, 4 sonucu sıfırdır: ZF = 1, CF = 0. JB yalnız CF = 1 iken atladığı için atlamaz. SI sıfırlanır, CX bire çıkar. CMP CX, 20h sonucunda CF = 1'dir (1 < 32) ve program aynı tabloyla ikinci tura başlar. İlk turda motor dört yarım adım, yani 45° dönmüştür.

10.2.4.5Gerçek donanım: unipolar motor ve ULN2003

emu8086'nın üç mıknatıslı motoru öğretim için basitleştirilmiş bir modeldir. Laboratuvarda en sık karşılaşılan step motor türü ise unipolar motordur. Bu motorda orta uçlu iki sargı vardır. Her sargı orta ucundan ikiye bölünür ve dört sargı yarısı oluşur: A, B, A̅ ve B̅. Orta uçlar motorun besleme gerilimine (+V) bağlanır. Motorun beş ya da altı teli bu yüzdendir.

Bir sargı yarısından akım geçirmek için onun öbür ucu toprağa çekilir. Sargı akımı yüzlerce miliamperdir; 8255'in bir çıkış ucu ise ancak birkaç miliamper sürebilir. Araya bir sürücü yonga konur: ULN2003. ULN2003'te yedi Darlington transistör kanalı vardır ve her kanal 500 mA'e kadar akım çekebilir. Kanalın girişi 1 olunca çıkışı toprağa çeker ve o sargı yarısından akım geçer.

8255ULN2003PA01B1CAPA12B2CBPA23B3CA̅PA34B4CB̅COM+V (motor beslemesi)unipolar motorAA̅BB̅orta uç
Şekil 10.2.11: 8255'in PA0–PA3 uçları ULN2003'ün dört kanalı üzerinden unipolar motorun A, B, A̅ ve B̅ sargı yarılarını sürer. Orta uçlar ve ULN2003'ün COM ucu motor beslemesine (+V) bağlıdır.

Sargı bir bobindir ve akımı kesildiği anda uçlarında ters yönde yüksek bir gerilim oluşur. Bu gerilim transistörü bozabilir. ULN2003'ün her kanalında bu iş için bir diyot vardır; COM ucu +V'ye bağlanınca diyotlar ters gerilimi beslemeye boşaltır. COM ucunun bağlanmaması, sürücünün kısa sürede bozulmasının en yaygın nedenidir.

Gerçek motor açık çevrimle sürülür: emu8086'nın motorundaki gibi bir “hazır” biti yoktur. Motorun hızını, program adımlar arasında ne kadar beklediğiyle belirler. Bekleme çok kısa tutulursa rotor bir sonraki uyarmaya yetişemez ve adım kaçırır. Program kaçan adımı fark edemez; mil, programın sandığı konumda değildir. Bu yüzden adım aralığı motorun katalogundaki en yüksek adım hızına göre seçilir.

Tablo 10.2.15: Unipolar motorun uyarma dizileri (port A'nın bit 3…0'ı = B̅ A̅ B A).
SürüşPort A dizisiÖzellik
Tek faz (dalga), tam adım01h 02h 04h 08hher an bir sargı yarısı; en az akım
İki faz, tam adım03h 06h 0Ch 09hher an iki sargı yarısı; daha yüksek tork
Yarım adım01h 03h 02h 06h 04h 0Ch 08h 09hiki diziyi karıştırır; adım açısı yarıya iner

Dizilerin mantığı bit düzeyinde görülür. Tek faz dizisinde 1 biti A'dan B'ye, A̅'ya, B̅'ye kayar: 0001, 0010, 0100, 1000. İki faz dizisinde yan yana iki bit birlikte kayar: 0011, 0110, 1100, 1001. Son bayt 1001b, B̅ ile A'nın birlikte açık olduğunu söyler; dizi bir halka gibi başa bağlanır. Yarım adım dizisi bu iki dizinin elemanlarını sırayla dizer: tek, çift, tek, çift. Böylece adım açısı yarıya iner; örneğin 1,8°'lik (turda 200 adım) bir motor 0,9° adımla döner. Dizi tersten uygulanırsa motor ters yönde döner.

emu8086 motorunun tabloları (110b, 100b …) bu dört fazlı motora uygulanamaz. Üç mıknatıslı modelin bit düzeni ile dört sargı yarılı motorunki farklıdır. Gerçek motor için tablo, sargıların porta bağlanış sırasına göre yeniden yazılır.

10.2.4.6Kod izleme: yarım adım sürüşü

Aşağıdaki program yarım adım dizisini 8255'in port A'sına gönderir. Port A'nın daha önce 82h ile çıkış yapıldığı varsayılır. Dizideki konum bellekteki adim sözcüğünde tutulur.

org 100h
ileri:
inc  adim           ; geri için: dec adim
mov  bx, adim
and  bx, 7          ; dizin 0–7
mov  al, yarim[bx]
mov  dx, 310h       ; port A
out  dx, al
call bekle          ; adım aralığı = hız
jmp  ileri
bekle: ret          ; (gecikme)
yarim db 01h,03h,02h,06h
      db 04h,0Ch,08h,09h
adim  dw 6

Programın püf noktası AND BX, 7'dir. adim sürekli artar: 6, 7, 8, 9 … Dizi ise yalnız sekiz elemanlıdır. 7 = 0000 0111b ile VE işlemi, sayının yalnız son üç bitini bırakır; sonuç her zaman 0–7 arasındadır. 8 = 1000b için sonuç 0, 9 için 1 olur ve dizi kendiliğinden başa sarar. Bu, 8'e bölümden kalanı bir bölme komutu olmadan bulmanın yoludur ve yalnız 2'nin kuvveti uzunluktaki dizilerde çalışır.

Geri döndürmek için INC adim yerine DEC adim yazmak yeter. adim 0'dan 1 azalınca FFFFh olur; AND BX, 7 bunu 7'ye çevirir ve dizi sondan başa doğru okunur. Sayaç bellekte durduğu için INC adim bir bellek işlenenli komuttur (FF 06 21 01): işlemci sözcüğü okur, bir artırır ve geri yazar. Gecikme yordamı bu izlemede yalnız RET'tir; gerçek programda adım aralığını, yani motorun hızını belirler.

Tablo 10.2.16: Gerçek motor için yarım adım dizisini süren programın izlemesi (adim başlangıçta 6).
AdımKomutadimBXALSPPort AAçıklama
1inc adim0007h0000h00hFFFEh—bellekteki sözcük 6 → 7
2mov bx, adim0007h0007h00hFFFEh—BX ← adim = 7
3and bx, 70007h0007h00hFFFEh—dizin 0–7 aralığında kalır: 7
4mov al, yarim[bx]0007h0007h09hFFFEh—AL ← yarim[7] = 09h = 1001b: B̅ ve A
5mov dx, 310h0007h0007h09hFFFEh—DX = 310h: 8255 port A
6out dx, al0007h0007h09hFFFEh09hsargılar sürülür: alan A ile B̅ arasında
7call bekle0007h0007h09hFFFCh09hdönüş adresi (0116h) yığına: SP = FFFCh
8bekle: ret0007h0007h09hFFFEh09hgecikme yordamı burada yalnız RET
9jmp ileri0007h0007h09hFFFEh09hsonraki adım
10inc adim0008h0007h09hFFFEh09hadim = 8
11mov bx, adim0008h0008h09hFFFEh09hBX = 8
12and bx, 70008h0000h09hFFFEh09h8 AND 7 = 0: dizi başa sarar
13mov al, yarim[bx]0008h0000h01hFFFEh09hAL ← yarim[0] = 01h: yalnız A
…DX = 310h
15out dx, al0008h0000h01hFFFEh01halan A sargısına döner: bir yarım adım
…CALL, RET, JMP
19inc adim0009h0000h01hFFFEh01hadim = 9
20mov bx, adim0009h0009h01hFFFEh01hBX = 9
21and bx, 70009h0001h01hFFFEh01h9 AND 7 = 1
22mov al, yarim[bx]0009h0001h03hFFFEh01hAL ← yarim[1] = 03h: A ve B
…DX = 310h
24out dx, al0009h0001h03hFFFEh03halan A ile B arasına geçer: bir yarım adım daha

İzlemedeki açılar alanın sargılar arasındaki yönünü gösterir. 09h = 1001b, B̅ ile A'nın birlikte açık olduğunu söyler; alan ikisinin arasına, yaklaşık 315°'ye yönelir. 01h'de yalnız A açıktır (0°), 03h'te A ve B (45°). Her OUT alanı 45°'lik bir sargı açısı kadar, rotoru bir yarım adım kadar ilerletir.

BX0000hAL01hyarim[ ]0100310220630440C5086097port A (310h) = 01hABA̅B̅alan ≈ 0°
Şekil 10.2.12: adim = 8 olunca AND BX, 7 dizini 0'a sarar; yarim[0] = 01h ile yalnız A sargısı açık kalır ve alan 0°'ye gelir.

10.2.5LED ekran ve 7 bölütlü gösterge

10.2.5.1emu8086 LED ekranı

emu8086'nın LED ekranı port 199'a bağlıdır ve beş basamaklı bir onluk sayı gösterir. Porta yazılan 16 bitlik sözcük, ikiye tümleyen gösterimde işaretli bir sayı olarak yorumlanır. Gösterilebilen aralık bu yüzden −32 768 … 32 767'dir. 199 ≤ 255 olduğundan port sabit olarak yazılır: OUT 199, AX. Ekran bir sözcük beklediği için AX kullanılır; OUT 199, AL yalnız düşük baytı gönderir.

00355port 199 (2 bayt) · AX = 0163h
Şekil 10.2.13: LED ekran: port 199'a yazılan AX = 0163h onluk 355 olarak görünür.

Ekranın işi, ikili sayıyı onluğa çevirmektir. Programcı sayıyı onluk olarak düşünür, assembler onu ikiliye çevirir, aygıt yeniden onluğa döndürür. MOV AX, 1234 komutu AX'e 04D2h yazar (1234 = 4 · 256 + 210 = 04D2h). Ekrana giden de bu ikili sözcüktür.

Eksi sayılar ikiye tümleyenle yazılır. −5678 için önce 5678'in onaltılık değeri bulunur: 162Eh. İkiye tümleyen 10000h − 162Eh = E9D2h'dir. MOV AX, -5678 yazıldığında bu hesabı assembler yapar ve B8 D2 E9 kodunu üretir. Ekran E9D2h'nin en yüksek bitinin 1 olduğunu görür ve sayıyı −5678 olarak gösterir. Aynı sözcük işaretsiz okunsaydı 59 858 olurdu; ekranın yorumu, bitlerin anlamını belirler.

emu8086'nın LED ekran örneği #make_bin# yönergesiyle başlar. Bu yönerge DOS'a bağlı olmayan düz bir ikili dosya (.bin) üretir: başlık yok, PSP yok, yalnız makine kodu. Gömülü bir sistemin ROM'una yazılacak dosya tam olarak budur.

10.2.5.2Kod izleme: LED ekran programı

#start=led_display.exe#
mov ax, 1234
out 199, ax
mov ax, -5678
out 199, ax
; porta sonsuz döngüde yaz:
mov ax, 0
x1:
out 199, ax
inc ax
jmp x1
hlt              ; hiç çalışmaz

Program önce iki sabit sayı gösterir, sonra 0'dan başlayıp durmadan artan bir sayaç kurar. Döngüde INC AX ve JMP x1 vardır, ama hiçbir çıkış koşulu yoktur. Bu yüzden döngüden sonraki HLT'ye hiç ulaşılmaz. HLT yalnız programın sonunu belirtmek için yazılmıştır.

Tablo 10.2.17: LED ekran programının izlemesi; ekran sütunu port 199'a son yazılan sözcüğün işaretli değeridir.
AdımKomutAXEkranAçıklama
1mov ax, 123404D2h—1234 = 04D2h
2out 199, ax04D2h1234ekranda 1234
3mov ax, -5678E9D2h1234−5678 = 10000h − 162Eh = E9D2h
4out 199, axE9D2h−5678ekran sözcüğü işaretli okur: −5678
5mov ax, 00000h−5678sayaç 0
6out 199, ax0000h0ekranda 0
7inc ax0001h0AX = 1
8jmp x10001h0x1'e geri; HLT'ye hiç ulaşılmaz
9out 199, ax0001h1ekranda 1
10inc ax0002h1AX = 2
11jmp x10002h1döngü
12out 199, ax0002h2ekranda 2

Sayaç 32 767'ye (7FFFh) ulaştıktan sonra INC AX onu 8000h yapar. Ekran 8000h'yi işaretli okur ve −32 768 gösterir. Sayaç oradan −1'e (FFFFh) kadar artar, sonra 0'a sarar. INC bu geçişte OF'yi 1 yapar ama program bayraklara bakmadığı için hiçbir şey olmaz.

emu8086'da adım gecikmesi (step delay) 0 iken sayılar okunamayacak kadar hızlı akar. Döngüye INT 15h/86h ile bir bekleme eklenirse sayaç gözle izlenebilir. Örneğin her sayıdan sonra 0,2 saniye beklemek için OUT'tan sonra MOV CX, 3, MOV DX, 0D40h, MOV AH, 86h, INT 15h eklenir. Bekleme AH'yi değiştirdiği için sayaç AX'ten başka bir yazmaca alınmalıdır; örneğin BX'te sayılıp OUT'tan önce MOV AX, BX ile AX'e kopyalanır.

10.2.5.37 bölütlü gösterge

emu8086'nın LED ekranı sayıyı kendisi çözer. Gerçek donanımda bu işi program yapar. En yaygın sayısal gösterge 7 bölütlü göstergedir (seven-segment display). Yedi çubuk biçimli LED (a–g) ve bir ondalık nokta (dp) sekizinci LED'dir. Her LED'e bir bölüt denir. Bölütün bellek kesimiyle ilgisi yoktur; İngilizcede ikisine de segment dendiği için Türkçede ayrı adlar kullanılır.

abcdefgdpD70dpD61gD50fD40eD31dD21cD11bD01aortak katot '3': 4Fhortak anot '3': B0h (tersi)PA7 … PA0 → dp g f e d c b a
Şekil 10.2.14: 7 bölütlü gösterge ve port A'ya bağlanışı (PA7 … PA0 → dp g f e d c b a). '3' rakamı için ortak katotta 4Fh, ortak anotta onun tersi B0h gönderilir.

Göstergenin sekiz LED'inin birer ucu ortak bir uçta birleşir. Bu ucun bağlanışı iki türü ayırır. Ortak katotlu (CC) göstergede ortak uç topraktadır; bölüt ucuna 1 verilince LED yanar. Ortak anotlu (CA) göstergede ortak uç +5 V'tadır; bölüt ucuna 0 verilince LED yanar. Ortak anotun kodu bu yüzden ortak katot kodunun bit bit tersidir ve NOT AL ile elde edilir. LED'ler doğrudan port ucuna değil, her biri bir akım sınırlayıcı direnç üzerinden bağlanır.

Bölüt kodu, yanması gereken bölütlerden hesaplanır. Port A'nın bitleri dp g f e d c b a sırasındadır: bit 0 = a, bit 6 = g, bit 7 = dp. '3' rakamında a, b, c, d ve g yanar; bit 0, 1, 2, 3 ve 6 birdir: 0100 1111b = 4Fh. Ortak anotta bunun tersi 1011 0000b = B0h gönderilir.

Tablo 10.2.18: Rakamların bölüt kodları (dp g f e d c b a sırası).
RakamYanan bölütlerOrtak katot (CC)Ortak anot (CA)
0a b c d e f3FhC0h
1b c06hF9h
2a b d e g5BhA4h
3a b c d g4FhB0h
4b c f g66h99h
5a c d f g6Dh92h
6a c d e f g7Dh82h
7a b c07hF8h
8a b c d e f g7Fh80h
9a b c d f g6Fh90h

Tablo yalnız rakamlarla sınırlı değildir. Onaltılık rakamlar ve bazı harfler de yedi bölütle yazılabilir. 'A' için a b c e f g yanar: 0111 0111b = 77h. Küçük 'b' için c d e f g: 0111 1100b = 7Ch. Eksi işareti yalnız g bölütüdür: 0100 0000b = 40h.

10.2.5.4Kod tablosu ve XLAT

Rakamı bölüt koduna çevirmek bir tablo aramasıdır. 8086'nın bu iş için tek baytlık bir komutu vardır: XLAT (translate). XLAT, AL ← DS:[BX + AL] işlemini yapar. BX tablonun başını, AL sıra numarasını gösterir; tablodaki bayt AL'ye yazılır.

; rakamı (0–9) bölüt koduna çevir
mov  bx, offset kod
mov  al, 3          ; rakam
xlat                ; AL = [BX + AL] = 4Fh
; not al            ; ortak anot ise
mov  dx, 310h       ; 8255 port A
out  dx, al
kod  db 3Fh,06h,5Bh,4Fh,66h
     db 6Dh,7Dh,07h,7Fh,6Fh

Örnekte AL = 3'tür ve XLAT kod tablosunun 3. baytını, 4Fh'yi getirir. Gösterge ortak anotluysa açıklama satırındaki NOT AL etkinleştirilir ve AL = B0h olur. Sonuç 8255'in port A'sına yazılır. XLAT'ın tek kısıtı tablonun en çok 256 bayt olmasıdır; AL 8 bitlik bir sıra numarasıdır. Komut bayraklara dokunmaz ve 11 saat turu sürer.

Tablo yönteminin hesaplama yöntemine üstünlüğü açıktır. Bölüt kodunu rakamdan bir formülle türetmenin kısa bir yolu yoktur; on ayrı karşılaştırma gerekirdi. Tablo ise her rakam için aynı sürede, tek komutla sonuç verir. Gösterge değişirse (ör. bölüt uçları porta başka sırayla bağlanırsa) yalnız tablo yeniden yazılır.

Sık yapılan hata

Rakamı ASCII olarak tabloya vermek. Klavyeden okunan '3' karakterinin kodu 33h = 51'dir; XLAT tablonun başından 51 bayt ötesini okur ve tablo dışından rastgele bir bayt getirir. ASCII rakam önce SUB AL, 30h ile sayıya çevrilir.

10.2.5.5Çoklamalı tarama

Dört basamaklı bir göstergenin her basamağına ayrı bir 8 bitlik port bağlamak 32 uç gerektirir. Çoklama (multiplexing) bu sayıyı 12'ye indirir. Dört basamağın aynı adlı bölüt uçları birbirine bağlanır ve ortak port A'ya gelir. Her basamağın ortak ucu ise ayrı bir transistörle port C'nin bir bitine bağlanır. Program her an yalnız bir basamağı yakar: o basamağın kodunu port A'ya yazar, port C'de yalnız o basamağın bitini 1 yapar.

Basamaklar sırayla ve hızla yakılınca göz dördünü birlikte yanıyor görür. Basamak başına yaklaşık 4 ms ayrılırsa tam bir tur 16 ms sürer; tarama sıklığı 1 / 16 ms ≈ 62 Hz olur. İnsan gözü yaklaşık 50 Hz'in üstündeki yanıp sönmeyi fark etmez. Tarama yavaşlarsa (ör. basamak başına 20 ms) göstergede belirgin bir titreme görülür.

tara:
mov  si, 0
hane:
mov  dx, 312h       ; port C: hane seçimi
mov  al, 0
out  dx, al         ; önce tüm haneler sönük
mov  al, rakam[si]
mov  bx, offset kod
xlat
mov  dx, 310h       ; port A: bölütler
out  dx, al
mov  al, secim[si]  ; 01h, 02h, 04h, 08h
mov  dx, 312h
out  dx, al         ; yalnız bu hane yanar
call bekle_4ms
inc  si
cmp  si, 4
jb   hane
jmp  tara
rakam db 1, 2, 3, 4
secim db 01h, 02h, 04h, 08h

Döngünün en önemli satırı, hane değiştirilmeden önce bütün haneleri söndüren OUT DX, AL'dir (AL = 0, DX = 312h). Bu satır olmasaydı port A'ya yeni kod yazıldığı anda önceki hane hâlâ yanık olurdu. Yeni kod bir anlığına önceki hanede de görünür ve orada soluk bir iz bırakır; buna gölge (ghosting) denir. Doğru sıra şudur: hepsini söndür, bölüt kodunu yaz, yalnız istenen haneyi yak, bekle.

secim tablosu her hane için port C'ye yazılacak baytı tutar. 01h, 02h, 04h ve 08h, sırasıyla PC0, PC1, PC2 ve PC3'ü 1 yapar. Bu değerler 1'i SI kadar sola kaydırarak da üretilebilirdi (MOV CX, SI · MOV AL, 1 · SHL AL, CL); tablo hem daha kısa hem daha hızlıdır.

Aşağıdaki kısaltılmış program göstergede 2026 sayısını bir tur tarar. Gecikme yordamı çıkarılmış, sonsuz döngü yerine tek tur ve RET konmuştur.

org 100h
mov  bx, offset kod
mov  si, 0
hane:
mov  dx, 312h      ; port C (hane)
mov  al, 0
out  dx, al        ; tümü sönük
mov  al, rakam[si]
xlat               ; bölüt kodu
mov  dx, 310h      ; port A (bölüt)
out  dx, al
mov  al, secim[si]
mov  dx, 312h
out  dx, al        ; tek hane yanar
inc  si
cmp  si, 4
jb   hane
ret
kod   db 3Fh,06h,5Bh,4Fh,66h
      db 6Dh,7Dh,07h,7Fh,6Fh
rakam db 2, 0, 2, 6
secim db 01h, 02h, 04h, 08h

BX döngünün dışında bir kez yüklenir, çünkü döngüde değişmez. Kod 36 bayttır ve tablolar onun hemen arkasından gelir: kod 0124h'de, rakam 012Eh'de, secim 0132h'dedir. Tablo 10.2.19 izlemenin önemli satırlarını verir; port sütunları o ana kadar porta yazılan son değerdir.

Tablo 10.2.19: Dört haneli göstergede 2026'yı çoklamalı tarayan programın izlemesi (ortak katot kodları).
AdımKomutSIALPort APort CAçıklama
1mov bx, offset kod0000h00h——BX = 0124h: kod tablosunun başı
2mov si, 00000h00h——SI = 0: ilk hane
3mov dx, 312h0000h00h——DX = 312h: port C, hane seçimi
4mov al, 00000h00h——AL = 0
5out dx, al0000h00h—00hbütün haneler sönük: gölge oluşmaz
6mov al, rakam[si]0000h02h—00hAL ← rakam[0] = 2
7xlat0000h5Bh—00hAL ← [BX + 2] = 5Bh
8mov dx, 310h0000h5Bh—00hDX = 310h: port A, bölütler
9out dx, al0000h5Bh5Bh00hbölüt kodu porta çıkar
10mov al, secim[si]0000h01h5Bh00hAL ← secim[0] = 01h
11mov dx, 312h0000h01h5Bh00hDX = 312h
12out dx, al0000h01h5Bh01hyalnız 1. hane yanar: “2”
13inc si0001h01h5Bh01hSI = 1
…CMP, JB; 2. hane: port C ← 00h, rakam 0
20xlat0001h3Fh5Bh00hXLAT: AL = 3Fh
22out dx, al0001h3Fh3Fh00hport A ← 3Fh
25out dx, al0001h02h3Fh02hyalnız 2. hane yanar: “0”
…3. hane: rakam 2
33xlat0002h5Bh3Fh00hXLAT: AL = 5Bh
38out dx, al0002h04h5Bh04hyalnız 3. hane yanar: “2”
…4. hane: rakam 6
46xlat0003h7Dh5Bh00hXLAT: AL = 7Dh
51out dx, al0003h08h7Dh08hyalnız 4. hane yanar: “6”
52inc si0004h08h7Dh08hSI = 4
53cmp si, 40004h08h7Dh08heşit: CF = 0
54jb hane0004h08h7Dh08hdöngü biter
55ret0004h08h7Dh08hgerçek programda tarama baştan başlar

İzlemede port A'nın değeri, port C 00h iken değişir. Örneğin 20. adımda XLAT 3Fh'yi getirdiğinde port A hâlâ 5Bh'dir, ama port C = 00h olduğu için hiçbir hane yanmaz. Port A 3Fh olduktan sonra (22. adım) port C 02h yapılır (25. adım). Her hane, kendi bölüt kodu porttayken ve yalnız kendisi seçiliyken yanar.

10.2.6Termometre ve termostat

10.2.6.1Aygıt

emu8086'nın termometre aygıtı bir odanın sıcaklığını ve odadaki ısıtıcıyı temsil eder. İki port kullanır. IN AL, 125 anlık hava sıcaklığını santigrat derece cinsinden okur; değer işaretli bir bayttır, yani −128 ile 127 °C arasında olabilir. OUT 127, AL ısıtıcıyı yönetir: AL = 1 ısıtıcıyı açar, AL = 0 kapatır. Isıtıcı açıkken sıcaklık yavaş yavaş artar, kapalıyken düşer.

-4004080120port 125: 70 °Cısıtıcı KAPALIport 127 = 0hedef aralık: 60–80 °C
Şekil 10.2.15: Termometre aygıtı: port 125'ten okunan sıcaklık 70 °C, ısıtıcı kapalı (port 127 = 0). Hedef aralık 60–80 °C.

Amaç, sıcaklığı 60–80 °C aralığında tutan bir termostattır. Kural basittir: sıcaklık 60'ın altına düşerse ısıtıcı açılır, 80'in üstüne çıkarsa kapatılır, arada kalırsa ısıtıcıya dokunulmaz. Aradaki bu “dokunma” bölgesi bilerek bırakılmıştır. Tek bir eşik (ör. 70 °C) kullanılsaydı sıcaklık eşiğin çevresinde salınırken ısıtıcı saniyede birkaç kez açılıp kapanırdı. İki eşik arasındaki boşluğa histerezis denir; ev tipi termostatlar, buzdolapları ve klimalar aynı yöntemle çalışır.

10.2.6.2Termostat programı

; DS'yi CS'ye eşitle:
mov ax, cs
mov ds, ax
start:
in  al, 125
cmp al, 60
jl  low
cmp al, 80
jle ok
jg  high
low:
mov al, 1
out 127, al      ; ısıtıcı açık
jmp ok
high:
mov al, 0
out 127, al      ; ısıtıcı kapalı
ok:
jmp start        ; sonsuz döngü

İlk iki satır DS'yi CS'ye eşitler. Kesim yazmacına sabit doğrudan yazılamadığı için değer önce AX'e alınır. Bu programda hiçbir değişken olmadığından satırlar zorunlu değildir; emu8086'nın özgün örneğinden kalmıştır. Bir .COM programında DS zaten CS'ye eşittir.

Döngü IN AL, 125 ile başlar. Ardından iki karşılaştırma gelir. CMP AL, 60 AL − 60 çıkarmasını yapar ve yalnız bayrakları kurar. JL low, sonuç “küçük” ise, yani SF ≠ OF ise atlar. Atlama olmazsa sıcaklık en az 60'tır. CMP AL, 80 ve JLE ok ikinci eşiği sınar: ZF = 1 ya da SF ≠ OF ise sıcaklık en çok 80'dir ve program ısıtıcıya dokunmadan döngüye döner. İkisi de atlamazsa sıcaklık 80'den büyüktür; JG high ısıtıcıyı kapatan koda gider.

JG high satırının koşulu aslında hep doğrudur. JLE atlamadıysa sıcaklık 80'den büyük olmak zorundadır; JG bu yüzden her zaman atlar ve yerine JMP high da yazılabilirdi. Satır tümüyle silinemez: hemen altında low bloğu durur ve program sıcak odada ısıtıcıyı açardı. JG, okuyana koşulu açıkça söylediği için tercih edilmiştir.

Karşılaştırmaların hepsi işaretli atlamalarla (JL, JLE, JG) yapılır, çünkü sıcaklık eksi olabilir. İşaretsiz atlamalar (JB, JBE, JA) baytı 0–255 arasında bir sayı sayar. Bu fark eksi sıcaklıklarda programın davranışını tersine çevirir.

Teorem 10.2.1 (İşaretli karşılaştırma)

CMP a, b sonrasında a ile b işaretli sayılar olarak karşılaştırılıyorsa a < b ancak ve ancak SF ≠ OF iken doğrudur. a ≤ b ise ZF = 1 ya da SF ≠ OF iken doğrudur. İşaretsiz karşılaştırmada a < b ancak ve ancak CF = 1'dir.

10.2.6.3Kod izleme

İzlemede port 125'in sırayla 55, 70 ve 85 °C döndürdüğü, ısıtıcının başta kapalı olduğu varsayılmıştır. Üç değer, programın üç yolunu birer kez sınar: soğuk, aralıkta ve sıcak.

Tablo 10.2.20: Termostat programının izlemesi; port 125 sırayla 55, 70 ve 85 °C döndürür, ısıtıcı başta kapalıdır.
AdımKomutALZFSFOFCFIsıtıcıAçıklama
1mov ax, cs00h0000kapalıAX = CS = 0700h
2mov ds, ax00h0000kapalıDS = 0700h; kesim yazmacına sabit doğrudan yazılamaz
3in al, 12537h0000kapalı55 °C = 37h
4cmp al, 6037h0101kapalı55 − 60 = −5: SF = 1, OF = 0
5jl low37h0101kapalıSF ≠ OF: atlar (low)
6mov al, 101h0101kapalıAL = 1
7out 127, al01h0101açıkısıtıcı açıldı
8jmp ok01h0101açıkok'a atla
9jmp start01h0101açıkyeni ölçüm
10in al, 12546h0101açık70 °C = 46h
11cmp al, 6046h0000açık70 − 60 = 10: SF = 0, OF = 0
12jl low46h0000açıkSF = OF: atlamaz
13cmp al, 8046h0101açık70 − 80 = −10: SF = 1, ZF = 0
14jle ok46h0101açıkZF = 1 ya da SF ≠ OF: atlar; ısıtıcıya dokunulmaz
15jmp start46h0101açıkyeni ölçüm
16in al, 12555h0101açık85 °C = 55h
17cmp al, 6055h0000açık85 − 60 = 25
18jl low55h0000açıkatlamaz
19cmp al, 8055h0000açık85 − 80 = 5: SF = 0, ZF = 0
20jle ok55h0000açıkatlamaz
21jg high55h0000açıkZF = 0 ve SF = OF: atlar (high)
22mov al, 000h0000açıkAL = 0
23out 127, al00h0000kapalıısıtıcı kapandı
24jmp start00h0000kapalıyeni ölçüm

Birinci ölçümde 55 − 60 = −5'tir. Sonuç eksi olduğu için SF = 1'dir; işaretli taşma olmadığından OF = 0'dır. SF ≠ OF olduğu için JL atlar ve ısıtıcı açılır. CF de 1'dir, çünkü işaretsiz olarak da 55 < 60'tır; bu değerler için işaretli ve işaretsiz yorum aynı sonucu verir.

İkinci ölçümde 70 − 60 = 10 ile SF = OF = 0'dır ve JL atlamaz. 70 − 80 = −10 ise SF = 1 yapar; JLE atlar ve program ısıtıcıya dokunmadan döner. Isıtıcı bir önceki turda açıldığı için açık kalır; sıcaklık artmaya devam eder. Üçüncü ölçümde 85 iki karşılaştırmayı da geçer: JG, ZF = 0 ve SF = OF koşuluyla atlar ve ısıtıcı kapanır.

Sunumun etkileşimli termostatı aynı programı zaman içinde çalıştırır: sıcaklık ısıtıcının durumuna göre yükselip alçalır ve programın hangi satırları yürüttüğü vurgulanır. Sıcaklığın 60 ile 80 arasında gidip geldiği, ısıtıcının ise yalnız eşiklerde durum değiştirdiği görülür.

10.2.6.4Eksi sıcaklık ve işaretsiz atlama

Sıcaklık −10 °C olsun. Port 125, −10'un ikiye tümleyenini, F6h'yi döndürür. İşaretli program için CMP AL, 60 −10 − 60 = −70 hesaplar. Sonuç −128'den büyük olduğu için taşma yoktur: SF = 1, OF = 0. JL atlar ve ısıtıcı açılır. Doğru davranış budur.

Program işaretsiz atlamalarla yazılmış olsaydı (JB low, JBE ok, JA high) F6h, 246 sayılırdı. 246 < 60 değildir: CF = 0 ve JB atlamaz. 246 ≤ 80 de değildir; JA atlar ve ısıtıcı kapatılır. Oda donarken termostat ısıtıcıyı kapalı tutar. Aynı hata −1 ile −128 arasındaki bütün sıcaklıklarda ortaya çıkar, çünkü bunların hepsi işaretsiz olarak 128'den büyüktür.

Sık yapılan hata

Aygıttan okunan değerin işaretli mi işaretsiz mi olduğuna bakmadan atlama komutu seçmek. Kural verinin anlamından gelir: sıcaklık, ivme, hız farkı gibi eksi olabilen büyüklükler JL/JG ailesiyle; adres, sayaç, ASCII kodu ve durum baytı gibi hiç eksi olmayan büyüklükler JB/JA ailesiyle karşılaştırılır.

10.2.7Yazıcı

10.2.7.1DOS yazıcı hizmeti ve denetim karakterleri

emu8086'nın yazıcısı port 130'a bağlıdır, ama program porta doğrudan yazmaz. Bunun yerine DOS'un yazıcı hizmetini kullanır: INT 21h, AH = 05h, DL'deki karakteri yazıcıya (PRN aygıtına) gönderir. Hizmet, yazıcının hazır olmasını bekleme işini de kendisi yapar. Gerçek bir PC'de bu hizmet BIOS'un INT 17h yordamını, o da paralel portun yazmaçlarını kullanır. Program bu ayrıntıların hiçbirini bilmek zorunda değildir.

Yazıcıya giden baytların çoğu basılacak karakterlerdir. Bir kısmı ise denetim karakteridir: kâğıda bir şey basmaz, yazıcının kafasını ya da kâğıdı hareket ettirir (Tablo 10.2.21).

Tablo 10.2.21: Yazıcıyı yöneten denetim karakterleri.
KodAdEtki
0Ah (10)LF (line feed)bir satır aşağı
0Dh (13)CR (carriage return)satır başına dön
09h (9)HT (horizontal tab)sonraki yatay sekmeye (8 sütunda bir)
0Ch (12)FF (form feed)sayfayı çıkar, yeni sayfaya geç

Bu karakterlerin adları daktilodan gelir. CR, daktilonun şaryosunu (kâğıdı taşıyan arabayı) satırın başına döndürür; LF kâğıdı bir satır yukarı iter. Daktiloda iki hareket ayrı kollarla yapılırdı; yazıcı da onları ayrı karakterler olarak bekler. Yeni bir satıra geçmek için ikisi de gerekir. Alışılmış sıra CR + LF'dir, ama LF + CR de aynı sonucu verir: kafa önce aşağı, sonra başa gider. Yalnız CR gönderilirse kafa aynı satırın başına döner ve sonraki karakterler eskilerin üzerine basılır. Dikey sekme (VT) 0Bh'dir ve bu programda kullanılmaz.

Hello, Printer!********Have a nice…0Ah, 0Dh: alt satırın başıCR (13): aynı satırın başına dönHT (9): 8. sütuna atla → üzerine basFF (12): sayfayı çıkar, yeni sayfa
Şekil 10.2.16: Yazıcı kâğıdında denetim karakterleri: LF ve CR alt satırın başına götürür; tek başına CR aynı satırın başına döndürür; HT 8. sütuna atlar; FF sayfayı çıkarır.

10.2.7.2Veri bölümü ve uzunluk hesabı

org 100h
jmp start
msg  db "Hello, Printer!", 0Ah, 0Dh
     db "***************"
     db 13, 9           ; CR ve yatay sekme (HT)
     db "Have a nice printing day!"
msg_end db 0
msg2 db "press any key to eject the page.$"

Metin birkaç DB satırına bölünmüştür, ama bellekte tek bir kesintisiz bayt dizisidir. Yazdırılacak karakter sayısı elle sayılmaz; assembler'a hesaplatılır:

offset msg_end − offset msg = 15 + 2 + 15 + 2 + 25 = 59 = 3Bh bayt.

“Hello, Printer!” 15, LF ile CR 2, yıldızlar 15, CR ile HT 2, “Have a nice printing day!” 25 karakterdir. msg 0102h'de başladığına göre msg_end 0102h + 3Bh = 013Dh'dedir. msg_end'deki 0 baytı yazdırılmaz; etiket yalnız son karakterden hemen sonraki adresi işaretlemek için vardır. Metin değişirse uzunluk kendiliğinden doğru hesaplanır.

msg2 ise '$' ile biter. Bu dizgi yazıcıya değil ekrana gider ve INT 21h/09h ile yazdırılır; 09h hizmeti '$' karakterine kadar yazar. 33 baytlık msg2 013Eh'de başlar ve 015Eh'de biter; kod bu yüzden 015Fh'den başlar ve ilk satırdaki JMP start (EB 5D) oraya atlar.

10.2.7.3Yazdırma döngüsü

start:
mov dl, 12        ; form feed: yeni sayfa
mov ah, 5
int 21h
mov si, offset msg
mov cx, offset msg_end - offset msg
print:
mov dl, [si]
mov ah, 5         ; DOS yazıcı işlevi
int 21h
inc si            ; sonraki karakter
loop print
mov dx, offset msg2
mov ah, 9
int 21h           ; ekrana mesaj
mov ax, 0
int 16h           ; tuş bekle
mov dl, 12        ; sayfayı çıkar
mov ah, 5
int 21h
ret

Program temiz bir sayfayla başlamak için önce bir form feed gönderir. Ardından SI dizginin başını, CX karakter sayısını alır. Döngü gövdesi bir karakteri DL'ye okur, AH'ye 05h yazar ve hizmeti çağırır. MOV AH, 5 her turda yinelenir. INT 21h hizmetleri AH'yi değiştirebileceği için güvenli yol budur. INC SI sonraki karaktere geçer; LOOP print CX'i bir azaltır ve sıfır değilse başa döner. Döngü tam 59 kez çalışır.

Döngüden sonra ekrana “press any key to eject the page.” yazılır ve INT 16h/00h ile bir tuş beklenir. Kullanıcı tuşa basınca son bir form feed sayfayı yazıcıdan çıkarır. Program RET ile biter.

Tablo 10.2.22: Yazıcı programının izlemesi (seçilmiş adımlar; “Yazıcıya” sütunu o adımda yazıcıya giden karakterdir).
AdımKomutCXDLSIYazıcıyaAçıklama
1jmp start0000h00h0000hveri baytlarının üzerinden 015Fh'ye atlanır
2mov dl, 120000h0Ch0000hDL = 12 = 0Ch: sayfa ilerletme
3mov ah, 50000h0Ch0000hAH = 05h: DOS yazıcı çıkışı
4int 21h0000h0Ch0000hFFyazıcı yeni sayfaya geçer
5mov si, offset msg0000h0Ch0102hSI = 0102h: msg
6mov cx, offset msg_end - offset msg003Bh0Ch0102h013Dh − 0102h = 3Bh = 59 karakter
7mov dl, [si]003Bh48h0102hDL ← [0102h] = 'H'
8mov ah, 5003Bh48h0102hAH = 05h
9int 21h003Bh48h0102h'H''H' yazıcıya gider
10inc si003Bh48h0103hSI = 0103h
11loop print003Ah48h0103hCX = 58 ≠ 0: print'e döner
…karakter karakter devam
84int 21h002Ch0Ah0111hLF16. bayt: LF, kâğıt bir satır ilerler
89int 21h002Bh0Dh0112hCR17. bayt: CR, satır başı
…15 yıldız
169int 21h001Bh0Dh0122hCR33. bayt: CR; LF yok, aynı satırın başına dönülür
174int 21h001Ah09h0123hHT34. bayt: HT, sekiz sütunluk sekmeye atlanır
…“Have a nice printing day” yıldızların üzerine basılır
297mov dl, [si]0001h21h013ChDL ← [013Ch] = '!'
299int 21h0001h21h013Ch'!'son karakter
300inc si0001h21h013DhSI = 013Dh = msg_end
301loop print0000h21h013DhCX = 0: döngü biter
302mov dx, offset msg20000h3Eh013DhDX = msg2'nin adresi (013Eh)
304int 21h0000h3Eh013Dhekrana mesaj ($'a kadar)
306int 16h0000h3Eh013Dhbir tuş beklenir
309int 21h0000h0Ch013DhFFson sayfa ilerletme: kâğıt çıkar
310ret0000h0Ch013Dhprogram biter

İzlemenin “Yazıcıya” sütunu her INT 21h'de yazıcıya giden baytı gösterir. 84. adımdaki LF ve 89. adımdaki CR kâğıdı ikinci satırın başına getirir. 169. adımdaki CR'den sonra LF yoktur: kafa aynı satırın, yıldızların başına döner. 174. adımdaki HT kafayı 8. sütuna (0'dan sayılınca) taşır. “Have a nice printing day!” bu yüzden ilk sekiz yıldızın arkasından başlar ve kalan yedi yıldızın üzerine basılır.

10.2.7.4Kâğıtta ne görünür?

Program bittiğinde sayfada iki satır vardır:

Hello, Printer!
********Have a nice printing day!

İkinci satırda yıldızların ilk sekizi görünür; 9.–15. yıldızlar, “Have a ” sözcüğünün harfleriyle aynı yere basılmıştır. Gerçek bir nokta vuruşlu yazıcıda bu yerlerde iki karakter üst üste görünür; emu8086'nın kâğıt görüntüsü ise üstteki karakteri gösterir. Satır başı ve sekme karakterlerinin bu birleşimi, daktilo döneminde altı çizili ya da kalın yazı elde etmek için bilerek kullanılırdı: aynı satır iki kez basılırdı.

Sık yapılan hata

Karakter sayısını elle yazmak: MOV CX, 59. Metne tek bir harf eklenince sayı yanlış olur; ya son karakter basılmaz ya da msg_end'in ötesindeki baytlar (burada msg2'nin başı) yazıcıya gider. offset msg_end - offset msg farkı derleme anında hesaplanır ve metinle birlikte kendiliğinden güncellenir.

10.2.8Robot

10.2.8.1Portlar, komutlar ve durum bitleri

emu8086'nın robotu bir ızgara dünyasında hareket eder. Dünyada duvarlar ve lambalar vardır. Robot önündeki hücreyi inceleyebilir, ileri gidebilir, sola ya da sağa dönebilir, önündeki lambayı yakıp söndürebilir. Dünya, aygıt penceresindeki araç kutusuyla düzenlenir: duvar ve lamba eklenir, robotun yeri değiştirilir.

command (9): 00000001data (10): 00000000status (11): 00000000yanan lambasönük lambaduvar
Şekil 10.2.17: Robot dünyası: duvarlar (taralı), yanan ve sönük lambalar, robot ve üç portun o anki baytları.

Robot üç port kullanır ve bu üçlü, gerçek aygıtlarda da sık görülen bir düzendir. Port 9 komut portudur: program robota ne yapacağını buraya yazar (OUT). Port 10 veri portudur: inceleme komutunun sonucu buradan okunur (IN). Port 11 durum portudur: robotun meşgul olup olmadığı, yeni veri bulunup bulunmadığı ve son komutun hata verip vermediği buradan okunur (IN).

Tablo 10.2.23: Robotun komutları (port 9) ve inceleme sonuçları (port 10).
KomutEtkiVeriÖnündeki
0hiçbir şey yapma255 (FFh)duvar
1ileri git0boş
2sola dön7yanan lamba
3sağa dön8sönük lamba
4önündekini incele
5lambayı yak
6lambayı söndür
Tablo 10.2.24: Robotun durum baytı (port 11).
Bit1 ise
bit 0veri yazmacında yeni veri var
bit 1robot meşgul (komut sürüyor)
bit 2son komutta hata

Durum baytının her biti ayrı bir bilgi taşır ve aynı anda birden fazlası 1 olabilir. 03h = 0000 0011b, robotun hem meşgul olduğunu hem de veri yazmacında okunmamış bir veri bulunduğunu söyler. 05h = 0000 0101b ise yeni veri ve hata bitlerinin birlikte kurulduğunu gösterir. Program bu yüzden durumu bir sayıyla karşılaştırmaz (CMP AL, 2), yalnız ilgilendiği biti sınar (TEST AL, 010b).

10.2.8.2Lambayı bul ve yak

Aşağıdaki program robotu dünyada dolaştırır ve rastladığı sönük lambaları yakar. Her turda önündekini inceler. Duvar varsa sağa döner, sönük lamba varsa yakar, başka bir şey varsa ileri gider.

#start=robot.exe#
dongu:  CALL hazir_bekle
        MOV  AL, 4         ; incele
        OUT  9, AL
        CALL veri_bekle
        IN   AL, 10
        CMP  AL, 0FFh      ; duvar?
        JE   don
        CMP  AL, 8         ; sönük lamba?
        JNE  ilerle
        CALL hazir_bekle
        MOV  AL, 5         ; yak
        OUT  9, AL
        JMP  dongu
don:    MOV  AL, 3         ; sağa dön
        JMP  gonder
ilerle: MOV  AL, 1         ; ileri
gonder: PUSH AX            ; komutu koru
        CALL hazir_bekle
        POP  AX
        OUT  9, AL
        JMP  dongu

hazir_bekle PROC
b1:     IN   AL, 11
        TEST AL, 010b      ; meşgul?
        JNZ  b1
        RET
hazir_bekle ENDP

veri_bekle PROC
v1:     IN   AL, 11
        TEST AL, 001b      ; yeni veri?
        JZ   v1
        RET
veri_bekle ENDP

Program iki bekleme yordamına dayanır. hazir_bekle durum baytının bit 1'ini sınar: meşgul biti 1 olduğu sürece (ZF = 0) JNZ geri döner. Robot önceki komutu bitirince yordam döner. veri_bekle bit 0'ı sınar ve ters koşulla döner: yeni veri biti 0 olduğu sürece (ZF = 1) JZ bekler. İki yordamın yapısı aynıdır; yalnız maske ve atlama koşulu farklıdır.

Her komuttan önce meşgul bitinin sıfırlanması beklenir. İnceleme (4) komutundan sonra ise veri hazır biti beklenir; ancak ondan sonra port 10 okunur. Okunan veri FFh ise JE don sağa dönme koduna gider. 8 ise JNE atlamaz ve lamba yakılır. Başka bir değerde JNE ilerle ileri gitme koduna gider. “Sağa dön” ve “ileri git” yolları gonder etiketinde birleşir: AL'deki komut porta yazılır ve döngü baştan başlar.

Bekleme işleri CALL ile çağrılan yordamlardır. Her çağrıda dönüş adresi yığına itilir (SP 2 azalır), RET onu geri alır. Aynı bekleme döngüsünün programa üç kez yazılması yerine tek bir yordamın üç yerden çağrılması kodu kısaltır ve hata olasılığını azaltır.

10.2.8.3AL'nin korunması

gonder yolunda bir incelik vardır. AL robotun komutunu (1 ya da 3) taşır. Ama komut gönderilmeden önce hazir_bekle çağrılır ve o yordam IN AL, 11 ile AL'yi durum baytıyla ezer. Yordam döndüğünde AL artık komut değil, son okunan durumdur. Bu yüzden komut çağrıdan önce PUSH AX ile yığına konur ve çağrıdan sonra POP AX ile geri alınır. PUSH 8 bitlik bir yazmaç kabul etmediği için AL yerine AX itilir.

Tablo 10.2.25 robotun önünde duvar varken bir turu izler. Port 11'in sırayla 00h (hazır), 01h (veri var), 03h (meşgul ve veri var) ve 01h döndürdüğü, port 10'un FFh (duvar) döndürdüğü varsayılmıştır.

Tablo 10.2.25: Robotun önünde duvar varken programın izlemesi; port 11 sırayla 00h, 01h, 03h, 01h, port 10 FFh döndürür.
AdımKomutALSPZFPort 9Açıklama
1dongu: CALL hazir_bekle00hFFFCh0—dönüş adresi 0103h yığına
2b1: IN AL, 1100hFFFCh0—durum = 00h
3TEST AL, 010b00hFFFCh1—bit 1 = 0: meşgul değil, ZF = 1
4JNZ b100hFFFCh1—atlamaz
5RET00hFFFEh1—dönüş: SP = FFFEh
6MOV AL, 404hFFFEh1—komut 4: incele
7OUT 9, AL04hFFFEh104hrobot önündekini inceler
8CALL veri_bekle04hFFFCh104hveri beklenir
9v1: IN AL, 1101hFFFCh104hdurum = 01h: yeni veri var
10TEST AL, 001b01hFFFCh004hbit 0 = 1: ZF = 0
11JZ v101hFFFCh004hatlamaz
12RET01hFFFEh004hdönüş
13IN AL, 10FFhFFFEh004hveri = FFh: duvar
14CMP AL, 0FFhFFhFFFEh104heşit: ZF = 1
15JE donFFhFFFEh104hdon'a atlar
16don: MOV AL, 303hFFFEh104hkomut 3: sağa dön
17JMP gonder03hFFFEh104hgonder
18gonder: PUSH AX03hFFFCh104hAX = 0003h yığına: SP = FFFCh
19CALL hazir_bekle03hFFFAh104hdönüş adresi yığına: SP = FFFAh
20b1: IN AL, 1103hFFFAh104hdurum = 03h: AL ezildi
21TEST AL, 010b03hFFFAh004hbit 1 = 1: meşgul, ZF = 0
22JNZ b103hFFFAh004hyeniden oku
23b1: IN AL, 1101hFFFAh004hdurum = 01h
24TEST AL, 010b01hFFFAh104hbit 1 = 0: ZF = 1
25JNZ b101hFFFAh104hatlamaz
26RET01hFFFCh104hdönüş: SP = FFFCh, AL = 01h
27POP AX03hFFFEh104hAL = 03h geri geldi
28OUT 9, AL03hFFFEh103hrobota “sağa dön” gider
29JMP dongu03hFFFEh103hdöngü yeniden başlar

18. adımda AX = 0003h yığına itilir ve SP FFFCh'ye iner. 19. adımdaki CALL dönüş adresini de iter: SP = FFFAh. Yordamın içinde AL önce 03h (meşgul), sonra 01h olur. 26. adımdaki RET dönüş adresini alır; AL'de 01h vardır. 27. adımdaki POP AX komutu geri getirir ve 28. adımda porta doğru değer, 3, gider. Yığın da dengelidir: SP başladığı yere, FFFEh'ye döner.

PUSH AX ve POP AX silinseydi aynı senaryoda porta 01h giderdi: “ileri git”. Robot duvara doğru yürümeye çalışır ve hata biti (bit 2) kurulur. Program bu hatayı denetlemediği için bir sonraki turda yeniden inceler, yeniden duvarı görür ve aynı hatayı yineler: robot duvarın önünde takılır kalır.

Sık yapılan hata

Bir yordam çağrısından sonra, yordamın kullandığı bir yazmacın eski değerine güvenmek. hazir_bekle AL'yi değiştirir; bunu yordamın adından anlamak mümkün değildir. Ya yordam değiştirdiği yazmaçları kendisi saklar (girişte PUSH, çıkışta POP), ya da çağıran taraf korumak istediği değeri yığına atar. Robot programı ikinci yolu kullanır.

Programın iki eksiği daha vardır. Birincisi, hata biti hiç okunmaz. Duvara doğru “ileri” komutu bit 2'yi kurar; sağlam bir program her komuttan sonra durum baytını okuyup bu biti sınamalıdır. İkincisi, yoklamanın bedelidir: işlemci robotun her hareketini beklerken tümüyle meşguldür. Gerçek bir robot denetleyicisinde bu bekleme kesmeyle yapılır ve işlemci arada algılayıcı verilerini işler.

10.2.9Basit G/Ç test aygıtı

10.2.9.1Port 110 ve 112

Test aygıtı (simple io test), IN ve OUT komutlarını denemek için yapılmış bir penceredir. Port 110 bir bayt, port 112 bir sözcük tutar. Pencereye yazılan değerler programa giriş olur; onaltılık değer için sonuna h eklenir (ör. A7h). Programın porta yazdığı değerler de pencerede görünür. Giriş portları her 100 ms'de bir güncellenir. Bu yüzden pencereye yazılan yeni değer, program çalışırken bile en geç 100 ms içinde okunur.

simple io testwrite byte to port 110:write word to port 112:read byte from port 110:A8hread word from port 112:1235h
Şekil 10.2.18: Test aygıtı penceresi. Program port 110'a A8h, port 112'ye 1235h yazmıştır; pencere bu değerleri gösterir.

Pencerenin bir hata ayıklama aracı olarak değeri büyüktür. Bir aygıt programı yazılırken porta giden değerin doğru olup olmadığı burada görülebilir; giriş değerleri de aygıt olmadan elle verilebilir. Sunumdaki örnekte pencereye port 110 için A7h, port 112 için 1234h girilir. Program birincisine 1 ekleyip A8h'yi, ikincisine 1 ekleyip 1235h'yi geri yazar.

10.2.9.2Kod izleme: bayt ve sözcük G/Ç

org 100h
in  al, 110      ; bayt oku
inc al
out 110, al      ; bayt yaz
in  ax, 112      ; sözcük oku
add ax, 1
out 112, ax      ; sözcük yaz
ret

Aynı program sınır değerlerle çalıştırılınca bayrakların davranışı ortaya çıkar. Pencereye port 110 için FFh, port 112 için 7FFFh girilmiş olsun.

Tablo 10.2.26: Test aygıtı programının izlemesi; pencerede port 110 = FFh, port 112 = 7FFFh girilmiştir.
AdımKomutAXZFSFOFCFG/ÇAçıklama
1in al, 11000FFh0000110 → FFhbayt okuma: AL = FFh
2inc al0000h1000FFh + 1 = 00h: ZF = 1; INC CF'ye dokunmaz
3out 110, al0000h1000110 ← 00hpencerede port 110 = 00h
4in ax, 1127FFFh1000112 → 7FFFhsözcük okuma: AL ← port 112, AH ← port 113
5add ax, 18000h01107FFFh + 1 = 8000h: iki pozitifin toplamı negatif, OF = 1
6out 112, ax8000h0110112 ← 8000hpencerede port 112 = 8000h
7ret8000h0110yığından 0000h → IP
8INT 20h (PSP:0000)8000h0110program biter

İlk kısım 8 bitlik bir taşmadır. FFh + 1 = 100h sonucunun 9. biti AL'ye sığmaz ve AL = 00h olur. ZF = 1'dir. INC komutu CF'yi değiştirmez; CF önceki değerinde, 0'da kalır. Aynı işlem ADD AL, 1 ile yapılsaydı CF = 1 olurdu. Porta 00h yazılır.

İkinci kısım işaretli taşmadır. IN AX, 112 AL'yi port 112'den, AH'yi port 113'ten alır: AX = 7FFFh, işaretli olarak +32 767. ADD AX, 1 sonucu 8000h'dir. İki pozitif sayının toplamı negatif bir sayı olarak görünmüştür: OF = 1, SF = 1. İşaretsiz toplamda taşma yoktur (32 767 + 1 = 32 768 < 65 536), bu yüzden CF = 0'dır. Pencerede port 112 = 8000h görünür. Aynı sözcük LED ekrana yazılsaydı −32 768 gösterilirdi.

10.2.10Ortak desen

Yedi aygıt birbirinden çok farklı görünür: biri lamba yakar, biri motor döndürür, biri sıcaklık okur. Programları ise aynı iskeletin çeşitlemeleridir (Tablo 10.2.27).

Tablo 10.2.27: Aygıtlar, portları ve temel fikirleri.
AygıtPortTemel fikir
Trafik lambaları4Her bit bir lamba; durum tablosu ve INT 15h/86h bekleme.
Step motor7Bit 0–2 mıknatıs, bit 7 hazır; yarım adım 11,25°, tam adım 22,5°.
LED ekran199Sözcük, işaretli onluk gösterim.
Termometre125, 127Okuma, işaretli karşılaştırma, ısıtıcıyı aç/kapa.
Yazıcı130INT 21h/05h; denetim karakterleri LF, CR, HT, FF.
Robot9, 10, 11Komut, veri, durum; meşgul ve veri bitlerini yokla.
Test aygıtı110, 112Bayt ve sözcük G/Ç denemesi.
8255 PPI (gerçek)310h–313hKontrol sözcüğü (ör. 82h) port yönlerini belirler; port > 255 → DX.

Ortak desen dört adımdır: durumu oku, ilgili biti sına, hazırsa veriyi yaz ya da oku, döngüye dön. Trafik ışıklarında “durum” zamandır ve sınama yerine bekleme yapılır. Termostatta durum, okunan sıcaklığın kendisidir. Step motor ve robotta durum ayrı bir porttaki bitlerdir. Her durumda IN ve OUT yalnız AL/AX ile çalışır, port 255'ten büyükse DX kullanılır. Aygıtların çoğu sonsuz bir döngüde sürülür.

10.2.11Alıştırmalar ve çözümleri

Alıştırma 1. Bütün yönlerde yalnız sarı lambaları yakan port 4 değeri nedir?

Sarı lambalar bit 3k + 1'dedir: bit 1, 4, 7 ve 10. Sözcük 2 + 16 + 128 + 1024 = 1170 = 0000 0100 1001 0010b = 0492h'dir.

Alıştırma 2. INT 15h/86h ile 2,5 saniyelik bekleme için CX ve DX ne olmalıdır?

2,5 s = 2 500 000 µs = 002625A0h. Yüksek sözcük CX = 0026h, düşük sözcük DX = 25A0h'dir. Kaynak koda mov cx, 26h ve mov dx, 25A0h yazılır.

Alıştırma 3. LED ekranda −1 göstermek için AX'e hangi onaltılık değer yazılır?

−1'in 16 bitlik ikiye tümleyeni 10000h − 1 = FFFFh'dir. mov ax, -1 yazıldığında assembler aynı değeri üretir.

Alıştırma 4. Termostat 20–25 °C aralığına nasıl uyarlanır? Sıcaklık −10 °C iken JL yerine JB kullanılsaydı ne olurdu?

Yalnız iki sabit değişir: cmp al, 20 · jl low · cmp al, 25 · jle ok · jg high. −10 °C porttan F6h olarak gelir. İşaretli karşılaştırmada −10 < 20'dir ve ısıtıcı açılır. JB ise F6h'yi 246 sayar; 246 < 20 olmadığı için atlamaz ve ısıtıcı açılmaz. İşaretsiz JA da kullanılmışsa ısıtıcı kapatılır.

Alıştırma 5. Step motor programında motoru yalnız saat yönünde ve tam adımla döndürmek için hangi satırlar değişmelidir?

BX başta tam adım tablosunu göstermelidir: mov bx, offset datcw_fs. Tablo değiştiren kısım (inc cx'ten son jmp next_step'e kadar) silinir ve mov si, 0'dan sonra doğrudan jmp next_step yazılır. CX artık kullanılmadığı için mov cx, 0 satırı da gereksizdir.

Alıştırma 6. Yazıcıya “Merhaba” yazıp sayfayı çıkaran en kısa program nasıl yazılır?

Her karakter için MOV DL, 'M' · MOV AH, 5 · INT 21h yazılabilir; dizgi ve döngüyle daha kısa olur:

org 100h
mov  si, offset m
mov  cx, 8           ; "Merhaba" + FF
yaz: mov dl, [si]
mov  ah, 5
int  21h
inc  si
loop yaz
ret
m    db "Merhaba", 12

Dizginin sonuna eklenen 12 (FF) sayfayı çıkarır; böylece ayrı bir form feed çağrısına gerek kalmaz.

Alıştırma 7. 8 MHz'lik bir 8086'da mov cx, N / loop ile yaklaşık 1 ms gecikme için N kaçtır?

1 ms, 8 000 saat turudur. 17N − 8 = 8 000 denkleminden N ≈ 471,06 çıkar. N = 471 ile 17 · 471 − 8 = 7 999 tur, yani 0,999875 ms elde edilir.

Alıştırma 8. 8255'te A giriş, B çıkış, C'nin tamamı giriş (mod 0) için kontrol sözcüğü nedir?

D7 = 1, D6 D5 = 00, D4 = 1 (A giriş), D3 = 1 (C üst giriş), D2 = 0, D1 = 0 (B çıkış), D0 = 1 (C alt giriş): 1 00 1 1 0 0 1b = 99h.

Alıştırma 9. Ortak anotlu göstergede '5' ve 'H' harfinin kodları nedir (dp g f e d c b a sırası)?

'5' için ortak katot kodu 6Dh = 0110 1101b'dir; tersi 1001 0010b = 92h. 'H' için b, c, e, f ve g yanar: 0111 0110b = 76h; ortak anot kodu 1000 1001b = 89h'dir.

10.2.12Özet

  • Çevre birimi işlemciye yazmaçlarıyla görünür; bu yazmaçların adresine G/Ç portu denir. Port numaralarını işlemci değil, sistemin adres çözücüsü belirler.
  • 8086'da porta yalnız IN ve OUT ile erişilir. Veri yalnız AL (bayt) ya da AX (sözcük) üzerinden geçer. Port 0–255 ise komuta sabit olarak, büyükse DX ile verilir. Sözcük biçimi port n ve n + 1'i birlikte kullanır.
  • Port eşlemeli G/Ç ayrı bir 64 K'lık adres uzayı kullanır (M/IO̅ = 0); bellek eşlemeli G/Ç'de aygıt bellek adreslerindedir ve her bellek komutuyla erişilir (ör. B800h metin ekranı).
  • Aygıtla eşlemenin üç yolu vardır: yoklama (işlemci durum bitini döngüde okur), kesme (aygıt işlemciyi uyarır) ve DMA (denetleyici veriyi işlemcisiz taşır).
  • 8255 PPI'nin üç 8 bitlik portu ve bir kontrol yazmacı vardır. D7 = 1 olan kontrol sözcüğü portların mod ve yönünü (1 = giriş) belirler; D7 = 0 olan bit kur/sil sözcüğü C'nin tek bir bitini değiştirir.
  • Trafik ışıklarında her lamba port 4'teki sözcüğün bir bitidir (direk k: kırmızı 3k, sarı 3k + 1, yeşil 3k + 2). Durumlar bir tabloda tutulur ve SI ile sırayla porta yazılır.
  • INT 15h/86h, CX:DX mikrosaniye bekler. MOV CX, N / LOOP gecikmesi 17N − 8 saat turudur ve saat hızına bağlıdır; 4,77 MHz'de 1 ms için N = 281.
  • emu8086 step motorunda bit 0–2 mıknatısları, bit 7 hazır bilgisini taşır. Program her bayttan önce bit 7'yi yoklar ve tabloyu [BX][SI] ile okur.
  • Gerçek unipolar motor ULN2003 gibi bir sürücüyle sürülür; tek faz, iki faz ve yarım adım dizileri vardır. Motor açık çevrimdir ve hızı adım aralığıyla belirlenir.
  • LED ekran port 199'daki sözcüğü işaretli onluk gösterir. 7 bölütlü göstergede kod tablodan XLAT ile alınır; ortak anotta kod tersine çevrilir. Çoklamalı taramada hane değiştirmeden önce bütün haneler söndürülür.
  • Termostat eksi sıcaklık okuyabildiği için işaretli atlamalar (JL, JLE, JG) kullanır; işaretsiz atlamalar eksi değerleri büyük pozitif sayı sanır.
  • Yazıcıya INT 21h/05h ile karakter gönderilir; LF, CR, HT ve FF kâğıdı ve kafayı yönetir. Uzunluk offset msg_end - offset msg ile derleme anında hesaplanır.
  • Robot komut, veri ve durum portlarıyla çalışır; bekleme yordamları AL'yi ezdiği için komut çağrıdan önce yığında korunur.

10.2.13Kavram listesi

10.2.14Kendini Dene

  1. Sağ alt ve sol üst direklerde kırmızı + sarı, öteki iki direkte yalnız kırmızı yakan port 4 sözcüğü nedir?

    Cevap

    Sağ alt: bit 0 ve 1; sağ üst: bit 3; sol üst: bit 6 ve 7; sol alt: bit 9. Toplam 1 + 2 + 8 + 64 + 128 + 512 = 715 = 0000 0010 1100 1011b = 02CBh.

  2. INT 15h/86h ile 3 saniye beklemek için CX ve DX ne olmalıdır?

    Cevap

    3 000 000 µs = 002DC6C0h: CX = 002Dh, DX = C6C0h. Kaynakta DX değeri harfle başladığı için 0C6C0h diye yazılır.

  3. IN AL, 300h neden derlenmez? Aynı iş nasıl yazılır?

    Cevap

    Sabit portlu biçimde port numarası tek bir bayta yazılır ve 300h bu bayta sığmaz. Doğrusu MOV DX, 300h · IN AL, DX'tir.

  4. 8255'in kontrol yazmacına 9Bh yazılırsa portlar ne olur? PC4'ü 1 yapan bit kur/sil sözcüğü nedir?

    Cevap

    9Bh = 1 00 1 1 0 1 1b: mod 0, A giriş, C üst giriş, B giriş, C alt giriş; bütün portlar giriştir. PC4 = 1 için D3–D1 = 100, D0 = 1: 0000 1001b = 09h.

  5. 4,77 MHz'lik 8086'da mov cx, 500 / bekle: loop bekle ne kadar sürer?

    Cevap

    17 · 500 − 8 = 8 492 saat turu; 8 492 ÷ 4,77 ≈ 1 780 µs ≈ 1,78 ms.

  6. 'E' harfinin ortak katot ve ortak anot bölüt kodları nedir?

    Cevap

    'E' için a, d, e, f ve g yanar: bit 0, 3, 4, 5, 6. Ortak katot 0111 1001b = 79h, ortak anot 1000 0110b = 86h.

  7. Termostat programında sıcaklık tam 80 °C okunursa hangi atlama gerçekleşir, ısıtıcıya ne olur?

    Cevap

    CMP AL, 60 sonucu pozitiftir ve JL atlamaz. CMP AL, 80 sonucu sıfırdır: ZF = 1 ve JLE ok atlar. Isıtıcıya dokunulmaz; önceki durumunda kalır.

  8. Robotun durum portu 06h döndürüyor. hazir_bekle ve veri_bekle yordamları ne yapar?

    Cevap

    06h = 0000 0110b: bit 1 (meşgul) ve bit 2 (hata) 1, bit 0 (veri) 0. hazir_bekle meşgul biti 1 olduğu için beklemeyi sürdürür; veri_bekle de veri biti 0 olduğu için bekler. İki yordam da hata bitine bakmaz.

  9. Test aygıtında port 110'a 7Fh girilmişken IN AL, 110 · INC AL çalışıyor. AL ve OF, SF, ZF ne olur?

    Cevap

    AL = 80h. +127'den bir fazlası 8 bitte −128 olarak görünür: OF = 1, SF = 1, ZF = 0. CF'ye INC dokunmaz.

  10. Bellek eşlemeli bir aygıtın durum yazmacı B800:0010'da olsaydı, bit 7'yi beklemek için hangi komutlar yeterdi?

    Cevap

    ES = B800h kurulduktan sonra bekle: TEST BYTE PTR ES:[0010h], 80h · JZ bekle. Bellek eşlemeli aygıtta TEST doğrudan bellek işlenenine uygulanır; AL'ye okumak gerekmez.

10.2.15İleri okuma

  • Barry B. Brey, The Intel Microprocessors, 8. baskı: temel G/Ç arayüzü bölümü IN/OUT, adres çözme, 8255 ve 7 bölütlü gösterge sürmeyi devre şemalarıyla anlatır.
  • Intel, 8255A Programmable Peripheral Interface veri sayfası: mod 0, 1 ve 2'nin zamanlama diyagramları ve kontrol sözcüğünün özgün tanımı.
  • Texas Instruments, ULN2003A veri sayfası: Darlington kanallarının akım sınırları ve endüktif yüklerde COM ucunun bağlanışı.
  • emu8086 yardım belgeleri, I/O ports and hardware interrupts ve virtual devices: sanal aygıtların port düzeni ve örnek programları.

10.2.16Kaynaklar

  1. emu8086 yardım belgeleri: I/O ports and hardware interrupts, Virtual devices ve örnek programlar (traffic_lights.asm, stepper_motor.asm, led_display_test.asm, thermometer.asm, printer.asm, robot.asm).
  2. Intel, The 8086 Family User's Manual, 1979 (IN/OUT komutları, G/Ç adres uzayı ve komut süreleri).
  3. Intel, 8255A Programmable Peripheral Interface veri sayfası.
  4. Texas Instruments, ULN2003A veri sayfası.
  5. M. Bodur, Mikroişlemcilere Giriş: Assembler ile Yazılım ve Arayüz, EMO, 2014.
  6. R. Brown, Ralf Brown's Interrupt List (INT 15h/86h, INT 21h/05h, INT 16h/00h).
  7. B. B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009, Bölüm 11 (Temel G/Ç arayüzü).

İlgili sayfalar: Bölüm 10.2 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.