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")
|
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:
Chato de digitar, não? Com o M80 é mais fácil:
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:
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.