sábado, 21 de junho de 2014

Emulador de Teclado - fotos e testes com o protótipo

Fiz hoje alguns testes com o emulador de teclado conectado ao TK.


Alguns problemas se apresentaram, a começar pela contenção da linha A13 por parte do AVR (vide post anterior).

Outro problema que eu tive foi com a rotina de interrupção em assembly. Em alguns momentos eu notava que o AVR parava de responder às interrupções e o TK travava com o sinal /wait em nível baixo. Por enquanto retornei o atendimento à interrupção de leitura de teclado para uma interrupção padrão do Arduino e mesmo com o overhead o TK parou de travar.

Ainda estou com dois problemas que tenho que resolver. Um deles é que de vez em quando o TK demora a iniciar ou não inicia, mas isso não é mais problema de contenção. Desconfio que ainda esterja relacionado ao acionamento incorreto do sinal que libera a linha de /WAIT.

Outro problema é que apesar da matriz estar sendo preenchida corretamente quando se pressionam as teclas (o circuito imprime o estado dos bits na serial) nenhum bit muda de estado na entrada do LS367. Vou precisar debugar esta rotina em separado (fora da interrupção) para descobrir o problema.

Por último, pode ser que eu precise colocar uns resistores em série com as linhas D0 a D4 do LS365 interno do TK a fim de evitar a contenção do barramento de dados quando o LS367 do emulador de teclado estiver ativo.




Treta no A13

Como nada nessa vida é fácil, a primeira vez que liguei o circuito do emulador de teclado no TK ele não inicializou.

Passei um tempo conferindo o circuito, retirei o decodificador e o buffer da placa, mas a coisa continuava na mesma.

Daí foi que lembrei que eu não tinha programado o AVR para colocar como entrada os pinos que vão ligados às linhas de endereço A8-A15! Eu comprovei isso retirando o AVR da placa.

Depois de um bom tempo procurando uma fonte de alimentação que servisse no meu adaptador RS232-TTL eu finalmente programei o AVR com o seguinte 'sketch':

void setup() {
  // put your setup code here, to run once:
  DDRB=0x00;
  PORTB=0xff;
  DDRC=0x00;
  PORTC=0x00;
  DDRD=0x00;
  PORTD=0xc0;
}

void loop() {
  // put your main code here, to run repeatedly: 
  
}

Daí o TK bootou!!

Mas como diz o meu amigo Ferraz, "nessa vida tudo tem que ser com dor", só o primeiro boot deu certo. No segundo o TK ficou com a tela cheia de artefatos, no terceiro ficou branca, no quarto ficou ok. Em resumo, a operação estava de forma intermitente!

Tentei em vão descobrir algo de errado com o osciloscópio mas sem muito sucesso. Daí segui um expediente que embora trabalhoso foi o que me levou a descobrir o problema.

Eu retirei todas as conexões das linhas A8-A15 e espetei a placa no TK. Funcionou (óbvio ne?).

Daí eu removi a placa e fiz a ligação do sinal A8. Recoloquei a placa no TK e funcionou.

Fiz o mesmo com as outras linhas até chegar na linha A13. Com a linha A13 no lugar o TK fica funcionandode modo intermitente.

Removi a ligação da linha A13 e continuei o procedimento com as linhas A14 e A15 . Tudo OK.

De fato tinha alguma treta na linha A13! Esta linha está ligada ao pino PB5 do AVR (sinal SCK).


Como o AVR tem um bootloader, desconfiei que alguma coisa poderia estar acontecendo durante o boot. Daí instalei um resistor de 4K7 entre a linha A13 e o pino PB5 do AVR e o TK bootou normalmente (todas as vezes ).

Medindo com o osciloscópio eu verifiquei que o AVR mantém esta linha em nível baixo por um pouco mais de um segundo durante o 'startup' do bootloader. Eu creio que isso seja um erro de implementação do bootloader, mas por enquanto vou deixar o resistor e prosseguir com os testes. Depois eu dou uma olhada no código fonte do bootloader e/ou entro em contato com o desenvolvedor, pois não considero isso um comportamento normal.

Laranja: PB5 do AVR  / Azul: A13 do Z80




quinta-feira, 19 de junho de 2014

