Tutorial de Assembler de
Adam Hyde 1.0 PARTE 4 |
Versão : 1.3
Data : 01-03-1996 /online by Renato 01-11-1998
Contato : blackcat@vale.faroc.com.au
http://www.faroc.com.au/~blackcat
;Renato :
rnbastos@ig.com.br
http://www.geocities.com/SiliconValley/Park/3174
Exemplo de Comparação | Shifts / Aritmética Hexadecima e Binária | Programado a VGA em Assembler
Bem-vindos mais uma vez, florescentes programadores Assembler. Os tutoriais parecem estar ficando populares agora, e eu tenho recebido e-mails pedindo-me para falar sobre o VGA, então eu vou dar olhada. Isso é basicamente para onde eu tenho conduzido no meu modo desconjuntado, de qualquer modo, já que programação gráfica não é só recompensante, é divertido também! Bem, eu acho que é. :)
Primeiramente porém, devemos terminar aquela coisa de CMP/JMP, e falar de shifts. Quando se está programando em Assembler, a gente acha que comparações, shifts e testar bits são operações muito comuns.
Eu não vou perder tempo explicando minuciosamente o seguinte exemplo - ele é muito
fácil de entender e você deve pegar a idéia basica seja lá como for.
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
FirstString DB 13, 10, "Este é um
grande tutorial ou o quê? :) - $"
SecondString DB 13, 10, "NÃO? NÃO? O que você quer dizer,
NÃO?$"
ThirdString DB 13, 10, "Excelente, vamos ouvir
você dizer isso de novo.$"
FourthString DB 13, 10, "Apenas um Y ou N j
basta.$"
ExitString DB 13, 10, "Bem, deixa pra
lá!$"
.CODE
START:
MOV AX, @DATA
; Novo modo de dizer:
MOV DS, AX
; DS -> SEG segmento de dados
KeepOnGoing:
MOV AH, 9
MOV DX, OFFSET FirstString ; DX
-> OFFSET FirstString
INT 21h
; Escreve a primeira mensagem
MOV AH, 0
; Pega uma tecla - armazena-a em AX
INT 16h
; AL - código ASCII, AH - "scan code"
; Ela não ecoa na tela, contudo,
; nós mesmos temos que fazer isso.
PUSH AX
; Aqui nós mostramos na tela o caracter
MOV DL, AL
; note que nós salvamos AX. Obviamente,
MOV AH, 2
; usando-se AH para imprimir uma string
INT 21h
; destrói-se AX
POP AX
CMP AL, "Y"
; Checa se foi teclado 'Y'
JNE HatesTute
; Se foi, continua
MOV AH, 9
; Mostra a mensagem "Excelente..."
MOV DX, OFFSET ThirdString
INT 21h
JMP KeepOnGoing
; Volta ao início e começa de novo
HatesTute:
CMP AL, "N"
; Certifica que foi teclado 'N'
JE DontLikeYou
; Infelizmente, sim.
MOV DX, OFFSET FourthString ; Pede ao
usuário para tentar de novo
MOV AH, 9
INT 21h
JMP KeepOnGoing
; Deixa ele tentar
DontLikeYou:
MOV DX, OFFSET SecondString ; Mostra a
string "NÃO? NÃO? O que..."
MOV AH, 9
INT 21h
MOV DX, OFFSET ExitString
; Mostra a string "Bem, deixa pra lá!"
MOV AH, 9
INT 21h
MOV AX, 4C00h
; Volta para o DOS
INT 21h
END START
Você deveria entender este exemplo, brincar um pouco com ele e escrever algo melhor. Aqueles com um livro do Peter Norton ou algo semelhante, experimentem as subfunções do teclado, e veja quais outras combinações de GetKey existem, ou melhor ainda, brinque com a interrupção 10h e entre em algum modo de vídeo sobrenatural - um que seu PC suporte! - e use algumas cores.
Um simples conceito, e um que eu já devia ter discutido antes, mas como eu disse - eu tenho minha própria maneira desconjuntada de fazer as coisas.
Primeiro você vai precisar de entender um pouco de aritmética hexadecimal e binária - um assunto que eu _deveria_ ter coberto antes. Eu geralmente uso uma calculadora científica - ei, eu sempre uso uma calculadora, eu não sou estúpido! - mas é bom ser capaz de saber como multiplicar, somar e converter entre as várias bases.
Você também não pode usar uma calculadora em provas de Computação, não na
Austrália.
CONVERTENDO DE BINÁRIO PARA DECIMAL:
De Volta ao Tutorial Um, nós vimos como números binários se parecem, então imagine que eu tenha um número binário de oito dígitos, como:
11001101
O que é isso em decimal??? Há várias formas de converter tal número, e eu uso a seguinte, que acredito se provavelmente a mais fácil:
Número Binário | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | |
Equivalente Decimal | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | |
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | ||
Valor Decimal | 128+64+0+0+8+4+0+1= |
205 |
Pegou a idéia? Note que para a última linha, seria mais preciso escrever:
1 x 128 + 1 x 64 + 0 x 32 + 0 x 16 + 1 x 8 + 1 x 4 + 0 x 2 + 1 x 1
= 128 + 64 +
0 + 0 +
8 + 4 + 0 +
1
= 205
Desculpe se isto é um pouco confuso, mas é difícil explicar sem demonstrar.
Aqui vai outro exemplo:
Número Binário | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | |
Equivalente Decimal | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | |
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | ||
Valor Decimal | 0+64+32+16+8+4+0+0= |
124 |
Obs.:
FATO ENGRAÇADO: Caso você não saiba ainda, bit vem de Binary digIT. :)
CONVERTENDO DE DECIMAL PARA BINÁRIO:
Isso é provavelmente mais fácil que da base-2 para base-10. Para calcular
o que 321 seria em binário, você faria o seguinte:
321
= 256 X 1
321 - 256 = 65 = 128
X 0
65
= 64 X 1
65 - 64 = 1
= 32
X 0
1
= 16 X 0
1
= 8 X 0
1
= 4 X 0
1
= 2 X 0
1
= 1 X 1
E você obteria o número binário - 101000001. Fácil, né? Vamos tentar outro para ter certeza que sabemos fazer:
198
= 128 X 1
198 - 128 = 70 =
64 X 1
70 - 64 = 6
= 32 X 0
6
= 16 X 0
6
= 8 X 0
6
= 4 X 1
6 - 4 = 2
=
2 X 1
2 - 2 = 0
=
1 X 0
E isto nos dá - 11000110. Note como você pode checar o primeiro dígito
para ver se você conseguiu sua conversão certa. Quando eu escrevi o primeiro exemplo, eu
notei que eu fiz um erro quando eu chequei o primeiro bit. No primeiro exemplo, eu
consegui 0 - não muito bom para um número ímpar. Eu entendi o erro e corrigi o exemplo.
CONVERTENDO DE HEXADECIMAL PARA DECIMAL:
Antes de começar, você deveria saber que o sistema numérico hexadecimal usa os 'dígitos':
0 = 0 (decimal)
= 0 (binário)
1 = 1 (decimal) =
1 (binário)
2 = 2 (decimal) =
10 (binário)
3 = 3 (decimal) =
11 (binário)
4 = 4 (decimal) =
100 (binário)
5 = 5 (decimal) =
101 (binário)
6 = 6 (decimal) =
110 (binário)
7 = 7 (decimal) =
111 (binário)
8 = 8 (decimal) =
1000 (binário)
9 = 9 (decimal) =
1001 (binário)
A = 10 (decimal) =
1010 (binário)
B = 11 (decimal) =
1011 (binário)
C = 12 (decimal) =
1100 (binário)
D = 13 (decimal) =
1101 (binário)
E = 14 (decimal) =
1110 (binário)
F = 15 (decimal) =
1111 (binário)
Você vai comumente ouvir hexadecimal referenciado como hex, ou base-16 e ela é comumente denotada por um 'h' - ex.: 4C00h, ou um '$', ex.: - $B800.
Trabalhar com hexadecimal não é tão difícil como pode parecer, e converter pra lá ou pra cá é bem fácil. Como exemplo, vamos converter B800h para decimal:
FATO ENGRAÇADO: B800h é o endereço inicial do vídeo em modo texto para CGA e placas superiores. :)
B = 4096 x B =
4096 x 11 = 45056
8 = 256 x 8
= 256 x 8 = 2048
0 =
16 x 0 = 16 x 0 =
0
0 =
1 x 0 = 1 x 0
= 0
Logo B800h = 45056 + 2048 + 0 + 0 = 47104
Obs.: Para números em hexadecimal maiores que FFFFh (65535 em decimal), você somente segue o mesmo procedimento como para binário, logo, para o quinto dígito hexadecimal, você multiplicaria por 65535.
Tecle 16 X X na sua calculadora, e fique apertando =.
Você verá os números que precisaria usar. O mesmo aplica-se
para binário. Ex.: 2 X X e = lhe daria 1, 2, 4, 8, 16...
etc.
OK, isso pareceu bem fácil. Eu acho que nem precisamos de um segundo exemplo.
Vamos dar uma olhada em:
CONVERTENDO DE DECIMAL PARA HEXADECIMAL:
Mais uma vez, o mesmo tipo de procedimento como usamos para
binário. Logo, para converter 32753 para hexadecimal, você faria assim:
32753 / 4096 = 7 (decimal) = 7h
32753 - (4096 x 7) = 4081
4081 / 256 = 15 (decimal) = Fh
4081 - (256 x 15) = 241
241 / 16 = 15 (decimal) = Fh
241 - (16 x 15) = 1
1 / 1
= 1 (decimal) = 1h
Assim, eventualmente temos 7FF1h como resposta. Este não é particularmente um bom processo e requer alguma explicação.
1) Quando você divide 32753 por 4096 você consegue 7.9963379... Não estamos interessados no lixo .9963379, só pegamos o 7, já que 7 é o maior número inteiro que podemos usar.
2) O resto da operação acima é 4081. Devemos agora realizar a mesma operação nisso, mas com 256. Dividindo 4081 por 256 nos dá 15.941406... Novamente, pegamos só o 15.
3) Agora temos um resto de 241. Dividindo isto por 16 nos dá
15.0625.
Pegamos o 15, e calculamos o resto.
4) Nosso último resto acontece que é um. Dividindo isso por um chegamos a, você advinhou - um. VOCÊ NÃO DEVERIA CONSEGUIR UMA RESPOSTA COM MUITAS CASAS DECIMAIS AQUI. SE VOCÊ TEM - VOCÊ CALCULOU ERRADO.
É um processo muito imundo, mas funciona. Eu não uso isso, exceto quando eu tenho que usar - eu não sou maluco. Eu uso uma calculadora científica, ou a calculadora do Windows <brrrrr> se eu precisar.
OK, agora que já lidamos com os cálculos horripilantes, você já está pronto para os shifts. Há geralmente 2 formas da instrução shift - SHL (shift left/esquerda) e SHR (shift right/direita). Basicamente, tudo o que essas instruções fazem é deslocar uma expressão para a esquerda ou direita um certo número de bits. Sua principal vantagem é a habilidade de lhe deixar substituir multiplicações lentas com shifts mais rápidos. Você vai achar que isso acelerará pra caramba os algoritmos de pixel/linhas/círculo.
Os PC's estão ficando cada vez mais rápidos a cada dia - um pouco rápido demais pro meu gosto. De volta aos dias do XT - a multiplicação era _realmente_ lenta - talvez levando atá 4 segundos para certas operações. Hoje em dia isso não acontece assim, mas é uma boa idéia otimizar seu código.
Quando nós plotamos um pixel na tela, temos que encontar o offset do pixel a plotar. Basicamente, o que fazemos é multiplicar a posição Y por 320, somar a posição X, e somar isso ao endereço A000h.
Assim basicamente, temos: A000:Yx320+X
Agora, seja lá quão rápido seu maravilhoso 486 ou Pentium é, isso poderia se feito um pouco mais rápido. Vamos reescrever aquela equação acima, assim, vamos usar alguns números diferentes:
Offset = Y
x 28 + Y x 26 + X
Ou:
Offset = Y
x 256 + y x 64 + X
Reconhece esses números? Eles parecem terrivelmente com aqueles que nós vimos naquela tabela de conversão binário-decimal. Contudo, nós ainda estamos usando multiplicação. Como podemos incorporar shifts?
Que tal:
Offset = Y SHL 8 + Y SHL 6 + X
Agora, isso é _muito_ mais rápido, já que tudo o que o computador tem que fazer é um shift à esquerda com o número - muito melhor. Note que o shift à esquerda AUMENTA o número, e o shift à direita DIMINUI o número.
Aqui está um exemplo que pode te ajudar se você ainda está em dúvida no que está
acontecendo. Digamos que estamos trabalhando em base-10 - decimal. Agora Peguemos o
número 36 como exemplo. "Shiftando" este número à esquerda de 1,
temos:
36 + 36 = 72
Agora SHL 2:
36 + 36 + 36 + 36 = 144
E SHL 3:
36 + 36 + 36 + 36 + 36 +
36 + 36 + 36 = 288
Notou os núeros que se formaram? Havia 2 36's com SHL 1, 4 36's com SHL 2 e 8 36's com SHL 3. Seguindo este padrão, seria justo assumir que 36 SHL 4 equivalerá a 36 x 16.
Note porém, o que está realmente acontecendo. Se você fosse trabalhar com o valor
binário de 36, que é mais ou menos isso: 100100, e então shiftasse 36 à esquerda de 2,
você teria 144, ou 10010000. Tudo o que a CPU faz na verdade é colocar alguns 1's e 0's
extras na posição de memória.
Como outro exemplo, pegue o número binário 1000101. Se fizermos um shift à esquerda de 3, terminaríamos com:
1 0 0 0 1 0 1
<---------- SHL 3
1 0 0 0 1 0 1 0 0 0
Agora vamos deslocar o número 45 à DIREITA de 2 unidades. Em binário isso é 101101. De onde:
1 0 1 1 0 1
SHR 2 ---->
1 0 1 1
Notou o que ocorreu? É muito mais fácil para a CPU apenas mover alguns bits (aproximadamente 2 unidades de clock), do que multiplicar um número. (Pode demorar até 133 unidades de clock).
Nós vamos usar bastante shifts quando estivermos programando a VGA, assim, tenha
certeza de que você entendeu os conceitos por trás disso.
PROGRAMANDO A VGA EM ASSEMBLER |
Eu tenho recebido um monte de mails me pedindo para cobrir a VGA. Então, para todos os
que pediram, nós estaremos gastando a maior parte do nosso tempo, mas não todo, em
programar a VGA. Além do mais, não querem todos programar
com gráficos?
Quando nós falamos sobre programar a VGA, nós estamos geralmente falando do modo 13h, ou um de seus parentes. Em VGA padrão este é o _único_ modo de usar 256 cores, e é provavelmente um dos modos mais fáceis também. Se você já tentou experiências com a SVGA, você vai entender o pesadelo que é para o programador dar suporte a todas as diferentes placas SVGA que existem - exceto se você usar VESA que é o que discutiremos outra hora. A grande vantagem do modo padrão 13h é que você sabe que todas as placas VGA que existem vão suportá-lo. As pessoas hoje frequentemente ignoram o modo 13h, achando a resolução muito granulada para os padrões de hoje, mas não se esqueça que Duke Nukem, DOOM, DOOM II, Halloween Harry e a maioria dos jogos da Apogee usam este modo para realizar alguns grandes efeitos.
A grande coisa sobre o modo 13h - isto é 320x200x256 caso você desconheça, é que acessar a VGA RAM é incrivelmente fácil. Como 320 x 200 é igual a 64,000, é possível encaixar a tela inteira em um segmento de 64K.
As más notícias são que o modo padrão 13h realmente só te dá uma página para
usar, seriamente embaraçante para scroll e page-flipping. nós vamos cobrir mais tarde
estes assuntos, como entrar em seus próprios modos - e modo X que evitará esses
problemas.
Então, como entrar no modo padrão 13h?
A resposta é simples. Usamos a interrupção 10h - interrupção de vídeo, e chamamos a subfunção 00h - seleciona o modo. Em Pascal, você poderia declarar uma procedure como esta:
Procedure Init300x200; Assembler;
Asm { Init300x200 }
mov ah, 00h {
Acerta o modo de vídeo }
mov al, 13h
{ Usa o modo 13h
}
int 10h
{ Faz isso
}
End; { Init300x200 }
você também pode ver:
mov ax, 13h
int 10h
Isso é perfeitamente correto, e provavelmente economiza um tempo de clock por não
colocar 00h em AH e então 13h em AL, mas é mais correto usar o primeiro exemplo.
OK, então estamos no modo 13h, mas o que podemos realmente fazer nele, além de olhar para uma tela em branco? Poderíamos voltar ao modo texto usando:
mov ah, 00h
mov al, 03h
int 10h
Mas isso é um pouco idiota. Porque não pintar um pixel?
Há inúmeros modos de colocar um pixel na tela. O modo mais fácil em Assembler
é usar interrupções. Você faria mais ou menos assim em Pascal:
Procedure PutPixel(X, Y : Integer; Color : Byte); Assembler;
Asm { PutPixel }
mov ah, 0Ch {
subfunção de desenhar pixel }
mov al, [Color] { Move a cor a plotar para AL
}
mov cx, [X] {
Move o valor X para CX }
mov dx, [Y] {
Move o valor Y para DX }
mov bx, 1h
{ BX = 1, p gina 1
}
int 10h
{ Plota
}
End; { PutPixel }
Contudo, mesmo isso sendo em Assembler, não é particularmente rápido. Por quê?, você pergunta. Porque isso usa interrupção. Interrupções são ótimas para entar e sair de modos de vídeo, ligar e desligar o cursor, etc... mas não para gráficos.
Você pode imaginar interrupções como uma secretária eletrônica. "A CPU está ocupada neste momento, mas se você deixar sua subfunção após o sinal - nós entraremos em contato."
Não é bom. Vamos usar a técnica que discutimos anteriormente durante shifts. O que queremos fazer é botar o valor da cor que desejamor plotar na VGA diretamente. Para fazer isso, precisamos mover o endereço da VGA para ES, e calcular o offset do pixel que queremos plotar. Um exemplo disso é mostrado abaixo:
Procedure PutPixel(X, Y : Integer; Color : Byte); Assembler;
Asm { PutPixel }
mov ax, 0A000h { Move o segmento da VGA para AX,
}
mov es, ax
{ e agora para ES
}
mov bx, [X]
{ Move o valor X para BX
}
mov dx, [Y]
{ Move o valor Y para DX
}
mov di, bx
{ Move X para DI
}
mov bx, dx
{ Move Y para BX
}
shl dx, 8
{
Nesta parte usamos shifts para multiplicar }
shl bx, 6
{ Y
por 320
}
add dx, bx
{ Agora somamos X ao valor
acima calculado, }
add di, dx
{ dando DI = Y x 320 +
X
}
mov al, [Color] { Põe a cor a plotar em AL
}
stosb
{ Põe o byte, AL, em ES:DI
}
End; { PutPixel }
Esta procedure é rápida o suficiente para começar, embora eu tenha dado uma muito mais rápida uns tutoriais atrás que usa uma técnica genial para pegar DI.
OK, acho que é o suficiente para essa semana. Brinque com as rotinas de PutPixel e
veja o que você pode fazer com elas. Para aqueles com um livro do Peter Norton, veja que
outros procedimentos você pode fazer usando interrupções.
COISAS PARA FAZER:
1) Cobrimos muita coisa nesse tutorial, e alguns conceitos
importantes
estão nele. Certifique-se de estar comfort vel
com comparações, porque
vamos começar a testar bits em breve.
2) Tenha certeza que entendeu aquela coisa de binário ->
decimal,
decimal -> binário, decimal -> hex e hex ->
decimal. Faça você mesmo
alguns exemplos de soma e teste suas respostas com a
calculadora do
Windows.
3) você _deve_ entender shifts. Se você ainda tem problemas, faça
algumas
expressões num papel e teste suas respostas num
programa como:
Begin { Main }
WriteLn(45 SHL 6);
ReadLn;
End. { Main }
e/ou a calculadora do Windows.
4) Dê uma olhada na parte de VGA, e certifique-se de ter pego a
teoria
por trás disso, porque na próxima semana vamos
entrar a fundo nisso.
Semana que vem vou tentar colocar alguns exemplos em C/C++ além de Pascal
para vocês programadores de C aí fora.
No próximo tutorial vamos ver:
Se você deseja ver um tópico discutido num tutorial no futuro, escreva-me, e eu vou ver o que eu posso fazer.
Não perca!!! Baixe o tutorial da próxima semana na minha homepage:
Vejo vocês na próxima semana!
- Adam.
- Renato Nunes Bastos
" Eu _nunca_ escrevo código com bugs, eu apenas coloco algumas características a mais sem querer! "