Curso de Assembly
Exemplos com Sub-rotinas do MSX
Você está em: MarMSX >> Cursos >> Assembly Z80
Este capítulo visa o aprendizado aprofundado nas sub-rotinas da BIOS do MSX, através de exercícios práticos.
Para testar as sub-rotinas em LM no Basic do MSX, utilize o seguinte programa:
10 E=&HC000
20 READ A$:IF A$="M" THEN 60
30 POKE E,VAL("&H"+A$)
40 E=E+1
50 GOTO 20
60 DEFUSR=&HC000:X=USR(0)
Acrescente ao programa as linhas fornecidas por cada exercício.
As informações contidas no Livro Vermelho para cada instrução são:
- Endereço - originalmente o endereço real da sub-rotina, que modificamos para o endereço do ponteiro para manter compatilibidade entre os MSXs.
- Nome - nome da sub-rotina.
- Entrada - registradores que devem ser modificados de modo a passarem parâmetros de entrada para a sub-rotina.
- Saída - registradores que armazenam informações de saída da sub-rotina (retorno da função).
- Modifica - registradores modificados durante a execução da sub-rotina.
Exercícios
1. Criar em LM um programa que mude a tela para screen 1 e escreva um caractere.
Consultando a lista de sub-rotinas da BIOS do MSX 1, encontramos as seguintes sub-rotinas que mudam o modo de tela para screen 1:
006FH INIT32 ; Screen 1
005FH CHGMOD ; Screen n
A sub-rotina CHGMOD é genérica para qualquer screen do MSX. Já a sub-rotina INIT32 é específica para a screen 1.
Descrição detalhada de cada uma [1]:
Endereço.. 005FH
Nome...... CHGMOD
Entrada... A=Modo de tela (0, 1, 2, 3)
Saída..... Nada
Modifica.. AF, BC, DE, HL, EI
Endereço.. 006FH
Nome...... INIT32
Entrada... Nada
Saída..... Nada
Modifica.. AF, BC, DE, HL, EI
As sub-rotinas para escrever na tela são:
00C6H POSIT ; Posiciona cursor na tela
00A2H CHPUT ; Escreve caractere na posição atual
004DH WRTVRM ; Escreve caractere em endereço da VRAM
A sub-rotina WRTVRM foi utilizada no capítulo anterior como exemplo para escrever um caractere na tela. Entretanto, ela necessita do endereço físico, ou seja, o endereço da VRAM, para escrever o caractere na tela.
A sub-rotina CHPUT escreve um caractere na posição atual do cursor. Podemos alterar a posição do cursor, utilizando a sub-rotina POSIT.
Detalhes das sub-rotinas POSIT e CHPUT [1]:
Endereço.. 006CH
Nome...... POSIT
Entrada... H=Coluna, L=Linha
Saída..... Nada
Modifica.. AF, EI
Endereço.. 00A2H
Nome...... CHPUT
Saída..... A=Caractere
Exit...... Nada
Modifica.. EI
Obs: a coordenada inicial da tela para o POSIT é 1,1, diferente do LOCATE do Basic, que é 0,0.
O programa então, fica:
10 ORG &HC000
20 INIT32: EQU &H6F
30 POSIT: EQU &HC6
40 CHPUT: EQU &HA2
50 CALL INIT32 ; Screen 1
60 LD HL,&H0304 ; Locate 2,3
70 CALL POSIT ; Posiciona
80 LD A,77 ; Caractere "M"
90 CALL CHPUT ; Imprime
100 RET
Para testar em Basic:
70 DATA CD,6F,00,21,04,03,CD,C6
80 DATA 00,3E,4D,CD,A2,00,C9,M
2. Criar em LM um programa que execute o INPUT do Basic.
Sub-rotina [1]:
Endereço.. 00B4H
Nome...... QINLIN
Entrada... Nada
Saída..... HL=Inicio do texto
Flag C, se houve CONTROL+STOP
Modifica.. AF, BC, DE, HL, EI
Observe pela descrição da sub-rotina que não há informações de entrada. Entretanto, após rodar, a sub-rotina informa em HL o endereço inicial do texto escrito e no flag de "carry" se as teclas CONTROL+STOP foram apertadas.
Programa:
10 ORG &HC000
20 QINLIN: EQU &HB4
30 CALL QINLIN
40 RET
Para testar em Basic:
70 DATA CD,B4,00,C9,M
3. Criar em LM dois programas que executem o LINE INPUT do Basic e um que escreva o conteúdo digitado normal e outro que escreva invertido.
Agora vamos juntar o que foi feito nos exercícios 1 e 2.
Sub-rotina do LINE INPUT:
Endereço.. 00B1H
Nome...... INLIN
Entrada... Nada
Saída..... HL=Inicio do texto
Flag C, se houve CONTROL+STOP
Modifica.. AF, BC, DE, HL, EI
É importante sabermos que a string digitada pelo LINE INPUT é colocada na memória apontada por HL ao final da execução da rotina, e que o código terminador do texto é "00". Observa-se que a sub-rotina insere o caractere "," antes da linha digitada. Dessa forma, devemos mover HL uma posição à frente.
Por exemplo, seja a linha digitada "MSX":
End | Hex | ASCII
-----+-----+------
D000 | 2C | , ← HL
D001 | 4D | M
D002 | 53 | S
D003 | 58 | X
D004 | 00 |
Programa para a escrita normal:
10 ORG &HC000
20 INLIN: EQU &HB1
30 CHPUT: EQU &HA2
40 CALL INLIN ; Rotina de LINE INPUT
50 JR C,FIM ; Se CTRL+STOP, fim
60 INC HL ; Ajusta posição de HL
70 L1: LD A,(HL) ; Lê a letra atual
80 CP 0 ; Vê se é fim da string
90 JR Z,FIM ; Termina se for
100 CALL CHPUT ; Imprime senão
110 INC HL ; Passa para a prox. pos.
120 JR L1 ; Loop
130 FIM: RET ; Fim
Para testar em Basic:
70 DATA CD,B1,00,38,0C,23,7E,FE
80 DATA 00,28,06,CD,A2,00,23,18
90 DATA F5,C9,M
Para escrever ao contrário, devemos percorrer a string até encontrar o final dela, e depois, vir escrevendo de trás para frente.
Programa para a escrita invertida:
10 ORG &HC000
20 INLIN: EQU &HB1
30 CHPUT: EQU &HA2
40 CALL INLIN ; Rotina de LINE INPUT
50 JR C,FIM ; Se CTRL+STOP, fim
60 INC HL ; Ajusta posição de HL
70 LD A,(HL) ; Termina, se
80 CP 0 ; a string digitada
90 JR Z,FIM ; for vazia
100 LD B,0 ; Zera contador B
110 L1: INC HL ; Passa para a prox. pos.
120 LD A,(HL) ; Lê caractere
130 INC B ; Incrementa contador
140 CP 0 ; Se código terminador
150 JR NZ,L1 ; Termina loop 1 (L1)
160 DEC HL ; Posiciona na ultima letra
170 L2: LD A,(HL) ; Imprime
180 CALL CHPUT ; até
190 DEC HL ; o fim do
200 DJNZ L2 ; contador B
210 FIM: RET
Para testar em Basic:
70 DATA CD,B1,00,38,17,23,7E,FE
80 DATA 00,28,11,06,00,23,7E,04
90 DATA FE,00,20,F9,2B,7E,CD,A2
100 DATA 00,2B,10,F9,C9,M
4. Criar em LM um programa que desenhe uma linha na screen 2.
O comando LINE está nas sub-rotinas do interpretador Basic, no capítulo 5 do Livro Vermelho [2].
Consultando a lista de sub-rotinas da BIOS do MSX 1, encontramos o comando LINE no endereço 4B0EH.
Vamos entender o que o interpretador Basic faz para interpretar essa instrução [1].
Primeiro, ele recebe o comando Basic:
LINE(Xi,Yi)-(Xf,Yf),C
Depois identifica o token relativo a instrução "LINE" e chama a sub-rotina que trata do comando LINE, em 4B0EH. Essa sub-rotina está atrelada ao comando gráfico LINE e também aos comandos LINE INPUT e LINE INPUT#. Quando o interpretador não encontra o INPUT logo após o LINE, ele sabe que se trata do LINE gráfico e vai para a sub-rotina localizada em 58A7H.
A sub-rotina em 58A7H tem como missão interpretar os argumentos passados com o comando LINE e armazená-los em algum lugar, para depois chamar uma das três sub-rotinas de linha. Os argumentos são:
(Xi,Yi)-(Xf,Yf),C
Essa sub-rotina lê as coordenadas de origem e armazena Xi em BC e Yi em DE. Então, lê as coordenadas de destino e armazena elas nas seguintes variáveis de sistema:
GXPOS FCB3H 2 bytes Xi
GYPOS FCB5H 2 bytes Yf
GRPACX FCB7H 2 bytes Xf
GRPACY FCB9H 2 bytes Yf
Depois, ela lê a cor da linha e chama a rotina em 584DH para setar a cor do pixel. Por fim, verifica se há as opções B ou BF. De acordo com o que ele encontra, dispara as seguintes sub-rotinas:
Encontra Sub-rotina Endereço
linedraw 58FCH
B box 5912H
BF boxfill 58BFH
Para desenharmos uma linha, não precisamos passar por toda a parte da interpretação do comando LINE. Basta fornecermos as informações necessárias para uma das três última sub-rotinas apresentadas e dispará-la diretamente.
Podemos alterar a cor do pixel, através da sub-rotina SETATR.
Endereço.. 011AH
Nome...... SETATR
Entrada... A=Código da cor
Saída..... Flag C se código ilegal
Modifica.. Flags
Programa que desenha uma linha magenta (cor 13) na screen 2, de (10,10)-(100,100):
10 ORG &HC000
20 CHGMOD: EQU &H5F
30 LINE: EQU &H58FC
40 SETATR: EQU &H11A
50 CHGET: EQU &H9F
60 LD A,2
70 CALL CHGMOD ; Screen 2
80 LD A,13
90 CALL SETATR ; Cor=13
100 LD BC,10 ; Xi=10
110 LD DE,10 ; Yi=10
120 LD IX,&HFCB3
130 LD (IX+0),100 ; GXPOS=100
140 LD (IX+2),100 ; GYPOS=100
150 LD (IX+4),100 ; GRPACX=100
160 LD (IX+6),100 ; GRPACY=100
170 CALL LINE ; Line
180 CALL CHGET ; intput$(1)
190 XOR A
200 CALL CHGMOD ; Screen 0
210 RET
Para testar em Basic:
70 DATA 3E,02,CD,5F,00,3E,0D,CD
80 DATA 1A,01,01,0A,00,11,0A,00
90 DATA DD,21,B3,FC,DD,36,00,64
100 DATA DD,36,02,64,DD,36,04,64
110 DATA DD,36,06,64,CD,FC,58,CD
120 DATA 9F,00,AF,CD,5F,00,C9,M
Obs: alterando-se a linha 30 do programa em Assembly, podemos desenhar um retângulo ou um retângulo preenchido:
30 LINE: EQU &H5912 ; Box
30 LINE: EQU &H58C1 ; Filled box
Do teste em Basic:
110 DATA DD,36,06,64,CD,12,59,CD ' Box
110 DATA DD,36,06,64,CD,C1,58,CD ' Filled box
Obs: como estamos acessando diretamente as sub-rotinas de linha, em "boxfill" devemos começar por &H58C1 em vez de &H58BF. Senão, um erro é lançado.
A rotina localizada em 593CH é a rotina que traça uma linha entre dois pontos. No Livro Vermelho [1], há a descrição de como funciona essa rotina.
Ao chamarmos a sub-rotina 593CH diretamente, precisamos somente de BC e HL para Xi,Yi e GXPOS e GYPOS para Xf,Yf.
Podemos também utilizar o programa para desenhar um linha nas screens 5-8 do MSX 2. Para isso, basta alterar a linha 60 para o valor da screen desejada.
5. Criar em LM um programa que modifique a tabela de caracteres da screen 0 ou 1 para bold.
Transformar os caracteres em bold (negrito) consiste em deslocar o caractere um pixel à direita e depois realizar a operação lógica OU entre o caractere antigo e novo.
Veja o exemplo com a letra "A":
Original
1
1 1
1 1
1 1
11111
1 1
1 1
Deslocado:
1
1 1
1 1
1 1
11111
1 1
1 1
Operação lógica
Original OU Deslocado:
11
1111
11 11
11 11
111111
11 11
11 11
Quando estamos na screen 0 ou 1, a tabela de caracteres é copiada da ROM para a VRAM, mas em diferentes endereços.
A variável de sistema CGPBAS, localizada em F924H, contém o endereço inicial da tabela de caracteres. Assim, através dessa variável, obtemos o endereço da tabela de caracteres, seja em qual modo de tela estivermos.
Cada caractere tem 8 bytes. O programa deverá varrer a tabela, que têm 256 caracteres, modificando caractere a caractere. O caractere com valor ASCII igual a 255 é o cursor e não é necessário aplicar o bold nele.
Programa:
10 ORG &HC000
20 RDVRM: EQU &H4A
30 WRTVRM: EQU &H4D
40 LD HL,(&HF924) ; End. tab. caracteres
50 LD B,&HFF ; De 0 a 254
60 LE: LD C,B ; Salva B em C
70 LD B,8 ; 8 Linhas do caractere
80 LI: CALL RDVRM ; Lê linha atual
90 LD D,A ; Salva linha em D
100 SRL D ; Desloca D p/ direita
110 OR D ; A OU D
120 CALL WRTVRM ; Copia linha modificada
130 INC HL ; Próxima linha
140 DJNZ LI ; Repete 8x
150 LD B,C ; Recupera contador do LE
160 DJNZ LE ; Repete 255x
170 RET
Para testar em Basic:
70 DATA 2A,24,F9,06,FF,48,06,08
80 DATA CD,4A,00,57,CB,3A,B2,CD
90 DATA 4D,00,23,10,F3,41,10,ED
100 DATA C9,M
6. Criar em LM um programa que modifique a tabela de caracteres da screen 0 ou 1 para italic.
Transformar os caracteres em italic (itálico) consiste em deslocar as linhas:
Linha 1: 3x →
Linha 2: 3x →
Linha 3: 2x →
Linha 4: 2x →
Linha 5: 1x →
Linha 6: 1x →
Veja o exemplo com a letra "A":
Original
1
1 1
1 1
1 1
11111
1 1
1 1
Deslocado
1
1 1
1 1
1 1
11111
1 1
1 1
Programa:
10 ORG &HC000
20 RDVRM: EQU &H4A
30 WRTVRM: EQU &H4D
40 LD HL,(&HF924) ; End. tab. caracteres
50 LD B,&HFF ; De 0 a 254
60 LOOP: PUSH BC ; Salva contador
70 LD B,2
80 L1L2: CALL RDVRM ; Desloca linhas 1 e 2
90 SRL A
100 SRL A
110 SRL A
120 CALL WRTVRM
130 INC HL
140 DJNZ L1L2
150 LD B,2
160 L3L4: CALL RDVRM ; Desloca linhas 3 e 4
170 SRL A
180 SRL A
190 CALL WRTVRM
200 INC HL
210 DJNZ L3L4
220 LD B,2
230 L5L6: CALL RDVRM ; Desloca linhas 5 e 6
240 SRL A
250 CALL WRTVRM
260 INC HL
270 DJNZ L5L6
280 INC HL
290 INC HL
280 POP BC
290 DJNZ LOOP
300 RET
Para testar em Basic:
70 DATA 2A,24,F9,06,FF,C5,06,02
80 DATA CD,4A,00,CB,3F,CB,3F,CB
90 DATA 3F,CD,4D,00,23,10,F1,06
100 DATA 02,CD,4A,00,CB,3F,CB,3F
110 DATA CD,4D,00,23,10,F3,06,02
120 DATA CD,4A,00,CB,3F,CD,4D,00
130 DATA 23,10,F5,23,23,C1,10,CD
140 DATA C9,M
7. Criar em LM um programa que crie uma animação e "afunde" os caracteres.
O "afundamento" consiste em pegar todos os 256 caracteres da tabela de caracteres e realizar um deslocamento uma linha abaixo em cada um, em 8 passos, até o caractere sumir por completo.
Veja o exemplo com a letra "A":
Inicio P1 P2 P3 P4 P5 P6 P7 P8
1
1 1 1
1 1 1 1 1
1 1 1 1 1 1 1
11111 1 1 1 1 1 1 1
1 1 11111 1 1 1 1 1 1 1
1 1 1 1 11111 1 1 1 1 1 1 1
1 1 1 1 11111 1 1 1 1 1 1 1
Em vez de ler linha a linha da VRAM, vamos trazer operações em bloco de 8 bytes, correspondente à um caractere. Trabalhar na RAM é mais fácil e mais rápido, além da transferência em bloco entre RAM e VRAM ser mais rápida do que byte a byte.
As sub-rotinas da BIOS LDIRMV e LDIRVM fazem transferência de blocos entre RAM e VRAM:
Endereço.. 0059H
Nome...... LDIRMV
Entrada... BC=comprimento
DE=Endereço da RAM
HL=Endereço da VRAM
Saída..... Nada
Modifica.. AF, BC, DE, EI
Endereço.. 005CH
Nome...... LDIRVM
Entrada... BC=comprimento
DE=Endereço da VRAM
HL=Endereço da RAM
Saída..... Nada
Modifica.. AF, BC, DE, EI
Observe que ambas as sub-rotinas afetam os registradores AF, BC, DE e EI. Portanto, antes de chamá-las, devemos armazenar os registradores afetados, caso estejamos usando algum deles.
O truque aplicado para "afundar" o caractere consiste em copiar o caractere da VRAM para a RAM, deslocar o ponteiro da RAM uma posição acima, e depois copiar de volta o bloco para a VRAM. Obviamente, a linha anterior deverá estar vazia (valor 0).
Programa:
10 ORG &HC000
20 LDIRMV: EQU &H59
30 LDIRVM: EQU &H5C
40 XOR A
50 LD (&HC0FF),A ; Limpa linha anterior da RAM
60 LD HL,(&HF924) ; Obtém tab. caracteres
70 LD B,8 ; Repete 8x na tabela
80 LE: PUSH HL ; Salva end. tab.
90 PUSH BC ; Salva contador
100 LD B,&HFF ; Aplica de 0 a 254
110 LI: PUSH BC ; Salva contador
120 CALL AFN ; Chama sub-rotina "afunda"
130 POP BC ; Recupera contador
140 DJNZ LI ; Loop
150 POP BC ; Recupera contador
160 POP HL ; Recupera HL
170 DJNZ LE ; Loop
180 RET ; Fim
184 ;
185 ; Sub-rotina "afunda"
186 ;
190 AFN: LD DE,&HC100 ; End. RAM
200 LD BC,8 ; Tamanho do bloco
210 CALL LDIRMV ; Copia para a RAM
220 LD DE,&HC100 ; Recupera DE
230 PUSH HL ; Salva HL
240 EX DE,HL ; Troca DE com HL
250 DEC HL ; Move acima
260 LD BC,8 ; Tamanho do bloco
270 CALL LDIRVM ; Copia de volta
280 POP HL ; Recupera HL
290 LD DE,8 ;
300 ADD HL,DE ; Próximo caractere
310 RET ; Retorna
Para testar em Basic:
70 DATA AF,32,FF,C0,2A,24,F9,06
80 DATA 08,E5,C5,06,FF,C5,CD,19
90 DATA C0,C1,10,F9,C1,E1,10,F1
100 DATA C9,11,00,C1,01,08,00,CD
110 DATA 59,00,11,00,C1,E5,EB,2B
120 DATA 01,08,00,CD,5C,00,E1,11
130 DATA 08,00,19,C9,M
Referências:
[1] - O Livro Vermelho do MSX, Avalon Software, editora McGraw Hill.