Emulador de teclado - Protótipo com AVR

Terminei de montar e conferir as ligções do protótipo do adaptador de teclado. Ainda faltam 2 capacitores para soldar e uma modificação para fazer a fim de poder usar o tanto o 74LS365 quanto o 74LS367 como buffer. É que o circuito foi projetado para o LS365 mas não consegui encontrar este chip. 

Emulador montado. Falta uma modificação para poder usar o LS367 no lugar do LS365.

O extensor não é necessário e só foi montado para o protótipo.
O bom de ter o protótipo em mãos é perceber alguns detalhes que devem ser corrigidos ou mesmo melhorados, como a ligação do botão de RESET que deveria ter passado por baixo da placa para não fechar curto com o metal da armação do botão.

Ali na placa, logo abaixo do 74HC74 tem um conector para o adaptador RS232 para testes e debug. O ATMega88 foi gravado com o bootloader do Arduino.

sexta-feira, 13 de junho de 2014

Rotina de interrupção em Assembly dentro do Arduino

A sintaxe do Assembly Inline do GCC é meio confusa, e isso me deu bastante trabalho para conseguir embutir um pouco de código Assembly dentro da IDE do Arduino. Mas finalmente consegui.

Eis o código para tratar a interrupção:

asm volatile ( 
   "in __tmp_reg__,__SREG__ \n\t"   // Salva registrador de Status  
   
   "ldi r26,lo8(mapa) \n\t"         // Ponteiro X = endereço de Keymap
   "ldi r27,hi8(mapa)\n\t"          //
   
   "ldi r18,0x1f \n\t"              // Mascara inicial => bits 0..5 em 1
   
   "in r19,%0 \n\t"                 // Amostra bits PB 0..5 (A8..A13)
   
   "ld r20,X+ \n\t"                 // R20 = Keymap[0] 
   "sbrs R19,0 \n\t"                // Bit [0] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[0] para R18
   
   "ld r20,X+ \n\t"                 // R20 = Keymap[1] 
   "sbrs R19,1 \n\t"                // Bit [1] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[1] para R18   
   
   "ld r20,X+ \n\t"                 // R20 = Keymap[2] 
   "sbrs R19,2 \n\t"                // Bit [2] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[2] para R18
   
   "ld r20,X+ \n\t"                 // R20 = Keymap[3] 
   "sbrs R19,3 \n\t"                // Bit [3] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[3] para R18   

   "ld r20,X+ \n\t"                 // R20 = Keymap[4] 
   "sbrs R19,4 \n\t"                // Bit [4] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[4] para R18   

   "ld r20,X+ \n\t"                 // R20 = Keymap[5] 
   "sbrs R19,5 \n\t"                // Bit [5] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[5] para R18

   "in r19,%1 \n\t"                 // Amostra bits PD 6..7 (A14..A15)

   "ld r20,X+ \n\t"                 // R20 = Keymap[6] 
   "sbrs R19,4 \n\t"                // Bit [6] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[6] para R18   

   "ld r20,X+ \n\t"                 // R20 = Keymap[7] 
   "sbrs R19,5 \n\t"                // Bit [7] = 0 ? 
   "and r18,r20 \n\t"               // sim, copia os bits '0' de Keymap[7] para R18
   
   "out %2,r18 \n\t"                // Escreve resultado nos bits D0-D4 do LS365 e libera Wait State
   "nop \n\t"
   "sbi %2,5 \n\t"                  // libera flip flop
   
   "out __SREG__,__tmp_reg__ \n\t"  // restaura registrador de Status
    
   :: "I" (_SFR_IO_ADDR(PINB)), "I" (_SFR_IO_ADDR(PIND)) , "I" (_SFR_IO_ADDR(PORTC)) );
}


Cabe observar que eu tentei antes incluir uma função externa em assembly e incluir no projeto, mas o Arduino não compila.

Pelas minhas contas são necessários 32 ciclos de clock entre a interrupção e a liberação do Wait, o que significa 1,6us a 20MHz ou 4us a 8MHz (clock interno).

quarta-feira, 11 de junho de 2014

Tabelas de Scancodes para teclados ABNT

Com o auxílio de um Arduino e um pequeno 'sketch' levantei a tabela de scancodes para teclados ABNT pois não havia conseguido encontrar esta tabela em lugar algum.

