Portando o ‘aclock’ para MSX-DOS

aclock-1_abertura

O aclock é um programa cuja função é a de transformar um outrora caríssimo mainframe, estação de trabalho ou microcomputador em um relógio de parede! Ele já foi compilado e executado em cerca de 250 plataformas diferentes de computadores e agora é o momento de acrescentar o MSX neste grupo. 🙂

Esta versão foi baseada no código original de CP/M Plus para o ZX Spectrum +3 e é compilada no Small Device C Compiler — com uma ajuda do back-end para MSX-DOS desenvolvido pelo Avelino Herrera — e pode ser executada em computadores MSX2, MSX2+, MSX turbo R rodando qualquer versão do MSX-DOS.

Óbvio que ele não funcionará em computadores MSX1 ou modelos convertidos para MSX2 ou MSX2+ que não possuam o circuito de relógio.

TL;DR O programa funcionando!

Aqui está o vídeo do programa rodando em um Panasonic FS-A1ST com turbo ativado (modo R800) e modo texto de 80 colunas em SCREEN 0. O código fonte, binário e imagem de disco podem ser diretamente baixados no fork que fiz do repositório do programa no GitHub.

Por que não usar o MSX-C?

O MSX-C é o compilador C oficial da arquitetura MSX, foi lançado pela ASCII Corporation no ano de 1988, inclui por padrão as bibliotecas necessárias para acessar as rotinas das BIOS, BDOS, Mathpack¹ etc e seria a escolha óbvia para a tarefa. mas tive alguns problemas utilizando-o, o primeiro deles foi a sintaxe:

Ele não parece ser ANSI C (ou já deixou de ser há um bom tempo), o ‘VOID’ precisa estar em letras maiúsculas e a declaração das funções segue a sintaxe que estava em voga lá pelo meio da década de 1980 com tipagem das variáveis em uma linha separada².

Por ser um binário de MSX-DOS a compilação precisa ser feita dentro do emulador (com todos os passos necessários para tal incluídos) e o comportamento errático de funções como locate() — da biblioteca “msxbios.h” — e printf() me fizeram desistir dele e optar pelo SDCC.

(¹) As rotinas aritméticas que fazem parte do interpretador MSX-BASIC.

(²) Esta sintaxe para as funções ainda é suportada na versão 7.x do GNU C.

Ajustando o código original

A versão do ZX Spectrum, foi escrita para utilizar o Hitech-C e compilou com alterações mínimas no SDCC — basicamente a remoção de duas funções específicas do speccy — mas a performance ficou “sofrível”.

aclock-1_primeira_versao

E a rotina de leitura do relógio indicando eternamente 6 horas, 28 minutos e 15 segundos, ou seja, algumas coisas precisavam ser melhoradas, corrigidas ou mesmo implementadas.

Tabelas para seno e cosseno

Para desenhar o mostrador do relógio e os ponteiros na tela não é necessário saber como calcular os valores possíveis de seno e cosseno para toda a circunferência, assim optei por armazenar todo os 60 valores necessários para seno e cosseno em tabelas — os arrays sin_t[] e cos_t[] — e com uma pequena reorganização na ordem correspondessem, respectivamente, aos valores da circunferência para as respectivas posições 0 até 59 do mostrador.

Modos de texto suportados pelo MSX-DOS

O código original do aclock ajusta tamanho e posição do relógio de acordo com as dimensões da tela e até compensa a largura dos caracteres para fazer com que seja impresso um círculo ao invés de uma elipse. No caso específico do CP/M Plus é implementado por software no ZX Spectrum +3 um “modo texto” de 51 colunas (matriz de 5×8) e este ajuste está fixo no código como 1,5.

Em compensação, o MSX-DOS trabalha em modos de 32, 40 ou 80 colunas com 24 linhas de texto em ASCII e, em modelos equipados com Kanji-ROM acrescentam-se modos com 64 colunas e também com 13 linhas e implementadas por software exibindo ideogramas na tela em codificação Shift_JIS.

