Leitura de teclado

O BASIC do MC1000 não tinha a função INKEY$, existente em outros microcomputadores, que verifica se há alguma tecla pressionada no momento e retorna o caracter correspondente, ou uma cadeia vazia ("") se não houver nenhuma tecla pressionada. Essa função é imprescindível para jogos de ação.

Mas mesmo que tivesse, o MC1000 tem uma característica que é um grande obstáculo à programação de jogos de ação em BASIC: o interpretador BASIC fica paralisado enquanto alguma tecla estiver pressionada! :-O

De todo modo, existe uma variável do sistema que armazena o código ASCII da última tecla pressionada: o endereço 283 (KEY0).

Não é a mesma coisa que um INKEY$, já que, por exemplo, não se pode saber se a pessoa apertou a mesma tecla mais de uma vez... mas pelo menos é melhor que a instrução INPUT! ¦-)

Programinha-brinde: Move um caracter "#" pela tela usando as teclas <W> (cima), <S> (baixo), <A> (esquerda) e <D> (direita).

10  HOME 20 C = 15:L = 7 30 T$ = CHR$ (PEEK (283)) 40 CC = (C + ABS (T$ = "D") - ABS (T$ = "A")) AND 31 50 LL = (L + ABS (T$ = "S") - ABS (T$ = "W")) AND 15 60  PRINT  CHR$ (27);"="; CHR$ (L); CHR$ (C + 32);" "; 70  PRINT  CHR$ (27);"="; CHR$ (LL); CHR$ (CC + 32);"#"; CHR$ (8); 80  POKE 910,0 90 C = CC : L = LL 100  GOTO 30

Solução para permitir que a execução do BASIC continue mesmo com uma tecla pressionada

(Desenvolvida por Ensjo em 19 de outubro de 2011.)

O MC1000 tem três rotinas na ROM relacionadas com a leitura do teclado: SKEY?, KEY? e KEY.

A causa do MC1000 travar quando uma tecla é pressionada é um CALL KEY no endereço $DDDC, em meio à interpretação do programa BASIC:

... DD5A call KEY?        ; Alguma tecla pressionada? DD5D or a DD5E call nz, $dddc   ; Sim: capturar e tratar. DD61 ... ... DDDC call KEY         ; Captura a tecla e espera ser liberada. DDDF cp $13           ; É CTRL+S? DDE1 jp nz, $de0b     ; (etc.) ...

A ideia é dar um jeito de impedir que essa chamada seja feita.

A rotina KEY? chama a rotina SKEY?, que por sua vez chama dois hooks em RAM:

Vamos então usar o hook JOB. Quando ele é atingido a partir da instrução CALL KEY? no endereço $DD5A, a pilha do Z80 contém:

O que vamos fazer é examinar o conteúdo de SP+14 e SP+15 e ver se temos lá o endereço $DD5D. Se for este o caso, sabemos que estamos no ponto "problemático" da ROM, perto da rotina que vai "congelar" o BASIC até a liberação da tecla pressionada. Então vamos modificar o conteúdo de SP+8 e SP+9 para que o controle não volte para KEY? ao final de SKEY?, mas vá para uma rotina própria que vai decidir o que fazer. Se o usuário não estiver pressionando tecla nenhuma, o controle deverá retornar para $DD61. Se o usuário estiver apertando alguma tecla, colocamos seu código no registrador A e chamamos a rotina $DDDF (pulando a famigerada instrução CALL KEY) antes de retornar o controle para $DD61.

A rotina foi projetada para ser POKEada em uma linha REM, que deve ser a primeira do programa (vide abaixo).