Aproveitei o embalo e organizei os scancodes em uma tabela com  132 entradas correspondentes aos códigos das teclas entre 0 e 0x83.

O valor _NONE corresponde a um código que não tem ação. Para popular a tabela basta substituir a entrada _NONE pelo código correspondente à posição da tecla na matriz do teclado.

Tomando por exemplo a tecla "Q". Na matriz do TK90 ela corresponde ao bit 2 da linha 0 de varredura (conforme explicado no post anterior).

Assim, a linha da tecla "Q" fica:

0x02,      // 0x14  Q 


Para simplificar mais ainda, basta definir as constantes das teclas:


#define _Q    0x02  // Tecla  Q 

E na tabela usar o valor definido

_Q      // 0x14  Q 

Segue abaixo a tabela 'virgem', ou seja, não preenchida.


const uint8_t PS2Keymap_Normal[] PROGMEM = { 
_NONE,     // 0x00   
_NONE,     // 0x01  F9
_NONE,     // 0x02  
_NONE,     // 0x03  F5
_NONE,     // 0x04  F3
_NONE,     // 0x05  F1
_NONE,     // 0x06  F2
_NONE,     // 0x07  F12           
_NONE,     // 0x08                
_NONE,     // 0x09  F10           
_NONE,     // 0x0A  F8            
_NONE,     // 0x0B  F6            
_NONE,     // 0x0C  F4            
_NONE,     // 0x0D  TAB           
_NONE,     // 0x0E  APOSTROPHE    '
_NONE,     // 0x0F                
_NONE,     // 0x10                
_NONE,     // 0x11  L ALT         
_NONE,     // 0x12  L SHFT        
_NONE,     // 0x13  L CTRL        
_NONE,     // 0x14  Q             
_NONE,     // 0x15  1             
_NONE,     // 0x16                
_NONE,     // 0x17                
_NONE,     // 0x18                
_NONE,     // 0x19                
_NONE,     // 0x1A  Z             
_NONE,     // 0x1B  S             
_NONE,     // 0x1C  A             
_NONE,     // 0x1D  W             
_NONE,     // 0x1E  2             
_NONE,     // 0x1F                
_NONE,     // 0x20                
_NONE,     // 0x21  C             
_NONE,     // 0x22  X             
_NONE,     // 0x23  D             
_NONE,     // 0x24  E             
_NONE,     // 0x25  4             
_NONE,     // 0x26  3             
_NONE,     // 0x27                
_NONE,     // 0x28                
_NONE,     // 0x29  SPACE         
_NONE,     // 0x2A  V             
_NONE,     // 0x2B  F             
_NONE,     // 0x2C  T             
_NONE,     // 0x2D  R             
_NONE,     // 0x2E  5             
_NONE,     // 0x2F                
_NONE,     // 0x30                
_NONE,     // 0x31  N             
_NONE,     // 0x32  B             
_NONE,     // 0x33  H             
_NONE,     // 0x34  G             
_NONE,     // 0x35  Y             
_NONE,     // 0x36  6             
_NONE,     // 0x37                
_NONE,     // 0x38                
_NONE,     // 0x39                
_NONE,     // 0x3A  M             
_NONE,     // 0x3B  J             
_NONE,     // 0x3C  U             
_NONE,     // 0x3D  7             
_NONE,     // 0x3E  8             
_NONE,     // 0x3F                
_NONE,     // 0x40                
_NONE,     // 0x41  COMMA         ,
_NONE,     // 0x42  K             
_NONE,     // 0x43  I             
_NONE,     // 0x44  O             
_NONE,     // 0x45  0             
_NONE,     // 0x46  9             
_NONE,     // 0x47                
_NONE,     // 0x48                
_NONE,     // 0x49  DOT           .
_NONE,     // 0x4A  SEMICOLON     ;
_NONE,     // 0x4B  L             
_NONE,     // 0x4C  CCCEDIL       Ç
_NONE,     // 0x4D  P             
_NONE,     // 0x4E  MINUS         -
_NONE,     // 0x4F  
_NONE,     // 0x50  
_NONE,     // 0x51  SLASH         /
_NONE,     // 0x52  TILDE         ~
_NONE,     // 0x53  
_NONE,     // 0x54  ACUTE         `
_NONE,     // 0x55  EQUAL         =
_NONE,     // 0x56  
_NONE,     // 0x57  
_NONE,     // 0x58  CAPS
_NONE,     // 0x59  R SHFT
_NONE,     // 0x5A  ENTER
_NONE,     // 0x5B  OPENBRACKET   [
_NONE,     // 0x5C  
_NONE,     // 0x5D  CLOSEBRACKET  ]
_NONE,     // 0x5E  
_NONE,     // 0x5F  
_NONE,     // 0x60  
_NONE,     // 0x61  BACKSLASH     \
_NONE,     // 0x62  
_NONE,     // 0x63  
_NONE,     // 0x64  
_NONE,     // 0x65  
_NONE,     // 0x66  BKSP
_NONE,     // 0x67  
_NONE,     // 0x68  
_NONE,     // 0x69  KP1
_NONE,     // 0x6A  
_NONE,     // 0x6B  KP4
_NONE,     // 0x6C  KP7
_NONE,     // 0x6D  KPDOT         .
_NONE,     // 0x6E  
_NONE,     // 0x6F  
_NONE,     // 0x70  KP0
_NONE,     // 0x71  KPCOMMA       ,
_NONE,     // 0x72  KP2
_NONE,     // 0x73  KP5
_NONE,     // 0x74  KP6
_NONE,     // 0x75  KP8
_NONE,     // 0x76  ESC
_NONE,     // 0x77  NUM
_NONE,     // 0x78  F11
_NONE,     // 0x79  KPPLUS        +
_NONE,     // 0x7A  KP3
_NONE,     // 0x7B  KPMINUS       -
_NONE,     // 0x7C  KPTIMES       *
_NONE,     // 0x7D  KP9
_NONE,     // 0x7E  SCROLL
_NONE,     // 0x7F  
_NONE,     // 0x80  
_NONE,     // 0x81  
_NONE,     // 0x82  
_NONE      // 0x83  F7
};  
  

