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:


  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.


<< Anterior Assembly Próxima >>