Algumas dicas para MSX

BASIC

Problema

Como ler um alfabeto do Graphos 3 em BASIC?

Solução

Se você estiver utilizando SCREEN 1, existe uma maneira muito fácil:

BLOAD "BOLD.ALF",S,-&H1200

Isso funciona pois o alfabeto do Graphos 3 é um arquivo binário começando em &H9200. Utilizando a opção S, você carrega o arquivo binário diretamente na VRAM. Basta apenas ajustar o endereço agora. A tabela de padrões na SCREEN 1 é armazenada no endereço &H0000, então basta colocar um offset no bload pra carregar no lugar certo. Como endereços para a VRAM, em SCREEN 1, são ajustados para a faixa &H0000-&H3FFF, basta colocar um offset negativo de &H1200 para deslocar o arquivo para o início da VRAM.


Problema

Como tirar o "Ok" do prompt do BASIC?

Solução

Em MSX 2 isso é muito fácil, basta usar o comando SET PROMPT. No MSX 1 é necessário usar um truque um pouco mais avançado. Os comandos abaixo devem ser digitados em uma única linha:

A=&HFF07:POKEA,&H21:POKEA+1,&H34:POKEA+2,&h41:POKEA+3,&HE3

O funcionamento é simples, isso altera o hook de sistema HREAD, chamado exatamente antes da impressão do "Ok". Tudo que a rotina faz é alterar o endereço de retorno do hook para um ponto após a impressão.


Problema

Como chamar um programa DOS a partir do BASIC?

Solução

A maneira mais simples é utilizando o buffer de teclado. A rotina abaixo chama o DOS e executa o programa definido pela variável A$:

10 A$="DIR":GOTO1000

1000 VDP(1)=VDP(1)AND&HDF:DEFUSR=&H156:A=USR(0)
1010 B=65536!+&HFBF0:A$=A$+CHR$(13):FORI=1TOLEN(A$):POKEB+I-1,ASC(MID$(A$,I,1)):NEXT
1020 A=&HF3F8:POKEA+3,&HFB:POKEA+2,&HF0:B=B+LEN(A$)
1030 C=INT(B/256):POKEA+1,C:POKEA,B-C*256:VDP(1)=VDP(1)OR&H20:CALLSYSTEM

Rotinas tradicionais de inserção no buffer do teclado costumam ser instáveis, se o usuário apertar alguma coisa antes ou durante a rotina, ela não funciona. Para garantir a confiabilidade, a versão apresentada aqui desliga as interrupções (através do comando VDP) e limpa o buffer do teclado antes de preenchê-lo (com o comando USR).

Se você estiver utilizando DOS 2, existe uma solução bem mais simples:

CALL SYSTEM ("PROGRAMA.COM")

Assembly

Problema

Como dividir um número por 9 rapidamente?

Solução

Esse tipo de problema ocorre quando você está implementando leitura de setores em disquetes. Apesar dos setores serem numerados de 0 a 1440, o acesso direto a eles deve ser feito utilizando trilhas. Para saber o número da trilha correspondente a um setor, basta dividir o numero do setor por 9 (e eventualmente corrigir o valor por 2 se o disco em questão for dupla face).

Entretanto, o Z80 não possui divisão por hardware, por isso classicamente usa-se uma rotina de deslocamento e subtração para conseguir o resultado, o que é bastante lento. Uma maneira mais inteligente de resolver o problema é utilizando a rotina abaixo:

        ; divisao por nove
        ; entra     HL = numero de 0 a 1440
        ; sai       A = HL/9
        ; destroi   HL,DE

DIV9:                           ; Z80  R800
        INC     HL              ;  7    1
        LD      D,H             ;  5    1
        LD      E,L             ;  5    1
        ADD     HL,HL           ;  12   1
        ADD     HL,HL           ;  12   1
        ADD     HL,HL           ;  12   1
        SBC     HL,DE           ;  17   2
        LD      E,0             ;  8    2
        LD      D,L             ;  5    1
        LD      A,H             ;  5    1
        ADD     HL,HL           ;  12   1
        ADD     HL,HL           ;  12   1
        ADD     HL,DE           ;  12   1
        ADC     A,E             ;  5    1
        XOR     H               ;  5    1
        AND     03FH            ;  8    2
        XOR     H               ;  5    1
        RLCA                    ;  5    1
        RLCA                    ;  5    1
        RET            ; total  = 157  22

Embora parece misteriosa uma rotina que divide por 9 sem usar nenhum loop, o princípio de funcionamento é bastante simples. Ao invés de dividir por 9, o número é multiplicado por 284/256, que é aproximadamente igual a 10/9. Utilizando sabiamente as instruções de bit do Z80, a unidade é subtraída do resultado final, chegando ao valor desejado de 1/9. É claro que a precisão não é total, a rotina falha para números grandes, mas ela funciona perfeitamente, sem nenhum erro, na faixa de 0 a 1440. E nosso problema era exatamente a divisão nessa faixa!


Problema

Afinal, pra que serve a instrução DAA?

Solução

DAA faz ajuste decimal do acumulador, útil pra quem está fazendo código em BCD. Por exemplo, imagine que A contém 29h e B contém 31h. Olhe agora o resultado dos códigos abaixo:

ADD A,B		A=5Ah
ADD A,B / DAA	A=60h