Estes aqui são todos os modos de texto exibidos por um MSX turbo R:

aclock-1_modos_texto_do_msx

Na ordem: 32×24 (SCREEN 1); 40×24 e 80×24 (SCREEN 0); 32×13 e 64×13 (KANJI0); 40×13 e 80×13 (KANJI1); 32×24 e 64×23 (KANJI2); 40×24 e 80×24 (KANJI3) — os modos KANJI2 e KANJI3 operam em modo entrelaçado.

Dimensões da tela

As dimensões da tela são obtidas através da leitura das variáveis de ambiente LINLEN — endereço 0xf3b0 — e CRTCNT — endereço 0xf3b1 — da BIOS. E com estas informações o programa consegue ajustar corretamente a posição dos textos na tela e também as dimensões do relógio.

E para compensar a largura do mostrador defini a regra de multiplicar por 2 para modos acima de 64 colunas, por 1,5 para mais que 40 colunas e por 1 no caso dos demais.

Acelerando a escrita na tela

A impressão na tela através das rotinas da BDOS é lenta visto que ela não escreve diretamente na VRAM e sim faz uma chamada entre slots da rotinas da BIOS para tal. Como não estava disposto a reimplementar o printf() e considerando a necessidade de manter a compatibilidade com os diversos modos de vídeo, resolvi encarar o problema de duas formas:

  1. Deixar no mostrador apenas os indicadores de horas e
  2. Criar dois ponteiros para as variáveis de ambiente CSRY — endereço 0xf3dc — e CSRX — endereço 0xf3dd — da BIOS e alterar a posição do cursor diretamente por lá.

Seria possível acelerar mais ainda mas para mantê-lo compatível com os modos de texto ASCII e Shift_JIS preferi parar neste ponto.

Acessando o relógio

Lembrando que as rotinas da biblioteca “time.h” do SDCC não estavam recuperando corretamente a hora do circuito de relógio do MSX. E aqui havia duas³ abordagens, a primeira acessando a rotina REDCLK (0x01f5) da sub-ROM e uma outra, bem mais simples, acessando a rotina GetTime (0x2c) da BDOS.

Mas em ambos os casos precisaria saber como chamar uma rotina em assembly de dentro do programa em C e também como recuperar a informação retornada por ela. A primeira parte foi “fácil”, bastando observar como o Avelino Herrera fez com as rotinas que ele escreveu.

Mas como devolver os valores lidos de volta para o C? A solução encontrada foi escrever pequenos programas com funções  enviando e recebendo parâmetros, compilar e estudar como era feito no código assembly Z80 gerado pelo SDCC e descobrir o que acontecia.

O “brinde” foi ter de aprender a sintaxe o sdasz80, o assembler de Z80 utilizado pelo SDCC, para saber que «ld c,$2c» e «ld (iy+0),h» deveriam, respectivamente, ser escritos como «ld c,#0x2c» e «ld 0 (iy),h».

Minha única gambiarra nesta implementação foi a necessidade de criar uma string de 4 posições chamada dummy que funciona como área temporária para armazenar a hora lida da BDOS antes de retorná-la para o programa.

(³) Na verdade três abordagens: implementar corretamente o suporte ao relógio do MSX no SDCC.

E para concluir…

aclock-1_final

Admito que C é uma linguagem de programação que conheço pouco mas converter o código, como toda a pesquisa que acabou sendo necessária, acabou servindo para aprender muito sobre a estrutura e sintaxe dela, além de entender um pouco mais sobre o processo de compilação (e funcionamento dos compiladores).

E, sim, foi bastante divertido, apesar de que em alguns momentos quase desisti para reescrever o programa todo logo em assembly! 😀

Anúncios

2 comentários sobre “Portando o ‘aclock’ para MSX-DOS

  1. Pingback: Quinta do Pitaco: Querendo programar em C para MSX mas não sabe por onde começar? | Retrocomputaria

  2. Pingback: Biblioteca do SDCC para o TMS9918A. | Retrocomputaria

Os comentários estão desativados.