Pode-se replicar esta tabela para as teclas ativadas com a  tecla SHIFT, como por exemplo as teclas sobre os números e as teclas de acentuação, colhetes, etc

Também é possível utilizar uma tabela para as teclas de códigos estendidos (como as setas de cursor), mas como estas teclas não são muitas é mais fácil tratar cada uma caso a caso. O mesmo se aplica à tecla F7. Caso esta tecla seja tratada separadamente dá para deixar a tabela com menos de 128 entradas.

       

domingo, 8 de junho de 2014

Placa para o Emulador de teclado com AVR (atualizado)

Uma vez que o gerador de /WAIT funcionou corretamente, projetei uma placa de circuito impresso para o circuito, com as seguintes funcionalidades:
  1. Botão de Reset (que reseta não só o Z80 mas também o AVR)
  2. Header Serial para a reprogramação do circuito (com suporte a Auto-Programação, como no Arduino Severino)
  3. Montagem em conector EDGE ou com headers para encaixar na Interface M1 do Edu Luccas (assim que pretendo utilizar a minha). Nesse caso tem que serrar a parte de baixo da placa e soldar os headers diretamente nas ilhas.
  4. Jumper para habilitar/desabilitar a interface
  5. Três opções para montagem do conector para o teclado:
  • Mini-DIN 6 pinos
  • USB (alguns teclados revertem para o modo PS/2 quando não conseguem estabelecer comunicação via USB)
  • Header para montagem do conector para painel (esta opção vai ser útil para montagem dentro da M1). 

Eu cheguei a pensar em manter apenas o conector USB pois o conector Mini-DIN é mais caro e mais difícil de ser encontrado. Porém mas acabei deixando desse jeito mesmo.

Também pensei (caso dispensasse o mini-din) foi colocar um bootloader USB e usar um cabo USB A-A para reprogramar o circuito usando um bootloader baseado em V-USB compatível com USBASP, mas acabei deixando a serial mesmo, por conta da facilidade que ela traz na hora de se fazer o Debug do circuito.




A placa ficou com aproximadamente 76 x 55 mm

As fotos abaixo são da placa do protótipo assim que terminou de ser corroída.