O DAA logo após uma operação aritmética conserta o resultado, de forma que você pode pensar no registro A como dois dígitos decimais distintos, ao invés de um único número binário. Daí 1+9=0 e vai um, 2+3+vai um=6 e o resultado dá 60h (ao invés de 5Ah que é a soma direta). Isso é muito prático pra fazer placar e manter resultados que vão ser impressos na tela, evita ter que fazer rotina de conversão binário->decimal, que são chatas e lentas.

É interessante notar que o Z80 é mais prático que o x86 nesse aspecto. Os flags do Z80 marcam qual foi a última operação realizada, então você não precisa ter várias instruções DAA otimizadas pra cada tipo de operação, como ocorre no x86 (que tem aaa, aas, aam e aad, respectivamente pra adição, subtração, multiplicação e divisão).


Problema

Qual a diferença entre os flags Carry e Overflow?

Solução

Este é, de fato, um ponto sutil do assembly Z80. Antes de ver o flag de Overflow, vale a pena revisar o Carry com cuidado. O flag de Carry é sempre equivalente ao vai-um da aritmética. Um uso direto dessa propriedade é pra conseguir variáveis longas no Z80. Por exemplo, podemos juntar H e L, de 8 bits cada, pra fazer HL, de 16 bits, nativamente. Mas e se precisarmos de 24 bits? Uma saída é juntar mais um registrador. Por exemplo, podemos fazer AHL ou BDE como registradores de 24 bits. Nesse caso, as operações de soma e subtração podem ser criadas usando o carry:

; soma BDE a AHL 
ADD HL,DE
ADC A,B

; subtrai BDE de AHL
OR  A
SBC HL,DE
SBC A,B

Note que não existe a instrução SUB HL,DE; por isso temos que usar SBC HL,DE em seu lugar, tomando o cuidado de zerar o carry antes com a instrução OR A.

O que é importante notar nesse estágio é que as instruções equivalentes de 24 bits que montamos valem tanto para números sem sinal, quanto para números com sinal, em complemento de dois (faça o teste e verifique você mesmo em um debugger).

Outro fato notável é que o Carry pode ser utilizado para fazer comparações entre números. Por exemplo, se você quiser saber se B é maior que A, basta fazer SUB B, e o resultado vem no Carry. A idéia é simples, a subtração A-B, quando B é maior que A, resulta um número negativo, cuja representação em complemento de dois seria um número formado de infinitos bits 1. Como infinitos bits 1 não cabem no acumulador, "vai-um" e o carry é setado.

Aqui vem o problema: o raciocínio acima só é válido para números sem sinal. Se usarmos a idéia de comparar pelo Carry com números negativos o resultado estará errado. Confira:

; assuma que A=-3 (0FDh) e B=-2 (0FEh)
SUB B

; após o SUB, A=FFh (-1) e o Carry=1

Veja como o resultado da subtração deu correto, já que (-3)-(-2)=(-1), mas a comparação deu errada: (-2) é maior que (-3), então o Carry deveria estar resetado, mas a operação setou o flag!

Pra isso o Z80 tem o flag Overflow. Esse flag é setado quando uma operação com sinal não coube no registrador. Dessa maneira, ele é equivalente a uma comparação, se as origens forem consideradas com sinal:

; assuma que A=-3 (0FDh) e B=-2 (0FEh)
SUB B

; após o SUB, A=FFh (-1) e o Overflow=0

Logo, de maneira sucinta, você deve usar o Carry quando estiver fazendo comparações sem sinal, e deve usar o Overflow quando estiver comparando com sinal.


Problema

Qual assembler você recomenda para programar o MSX?

Solução

Para os iniciantes, Mega-Assembler e RSCII são os recomendados. Mas, dos níveis intermediários pra cima, o M80 é muito mais versátil.

Por exemplo, quem programa com freqüência, sabe que o Z80 não tem rotação de nibble, então você precisa fazer isso na unha quando é preciso:

RLCA
RLCA
RLCA
RLCA

Chato de digitar, não? Com o M80 é mais fácil:

REPT 4
  RLCA
ENDM

Além disso, se você tiver que usar rotações de tamanhos diferentes ao longo do código, pode automatizar o processo. Primeiro você define uma macro:

RLCA_COMBO MACRO N
  REPT N
    RLCA
  ENDM
ENDM

Depois é só invocar a macro! O exemplo abaixo faz o mesmo que o exemplo anterior:

RLCA_COMBO 4

E melhor ainda, você pode fazer montagem condicional. Por exemplo, rotacionar 7 vezes pra esquerda é o mesmo que rotacionar uma vez pra direita. Usando o M80 de maneira esperta, você não precisa se preocupar com isso:

RLCA_SMART MACRO N
  IF (N AND 7) LT 5
    REPT (N AND 7)
      RLCA
    ENDM
  ELSE
    REPT 8-(N AND 7)
      RRCA
    ENDM
  ENDIF
ENDM

Desse jeito, RLCA_SMART 4 retorna o mesmo que os outros exemplos, mas RLCA_SMART 7 retorna apenas RRCA! Mais legal ainda, se você fizer RLCA_SMART 8, ele corretamente não retorna nada, já que rotacionar 8 vezes é o mesmo que não fazer nada.

< Voltar para o blog do Ricbit

< Voltar para o Mundo Bizarro
Autor: Ricardo Bittencourt
Data: 2002.10.9
Copyright © 2002 Ricardo Bittencourt