Curso de Basic
Basic com Assembly


  A linguagem Basic é interpretada, ou seja, a cada instrução, o interpretador Basic deve traduzir o código em Basic para a linguagem de máquina e então executar, tornando a execução de um programa bem mais lenta que se fosse direta em Linguagem de Máquina.
  Vejamos a seguinte comparação: dois programas que preenchem toda a tela da screen 0 com o caractere "A", um feito em Basic, o outro em Assembly.

Programa em Basic:
10 TIME=0
20 FOR I=0 TO 959
30 VPOKE I,&H41
40 NEXT
50 PRINT TIME/60
  Tempo gasto para escrever: 3.42 segundos.

Programa em Assembly:
10 FOR E=&HC000 TO &HC00B
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DEFUSR=&HC000
60 TIME=0
70 X=USR(0)
80 PRINT TIME/60
90 END
100 DATA 3E,41,21,00,00,01,C0,03,CD,56,00,C9
  Tempo gasto para escrever: 0.017 segundos.

  Código fonte em Assembly (RSCII):
Endereço  Assembly  Linha  Mnemônico

0056                10     FILVRM: EQU &H56
C000                20     ORG &HC000
C000      3E41      30     LD A,&H41
C002      210000    40     LD HL,0
C005      01C003    50     LD BC,&H3C0
C008      CD5600    50     CALL FILVRM
C00B      C9        60     RET

  O programa em Assembly foi 200 vezes mais rápido do que o seu correspondente em Basic.

  O objetivo desse capítulo é mostrar como chamar rotinas em Linguagem de Máquina a partir do Basic, de forma a acelerar os programas ou, até mesmo, utilizar recursos que são possíveis somente em Linguagem de Máquina.

  Conforme pode ser visto na seção Dicas dos leitores da revista Micro Sistemas, muitos programadores utilizavam o recurso de escrever partes críticas de um programa em Linguagem de Máquina.


  Preparando a área do programa

  Antes de colocarmos nosso programa na memória do MSX, temos que conhecer a RAM ocupada pelo Basic e as variáveis de sistema, de forma a não termos conflitos de endereços durante a execução de um programa Basic com rotinas em Assembly.

  Mapa da RAM do MSX:

8000H  ┌──────────────────┐ TXTTAB - 8001H
       │ Área do programa │
       │    em Basic      │
       ├──────────────────┤ VARTAB - 8003H
       │     Área de      │
       │    variáveis     │
       ├──────────────────┤ ARYTAB - 8003H
       │     Área de      │
       │     matrizes     │
       ├──────────────────┤ STREND - 8003H
       │      Área        │
       │      livre       │
       ├──────────────────┤ 
       │     Área de      │
       │      stack       │
       ├──────────────────┤ STKTOP - F0A0H
       │     Área de      │
       │     string       │
       ├──────────────────┤ MEMSIZ - F168H
       │ Bloco de contr.  │ FRETOP - F168H
       │   de arquivo     │
F380H  ├──────────────────┤
       │     Área do      │
       │     sistema      │ HIMEM - F380H
FFFFH  └──────────────────┘

  Descrição das áreas:
  As variáveis apresentadas no gráfico anterior são descritas a seguir.

Variável Endereço Valor inicial Descrição
TXTTAB F676H 8001H Contém o endereço do primeiro byte da área de programa.
VARTAB F6C2H 8003H Contém o endereço do primeiro byte da área de variáveis.
ARYTAB F6C4H 8003H Contém o endereço do primeiro byte da área de matrizes.
STREND F6C6H 8003H Contém o próximo endereço livre ao final da área de matrizes.
STKTOP F674H F0A0H Contém o endereço do topo da pilha do Z80.
MEMSIZ F672H F168H Contém o endereço do topo da área de strings.
FRETOP F69BH F168H Contém o primeiro endereço livre após a área de strings.
HIMEM FC4AH F380H Contém o primeiro endereço após o final da RAM utilizada pelo interpretador Basic.

  Obs: os valores iniciais de algumas dessas variáveis muda quando há um disk-drive acoplado ao sistema.

  Exemplo de uso das variáveis em um MSX 1 com disk-drive:
10 DEF FN HX$(E)=RIGHT$("00"+HEX$(PEEK(E+1)),2)+RIGHT$("00"+HEX$(PEEK(E)),2)
20 PRINT"TXTTAB: ";FN HX$(&HF676)
30 PRINT"STKTOP: ";FN HX$(&HF674)
40 PRINT"HIMEM : ";FN HX$(&HFC4A)
  Saída:
  TXTTAB: 8001
  STKTOP: DB97
  HIMEM : DE77

  A inclusão de um disk-drive diminui consideravelmente a área total do Basic.

  O comando CLEAR poderá ser utilizado para "proteger" a área do programa em Linguagem de Máquina, uma vez que ele define a área ocupada por todo o programa Basic.

  Sintaxe:
CLEAR tamanho_da_area_de_caracteres, endereco_da_RAMTOP

  O comando CLEAR faz:
  Exemplo:
CLEAR 200,&HD000
  Define o HIMEM para &HD000, ficando a área de D000H a F379H livre para um programa em Linguagem de Máquina. Observa-se que os endereços de F380H a FD99H reside a área de trabalho do MSX, onde são armazenadas as variáveis utilizadas pelo interpretador Basic e a BIOS.

  Se o seu programa em Basic não for muito grande, ele poderá ser armazenado sem maiores problemas na "área livre" do Basic, como por exemplo a partir do endereço &HC000.
  A necessidade de proteção surge quando há possibilidade de conflitos de endereçamento, como por exemplo um programa Basic grande ou com bastante variáveis e strings. Definindo o endereço do programa em &HC000, sobram 16383 bytes para o código do programa mais as variáveis.


  Carregando e executando um programa em LM na memória

  Um programa em Linguagem de Máquina é carregado na memória RAM do MSX, byte a byte, através da instrução POKE.

  Sintaxe dos comandos de E/S para a memória RAM:
