terça-feira, 23 de maio de 2023

Relógio BCD para o TK85 no ZX Assembler da Artic

Um relógio BCD (binary-coded decimal) mostra os dígitos decimais em formato binário. Cada dígito decimal das horas, minutos e segundos está codificado em binário. Assim, cada coluna corresponde a um dígito do relógio (HH:MM:SS), mas em formato binário: quadrados preenchidos = 1, quadrados vazios = 0.

A leitura dos valores no caso do relógio que implementei se dá de baixo para cima. A primeira linha de bits tem valor 1, a segunda 2, a terceira 4 e a quarta 8. O valor resultante é a soma dos valores dos bits. Na figura abaixo, por exemplo, o bits da quinta coluna somam 5 e o da sexta coluna 9, indicando 59 segundos.

O relógio BCD com os valores de cada casa de bits e o horário: são 12h 46min e 59s.

Bom, foi isso que eu procurei implementar para a linha Sinclair ZX81. Já tinha feito um relógio BCD bem simples no BASIC do MSX numa manhã de domingo. Mas desta vez quis fazer algo na tela cheia, então fui de assembly para dar conta das limitações do TK85. Olha aí ele fornecendo as horas:



Para executar, digite RUN e entre com o horário. Confirme e pronto. Lá está o seu relógio BCD funcionado.

Durante o seu funcionamento, se você pressionar a tecla "D" (de decimal) as horas vão aparecer com dígitos normais no canto superior esquerdo da tecla. Tecle "D" novamente para que esse recurso desapareça. Pressionando a tecla "NEW LINE", o programa para e volta ao BASIC. É preciso de um pouco de paciência com esses comandos, já que a leitura do teclado só é feita na virada de cada segundo.

Segue o link para download do arquivo .p:

Download

Sobre o ZX Assembler da Artic

Para falar a verdade, a programação do relógio surgiu apenas como desculpa para  experimentar o ZX Assembler, da britânica Artic Computing Ltd. Queria ver como seria a programação em assembly utilizando uma ferramenta nativa. Eu procuro utilizar assemblers nativos em todas as máquinas que programo: GEN80 e Mega Assembler no MSX, Edtasm no TRS-80 e Edtasm+ para o TRS-Color. Mas no caso do ZX81, pensava que as limitações seriam torturantes demais e, por isso, estava utilizando o Pasmo para gerar o binário Z80 e o appmake (este do pacote z88dk) para gerar o arquivo ".p". Mais um pequeno bash script e o fluxo de trabalho fica simplesmente fantástico, gerando o arquivo .p no diretório do emulador e um arquivo texto (.lst) com a listagem dos endereços de montagem para referência, se necessário. E tudo no terminal, já que não uso ambiente gráfico para emulação no Raspberry Pi.

Mas sobre a impossibilidade prática de utilizar o micro real para programação assembly, eu estava enganado, pelo menos em parte. Para minha grata surpresa, depois de um "Hello World" no ZX Assembler, descobri que é sim possível trabalhar nativamente na máquina. Isto é, desde que não se utilize fita cassete como mídia (imagine carregar o ZX Assembler por 3:30 minutos, mais o  tempo de carregamento do programa fonte, toda vez que der um pau na execução do programa? Não dá). Então resolvi fazer algo para ver o quanto é viável utilizá-lo em projetos maiores. É verdade que, para isso, utilizei o emulador. Mas o fluxo de trabalho não seria muito diferente caso o usuário possua uma ZXpand ou um Blue Drive da vida. Eu aqui tenho um Blue Drive mas meu TK85, perfeitinho e original, não tem o mod de vídeo-composto que a ROM de 50Hz do Blue Drive exige, e a tela fica rolando no RF. Um dia eu resolvo essa história.

Obviamente, o ZX Assembler é bastante simples e limitado, mas ao mesmo tempo a forma que foi implementado é surpreendentemente inteligente. A primeira coisa que me chamou a atenção foi o editor: gostei muito por ser simples, limpo e direto. Diria é quase perfeito e que faltou apenas um par de comandos, page up e page down para navegar no texto mais rápido. Em lugar disso o programador pode lançar mão de uma busca por um rótulo para ir direto ao ponto do programa a ser editado. Mas no geral, excelente editor.