key0:   equ $011b job:    equ $0120 resto:  equ $dd61 teclap: equ $dddf           org $03da   init: ; Direciona o hook JOB (chamado em SKEY?) para a rotina "checa".         ld hl,checa         ld (job+1),hl         ld a,$c3         ld (job),a         ret   checa: ; Verifica se estamos em meio ao CALL KEY? feito em #DD5A.         push ix         ld ix,$ffff ; Estas duas linhas substituem ld ix,$0000.         inc ix      ; Não pode haver byte $00 na linha REM.         add ix,sp         ld a,$5d         cp (ix+16)         jr nz,sai         ld a,$dd         cp (ix+17)         jr nz,sai ; Sim: Modifica a pilha do Z80 para que, ao final da ; rotina SKEY?, o controle não volte para KEY?, mas ; para nossa rotina personalizada "desvia".         push hl         ld hl,desvia         ld (ix+10),l         ld (ix+11),h         pop hl sai:         pop ix         ret   desvia: ; Restaura pilha e registradores como estariam ; ao final da execução de KEY?, e desvia ; para os pontos apropriados do interpretador ; BASIC, evitando o CALL KEY em #DDDC.         pop bc ; Desempilha valores empilhados por KEY?         pop de           inc sp ; Descarta o ponto de retorno original.         inc sp           ld (key0+2),a ; FUNCIONALIDADE ADICIONAL:                       ; Salva em KEY0 + 2 a informação sobre se                       ; uma tecla foi pressionada (#FF) ou não (#00).         or a           ld a,(key0)   ; Copia código da tecla em KEY0 + 1.         ld (key0+1),a ; (É algo que a rotina KEY? faz.)           call nz,teclap ; Se tecla pressionada, chama rotina                        ; de tratamento situada logo depois a problemática                        ; instrução CALL KEY.         jp resto       ; Retorna para o interpretador BASIC.

Eis abaixo o início de um programa em BASIC. As linhas de 1 a 6 "instalam" o programa acima em uma linha REM, que deve ser a primeira do programa e ter 70 caracteres. A linha 7 ativa o programa.

0  REM --------10--------20--------30--------40--------50--------60--------70 1  DATA 33,230,3,34,33,1 2  DATA 62,195,50,32,1,201,221,229,221,33,255,255,221,35,221,57 3  DATA 62,93,221,190,16,32,18,62,221,221,190,17,32,11,229,33 4  DATA 12,4,221,117,10,221,116,11,225,221,225,201,193,209,51,51 5  DATA 50,29,1,183,58,27,1,50,28,1,196,223,221,195,97,221 6  FOR I = 0 TO 69: READ B: POKE 986 + I,B: NEXT I 7  CALL 986

As linhas de 1 a 6 podem até ser apagadas depois da primeira execução, pois então o programa em código de máquina já estará instalado na linha REM.

Pode-se também apenas digitar a linha 0 com 70 caracteres e a instrução CALL 986, e adicionar o programa em código de máquina manualmente via DEBUG:

03DA 21 E6 03 22 21 01 03E0 3E C3 32 20 01 C9 DD E5 DD 21 FF FF DD 23 DD 39 03F0 3E 5D DD BE 10 20 12 3E DD DD BE 11 20 0B E5 21 0400 0C 04 DD 75 0A DD 74 0B E1 DD E1 C9 C1 D1 33 33 0410 32 1D 01 B7 3A 1B 01 32 1C 01 C4 DF DD C3 61 DD

Uma funcionalidade adicional desta rotina é salvar na posição de memória KEY0+2 ($011D / 285) a indicação sobre se uma tecla está sendo pressionada ou não. Se não estiver, PEEK(285) retorna 0; se estiver, retorna 255.

Agora a função INKEY$ dos outros micros pode ser implementada assim:

I$ = "": IF PEEK (285) THEN I$ =  CHR$ ( PEEK (283))

Vamos reescrever o programa no início desta página. Digita as linhas 0 a 7 e acrescenta:

... 10  HOME 20 C = 15:L = 7 30 T$ = "": IF  PEEK (285) THEN T$ =  CHR$ ( PEEK (283)) 40 CC = (C + ABS (T$ = "D") - ABS (T$ = "A")) AND 31 50 LL = (L + ABS (T$ = "S") - ABS (T$ = "W")) AND 15 60  PRINT  CHR$ (27);"="; CHR$ (L); CHR$ (C + 32);" "; 70  PRINT  CHR$ (27);"="; CHR$ (LL); CHR$ (CC + 32);"#"; CHR$ (8); 80  POKE 910,0 90 C = CC : L = LL 100  GOTO 30

Executa ambos os programas e sente a diferença.

(Nova versão desenvolvida em 2 de julho de 2015.)

Esta é uma versão da rotina para se localizar na parte alta da VRAM, na área de 256 bytes que o MC1000 reserva para o usuário no final da RAM.