Protótipo para o emulador de teclado com AVR

Definidos os componentes básicos desenhei um circuito e comecei a montagem em proto-board para seguir com o desenvolvimento do emulador de teclado.

Montagem inicial em proto-board
Inicialmente conectei apenas a alimentação e os sinais A0, /RD, /IORQ, /WAIT e M1 a fim de testar o gerador de WAIT. Como as linhas de dados ainda não estavam conectadas, o AVR foi programado via SPI.

Protótipo ligado ao TK para monitorar as formas de onda
Com o protótipo montado registrei as formas de onda abaixo:

Em primeiro lugar seguem as formas de onda originais. A ULA do TK gera um sinal de seleção para o 74LS365 que lê o teclado (e o cassete). O atraso na decodificação em relação ao sinal /IORQ é de aproximadamente 40ns

Laranja: Pino 1 do 74LS365 / Azul: /IORQ

  O tempo total entre a decodificação do sinal de leitura e a borda de descida do sinal de clock ao final do tempo T3 é de 600ns.

Laranja: Pino 1 do 74LS365 / Azul: Clock do Z80

Durante o funcionamento normal o TK faz a leitura das 8 linhas do teclado. A linha azul na forma de onda abaixo já é a decodificação externa usando o 74HC138.

Laranja: Pino 1 do 74LS365 / Azul: /ULARD
O tempo gasto na decodificação do sinal /ULARD é de apenas 25ns, um pouco menos do que a ULA.

Laranja: /ULARD / Azul: /IORQ

O tempo que se ganhou na decodificação também pode ser visto na forma de onda abaixo, onde o tempo entre a descida do sinal /ULARD e a borda de descida do ciclo T3 aumentou de 600 para 630ns.
Laranja: Pino /ULARD / Azul: Clock do Z80
Aforma de onda abaixo mostra os tês sinais juntos
Laranja: /ULARD / Azul: /IORQ / Cinza: Clock Z80

O AVR foi programado para atender à interrupção gerada pelo sinal /ULARD e enviar um pulso negativo ao gerador de WAIT. O AVR estava rodando a 20MHz mas por se tratar de prova de conceito nenhuma otimização foi utilizada ainda.

ISR(INT0_vect)
{
release();   
}

A figura abaixo contém a superposição de vários sinais com a mesma referência a fim de resumir o funcionamento do circuito.

Se tomarmos como referência a forma de onda WAIT (OUT) é possível notar que o AVR está demorando por volta de 2,5us para atender a interrupção (5 divisões de 500ns). Como a velocidade de clock é de 20MHz, dá pra se notar que o AVR está executando 50 instruções (50 x 50ns = 2500ns), o que nem é tanto assim. O sinal de WAIT gerado é aplicado ao Z80 através de um diodo (só é possível forçar o nível zero na linha). Por isso a linha demora um pouco a subir. Mas note que quando ela sóbe só acontece mais um ciclo de clock, que é exatamente o ciclo T3. Logo em seguida o Z80 libera as linhas de endereço e de controle e com isso o sinal /ULARD retorna a nível alto e o Z80 já começa a buscar a próxima instrução.

Ciclo de funcionamento do circuito com o gerador de WAIT

A título de comparação eis as mesmas formas de onda, porém sem WAIT gerado pelo circuito.










sábado, 7 de junho de 2014

Escolhendo um bootloader para o ATMega88

Como os pinos de programação do ATMega88 vão estar conectados às linhas de endereço do Z80 fica difícil fazer o desenvolvimento usando o modo de programação ISP do microcontrolador. 

Para contornar este problema e também para ajudar no debug a solução é usar a comunicação serial do microcontrolador tanto para a programação quanto para o Debug. 

Eu escolhi utilizar um bootloader compatível com o Arduino porque assim consigo aproveitar algumas coisas que prototipei num Arduino UNO usando a biblioteca PS/2. Por enquanto vou utilizando esta biblioteca no desenvolvimento.

Como estou utilizando um ATMega88 eu encontrei um bootloader para ele que funcionou direitinho dentro da IDE do Arduino (link).

Emulador de Teclado com AVR para os TKs

Como o gerador de Wait State funcionou bem, já é possível delinear um emulador de teclado para o TK usando apenas 4 chips. 