POKE endereco, dado	' Escreve dado na RAM
PEEK (endereco)		' Lê dado da RAM

  Seja o seguinte programa em Assembly, que limpa a tela.
Endereço  Assembly  Linha  Mnemônico

00C3                10     CLS: EQU &HC3
C000                20     ORG &HC000
C000      CDC300    30     CALL CLS 
C003      C9        40     RET

  Ele poderá ser carregado na memória assim:
10 POKE &HC000,&HCD
20 POKE &HC001,&HC3
30 POKE &HC002,&H00
40 POKE &HC003,&HC9

  Ou assim:
10 FOR E=&HC000 TO &HC003
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DATA CD,C3,00,C9

  Escrevemos o programa na memória, mas como chamá-lo? É isso que fazem os comandos DEFUSR e USR. O primeiro define o endereço inicial de uma sub-rotina em Linguagem de Máquina, enquanto que o segundo executa a sub-rotina.

  Sintaxe:
DEFUSRn = endereco

  DEFUSR permite armazenar até 10 endereços de execução de sub-rotinas em linguagem de máquina (0-9), através da variável "n". Se "n" for omitido, assume-se o valor igual a 0.
  Exemplos:
DEFUSR=&HC000
DEFUSR1=&HC010

  Sintaxe:
valor_recebido = USRn(valor_enviado)

  O valor de "n" corresponde ao endereço definido por DEFUSR, o valor_enviado é uma valor passado para a sub-rotina e o valor_recebido é o valor devolvido pela sub-rotina.

  Para executar o programa que limpa a tela, digitamos:
DEFUSR = &HC000 
X=USR(0) 

  O valor_enviado pode ser qualquer valor, quando a sub-rotina não recebe dados. Entretanto, se receber, o valor enviado será lido pela sub-rotina da seguinte maneira [2]:
Tipo enviado   Registro A  Registro HL  Local dos dados
-------------------------------------------------------
inteiro            8         &HF7F6     &HF7F6-&HF7FD
precisão simples   4         &HF7F6     &HF7F6-&HF7F9
dupla precisão     2         &HF7F6     &HF7F8-&HF7F9
  O registro A indica o tipo de dado e o registro HL o endereço inicial do dado, exceto para o número inteiro, que deverá ser incrementado de 2.

Tipo enviado   Registro A  Registro DE
---------------------------------------------
string             3       End. do ponteiro
                           p/ string
  O registro A indica o tipo de dado e o registro DE o endereço do bloco de informações da string, que é apresentado a seguir:
OFFSET Tamanho Dado
 00       1    Comprimento da string
 01       2    Local da string

  Importante: o tipo de retorno é o mesmo tipo de envio.

  Exemplo para um tipo de envio/retorno inteiro: um programa em LM que dobra o valor passado.
Endereço  Assembly  Linha  Mnemônico

C000                10     ORG &HC000
C000      23        20     INC HL	; Posiciona HL em
C001      23        30     INC HL	; &HF7F8
C002      7E        40     LD A,(HL)    ; Lê o valor passado
C003      87        50     ADD A,A      ; Dobra o valor
C004      77        60     LD (HL),A    ; Armazena o resultado
C005      C9        100    RET

  Programa em Basic para testar:
10 FOR E=&HC000 TO &HC005
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DEFUSR=&HC000
60 PRINT USR(4)
100 DATA 23,23,7E,87,77,C9
  Saída:
  8

  Agora vamos ver um exemplo usando string [3].
  O programa a seguir recebe uma string e aplica uma rotação de 1 posição na tabela ASCII. Por exemplo, "ABC" fica "BCD".
Endereço  Assembly  Linha  Mnemônico

C000                10     ORG &HC000
C000      FE03      20     CP   3		; Verifica se A é uma string
C002      C0        30     RET  NZ		; Retorna se não for
C003      1A        40     LD   A,(DE)  	; Lê o número de caracteres
C004      B7        50     OR   A
C005      C8        60     RET  Z       	; Retorna se num. caract. for 0
C006      47        70     LD   B,A     	; Armazena em B o compr. da string
C007      EB        80     EX   DE,HL   	; Troca conteúdo de DE e HL
C008      23        90     INC  HL
C009      5E       100     LD   E,(HL)  	; Grava em DE o endereço da string
C00A      23       110     INC  HL
C00B      56       120     LD   D,(HL)
C00C      1A       130     LOOP: LD   A,(DE)	; Carrega caractere
C00D      3C       140     INC  A		; Soma 1 ao código ASCII
C00E      12       150     LD   (DE),A  	; Salva caractere modificado
C00F      13       160     INC  DE
C010      10FA     170     DJNZ LOOP
C012      C9       180     RET

  Programa em Basic para testar:
10 FOR E=&HC000 TO &HC012
20 READ A$
30 POKE E,VAL("&H"+A$)
40 NEXT
50 DEFUSR=&HC000
60 PRINT USR("marmsx")
100 DATA FE,03,C0,1A,B7,C8,47,EB,23,5E,
23,56,1A,3C,12,13,10,FA,C9
  Saída:
  nbsnty



  Referências:

  [1] - Linguagem Basic MSX, editora Aleph, 5a. Edição, 1987.
  [2] - O Livro Vermelho do MSX, Avalon Software, editora McGraw Hill.
  [3] - Adaptado do programa "Inverse no MSX", publicado na Micro Sistemas.


<< Anterior Basic Próxima >>


/MARMSX/CURSOS/Basic