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/Ç
INveOUTkomutları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.
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.
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.
| Aygıt | Port(lar) | Yön | Veri |
|---|---|---|---|
| Trafik lambaları (traffic lights) | 4 | OUT | sözcük: 12 lamba biti |
| Step motor (stepper motor) | 7 | OUT / IN | bit 0–2 mıknatıslar, bit 7 hazır |
| Robot | 9, 10, 11 | OUT / IN | komut, veri, durum |
| Test aygıtı (simple io test) | 110, 112 | IN / OUT | bayt (110), sözcük (112) |
| Termometre ve ısıtıcı | 125, 127 | IN / OUT | sıcaklık, ısıtıcı aç/kapa |
| Yazıcı (printer) | 130 | OUT | karakter (INT 21h/05h ile) |
| LED ekran (LED display) | 199 | OUT | sö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.
| Biçim | Anlamı | Port aralığı | Makine kodu | Saat turu |
|---|---|---|---|---|
IN AL, port | porttan 1 bayt oku | 0–255 (sabit) | E4 pp | 10 |
IN AX, port | porttan 1 sözcük oku | 0–255 (sabit) | E5 pp | 10 |
IN AL, DX / IN AX, DX | port numarası DX'te | 0–65 535 | EC / ED | 8 |
OUT port, AL / OUT port, AX | porta yaz | 0–255 (sabit) | E6 pp / E7 pp | 10 |
OUT DX, AL / OUT DX, AX | port numarası DX'te | 0–65 535 | EE / EF | 8 |
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.
İ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.
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.
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.
İş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'
retProgramda 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.
| Adım | Komut | AX | ES | DI | Belleğe yazılan | Açıklama |
|---|---|---|---|---|---|---|
| 1 | mov ax, 0B800h | B800h | 0700h | 0000h | kesim değeri önce genel yazmaca | |
| 2 | mov es, ax | B800h | B800h | 0000h | ES = B800h: metin ekranı belleği | |
| 3 | mov di, 0 | B800h | B800h | 0000h | ilk hücre: satır 0, sütun 0 | |
| 4 | mov al, 'A' | B841h | B800h | 0000h | AL = 41h ('A') | |
| 5 | mov ah, 1Eh | 1E41h | B800h | 0000h | AH = 1Eh: mavi zemin, sarı yazı | |
| 6 | mov es:[di], ax | 1E41h | B800h | 0000h | B800:0000 ← 41h, B800:0001 ← 1Eh | tek MOV: karakter ve öznitelik; ekranda renkli A |
| 7 | mov byte ptr es:[di+2], 'B' | 1E41h | B800h | 0000h | B800:0002 ← 42h | ikinci hücrenin karakter baytı; öznitelik (07h) korunur |
| 8 | ret | 1E41h | B800h | 0000h | program 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.
| Özellik | Port eşlemeli | Bellek eşlemeli |
|---|---|---|
| Adres uzayı | ayrı, 64 K (0000h–FFFFh) | bellekle ortak, 1 MB'ın bir bölümü |
| Komutlar | yalnız IN, OUT | MOV, ADD, CMP, TEST, AND … |
| Veri yazmacı | yalnız AL / AX | herhangi bir yazmaç ya da sabit |
| Adresleme | sabit (0–255) ya da DX | bütün bellek adresleme modları |
| Bellek kaybı | yok | aygıtın kapladığı adresler |
| 8086 sinyali | M/IO̅ = 0 | M/IO̅ = 1 |
| Örnek | emu8086 aygıtları, 8255, 8259A | B800h 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.
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 okuTEST, 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.
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.
| Yöntem | Kim bekler? | Veriyi kim taşır? | Uygun olduğu iş |
|---|---|---|---|
| Yoklama | işlemci, döngüde | işlemci (IN/OUT) | tek aygıt, basit gömülü sistem |
| Kesme | kimse; aygıt haber verir | işlemci (hizmet yordamında) | klavye, seri hat, zamanlayıcı |
| DMA | kimse; denetleyici yolu alır | DMA 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.
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.
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.
| A1 A0 | Yazmaç | Adres |
|---|---|---|
| 0 0 | port A | 310h |
| 0 1 | port B | 311h |
| 1 0 | port C | 312h |
| 1 1 | kontrol 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.
| D7 | D6 D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|
| 1 = mod seçimi | A grubu modu (00 = mod 0) | port A | PC7–PC4 | B grubu modu (0 = mod 0) | port B | PC3–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.
İ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.
- D7 = 1: bu bir mod seçme sözcüğüdür.
- D6 D5 = 00 ve D2 = 0: iki grup da mod 0.
- D4 = 0 (A çıkış), D3 = 0 (C üst çıkış), D1 = 1 (B giriş), D0 = 0 (C alt çıkış).
- 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.
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.
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
retProgramı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.
| Adım | Komut | DX | AL | G/Ç | Açıklama |
|---|---|---|---|---|---|
| 1 | mov dx, 313h | 0313h | 00h | DX = 313h: kontrol yazmacının adresi; 255'ten büyük olduğu için DX'te tutulur | |
| 2 | mov al, 82h | 0313h | 82h | AL = 82h = 1000 0010b: mod 0; A çıkış, C üst çıkış, B giriş, C alt çıkış | |
| 3 | out dx, al | 0313h | 82h | 313h ← 82h | kontrol sözcüğü yongaya yazıldı; port yönleri artık 82h'ye göre |
| 4 | mov dx, 311h | 0311h | 82h | DX = 311h: port B | |
| 5 | in al, dx | 0311h | 96h | 311h → 96h | anahtarlar okundu: AL = 96h = 1001 0110b |
| 6 | mov dx, 310h | 0310h | 96h | DX = 310h: port A | |
| 7 | out dx, al | 0310h | 96h | 310h ← 96h | LED'ler anahtarları kopyalar: bit 7, 4, 2 ve 1 yanar |
| 8 | mov dx, 313h | 0313h | 96h | DX yeniden kontrol yazmacını gösterir | |
| 9 | mov al, 0Fh | 0313h | 0Fh | AL = 0Fh = 0000 1111b: D7 = 0, yani bit kur/sil sözcüğü | |
| 10 | out dx, al | 0313h | 0Fh | 313h ← 0Fh | D3–D1 = 111 (PC7), D0 = 1: yalnız PC7 = 1 olur; yönler değişmez |
| 11 | ret | 0313h | 0Fh | yığından 0000h → IP; PSP'deki INT 20h'ye dönülür | |
| 12 | INT 20h (PSP:0000) | 0313h | 0Fh | program 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.
Ş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.
Bütün yönlerde yalnız yeşil lambaları yakan sözcük nedir?
- Yeşil lambalar bit 3k + 2'dedir: k = 0, 1, 2, 3 için bit 2, 5, 8, 11.
- Sözcük 2² + 2⁵ + 2⁸ + 2¹¹ = 4 + 32 + 256 + 2048 = 2340'tır.
- İ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_1001bSayı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.
| Ad | Hex | Sağ alt | Sağ üst | Sol üst | Sol alt |
|---|---|---|---|---|---|
| situation | 030Ch | yeşil | kırmızı | yeşil | kırmızı |
| s1 | 069Ah | sarı | kırmızı + sarı | sarı | kırmızı + sarı |
| s2 | 0861h | kırmızı | yeşil | kırmızı | yeşil |
| s3 | 0861h | kırmızı | yeşil | kırmızı | yeşil |
| s4 | 04D3h | kırmızı + sarı | sarı | kırmızı + sarı | sarı |
| all_red | 0249h | kı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.
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.
5 saniyelik bekleme için CX ve DX ne olmalıdır?
- Süre mikrosaniyeye çevrilir: 5 s = 5 × 1 000 000 = 5 000 000 µs.
- 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.
- Kalanlar sondan başa okunur: 4C4B40h. 32 bitlik yazımı 004C4B40h'dir.
- Sayı ikiye bölünür: CX = 004Ch, DX = 4B40h.
; 5 saniye bekle (5 milyon µs)
mov cx, 4Ch ; 004C4B40h = 5 000 000
mov dx, 4B40h
mov ah, 86h
int 15hAynı 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ü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.
| Komut | Saat turu |
|---|---|
MOV CX, sabit | 4 |
LOOP (CX ≠ 0, atlar) | 17 |
LOOP (CX = 0, atlamaz) | 5 |
DEC r16 | 2 |
JNZ (atlar / atlamaz) | 16 / 4 |
CALL yakın / RET yakın | 19 / 8 |
; ≈ 1 ms gecikme (4,77 MHz 8086)
mov cx, 281 ; 4 tur
bekle:
loop bekle ; 280 × 17 + 1 × 5 turCX = 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:
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 4Dış 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| Adım | Komut | CX | IP | Açıklama |
|---|---|---|---|---|
| 1 | mov cx, 4 | 0004h | 0103h | 4 tur; toplam 4 |
| 2 | loop bekle | 0003h | 0103h | CX = 3 ≠ 0: aynı komuta atlar, 17 tur; toplam 21 |
| 3 | loop bekle | 0002h | 0103h | CX = 2: 17 tur; toplam 38 |
| 4 | loop bekle | 0001h | 0103h | CX = 1: 17 tur; toplam 55 |
| 5 | loop bekle | 0000h | 0105h | CX = 0: atlamaz, 5 tur; toplam 60 = 17 · 4 − 8 |
| 6 | ret | 0000h | 0000h | dö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.
| Adım | Komut | AX | SI | CX | DX | CF | Port 4 | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | mov ax, all_red | 0249h | 0000h | 0000h | 0000h | 0 | — | AX = all_red = 0249h; EQU sabiti, bellekte yer kaplamaz |
| 2 | out 4, ax | 0249h | 0000h | 0000h | 0000h | 0 | 0249h | bütün yönler kırmızı |
| 3 | mov si, offset situation | 0249h | 0124h | 0000h | 0000h | 0 | 0249h | SI = 0124h: tablonun ilk sözcüğü (situation) |
| 4 | mov ax, [si] | 030Ch | 0124h | 0000h | 0000h | 0 | 0249h | AX ← [0124h] = 030Ch |
| 5 | out 4, ax | 030Ch | 0124h | 0000h | 0000h | 0 | 030Ch | lambalar situation durumuna geçer |
| 6 | mov cx, 4Ch | 030Ch | 0124h | 004Ch | 0000h | 0 | 030Ch | CX = 004Ch: 5 000 000 µs'nin yüksek sözcüğü |
| 7 | mov dx, 4B40h | 030Ch | 0124h | 004Ch | 4B40h | 0 | 030Ch | DX = 4B40h: düşük sözcük |
| 8 | mov ah, 86h | 860Ch | 0124h | 004Ch | 4B40h | 0 | 030Ch | AH = 86h: BIOS bekleme hizmeti; AL değişmez |
| 9 | int 15h | 860Ch | 0124h | 004Ch | 4B40h | 0 | 030Ch | 5 saniye beklenir; başarıda CF = 0 |
| 10 | add si, 2 | 860Ch | 0126h | 004Ch | 4B40h | 0 | 030Ch | SI = 0126h: sonraki durum |
| 11 | cmp si, sit_end | 860Ch | 0126h | 004Ch | 4B40h | 1 | 030Ch | 0126h − 012Eh: borç var, CF = 1 |
| 12 | jb next | 860Ch | 0126h | 004Ch | 4B40h | 1 | 030Ch | JB atlar: next |
| 13 | mov ax, [si] | 069Ah | 0126h | 004Ch | 4B40h | 1 | 030Ch | AX ← [0126h] = 069Ah (s1) |
| 14 | out 4, ax | 069Ah | 0126h | 004Ch | 4B40h | 1 | 069Ah | sarı geçiş durumu |
| … | bekleme, SI = 0128h, CMP ve JB | |||||||
| 23 | out 4, ax | 0861h | 0128h | 004Ch | 4B40h | 1 | 0861h | s2 = 0861h; yan yol yeşil |
| … | bekleme, SI = 012Ah | |||||||
| 32 | out 4, ax | 0861h | 012Ah | 004Ch | 4B40h | 1 | 0861h | s3 = 0861h; aynı durum 5 saniye daha sürer |
| … | bekleme, SI = 012Ch | |||||||
| 41 | out 4, ax | 04D3h | 012Ch | 004Ch | 4B40h | 1 | 04D3h | s4 = 04D3h; ana yola dönüş hazırlığı |
| … | bekleme | |||||||
| 46 | add si, 2 | 86D3h | 012Eh | 004Ch | 4B40h | 0 | 04D3h | SI = 012Eh = sit_end |
| 47 | cmp si, sit_end | 86D3h | 012Eh | 004Ch | 4B40h | 0 | 04D3h | eşit: CF = 0, ZF = 1 |
| 48 | jb next | 86D3h | 012Eh | 004Ch | 4B40h | 0 | 04D3h | JB atlamaz: tablo bitti |
| 49 | mov si, offset situation | 86D3h | 0124h | 004Ch | 4B40h | 0 | 04D3h | SI tablonun başına döner |
| 50 | jmp next | 86D3h | 0124h | 004Ch | 4B40h | 0 | 04D3h | sonsuz döngü |
| 51 | mov ax, [si] | 030Ch | 0124h | 004Ch | 4B40h | 0 | 04D3h | AX ← [0124h] = 030Ch |
| 52 | out 4, ax | 030Ch | 0124h | 004Ch | 4B40h | 0 | 030Ch | situation 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.
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.
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 | Adres | Anlam | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|---|
datcw | 0102h | CW yarım adım | 110 | 100 | 011 | 010 |
datccw | 0106h | CCW yarım adım | 011 | 001 | 110 | 010 |
datcw_fs | 010Ah | CW tam adım | 001 | 011 | 110 | 000 |
datccw_fs | 010Eh | CCW tam adım | 100 | 110 | 011 | 000 |
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.
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.
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_stepsteps_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.
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.
| Adım | Komut | AL | BX | SI | CX | ZF | CF | Port 7 | Açıklama |
|---|---|---|---|---|---|---|---|---|---|
| 1 | jmp start | 00h | 0000h | 0000h | 0000h | 0 | 0 | — | veri tablolarının üzerinden atlanır |
| 2 | mov bx, offset datcw | 00h | 0102h | 0000h | 0000h | 0 | 0 | — | BX = 0102h: datcw (saat yönünde yarım adım) |
| 3 | mov si, 0 | 00h | 0102h | 0000h | 0000h | 0 | 0 | — | SI = 0: tablodaki sıra |
| 4 | mov cx, 0 | 00h | 0102h | 0000h | 0000h | 0 | 0 | — | CX = 0: tur sayacı |
| 5 | wait: in al, 7 | 00h | 0102h | 0000h | 0000h | 0 | 0 | — | AL = 00h: bit 7 = 0, motor meşgul |
| 6 | test al, 10000000b | 00h | 0102h | 0000h | 0000h | 1 | 0 | — | 00h AND 80h = 0: ZF = 1 |
| 7 | jz wait | 00h | 0102h | 0000h | 0000h | 1 | 0 | — | ZF = 1: yeniden oku |
| 8 | wait: in al, 7 | 80h | 0102h | 0000h | 0000h | 1 | 0 | — | AL = 80h: motor hazır |
| 9 | test al, 10000000b | 80h | 0102h | 0000h | 0000h | 0 | 0 | — | 80h AND 80h ≠ 0: ZF = 0 |
| 10 | jz wait | 80h | 0102h | 0000h | 0000h | 0 | 0 | — | atlamaz: bayt gönderilecek |
| 11 | mov al, [bx][si] | 06h | 0102h | 0000h | 0000h | 0 | 0 | — | AL ← [0102h + 0] = 06h = 110b |
| 12 | out 7, al | 06h | 0102h | 0000h | 0000h | 0 | 0 | 06h | mıknatıs 2 ve 1 açık: yarım adım (11,25°) |
| 13 | inc si | 06h | 0102h | 0001h | 0000h | 0 | 0 | 06h | SI = 1 |
| 14 | cmp si, 4 | 06h | 0102h | 0001h | 0000h | 0 | 1 | 06h | 1 − 4: CF = 1 |
| 15 | jb next_step | 06h | 0102h | 0001h | 0000h | 0 | 1 | 06h | JB atlar: sonraki bayt |
| … | hazır biti okunur (80h) | ||||||||
| 19 | mov al, [bx][si] | 04h | 0102h | 0001h | 0000h | 0 | 0 | 06h | AL ← [0103h] = 04h = 100b |
| 20 | out 7, al | 04h | 0102h | 0001h | 0000h | 0 | 0 | 04h | yalnız mıknatıs 2 açık |
| … | SI = 2, yoklama | ||||||||
| 27 | mov al, [bx][si] | 03h | 0102h | 0002h | 0000h | 0 | 0 | 04h | AL ← [0104h] = 03h = 011b |
| 28 | out 7, al | 03h | 0102h | 0002h | 0000h | 0 | 0 | 03h | mıknatıs 1 ve 0 açık |
| … | SI = 3, yoklama | ||||||||
| 35 | mov al, [bx][si] | 02h | 0102h | 0003h | 0000h | 0 | 0 | 03h | AL ← [0105h] = 02h = 010b |
| 36 | out 7, al | 02h | 0102h | 0003h | 0000h | 0 | 0 | 02h | yalnız mıknatıs 1 açık: ilk tur bitti |
| 37 | inc si | 02h | 0102h | 0004h | 0000h | 0 | 0 | 02h | SI = 4 |
| 38 | cmp si, 4 | 02h | 0102h | 0004h | 0000h | 1 | 0 | 02h | 4 − 4 = 0: ZF = 1, CF = 0 |
| 39 | jb next_step | 02h | 0102h | 0004h | 0000h | 1 | 0 | 02h | JB atlamaz |
| 40 | mov si, 0 | 02h | 0102h | 0000h | 0000h | 1 | 0 | 02h | SI = 0: tablonun başına |
| 41 | inc cx | 02h | 0102h | 0000h | 0001h | 0 | 0 | 02h | CX = 1: bir tur (4 bayt) tamam |
| 42 | cmp cx, steps_before_direction_change | 02h | 0102h | 0000h | 0001h | 0 | 1 | 02h | 1 − 20h: CF = 1 |
| 43 | jb next_step | 02h | 0102h | 0000h | 0001h | 0 | 1 | 02h | atlar: 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.
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.
| Sürüş | Port A dizisi | Özellik |
|---|---|---|
| Tek faz (dalga), tam adım | 01h 02h 04h 08h | her an bir sargı yarısı; en az akım |
| İki faz, tam adım | 03h 06h 0Ch 09h | her an iki sargı yarısı; daha yüksek tork |
| Yarım adım | 01h 03h 02h 06h 04h 0Ch 08h 09h | iki 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 6Programı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.
| Adım | Komut | adim | BX | AL | SP | Port A | Açıklama |
|---|---|---|---|---|---|---|---|
| 1 | inc adim | 0007h | 0000h | 00h | FFFEh | — | bellekteki sözcük 6 → 7 |
| 2 | mov bx, adim | 0007h | 0007h | 00h | FFFEh | — | BX ← adim = 7 |
| 3 | and bx, 7 | 0007h | 0007h | 00h | FFFEh | — | dizin 0–7 aralığında kalır: 7 |
| 4 | mov al, yarim[bx] | 0007h | 0007h | 09h | FFFEh | — | AL ← yarim[7] = 09h = 1001b: B̅ ve A |
| 5 | mov dx, 310h | 0007h | 0007h | 09h | FFFEh | — | DX = 310h: 8255 port A |
| 6 | out dx, al | 0007h | 0007h | 09h | FFFEh | 09h | sargılar sürülür: alan A ile B̅ arasında |
| 7 | call bekle | 0007h | 0007h | 09h | FFFCh | 09h | dönüş adresi (0116h) yığına: SP = FFFCh |
| 8 | bekle: ret | 0007h | 0007h | 09h | FFFEh | 09h | gecikme yordamı burada yalnız RET |
| 9 | jmp ileri | 0007h | 0007h | 09h | FFFEh | 09h | sonraki adım |
| 10 | inc adim | 0008h | 0007h | 09h | FFFEh | 09h | adim = 8 |
| 11 | mov bx, adim | 0008h | 0008h | 09h | FFFEh | 09h | BX = 8 |
| 12 | and bx, 7 | 0008h | 0000h | 09h | FFFEh | 09h | 8 AND 7 = 0: dizi başa sarar |
| 13 | mov al, yarim[bx] | 0008h | 0000h | 01h | FFFEh | 09h | AL ← yarim[0] = 01h: yalnız A |
| … | DX = 310h | ||||||
| 15 | out dx, al | 0008h | 0000h | 01h | FFFEh | 01h | alan A sargısına döner: bir yarım adım |
| … | CALL, RET, JMP | ||||||
| 19 | inc adim | 0009h | 0000h | 01h | FFFEh | 01h | adim = 9 |
| 20 | mov bx, adim | 0009h | 0009h | 01h | FFFEh | 01h | BX = 9 |
| 21 | and bx, 7 | 0009h | 0001h | 01h | FFFEh | 01h | 9 AND 7 = 1 |
| 22 | mov al, yarim[bx] | 0009h | 0001h | 03h | FFFEh | 01h | AL ← yarim[1] = 03h: A ve B |
| … | DX = 310h | ||||||
| 24 | out dx, al | 0009h | 0001h | 03h | FFFEh | 03h | alan 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.
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.
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ışmazProgram ö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.
| Adım | Komut | AX | Ekran | Açıklama |
|---|---|---|---|---|
| 1 | mov ax, 1234 | 04D2h | — | 1234 = 04D2h |
| 2 | out 199, ax | 04D2h | 1234 | ekranda 1234 |
| 3 | mov ax, -5678 | E9D2h | 1234 | −5678 = 10000h − 162Eh = E9D2h |
| 4 | out 199, ax | E9D2h | −5678 | ekran sözcüğü işaretli okur: −5678 |
| 5 | mov ax, 0 | 0000h | −5678 | sayaç 0 |
| 6 | out 199, ax | 0000h | 0 | ekranda 0 |
| 7 | inc ax | 0001h | 0 | AX = 1 |
| 8 | jmp x1 | 0001h | 0 | x1'e geri; HLT'ye hiç ulaşılmaz |
| 9 | out 199, ax | 0001h | 1 | ekranda 1 |
| 10 | inc ax | 0002h | 1 | AX = 2 |
| 11 | jmp x1 | 0002h | 1 | döngü |
| 12 | out 199, ax | 0002h | 2 | ekranda 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.
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.
| Rakam | Yanan bölütler | Ortak katot (CC) | Ortak anot (CA) |
|---|---|---|---|
| 0 | a b c d e f | 3Fh | C0h |
| 1 | b c | 06h | F9h |
| 2 | a b d e g | 5Bh | A4h |
| 3 | a b c d g | 4Fh | B0h |
| 4 | b c f g | 66h | 99h |
| 5 | a c d f g | 6Dh | 92h |
| 6 | a c d e f g | 7Dh | 82h |
| 7 | a b c | 07h | F8h |
| 8 | a b c d e f g | 7Fh | 80h |
| 9 | a b c d f g | 6Fh | 90h |
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.
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, 08hDö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, 08hBX 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.
| Adım | Komut | SI | AL | Port A | Port C | Açıklama |
|---|---|---|---|---|---|---|
| 1 | mov bx, offset kod | 0000h | 00h | — | — | BX = 0124h: kod tablosunun başı |
| 2 | mov si, 0 | 0000h | 00h | — | — | SI = 0: ilk hane |
| 3 | mov dx, 312h | 0000h | 00h | — | — | DX = 312h: port C, hane seçimi |
| 4 | mov al, 0 | 0000h | 00h | — | — | AL = 0 |
| 5 | out dx, al | 0000h | 00h | — | 00h | bütün haneler sönük: gölge oluşmaz |
| 6 | mov al, rakam[si] | 0000h | 02h | — | 00h | AL ← rakam[0] = 2 |
| 7 | xlat | 0000h | 5Bh | — | 00h | AL ← [BX + 2] = 5Bh |
| 8 | mov dx, 310h | 0000h | 5Bh | — | 00h | DX = 310h: port A, bölütler |
| 9 | out dx, al | 0000h | 5Bh | 5Bh | 00h | bölüt kodu porta çıkar |
| 10 | mov al, secim[si] | 0000h | 01h | 5Bh | 00h | AL ← secim[0] = 01h |
| 11 | mov dx, 312h | 0000h | 01h | 5Bh | 00h | DX = 312h |
| 12 | out dx, al | 0000h | 01h | 5Bh | 01h | yalnız 1. hane yanar: “2” |
| 13 | inc si | 0001h | 01h | 5Bh | 01h | SI = 1 |
| … | CMP, JB; 2. hane: port C ← 00h, rakam 0 | |||||
| 20 | xlat | 0001h | 3Fh | 5Bh | 00h | XLAT: AL = 3Fh |
| 22 | out dx, al | 0001h | 3Fh | 3Fh | 00h | port A ← 3Fh |
| 25 | out dx, al | 0001h | 02h | 3Fh | 02h | yalnız 2. hane yanar: “0” |
| … | 3. hane: rakam 2 | |||||
| 33 | xlat | 0002h | 5Bh | 3Fh | 00h | XLAT: AL = 5Bh |
| 38 | out dx, al | 0002h | 04h | 5Bh | 04h | yalnız 3. hane yanar: “2” |
| … | 4. hane: rakam 6 | |||||
| 46 | xlat | 0003h | 7Dh | 5Bh | 00h | XLAT: AL = 7Dh |
| 51 | out dx, al | 0003h | 08h | 7Dh | 08h | yalnız 4. hane yanar: “6” |
| 52 | inc si | 0004h | 08h | 7Dh | 08h | SI = 4 |
| 53 | cmp si, 4 | 0004h | 08h | 7Dh | 08h | eşit: CF = 0 |
| 54 | jb hane | 0004h | 08h | 7Dh | 08h | döngü biter |
| 55 | ret | 0004h | 08h | 7Dh | 08h | gerç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.
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.
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.
| Adım | Komut | AL | ZF | SF | OF | CF | Isıtıcı | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | mov ax, cs | 00h | 0 | 0 | 0 | 0 | kapalı | AX = CS = 0700h |
| 2 | mov ds, ax | 00h | 0 | 0 | 0 | 0 | kapalı | DS = 0700h; kesim yazmacına sabit doğrudan yazılamaz |
| 3 | in al, 125 | 37h | 0 | 0 | 0 | 0 | kapalı | 55 °C = 37h |
| 4 | cmp al, 60 | 37h | 0 | 1 | 0 | 1 | kapalı | 55 − 60 = −5: SF = 1, OF = 0 |
| 5 | jl low | 37h | 0 | 1 | 0 | 1 | kapalı | SF ≠ OF: atlar (low) |
| 6 | mov al, 1 | 01h | 0 | 1 | 0 | 1 | kapalı | AL = 1 |
| 7 | out 127, al | 01h | 0 | 1 | 0 | 1 | açık | ısıtıcı açıldı |
| 8 | jmp ok | 01h | 0 | 1 | 0 | 1 | açık | ok'a atla |
| 9 | jmp start | 01h | 0 | 1 | 0 | 1 | açık | yeni ölçüm |
| 10 | in al, 125 | 46h | 0 | 1 | 0 | 1 | açık | 70 °C = 46h |
| 11 | cmp al, 60 | 46h | 0 | 0 | 0 | 0 | açık | 70 − 60 = 10: SF = 0, OF = 0 |
| 12 | jl low | 46h | 0 | 0 | 0 | 0 | açık | SF = OF: atlamaz |
| 13 | cmp al, 80 | 46h | 0 | 1 | 0 | 1 | açık | 70 − 80 = −10: SF = 1, ZF = 0 |
| 14 | jle ok | 46h | 0 | 1 | 0 | 1 | açık | ZF = 1 ya da SF ≠ OF: atlar; ısıtıcıya dokunulmaz |
| 15 | jmp start | 46h | 0 | 1 | 0 | 1 | açık | yeni ölçüm |
| 16 | in al, 125 | 55h | 0 | 1 | 0 | 1 | açık | 85 °C = 55h |
| 17 | cmp al, 60 | 55h | 0 | 0 | 0 | 0 | açık | 85 − 60 = 25 |
| 18 | jl low | 55h | 0 | 0 | 0 | 0 | açık | atlamaz |
| 19 | cmp al, 80 | 55h | 0 | 0 | 0 | 0 | açık | 85 − 80 = 5: SF = 0, ZF = 0 |
| 20 | jle ok | 55h | 0 | 0 | 0 | 0 | açık | atlamaz |
| 21 | jg high | 55h | 0 | 0 | 0 | 0 | açık | ZF = 0 ve SF = OF: atlar (high) |
| 22 | mov al, 0 | 00h | 0 | 0 | 0 | 0 | açık | AL = 0 |
| 23 | out 127, al | 00h | 0 | 0 | 0 | 0 | kapalı | ısıtıcı kapandı |
| 24 | jmp start | 00h | 0 | 0 | 0 | 0 | kapalı | 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.
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).
| Kod | Ad | Etki |
|---|---|---|
| 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.
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:
“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
retProgram 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.
| Adım | Komut | CX | DL | SI | Yazıcıya | Açıklama |
|---|---|---|---|---|---|---|
| 1 | jmp start | 0000h | 00h | 0000h | veri baytlarının üzerinden 015Fh'ye atlanır | |
| 2 | mov dl, 12 | 0000h | 0Ch | 0000h | DL = 12 = 0Ch: sayfa ilerletme | |
| 3 | mov ah, 5 | 0000h | 0Ch | 0000h | AH = 05h: DOS yazıcı çıkışı | |
| 4 | int 21h | 0000h | 0Ch | 0000h | FF | yazıcı yeni sayfaya geçer |
| 5 | mov si, offset msg | 0000h | 0Ch | 0102h | SI = 0102h: msg | |
| 6 | mov cx, offset msg_end - offset msg | 003Bh | 0Ch | 0102h | 013Dh − 0102h = 3Bh = 59 karakter | |
| 7 | mov dl, [si] | 003Bh | 48h | 0102h | DL ← [0102h] = 'H' | |
| 8 | mov ah, 5 | 003Bh | 48h | 0102h | AH = 05h | |
| 9 | int 21h | 003Bh | 48h | 0102h | 'H' | 'H' yazıcıya gider |
| 10 | inc si | 003Bh | 48h | 0103h | SI = 0103h | |
| 11 | loop print | 003Ah | 48h | 0103h | CX = 58 ≠ 0: print'e döner | |
| … | karakter karakter devam | |||||
| 84 | int 21h | 002Ch | 0Ah | 0111h | LF | 16. bayt: LF, kâğıt bir satır ilerler |
| 89 | int 21h | 002Bh | 0Dh | 0112h | CR | 17. bayt: CR, satır başı |
| … | 15 yıldız | |||||
| 169 | int 21h | 001Bh | 0Dh | 0122h | CR | 33. bayt: CR; LF yok, aynı satırın başına dönülür |
| 174 | int 21h | 001Ah | 09h | 0123h | HT | 34. bayt: HT, sekiz sütunluk sekmeye atlanır |
| … | “Have a nice printing day” yıldızların üzerine basılır | |||||
| 297 | mov dl, [si] | 0001h | 21h | 013Ch | DL ← [013Ch] = '!' | |
| 299 | int 21h | 0001h | 21h | 013Ch | '!' | son karakter |
| 300 | inc si | 0001h | 21h | 013Dh | SI = 013Dh = msg_end | |
| 301 | loop print | 0000h | 21h | 013Dh | CX = 0: döngü biter | |
| 302 | mov dx, offset msg2 | 0000h | 3Eh | 013Dh | DX = msg2'nin adresi (013Eh) | |
| 304 | int 21h | 0000h | 3Eh | 013Dh | ekrana mesaj ($'a kadar) | |
| 306 | int 16h | 0000h | 3Eh | 013Dh | bir tuş beklenir | |
| 309 | int 21h | 0000h | 0Ch | 013Dh | FF | son sayfa ilerletme: kâğıt çıkar |
| 310 | ret | 0000h | 0Ch | 013Dh | program 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ı.
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.
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).
| Komut | Etki | Veri | Önündeki |
|---|---|---|---|
| 0 | hiçbir şey yapma | 255 (FFh) | duvar |
| 1 | ileri git | 0 | boş |
| 2 | sola dön | 7 | yanan lamba |
| 3 | sağa dön | 8 | sönük lamba |
| 4 | önündekini incele | ||
| 5 | lambayı yak | ||
| 6 | lambayı söndür |
| Bit | 1 ise |
|---|---|
| bit 0 | veri yazmacında yeni veri var |
| bit 1 | robot meşgul (komut sürüyor) |
| bit 2 | son 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 ENDPProgram 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.
| Adım | Komut | AL | SP | ZF | Port 9 | Açıklama |
|---|---|---|---|---|---|---|
| 1 | dongu: CALL hazir_bekle | 00h | FFFCh | 0 | — | dönüş adresi 0103h yığına |
| 2 | b1: IN AL, 11 | 00h | FFFCh | 0 | — | durum = 00h |
| 3 | TEST AL, 010b | 00h | FFFCh | 1 | — | bit 1 = 0: meşgul değil, ZF = 1 |
| 4 | JNZ b1 | 00h | FFFCh | 1 | — | atlamaz |
| 5 | RET | 00h | FFFEh | 1 | — | dönüş: SP = FFFEh |
| 6 | MOV AL, 4 | 04h | FFFEh | 1 | — | komut 4: incele |
| 7 | OUT 9, AL | 04h | FFFEh | 1 | 04h | robot önündekini inceler |
| 8 | CALL veri_bekle | 04h | FFFCh | 1 | 04h | veri beklenir |
| 9 | v1: IN AL, 11 | 01h | FFFCh | 1 | 04h | durum = 01h: yeni veri var |
| 10 | TEST AL, 001b | 01h | FFFCh | 0 | 04h | bit 0 = 1: ZF = 0 |
| 11 | JZ v1 | 01h | FFFCh | 0 | 04h | atlamaz |
| 12 | RET | 01h | FFFEh | 0 | 04h | dönüş |
| 13 | IN AL, 10 | FFh | FFFEh | 0 | 04h | veri = FFh: duvar |
| 14 | CMP AL, 0FFh | FFh | FFFEh | 1 | 04h | eşit: ZF = 1 |
| 15 | JE don | FFh | FFFEh | 1 | 04h | don'a atlar |
| 16 | don: MOV AL, 3 | 03h | FFFEh | 1 | 04h | komut 3: sağa dön |
| 17 | JMP gonder | 03h | FFFEh | 1 | 04h | gonder |
| 18 | gonder: PUSH AX | 03h | FFFCh | 1 | 04h | AX = 0003h yığına: SP = FFFCh |
| 19 | CALL hazir_bekle | 03h | FFFAh | 1 | 04h | dönüş adresi yığına: SP = FFFAh |
| 20 | b1: IN AL, 11 | 03h | FFFAh | 1 | 04h | durum = 03h: AL ezildi |
| 21 | TEST AL, 010b | 03h | FFFAh | 0 | 04h | bit 1 = 1: meşgul, ZF = 0 |
| 22 | JNZ b1 | 03h | FFFAh | 0 | 04h | yeniden oku |
| 23 | b1: IN AL, 11 | 01h | FFFAh | 0 | 04h | durum = 01h |
| 24 | TEST AL, 010b | 01h | FFFAh | 1 | 04h | bit 1 = 0: ZF = 1 |
| 25 | JNZ b1 | 01h | FFFAh | 1 | 04h | atlamaz |
| 26 | RET | 01h | FFFCh | 1 | 04h | dönüş: SP = FFFCh, AL = 01h |
| 27 | POP AX | 03h | FFFEh | 1 | 04h | AL = 03h geri geldi |
| 28 | OUT 9, AL | 03h | FFFEh | 1 | 03h | robota “sağa dön” gider |
| 29 | JMP dongu | 03h | FFFEh | 1 | 03h | dö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.
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.
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
retAynı 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.
| Adım | Komut | AX | ZF | SF | OF | CF | G/Ç | Açıklama |
|---|---|---|---|---|---|---|---|---|
| 1 | in al, 110 | 00FFh | 0 | 0 | 0 | 0 | 110 → FFh | bayt okuma: AL = FFh |
| 2 | inc al | 0000h | 1 | 0 | 0 | 0 | FFh + 1 = 00h: ZF = 1; INC CF'ye dokunmaz | |
| 3 | out 110, al | 0000h | 1 | 0 | 0 | 0 | 110 ← 00h | pencerede port 110 = 00h |
| 4 | in ax, 112 | 7FFFh | 1 | 0 | 0 | 0 | 112 → 7FFFh | sözcük okuma: AL ← port 112, AH ← port 113 |
| 5 | add ax, 1 | 8000h | 0 | 1 | 1 | 0 | 7FFFh + 1 = 8000h: iki pozitifin toplamı negatif, OF = 1 | |
| 6 | out 112, ax | 8000h | 0 | 1 | 1 | 0 | 112 ← 8000h | pencerede port 112 = 8000h |
| 7 | ret | 8000h | 0 | 1 | 1 | 0 | yığından 0000h → IP | |
| 8 | INT 20h (PSP:0000) | 8000h | 0 | 1 | 1 | 0 | program 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).
| Aygıt | Port | Temel fikir |
|---|---|---|
| Trafik lambaları | 4 | Her bit bir lamba; durum tablosu ve INT 15h/86h bekleme. |
| Step motor | 7 | Bit 0–2 mıknatıs, bit 7 hazır; yarım adım 11,25°, tam adım 22,5°. |
| LED ekran | 199 | Sözcük, işaretli onluk gösterim. |
| Termometre | 125, 127 | Okuma, işaretli karşılaştırma, ısıtıcıyı aç/kapa. |
| Yazıcı | 130 | INT 21h/05h; denetim karakterleri LF, CR, HT, FF. |
| Robot | 9, 10, 11 | Komut, veri, durum; meşgul ve veri bitlerini yokla. |
| Test aygıtı | 110, 112 | Bayt ve sözcük G/Ç denemesi. |
| 8255 PPI (gerçek) | 310h–313h | Kontrol 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", 12Dizginin 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
INveOUTile 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/LOOPgecikmesi 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
XLATile 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 msgile 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
- çevre birimi peripheral
- G/Ç portu I/O port
- port eşlemeli / bellek eşlemeli G/Ç port-mapped / memory-mapped I/O
- adres çözücü address decoder
- yoklama polling
- el sıkışma handshaking
- kesme interrupt
- doğrudan bellek erişimi (DMA) direct memory access
- programlanabilir çevre arayüzü (8255 PPI) programmable peripheral interface
- kontrol sözcüğü control word
- yazılımla gecikme software delay
- saat turu clock cycle
- step motor stepper motor
- 7 bölütlü gösterge seven-segment display
- bölüt segment (display)
- kontrol karakteri control character
- öykünücü emulator
10.2.14Kendini Dene
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.
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
0C6C0hdiye yazılır.IN AL, 300hneden 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.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.
4,77 MHz'lik 8086'da
mov cx, 500/bekle: loop beklene kadar sürer?Cevap
17 · 500 − 8 = 8 492 saat turu; 8 492 ÷ 4,77 ≈ 1 780 µs ≈ 1,78 ms.
'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.
Termostat programında sıcaklık tam 80 °C okunursa hangi atlama gerçekleşir, ısıtıcıya ne olur?
Cevap
CMP AL, 60sonucu pozitiftir veJLatlamaz.CMP AL, 80sonucu sıfırdır: ZF = 1 veJLE okatlar. Isıtıcıya dokunulmaz; önceki durumunda kalır.Robotun durum portu 06h döndürüyor.
hazir_bekleveveri_bekleyordamları ne yapar?Cevap
06h = 0000 0110b: bit 1 (meşgul) ve bit 2 (hata) 1, bit 0 (veri) 0.
hazir_beklemeşgul biti 1 olduğu için beklemeyi sürdürür;veri_beklede veri biti 0 olduğu için bekler. İki yordam da hata bitine bakmaz.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
INCdokunmaz.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ıttaTESTdoğ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
- 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).
- Intel, The 8086 Family User's Manual, 1979 (IN/OUT komutları, G/Ç adres uzayı ve komut süreleri).
- Intel, 8255A Programmable Peripheral Interface veri sayfası.
- Texas Instruments, ULN2003A veri sayfası.
- M. Bodur, Mikroişlemcilere Giriş: Assembler ile Yazılım ve Arayüz, EMO, 2014.
- R. Brown, Ralf Brown's Interrupt List (INT 15h/86h, INT 21h/05h, INT 16h/00h).
- 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.