ROM melhorada: Viabilizando INKEY$ e revivendo fóssil
Autor: Emerson José Silveira da Costa | Publicado: 2016-04-18T11:33:00-03:00

Estes tempos eu e outros andamos pensando em ideias para uma futura ROM melhorada para o MC-1000 que vá além da correção dos bugs da ROM original da CCE.

Um notório inconveniente do micro é o fato de o interpretador BASIC ficar em espera ocupada até que o usuário libere alguma tecla pressionada — o que inviabiliza joguinhos de ação em BASIC. Há algum tempo desenvolvi uma rotina em código de máquina que, adicionada a um programa BASIC, permite driblar o problema:




A melhora da ROM é a oportunidade para atacar o problema de modo mais limpo e eficaz.

Antes de mais nada, eis o funcionamento geral das rotinas de leitura de teclado disponíveis na ROM:

SKEY? — Se alguma tecla estiver sendo pressionada no momento, armazena o código da tecla na variável do sistema KEY0 e retorna $FF no registrador A. Senão, retorna $00.
KEY? — Faz duas chamadas a SKEY? com 7ms de intervalo entre elas. Se a mesma tecla for detectada nas duas leituras, armazena o código da tecla em KEY0+1 e retorna $FF no registrador A e na variável do sistema KEYSW. Senão, retorna $00.
KEY — Faz chamadas a KEY? até que KEYSW seja $FF (tecla pressionada); então faz chamadas a KEY? até que KEYSW seja $00 (tecla liberada). Finalmente a rotina retorna o código da tecla armazenado em KEY0+1 no registrador A.

Como se vê, o controle só retorna de KEY após a tecla pressionada ser liberada.

Isto posto, vamos ao código do interpretador BASIC. O interpretador precisa checar o teclado ao fim da execução de cada instrução para ver se o usuário pressionou CTRL+S para pausar o programa (esperando até que o usuário pressione qualquer tecla para continuar) ou CTRL+C para interromper a execução e voltar ao modo direto.

Após o fim de uma instrução temos:

          org     $dd5a

          call    KEY? ; Lê teclado. (¹)
          or      a ; Alguma tecla está sendo pressionada?
          call    nz,TrataTeclaAposInstrucao ; Sim: Trata.


Quando do uma tecla é pressionada, o controle desvia para o trecho a seguir. Começa com uma chamada a KEY — consequentemente o interpretador vai parar enquanto o usuário não soltar a tecla. Então ele checa se usuário pressionou CTRL+S ou CTRL+C e, caso positivo, executa as operações correspondentes.

          org     $dddc

TrataTeclaAposInstrucao:
          call    KEY ; Espera o usuário soltar a tecla. (²)
          cp      $13 ; É CTRL+S?
          jp      nz,TrataTeclaAposInstrucao_1 ; Não: Próximo teste.
          [...]
          call    KEY ; Espera usuário pressionar e soltar alguma tecla.
          [...]
TrataTeclaAposInstrucao_1:
          cp      $03 ; É CTRL+C?
          jr      z,ExecSTOP ; Sim: Executa instrução STOP.


Esperar o usuário soltar o teclado é realmente necessário, mas apenas quando se tratar das combinações CTRL+S e CTRL+C. Em todos os outros casos, o desejável é que o interpretador continue a execução como se nada tivesse acontecido.

A solução que desenvolvi consiste em criar (no espaço livre no fim da ROM) rotinas que fazem testes e preparações em torno de KEY? e KEY:

XKEY?:
          xor     a
          ld      (KEYSW),a ; Zera KEYSW.
          jp      KEY?

XKEY:
          ld      a,(KEY0+1) ; Código da última tecla lida com KEY?.
          cp      $13 ; É CTRL+S?
          jp      z,KEY ; Sim: Espera o usuário soltar a tecla.
          cp      $03 ; É CTRL+C?
          jp      z,KEY ; Sim: Espera o usuário soltar a tecla.
          ret


XKEY? é necessária por uma questão de construção interna de KEY?. Se ela for chamada e KEYSW já contiver um valor $FF, ela retorna sem fazer nada, o que não nos interessa.

