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.
sábado, 21 de junho de 2014
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.
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)) );
}
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.
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
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:
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.
- Botão de Reset (que reseta não só o Z80 mas também o AVR)
- Header Serial para a reprogramação do circuito (com suporte a Auto-Programação, como no Arduino Severino)
- 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.
- Jumper para habilitar/desabilitar a interface
- 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 |
Protótipo ligado ao TK para monitorar as formas de onda |
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 |
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 |
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 |
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();
}
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.
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
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.
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
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
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.
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
...
...
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
...
Assinar:
Postagens (Atom)