Se considerarmos que para conectar um um teclado externo no TK já é necessário utilizar um decodificador e um buffer, então dá pra ver que este circuito já está beirando o limite da simplicidade. Talvez seja possível utilizar um PIC que possua uma PSP (como o 18F4520) e deixar de usar o buffer, daí a interface fica com apenas 3 CIs, mas não creio que a diferença de preço compense.

Eu estou considerando usar um ATMega88, porém qualquer microcontrolador com pelo menos 17 pinos consegue dar conta do recado.

Os sinais necessários são:
A[8..15] - (8 pinos) para a seleção de linha
D[0..4] - (5 pinos) para a leitura das teclas de cada linha (via buffer)  
PS2CLK e PS2DAT - (2 pinos) para interface com o teclado PS/2
/ULARD - (1 pino) Interrupção externa.   
RELEASE - (1 pino) para destravar o gerador de Wait State

 




Gerador de Wait State

Terminei de desenvolver o gerador de 'wait state', que nada mais é do que um flip-flop.

Quando o Z80 do TK90 acessa o teclado ele leva a nível zero as linhas A0, /IORQ e /RD. Como se trata de uma operação de I/O e não de um atendimento a interrupção, o sinal M1 permanece em nível alto.

Para reduzir a quantidade de chips utilizados, e ao mesmo tempo evitar o uso de componentes difíceis de serem encontrados eu procuro utilizar sempre os CIs TTL mais 'feijão com arroz', no caso a decodificação ficou por conta de um 74138 e o flip flop  escolhido foi o 7474

O 74138 usa os sinais A0, /IORQ e /RD e M1 para ativar a saída /Q0 que chamei de /ULARD. Quando o Z80 faz a leitura do teclado, esse sinal cai a nível zero.

Nos primeiros testes o sinal /ULARD foi utilizado na entrada CLEAR do flip flop, porém isso não funcionou, pois enquanto o sinal /WAIT permanece em nível zero, o Z80 mantém o último estado das linhas /RD, /IORQ, M1 e A0. O problema é que enquanto a entrada CLEAR do flip flop está em nível zero, é impossível mudar o seu estado de saída, seja pelo acionamento da entrada PRESET seja pela transferência de um dado pelo sinal de CLOCK.

A situação ideal seria utilizar a entrada de CLOCK para acionar o sinal de WAIT, porém essa entrada é sensível à borda de subida.

Usar outro decodificador que gere um nível 1 quando a entrada correspondente está ativada estava fora que questão uma vez que eu nunca vi um 74137 em minha vida e nem sei se existe isso pra vender hoje em dia. Também descartei a possibilidade de usar um inversor, pois isso significaria colocar um CI a mais no projeto.

Depois de pensar um pouco, consegui aproveitar o flip flop que estava ocioso num circuito de acionamento sequencial.


Quando o sinal /ULARD é decodificado ele ativa o CLEAR do flip flop da esquerda fazendo a saída Q (pino 5) ir a nível zero e a saída /Q (pino 6) ir a nível alto.

A mudança de estado do pino 6 faz com que o flip flop da direita transfira o nível alto presente na entrada D (pino 12) para a saída Q (pino 9). Simultaneamente a saída /Q (pino 8) vai a nível zero, ativando o sinal de /Wait, que permanece nesse estado enquanto o microcontrolador está processando a interrupção que foi gerada na borda de descida do sinal /ULARD.

Quando o microcontrolador termina de processar a interrupção ele traz abaixo a linha RELEASE que ativa o CLEAR do flip flop da direita e faz com que o pino 8 volte a nível alto, liberando o sinal de WAIT

O Z80 termina de executar a instrução IN (ciclo T3) e com isso o sinal /ULARD volta a nível alto. Como a linha de CLEAR e a de CLOCK estão interligadas, quando essa mudança de estado provoca a transferência do nível alto na entrada D (pino 2) para a saída Q (pino 5) e do nível baixo para a saída /Q (pino 6). Com isso o circuito volta a seu estado de repouso

O circuito foi testado num proto-board e funcionou como desejado.

Agora eu já consigo fazer um emulador de teclado para o TK usando qualquer família de microcontroladores.




