Curso de Assembly
Exemplos com Sub-rotinas do MSX


  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 >>


/MARMSX/CURSOS/Assembly