XKEY só chama KEY nos casos que realmente interessam, isto é, se o usuário tiver pressionado CTRL+S ou CTRL+C.

Substituindo as chamadas a KEY? e KEY indicadas por (¹) e (²) na listagem acima por XKEY? e XKEY, respectivamente, o interpretador BASIC consegue continuar rodando enquanto o usuário estiver teclando qualquer outra coisa.

(Na verdade, para que a coisa fique completa, é preciso substituir também uma chamada a KEY? em $DDD7, que faz parte do código da instrução LIST, que também pode ser pausada ou interrompida com CTRL+S e CTRL+C.)

A variável de sistema&nbsp;KEYSW&nbsp;originalmente era algo interno ao funcionamento de KEY? e KEY. Para o programa BASIC, ela sempre era visível apenas com o valor 0 (tecla não pressionada). A modificação proposta torna KEYSW disponível para o programa BASIC também. Independentemente de outras modificações na ROM, uma leitura de teclado com INKEY$ passa a poder ser implementada assim:


T$ = "": IF  PEEK (302) THEN T$ =  CHR$ ( PEEK (284))


E que história é essa de "revivendo fóssil" no título da postagem?

O curioso é que em KEY, logo depois da verificação de se o usuário teclou CTRL+C, encontramos este código:

          cp      ' ' ; É espaço?
          ret     nz ; Não, retorna.
          ; Usuário pressionou tecla SPACE.
          ld      a,($0360) ; Flag de SLOW.
          or      a ; Está ativado?
          ret     nz ; Sim, retorna.
          ; Usuário pressionou tecla SPACE em modo FAST.
          push    bc
          ld      bc,128
          call    DELAYB ; Produz pausa de BC milisegundos.
          pop     bc
          ret


O par de comandos SLOW e FAST é uma das funcionalidades do MC-1000 (parcialmente) inviabilizadas por conta de bugs. O comando SLOW ativa a inserção de uma pequena pausa de 128ms após cada instrução, ou após cada linha listada pelo LIST.

Nos micros Sinclair ZX81 há um par de comandos com os mesmos nomes. Ali, o vídeo é gerado pela UCP Z80, que precisa dedicar boa parte de seu tempo de processamento a essa tarefa. O modo SLOW é o modo padrão. Entrar em modo FAST faz com que o Z80 pare de gerar o vídeo (a tela passa a exibir ruído branco), podendo então dedicar todo seu tempo ao processamento.

Esse não é o caso do MC-1000. O vídeo é gerado pelo co-processador de vídeo MC6847. O modo padrão de operação é o FAST. Para que ter um comando SLOW que apenas deixa o BASIC mais lento? Bem, em conjunção com o comando TRO&nbsp;N (trace on) que exibe o número de linha atual sendo executada, uma pequena pausa entre as instruções pode ser útil quando o usuário quer depurar um programa, e tem mais chance de pausá-lo com CTRL+S ou interrompê-lo com CTRL+C no momento mais adequado. O mesmo se pode dizer da listagem de um programa, que geralmente passa muito rápido pela tela.

O que o código acima faz é dar ao usuário a chance de gerar a pausa mesmo em modo FAST, pressionando a tecla &lt;SPACE&gt;.

Legal, interessante... Mas espera aí! Por causa do jeito como o interpretador BASIC lê o teclado, quando o usuário pressiona &lt;SPACE&gt; ele fica parado esperando até o usuário soltar a tecla. E quando o usuário a solta, e só nesse momento, a pausa de 128ms é gerada. Isso é completamente inútil! Com o interpretador BASIC parado pela pressão da tecla eu já tive todo o tempo para olhar o que eu quisesse na tela!

Essa funcionalidade só faz sentido quando o interpretador BASIC não fica parado esperando a liberação de uma tecla. O que isso está fazendo ali? Será que é um fóssil de algo que a princípio funcionava e que depois foi inutilizado por alguma mudança posterior nas rotinas de teclado ou do interpretador? Será que tentaram introduzir a funcionalidade depois, sem levar em conta que as rotinas de teclado não permitiram que ela funcionasse?

Seja como for, a eliminação da espera de brinde faz reviver essa funcionalidade que estava fossilizada na ROM.