sexta-feira, 6 de junho de 2014

Temporização da leitura do teclado nos TKs

Em muitos micros da década de 80 a leitura do teclado utiliza duas operações, sendo uma de escrita, onde se seleciona a linha desejada e outra de leitura, onde se obtém o estado das teclas da linha selecionada.

Como exemplo segue parte da rotina SENSMAT do MSX

...
1459  OUT (0AAh),A
145B  IN A,(0A9h)
...

Nos TKs (e ZXs) a seleção da linha é feita utilizando-se a parte alta das linhas de endereço, o que implica que a seleção da linha ocorre na mesma instrução que a leitura.

...
;; KEY-LINE
L0296:  IN A,(C)
...

Nos TKs o clock do Z80 é de aproximadamente 3,57MHz, o que significa que cada ciclo de clock dura aproximadamente 280ns.

Olhando-se o diagrama de temporização do manual do Z80 podemos então ter uma idéia da temporização envolvida na leitura do teclado dos TKs:


A execução da instrução "IN" dura 4 ciclos de clock : T1, T2, TW e T3.

Logo no início do ciclo T1, o Z80 ativa a linha de endereços. Nesse momento acontece a seleção da linha. A partir deste momento, o barramento de endereços fica estável até o final da execução.

Logo após o início do ciclo T2, os sinis /IORQ e /RD são ativados. Só a partir deste momento é que é possível identificar que o TK está fazendo a leitura do teclado.

Ao final do ciclo T2 o Z80 insere automaticamente o ciclo de espera TW. É neste momento (mais precisamente na borda de descida do CLOCK) que o Z80 faz a amostragem do sinal /WAIT. O tempo transcorrido desde a descida do sinal de /IORQ à amostragem do sinal de /WAIT é de aproximadamente 390ns

Quando acontece a borda de descida do sinal de CLOCK no ciclo D3 o Z80 amostra o estado das colunas, Mas note que é necessário que o sinal de dados seja válido um pouco antes, para que o Z80 consiga armazenar internamente o estado das linhas de dado (tempo de acomodação). Com isso o tempo transcorrido entre a descida do sinal de /IORQ e os dados estarem estáveis no barramento para serem lidos pelo Z80 é de aproximadamente 600ns

Um pouco após a leitura, ainda no ciclo T3 os sinais/ IORQ e /RD são desativados.

Em resumo:
 - O tempo de resposta para a ativação do sinal /WAIT do Z80, caso este seja utilizado, é de apenas 390ns
- O tempo de resposta necessário para ativar o valor das colunas (teclas) no barramento do Z80 é de apenas 600ns

Estes tempos caem para 350ns e 560ns se considerarmos uns 40ns de atraso nas lógicas de decodificação.

Para se ter uma idéia dos requisitos necessários a um microcontrolador para atender a estes tempos, vamos considerar alguns exemplos:

AVR:
A latência mínima para atender a uma interrupção é de 7 ciclos de clock

Para acionar um pino de saída, no caso o WAIT são necessários mais pelo menos 2 ciclos de clock. No total temos então 9 pulsos de clock.
Para atender isso em menos de 350ns o AVR precisaria de um clock de aproximadamente 26MHz

...
  IRQ:                 ; 7 ciclos de latencia
  cbi   PORTB,WAITPIN  ; 2 ciclos de clock
...

Já para o caso de escrever diretamente na porta de saída precisamos de uns 12 ciclos. Para atender isso em menos de 560ns precisaríamos de um clock de  21,5MHz

...
  IRQ:                 ;  7 ciclos de latencia
  in SavStatus,SREG    ; +1 Salva Status
  in YL,PIND           ; +1 lê o valor das linhas A8..A15
  ld temp,Y            ; +2 temp=memoria[YH:YL]->prepara dado
  out DDRB,temp        ; +1 escreve dado
                       ;=12 ciclos
...

 Uma solução intermediária é utilizar um flip flop para gerar o sinal de /WAIT ao mesmo tempo em que ativa uma interrupção no microcontrolador. Com isso o Z80 passa a esperar microcontrolador ler as linhas de endereço, preparar o dado para a saída, depois escrever a saída e finalmente ativar o flip flop de forma a liberar o Z80.