O montador em si é minimalista. Você dá o comando e se o montador não encontrar erros, não diz nada, apenas volta para o prompt. E como a montagem se dá na memória, é extremamente rápida. Se o programa encontrar um erro na montagem, sinaliza o tipo de erro e, se o usuário teclar "E" para voltar ao editor, a linha com erro já é mostrada. Simplicidade genial. 

A forma como o ZX Assembler contorna os problemas de design do ZX81 também é ótima. Pra quem não conhece, o código de máquina no ZX81 deve ficar guardado em uma linha REM, para poder ser salvo. Não há outra forma (simples) de salvar um programa em código de máquina. Assim, o ZX Assembler gera o código-objeto na linha 1 do BASIC, e o código-fonte assembly na linha 2. O programador pode então adicionar código em BASIC a partir da linha 3 em diante. Eventuais alterações nos códigos fonte e/ou objeto em assembly não afetam o conteúdo em BASIC já digitado, e vice-versa. E tudo pode ser salvo por um comando SAVE do BASIC. Ao finalizar, o código-fonte pode ser eliminado pela simples exclusão da linha 2, e lá vão estar a linha 1 REM com o código em linguagem de máquina e o código BASIC em seguida. Em compensação, por toda essa conveniência, não é possível montar o programa em endereço diferente de 16516.

Além do montador e do editor, há também algumas funções de monitor, como verificação e edição de endereços de memória, verificação e alteração de valores de registradores, cálculo de desvios relativos, etc; e a função de execução do programa com retorno ao monitor (o break-point é um JP específico que deve ser inserido manualmente no código-fonte).

Há ainda uma rotina utilitária de cópia de bloco de memória. Mas não há disassembler disponível no pacote. Sem problema. Seria exigir demais do programinha.

O montador é feito para ser minimista por boa razão:  a memória é um artigo de luxo para o ZX81. A configuração com expansão de memória mais comum, à época, era de 16kB. Assim, para deixar o maior espaço possível ao programador (códigos fonte e objeto + eventual código BASIC), o código do ZX Assembler deve ser o mais enxuto possível.

Isso reflete na falta de alguns recursos básicos que vemos em qualquer montador. Notadamente, o silêncio do montador ao gerar o código-objeto. Ele não lista o código-objeto com os respectivos endereços de montagem, não gera informações sobre o tamanho ocupado na memória nem sobre os endereços dos rótulos. Assim, o programador fica no escuro quanto a quantidade de memória utilizada e, principalmente, terá bastante dificuldade para calcular o endereço de uma eventual rotina no meio do código-objeto se quiser executá-la a partir do BASIC. O único endereço de execução fornecido é o do início do código-objeto, que consta no manual do ZX Assembler. Considero que o programa poderia pelo menos informar o endereço dos rótulos já que isso não demandaria grande espaço de memória.

Enfim, a conclusão é que o ZX Assembler pode ser usado tranquilamente para programas simples em lugar de ferramentas externas que estragam a experiência de imersão, ainda que seja por emulador. Mas como tudo está na memória (código-fonte, código-objeto, o assembler em si e mais, eventualmente, linhas em código BASIC), o espaço de memória fica bastante limitado.

Acrescento aqui informações que não constam do manual:

  • Os endereços 16514 e 16515 contêm o código 118 (76h), ou seja, NEW LINE. Provavelmente para evitar a listagem do código de máquina decodificado pelo comando LIST do BASIC, enchendo a tela com caracteres e comandos. É por isso que o programa em LM deve ser executado com RAND USR 16516;
  • O assembler reside acima da RAMTOP, setada em 25684. Em 30000 há um desvio (JP) para 30742, endereço efetivo do início de execução do programa. Mas o atalho RAND USR 3E4 (30000), fornecido no manual, é mais simples de lembrar e digitar;
  • Ao usuário fica disponível pouco menos de 9kB para código-objeto, código-fonte e linhas BASIC. O código-fonte é armazenado em modo texto, não há qualquer tipo de compressão ou pré-codificação dos mnemônicos.



segunda-feira, 5 de setembro de 2022

O Panfleto Pitfall! para Commodore64 no MSX

Inspirado por um colega do grupo MSX Brasil (Facebook), analisei com mais cuidado um vídeo postado no canal do Youtube "8-bit Show and Tell" sobre um panfleto publicado pela Actvision nos anos 80 contendo uma listagem de um programa para o Commodore 64. O programa é relativamente simples, mas como é assinado pelo próprio David Crane (autor de clássicos do Atari 2600 como Pitfall! e Decathlon), concordei com o colega que ele merecia uma versão para MSX.

 