key0:   equ $011b keysw:  equ $012e resto:  equ $dd61 teclap: equ $dddf  ; Para ativar: Configurar hook JOB ($0120) com um jump para "checa".          .org $3f00   checa: ; Verifica se estamos em meio ao CALL KEY? feito em #DD5A.         ld hl,+14         add hl,sp         ld a,(hl)         cp $5d         ret nz         inc hl         ld a,(hl)         cp $dd         ret nz ; Sim: Modifica a pilha do Z80 para que, ao final da ; rotina SKEY?, o controle não volte para KEY?, mas ; para nossa rotina personalizada "desvia". ld hl,+8         add hl,sp         ld de,desvia         ld (hl),e         inc hl         ld (hl),d         ret  desvia: ; Restaura pilha e registradores como estariam ; ao final da execução de KEY?, e desvia ; para os pontos apropriados do interpretador ; BASIC, evitando o CALL KEY em #DDDC.         pop bc ; Desempilha valores empilhados por KEY?         pop de          inc sp ; Descarta o ponto de retorno original.         inc sp           ld (key0+2),a ; FUNCIONALIDADE ADICIONAL:                       ; Registra em KEY0+2 a informação sobre se                       ; uma tecla foi pressionada (#FF) ou não (#00).         or a           ld a,(key0)   ; Copia código da tecla em KEY0 + 1.         ld (key0+1),a ; (É algo que a rotina KEY? faz.)           call nz,teclap ; Se tecla pressionada, chama rotina                        ; de tratamento situada logo depois a problemática                        ; instrução CALL KEY.         jp resto       ; Retorna para o interpretador BASIC.

Eis um programa BASIC que insere a rotina na memória e o ativa:

1  RESTORE 2:A0 =  PEEK (914) + 256 *  PEEK (915) + 1:A1 = A0 + 18:A2 = A0 + 24: POKE 288,201: FOR A = A0 TO A0 + 43: READ B: POKE A,B: NEXT : POKE A1,A2 AND 255: POKE A1 + 1,A2 / 256: POKE 289,A0 AND 255: POKE 290,A0 / 256: POKE 288,195 2  DATA 33,14,,57,126,254,93,192,35,126,254,221,192,33,8,,57,17,24,63,115,35,114,201,193,209,51,51,50,29,1,183,58,27,1,50,28,1,196,223,221,195,97,221

A variável A0 calcula o início da área do usuário no topo da RAM, que varia conforme a memória do micro (por padrão, $3F00 / 16128 nas máquinas com 16KiB, e $BF00 / 48896 nas máquinas com 64KiB) e também pode ser redefinido pela instrução CLEAR.

Alternativamente, pode-se inserir a rotina em uma primeira linha REM do programa BASIC com 44 caracteres, substituindo o cálculo de A0 por A0 =  PEEK (841) + 256 *  PEEK (842) + 5:

0  REM --------10--------20--------30--------40--44 1  RESTORE 2:A0 =  PEEK (841) + 256 *  PEEK (842) + 5:A1 = A0 + 18:A2 = A0 + 24: POKE 288,201: FOR A = A0 TO A0 + 43: READ B: POKE A,B: NEXT : POKE A1,A2 AND 255: POKE A1 + 1,A2 / 256: POKE 289,A0 AND 255: POKE 290,A0 / 256: POKE 288,195 2  DATA 33,14,,57,126,254,93,192,35,126,254,221,192,33,8,,57,17,24,63,115,35,114,201,193,209,51,51,50,29,1,183,58,27,1,50,28,1,196,223,221,195,97,221

Ainda outra possibilidade é colocá-la num espaço no início da RAM (a partir de $003b / 59).

1  RESTORE 2:A0 = 59:A1 = A0 + 18:A2 = A0 + 24: POKE 288,201: FOR A = A0 TO A0 + 43: READ B: POKE A,B: NEXT : POKE A1,A2 AND 255: POKE A1 + 1,A2 / 256: POKE 289,A0 AND 255: POKE 290,A0 / 256: POKE 288,195 2  DATA 33,14,,57,126,254,93,192,35,126,254,221,192,33,8,,57,17,24,63,115,35,114,201,193,209,51,51,50,29,1,183,58,27,1,50,28,1,196,223,221,195,97,221