-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlek2.html
316 lines (266 loc) · 13.9 KB
/
lek2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<html>
<head>
<title>'yet another text tutor'</title>
</head>
<body>
<h2>Välkomna kamrater!</h2>
<h4>Lektion 2, skärmminnet</h4>
Detta är del 2 i Albert's assemblerskola för PC. Den här gången tänkte jag
berätta om hur man skriver text direkt i skärmminnet istället för via int 21h
(se del 1).
<p>
Skärmen i textläge 3 (det grafikläge som datorn har när den startas upp)
startar på adress 0B800 hex. Varje tecken på skärmen motsvaras då av två bytes
i skärmminnet, en byte för tecknet och en byte för tecknets attribut
(bakgrunds- och förgrunds-färg). Skärmen har 25 rader och det går 80 tecken
på varje rad, det ger 80*2 = 160 bytes per rad. Sammanlagt tar en skärmsida
160*25 = 4000 bytes.
<p>
För att skriva på skärmen behöver man bara ändra i skärmminnet. Detta görs genom
att först ladda startaddressen till skärmen i ett segmentregister:
<pre>
mov ax,0B800h ;det går inte att göra mov direkt till seg.reg.
mov es,ax ;utan man måste gå via t ex ax
</pre>
Es är ett lämpligt segmentregister som brukar användas när man vill skriva
saker till minnet (när man vill läsa ur minnet brukar ds användas).
<p>
Sedan måste en offset laddas till ett offsetregister, t ex di (destinationsindex).
Det går även bra med bx, dx eller si, men di har en speciell fördel som jag
kommer till senare. Offseten beräknas enligt:
<p>
Offset = Ypos * 160 + Xpos * 2
<p>
där Xpos är tecknets X-koordinat och Y-pos är tecknets Y-koordinat. Formeln
bygger på det faktum att det går 160 bytes / rad och varje tecken tar 2 bytes.
X-pos kan vara i intervallet 0 - 79, Ypos 0 - 24. Bytarna ligger konsekutivt
(följande efter varandra) med först en tecken-byte sedan en attribut-byte.
<pre>
Koordinat Offset tecken Attribut Position
(0,0) 0*160 + 0*2 = 0 1 övre vänstra hörnet
(1,0) 0*160 + 1*2 = 2 3 tecken nr 2 på översta raden
(0,1) 1*160 + 0*2 = 160 161 Första tecknet på andra raden
(4,10) 10*160 + 4*2 = 1608 1609 4:de tecknet på 10:de raden
(79,24) 10*160 + 4*2 = 3998 3999 sista tecknet på sista raden
</pre>
Varje tecken består som sagt av två bytes, den första innehåller helt enkelt
ascii-värdet för tecknet som visas och den andra byten innehåller attributen.
Attribut-byten består av fyra bitar för bakgrundsfärgen och fyra bitar för
förgrundsfärgen.
<p>
<pre>
Färgerna i grafikmod 3 (text-läge)
Svart 0 Färg 0 - 7 kan användas till både
Blått 1 Bakgrunds- och förgrunds-färg
Grönt 2
Cyan 3 Attributbyten beräknas enligt:
Rött 4 ( 16*bakgrundsfärg + textfärg )
Violett 5
Brunt 6 Ex: 16*4 + 7 = 71 => Vit text på röd bakgrund
Vitt 7
I Grått 8 I = Intensivt
I Blått 9
I Grönt 10 Färg 8 - 15 kan endast användast till textfärg (förgrund)
I Cyan 11
I Rött 12 För blinkande text sätt översta biten till 1
I Lila 13 ( t ex genom att addera 128 till attributet)
I Brunt 14
I Vitt 15 16*1 + 4 + 128 = 148 => blinkande röd text på blå bakgrund
------------------------------ KLIPP HÄR -------------------------------------
;Kort exempelprogram som skriver ett A på skärmen med gräsligt attribut
DOSSEG ;Dela upp minnet i segment precis som dos gör
.MODEL SMALL ;välj vilken minnesmodell programmet ska ha
.STACK 100h ;definiera en stack på 256 bytes (100 hex)
.DATA ;datasegmentet tomt i det här programmet
.CODE ;kod segment
START: ;start är en label
mov ax,0B800h ;startadressen för skärmminnet till ax
mov es,ax ;lägg startadressen i es
mov di, 160*12+39*2 ;offset för rad 12, tecken 39 till di
mov al, 65 ;65 = ascii för tecknet A
mov es:[di],al ;skriv tecknet till skärmminnet
inc di ;öka di till attributbyten
mov al, 16*2+4+128 ;Attribut = blinkande rött på grön bakgr.
mov es:[di],al ;skriv attributet till skärmminnet
mov ax,4c00h ;hoppa tillbaka till dos
int 21h ;programmet krashar utan dessa 2 rader!
END START ;SLUT (tala om att koden börjar vid labeln start)
------------------------------ KLIPP HÄR -------------------------------------
</pre>
En optimering av koden vore att skriva både tecknet och attributet samtidigt
till skärmen. Det kan man göra genom att lägga tecknet i al och attributet
i ah och sen skriva hela ax till skärmen.
<pre>
mov al,65 ;Lägg tecknet 'A' i al
mov ah,16*2+4+128 ;lägg attributet 'blinkande rött på grönt'i ah
mov es:[di],ax ;flytta hela ax till es:[di] (skärmminnet)
</pre>
En annan optimering är att använda instruktionen stos. Stosb tar en byte i
Al och skriver den till adressen som pekas ut av es:[di]. Precis vad som
behövs i det här programmet. Genom att skriva stosw flyttar man ett helt word
från Ax till es:[di] och stosd flyttar ett helt doubleword (endast 386 och
högre) från Eax till es:[di]. 386 specifika instruktioner hör till överkursen
men jag kommer kanske gå igenom några i en framtida del av assemblerskolan.
<pre>
------------------------------ KLIPP HÄR -------------------------------------
;Optimerat program som skriver blinkande rött 'A' på grön bakgrund
;på rad 12, tecken 39 (39,12).
DOSSEG ;Dela upp minnet i segment precis som dos gör
.MODEL SMALL ;välj vilken minnesmodell programmet ska ha
.STACK 100h ;definiera en stack på 256 bytes (100 hex)
.DATA ;datasegmentet tomt i det här programmet
.CODE ;kod segment
START: ;start är en label
mov ax,0B800h ;startadressen för skärmminnet till ax
mov es,ax ;lägg startadressen i es
mov di, 160*12+39*2 ;offset för rad 12, tecken 39 till di
mov al, 65 ;65 = ascii för tecknet 'A' i al
mov ah, 16*2+4+128 ;Attribut = blinkande rött på grön bakgr i ah.
stosw ;skriv ax till skärmminnet
mov ax,4c00h ;hoppa tillbaka till dos
int 21h ;programmet krashar utan dessa 2 rader!
END START ;SLUT (tala om att koden börjar vid labeln start)
------------------------------ KLIPP HÄR -------------------------------------
</pre>
Stosb flyttar inte bara en byte från al till es:di utan räknar också upp
di med en byte efter flyttningen. Detta gör att om man skriver stosb tio ggr
i rad så kopieras al tio ggr till adress es:di och di räknas upp varje gång,
vilket gör att bytarna hamnar efter varandra i minnet. På samma sätt räknar
stosw upp di 2 bytes efter flyttningen. Detta kan man utnyttja t ex till att
fylla skärmen med ett och samma tecken.
<pre>
------------------------------ KLIPP HÄR -------------------------------------
;program som fyller skärmen med blåa 'A' på röd bakgrund.
DOSSEG ;Dela upp minnet i segment precis som dos gör
.MODEL SMALL ;välj vilken minnesmodell programmet ska ha
.STACK 100h ;definiera en stack på 256 bytes (100 hex)
.DATA ;inga data i det här programmet heller
.CODE ;kod segment
START: ;start är en label
mov ax,0B800h ;startadressen för skärmminnet till ax
mov es,ax ;lägg startadressen i es
xor di,di ;nollställ di (börja vid början av skärmen)
mov al, 65 ;65 = ascii för tecknet 'A' till al
mov ah, 16*4+1 ;Attribut = blå text på grön bakgrund till ah.
mov cx,2000 ;25*80 = 2000 tecken på skärmen
;cx = loopräknare
loop1: stosw ;skriv ax till skärmminnet och räkna upp di
dec cx ;minska cx med ett
jnz loop1 ;om cx inte noll hoppa till loop1
mov ax,4c00h ;hoppa tillbaka till dos
int 21h ;programmet krashar utan dessa 2 rader!
END START ;SLUT (tala om att koden börjar vid labeln start)
------------------------------ KLIPP HÄR -------------------------------------
</pre>
Programmet innehåller en loop där cx är loopräknare. Cx bestämmer hur många
varv programmet skall snurra i loopen. Dec Cx minskar Cx med ett.
<pre>
jnz loop1 ;hoppar till labeln loop1 om resultatet
;av föregående instruktion skilt från noll
</pre>
jnz = jump if not zero, är ett så kallat villkorligt hopp. Lite noggrannare
definition: Om zero-flaggan inte är satt, hoppa till labeln som anges.
zero-flaggan sätts om resultatet av föregående instruktion är noll.
t ex om cx=1 och man gör ett dec cx => cx = 0 => zero-flaggan sätts.
<p>
Cx registret är specialiserat på att agera loopräknare. Därför finns det ett
par speciella loopinstruktion som involverar cx.
LOOP instruktionen räknar ned cx med ett och hoppar till labeln om cx > 0.
<p>
Föregående program skulle alltså kunna optimeras genom att skriva:
<pre>
mov cx,2000 ;antalet varv som loopen ska snurra till cx
loop1: ;loop1 en label
stosw ;detta är vad som utförs i loopen
loop loop1 ;loopa till labeln loop1
loop loop1 ersätter alltså raderna dec cx och jnz loop1 i programexemplet.
</pre>
Detta går att skriva ännu kompaktare med rep instrutionen. REP repeterar
efterföljande instruktion CX antal gånger.
<pre>
mov cx,2000 ;lägg 2000 i cx
rep stosw ;gör stosw 2000 gånger
</pre>
Dessa två rader flyttar alltså innehållet i Ax till adressen som pekas ut
av es:di 2000 ggr och räknar upp di efter varje gång, ganska kompakt eller hur!
<pre>
------------------------------ KLIPP HÄR -------------------------------------
;optimerat program som fyller skärmen med blåa 'A' på röd bakgrund.
;optimeringen består av att använda rep stosw instruktionen.
DOSSEG ;Dela upp minnet i likandana segment som dos gör
.MODEL SMALL ;välj vilken minnesmodell small
.STACK 100h ;definiera en stack på 256 bytes (100 hex)
.DATA ;inga data i det här programmet
.CODE ;kod segment
START: ;start är en label
mov ax,0B800h ;startadressen för skärmminnet (i textläge)
mov es,ax ;lägg startadressen i es
xor di,di ;nollställ di (börja vid början av skärmen)
mov al, 65 ;65 = ascii för tecknet 'A' till al
mov ah, 16*4+1 ;Attribut = blå text på röd bakgrund till ah.
mov cx,2000 ;25*80 = 2000 tecken på skärmen
rep stosw ;skriv ax till skärmminnet och räkna upp di 2000 ggr
mov ax,4c00h ;hoppa tillbaka till dos
int 21h ;programmet krashar utan dessa 2 rader!
END START ;SLUT (tala om att koden börjar vid labeln start)
------------------------------ KLIPP HÄR -------------------------------------
</pre>
Här kommer exakt samma program med inline assembler i c:
<pre>
------------------------------ KLIPP HÄR -------------------------------------
//optimerat program som fyller skärmen med blåa 'A' på röd bakgrund.
void main(void)
{
asm{
mov ax,0B800h //startadressen för skärmminnet (i textläge)
mov es,ax //lägg startadressen i es
xor di,di //nollställ di (börja vid början av skärmen)
mov al, 65 //65 = ascii för tecknet 'A' till al
mov ah, 16*4+1 //Attribut = blå text på röd bakgrund till ah.
mov cx,2000 //25*80 = 2000 tecken på skärmen
rep stosw //skriv ax till skärmminnet och räkna upp di 2000 ggr
}
}
------------------------------ KLIPP HÄR -------------------------------------
</pre>
Som ni ser slipper man lite kod i början och slutet av programmet genom att använda
inline assembler, det är dessutom lättare att hålla reda på saker och ting när man
börjar skriva längre program. Nackdelen med inline assembler är Bl.a. att exe-filen
blir MYCKET större än om man använder 'ren' assembler. Dessutom har man större kontroll
över varenda instruktion när man använder ren assembler. C kan förvränga koden lite
vid kompileringen. Vilket som går snabbast kan man diskutera. En sak är i alla fall säker,
när man programmerar någonting som har med grafik att göra är assembler oumbärligt
(försök använda Borlands medföljande grafik-bibliotek så får ni se).
Prova gärna olika metoder att utföra loopar på som träning.
Personligen brukar jag använda rep och loop instruktionerna så ofta det går.
Kan du t ex rita ut svenska flaggan på skärmen i textläge? Det är en bra övning
på loopar.
<p>
TIPS1: använd blå bakgrund och gul förgrund. ascii för fylld fyrkant=219. (Û)
<p>
TIPS2: Om man vill rensa skärmen kan man fylla den med mellanslag (ascii 32)
och attributet bakgrund svart (0), förgrund vit (7) vilket är standard
attributet som används när man slår på datorn.
<p>
<pre>
*******************
* ## *
* ## *
*#################*
* ## *
* ## *
*******************
*
*
*
* Abe
*
* s-mail: Albert Veli
* Sprisringsg. 9
* 724 76 V-S
* </pre>
<address>
mail:<a href="mailto:[email protected]">[email protected]</a>.
</address>
</body>
</html>