Panfleto publicado pela Actvision em 1984

 

O programa do panfleto está em BASIC, mas tem uma certa quantidade de dados (linhas DATA) referentes à imagem do Harry na tela e também um pouco de código de máquina. Resulta na animação do personagem Pitfall Harry correndo olimpicamente pela tela, desparecendo no lado direito e reaparecendo no lado esquerdo. Ele para quando a tecla de espaço é pressionada.

A maior curiosidade talvez seja o fato de que mesmo que o programa BASIC seja parado ("break"), o personagem continua a correr pela tela. Isto porque existe uma rotina em linguagem de máquina ligada à interrupção do sistema.

 



Os sprites no MSX

Não foi muito difícil replicar o programa no MSX. Na realidade eu não desassemblei o programa original, apenas reproduzi seu comportamento no MSX. 

Entretanto, não sem algumas adaptações. O chip gráfico do Commodore 64 tem capacidade de produzir sprites multicoloridos, de até três cores, que é o caso daqueles usados no programa original. O MSX não tem essa capacidade, pois os sprites possuem apenas uma cor. A única maneira de contornar essa situação é sobrepondo sprites de cores variadas para obter o mesmo resultado.

Assim, os 6 sprites coloridos no Commodore 64 (5 representando a corrida e 1 do Harry parado) viraram 18 sprites no MSX. A tarefa de desenhá-los e de gerar os dados correspondentes foi bastante facilitada pelo aplicativo web chamado TinySprite, de autoria do Rafael Janone. Depois, no MSX, eu só tive o trabalho de converter os dados de hexadecimal para decimal e assim diminuir o tempo de carregamento dos dados.

Outro detalhe (que francamente não sei como funciona no Commodore 64) é que o Harry desaparece ao poucos do lado direito e reaparece, também aos poucos no lado esquerdo da tela. Ocorre que a resolução horizontal (eixo x) do MSX é de 256 pixels. No MSX-BASIC o efeito de um sprite aparecer gradualmente na margem esquerda da tela é obtido com valores negativos no eixo x do sprite, de modo que quando o valor 0 é atingido, o sprite já aparece por completo na margem esquerda. 

Entretanto, em linguagem de máquina o eixo x é representado por apenas um byte e, sendo assim, este pode assumir valores de 0 a 255, que são todos considerados como positivos pelo VDP. Para que o sprite inicie no eixo x antes do início da margem esquerda, é necessário setar o bit 7, chamado early clock, do quarto byte de atributos do sprite (que também contém sua cor). Isso irá localizar o sprite 32 pixels para dentro da margem esquerda. Não é o fim do mundo, mas esse procedimento demanda que o programador preveja manualmente quando o early clock deve ser setado ou zerado e, ainda, que se faça o devido ajuste no valor do eixo x no programa assembly.

A rotina de interrupção

Atrelei o programinha em assembly ao gancho do sistema (H.TIMI). Isso faz com que a animação do Harry seja atualizada 60 vezes por segundo no caso das máquinas PAL-M e NTSC (60Hz de frequência vertical) e 50 vezes por segundo no caso das máquinas PAL-G/PAL-N (50Hz). Como resultado, assim como no programa original, o personagem continua correndo pela tela mesmo com a interrupção do programa BASIC pelo usuário com um Control+Stop (break). 

Entretanto, uma diferença é que no caso do Commodore 64 o sprite pode ser programado para aparecer debaixo das letras que estão na tela. Já no MSX isso simplesmente não é possível, uma vez os planos dos sprites sempre sobrepõem o plano no qual os caracteres são impressos por definição do hardware (VDP). O Harry no MSX só pode correr por sobre o texto, nunca sob ele.

O que mais não ficou igual

Uma das coisas que mais me chamou atenção no vídeo do "8-bit Show and Tell" foi que o programa BASIC era interrompido e então o comando LIST era executado e o Harry continuava a correr sob a listagem do programa.

Infelizmente, não consegui fazer que esse efeito funcionasse a contento no MSX. No meu programa, tudo está bem quando comandos simples são executados, como um PRINT ou algo assim. Entretanto, se diversos comandos PRINT ou mesmo um único LIST é executado de modo que ocorra um scroll da tela, a memória de vídeo (VRAM) é afetada e coisas estranhas aparecem na tela. Os comandos CLS e SCREEN também não funcionam como deveriam, esculhambando a tela com lixo da VRAM.

Quebrei bastante a cabeça tentando evitar a corrupção da memória de vídeo na execução desses comandos enquanto o Harry corria pela tela e cheguei a conclusão que não há forma simples de evitar que as rotinas do BIOS utilizadas pelos comandos do MSX-BASIC conflitem com o programinha do Pitfall. É que essas rotinas acessam os registros do VDP de forma mais intensa e acabam colidindo como acesso do programa do Pitfall ao VDP e à VRAM (60 vezes por segundo) gerando lixo na memória de vídeo. 

Pela mesma razão, alterar as cores dos sprites do Harry enquanto ele está em movimento também acarretam a corrupção da memória de vídeo, estragando um pouco a brincadeira (update 20.09.22: modifiquei a rotina em linguagem de máquina e agora é muito raro que haja corrupção na memória de vídeo utilizando os pokes para alteração das cores do Harry).

Assim, eu me dei por satisfeito e deixei o programa como está (e no fim ponho a culpa no hardware... rs).

Para quem quiser tentar, as cores da roupa podem ser trocadas para azul, por exemplo, com POKE &HE0C9,4. É esse último valor "4" que contém o código da cor, que pode ser de 0 a 15. 

A cor do cabelo/cinto/sapatos e a cor da pele do Harry também podem ser mudados alterando-se o valor dos endereços &HE0C1 e &HE0C5 respectivamente, usando o mesmo critério descrito acima. Por exemplo, POKE &HE0C5,6:POKE &HEC1,10 deixa o Harry com pele "morena" e cabelos louros. 

O programa

Enquanto o programa BASIC estiver sendo executado, a tecla ESPAÇO (na realidade, qualquer tecla) faz com que o Harry pare por um instante. Esta é a única interação que o usuário pode ter com o personagem. Isto está no programa original, mas provavelmente minha implementação de como isso é feito é diferente (eu utilizei uma flag para que o BASIC indique ao programa assembly que uma tecla foi pressionada).

Após interromper o programa BASIC com Control+Stop, o Harry continua correndo até que o usuário comande GOTO 180, que irá desligar a rotina de interrupção, colocando um C9 (opcode RET) no início do gancho do sistema H.TIMI. O correto mesmo seria que a rotina original do gancho do sistema fosse salva e depois recuperada. Mas como isso não funcionaria se o programa fosse iniciado novamente com RUN (porque o gacho já alterado seria salvo), optei por uma solução menos elaborada (displicente, talvez, mas funcional).

Como explicado anteriormente, os endereços &HE0C1, &hE0C5 e &HE0C9 podem ser alterados com o comando POKE para mudar as cores do Harry.

Disponibilizo no link abaixo a imagem de disco contendo o programa BASIC para MSX, com comentários delimitando os blocos e, ainda, a rotina assembly responsável pela animação do Harry, no formato do MegaAssembler em que foi criado. Este está sem comentários, pelo que peço desculpas. Incluí ainda, no pacote zip, a listagem do programa assembly em formato texto.

Download (update 20.09.22)


domingo, 31 de outubro de 2021

Doom Fire para MSX, ZX81 e TRS-80

Após descobrir o tal "algoritmo do fogo do Doom", cuja existência até então ignorava por completo, resolvi brincar com ele. Desta vez, fiz logo três versões de uma vez. 

Primeiro para o ZX81, onde desenvolvi o mecanismo e fui otimizando o código. Depois disso, portar para o TRS-80 foi bem tranquilo. Na verdade colocar coisas na tela do TRS-80 é mais simples do que no ZX81, então foi só um pequeno trabalho de descomplicação. 

Já no MSX, tive que aprender como funciona a tela no modo 3 (multicolor), que nunca havia lidado a não ser no BASIC. A cada frame os dados são processados em três etapas. A primeira atualiza os valores dos 1536 bytes correspondentes, cada um, a uma célula do fogo. Como a Screen 3 não é um modo bitmap, os dados são organizados, na segunda etapa, para que fiquem dispostos num buffer da maneira que exige o processador gráfico. Só então os dados são impressos na tela, numa terceira etapa.

A versão do ZX81 desenvolvi na tabelinha nano/Pasmo, testando no emulador sz81. A do TRS-80, fiz no EDTASM, rodando no emulador SLDTRS. E a do MSX fiz no Mega Assembler, rodando no openMSX. 

Download: Doom Fire - MSX/ZX81/TRS-80

Versão MSX (a versão final tem a animação um pouco mais fluída do que a que aparece no vídeo):


Versão TRS-80:


Versão ZX81:








sábado, 30 de outubro de 2021

Matrix - Raining Code para TRS-80



Portar o código em si do ZX-81 para o TRS-80 foi bem simples. O que me deu um pouco mais de trabalho foi entrar no mundo do TRS-80. Como só uso ferramentas externas em último caso (em único caso, na verdade, do ZX-81, que uso cross-assembler), aprendi basicamente como funciona o emulador SDLTRS, o sistema operacional do TRS-80 e o assembler EDTASM. Levei um tempinho pra me acostumar com todas essas novidades, mas valeu a pena. Afinal eu me meto nessas velharias mesmo pra ter o prazer de aprender e lidar com elas.  E faz tempo que eu queria mexer com o TRS-80, porque o CP-500 foi um dos primeiros computadores que vi funcionar. E que impressão aquele monstrinho branco dava!

Bom, segue o download de uma imagem de disquete contendo o programa.

Download: Matrix - Raining Code - TRS-80




quinta-feira, 23 de julho de 2020

Matrix - Raining Code para o ZX81

Portei  para o ZX81(linha que no Brasil foi representada pelo TK-85, CP-200, etc) o programa que havia escrito para o MSX. Uma boa parte teve que ser reescrita, principalmente porque na versão para MSX eu utilizei o modo gráfico ("screen 2"). O algoritmo de animação, de um modo geral, é o mesmo, respeitadas as limitações da maquininha.

Para inciar o programa, digite RUN. Para interromper a execução, tecle NEW LINE.

Download: matrix.p





terça-feira, 14 de julho de 2020

Blackjack para o ZX81

A ideia


Ok, eu sei, "mais um blackjack para o ZX81", mas enfim... A ideia era fazer o programa em BASIC e apenas inserir umas rotinas em assembler para deixar mais bonito e fluído. E, o mais importante, aprender com o processo.

E nesta minha primeira aventura em programar para o ZX81 (no Brasil, TK-85, CP-200, etc), realmente aprendi bastante sobre essa maquininha, suas limitações práticas de hardware, a estrutura da memória, o uso da pilha de cálculo e até as limitações, ao programador, no uso do Z80 (que comento mais ao final).

Por razões óbvias, abandonei minha ideia inicial (besta) de fazer tudo direto no emulador do ZX81, utilizando o MSX para montar as rotinas em linguagem de máquina. Acabei usando o Pasmo como assembler, e o Appmake (do pacote z88dk) para gerar o arquivo ".p".  Depois eu integro ao resto do programa BASIC usando um ótimo utilitário do próprio  ZX81 (Toolkit, da Artic).

O processo


Fiz questão de completar todo o programa em BASIC mesmo, até para conhecer as peculiaridades do Sinclair BASIC, pegar intimidade com a máquina e ver até onde chegavam as otimizações. Quando não tinha mais jeito, passei a resolver com assembler.

Pra quem não conhece o ZX81 e seus clones da vida (TK82, TK83, TK85 e CP-200 aqui no Brasil), pode soar estranho utilizar rotinas em código de máquina para um jogo tão simples. Acontece que as limitações do Sinclair BASIC e o do próprio hardware exigem que partes do programa, que rodariam tranquilamente em qualquer BASIC interpretado da época, devam ser feitas em linguagem de máquina no ZX81.

Assim, desde o início, percebi que a rotina de embaralhamento das cartas e, para minha surpresa, a atualização dos valores do "caixa" e da "aposta" na tela, teriam que ser feitas em linguagem de máquina.

Uma rotina de embaralhamento adequada simula um baralho real e permite ao jogador contar cartas durante as partidas, o que dá uma profundidade extra ao gameplay. A solução em linguagem de máquina foi bastante simples e a sua execução é praticamente instantânea.

Agora, a atualização dos valores de aposta e caixa me deu algum trabalho, quero dizer, muito mais pelo estudo que demandou do que pela solução em si. Pensei inicialmente que o problema estava no cálculo dos valores em BASIC, mas não. O que acontece é que, para imprimir um número na tela, o computador converte o valor em uma string de caracteres. E é essa conversão, feita pela ROM do ZX81, que leva uma eternidade. Então tive que fazer uma rotina própria de conversão, capturando os valores das variáveis do BASIC, para depois imprimir o resultado diretamente na tela. Como resultado, a rotina ficou bastante rápida. Nesse aprendizado, percorri todos os livros que dispunha (físicos e virtuais), mas foi o Mario Schaefer (Usando Linguagem de Máquina - Aplicações em Assembly Z80) quem me deu a dica, mas não a solução. Esta, eu fui encontrar apenas nos rolos de pergaminho de Toni Baker.

O resto foi trivial, tratando mais da parte cosmética, além de otimizações menores.

O que é executado em linguagem de máquina


  • A limpeza de tela (tanto em fundo preto, como em fundo branco). A rotina do CLS do BASIC é lenta demais, na minha opinião;
  • A impressão e a animação de parte da tela inicial;
  • O embaralhamento das cartas (como num baralho real, utilizando o algoritmo knuth shuffle) e a animação das cartas na tela; 
  • A decodificação do valor e naipe das cartas para o BASIC ;
  • A atualização dos valores do caixa e aposta na tela;
  • A impressão das cartas. Eu até gostava da velocidade de impressão das cartas no BASIC, que conferiam uma certa dramaticidade, mas no final achei que não estava mais combinando com o ritmo do resto do jogo;
  • A leitura de teclas dentro da rotina de aposta, que possibilitou um processamento mais rápido dos valores retornados no BASIC;
  • A tela final do jogo, quando o  jogador "quebra a banca" ao ganhar mais de $ 30.000.


O que aprendi nessa aventura


  • Toda a estrutura de memória do ZX81;
  • A estrutura das linhas do BASIC na memória;
  • Local e forma de armazenamento de variáveis na memória;
  • A utilização da pilha de cálculo e rotinas de cálculo do ZX81 em linguagem de máquina;

Aprendi também que "coisas terríveis irão acontecer" (crash) na execução de uma rotina em linguagem de máquina no ZX81 se você:

  • Utilizar EX AF,AF';
  • Não devolver a pilha de cálculo na mesma posição que a encontrou no início da rotina LM;
  • Ao voltar ao BASIC, não devolver HL' com o mesmo valor que entrou na rotina em LM;
  • Num cross-assembler, inserir dados em ASCII e mandar imprimir no ZX81. Isto é óbvio, mas como sou distraído, gastei uns bons minutos debugando a rotina em assembler para chegar à conclusão que ela, em si, estava correta. O problema eram os dados em ASCII, que devem ser convertidos previamente para os valores utilizados pelo ZX81.

Agradecimentos


Agradeço a Sandro Lemos pelo apoio moral, dicas de onde encontrar utilitários para o ZX81 e pelo beta-testing. Agradeço igualmente ao Daniel Afarelli, também pelo apoio moral e pelo beta-testing.





Download: blackjack.p

domingo, 3 de novembro de 2019

Matrix - Raining Code no MSX

Implementação do efeito "raining code" no MSX, em assembler, utilizando a "screen 2". Para retornar ao Basic, tecle ESC.

Programa montado no Mega Assembler.

Update 15.10.2020

Fiz algumas alterações e ajustes no programa .bin. Agora o programa inicia com mensagens no "terminal".

Também fiz uma versão para MSX-DOS (matrix.com). Para isso, desta vez utilizei o Gen80. No final, a adaptação foi mais simples do que imaginei. Digo, "mais simples" depois de pronto, porque levei um tempinho pra descobrir como fazer. Leiam os manuais! Agradeço a ideia da versão DOS, dada por Xavi Sorinas. ¡Muchas gracias, Xavi! 

As duas versões estão em imagens de disco separadas, dentro do pacote compactado.

Download: Matrix - Raining Code


O vídeo é da primeira versão, mas dá pra ter uma ideia de como funciona:

 

Relógio BCD para o TK85 no ZX Assembler da Artic

Um relógio BCD (binary-coded decimal) mostra os dígitos decimais em formato binário. Cada dígito decimal das horas, minutos e segundos está ...