quarta-feira, fevereiro 06, 2019

Usando um Teclado PS/2 com um Arduino

Um projeto que está na minha lista há algum tempo é um mini terminal baseado no Arduino para uso com os computadores básicos que eu montei com o Z80 e 6502. Para o display vou usar um LCD alfanumérico de 4 linhas de 40 caracteres; para o teclado a ideia é usar um teclado de PC, o que nos traz a este post.


Um Pouco de História

O teclado do PC IBM original utilizou um conector DIN de 5 pinos e uma comunicação serial unidirecional (do teclado para o PC) usando dois sinais (dado e clock). No PC AT o conector foi mantido, porém o protocolo foi alterado para permitir comunicação bidirecional. Por último, nos computadores PS/2 o conector foi trocado por um DIN de 6 pinos de dimensão menor.



Porque Usar Um Teclado PS/2?

A interface e o protocolo são simples (principalmente comparados com USB) e ainda se encontra uma quantidade grande destes teclados em boas condições.  O conector DIN fêmea  pode dar um pouco de trabalho para achar (eu comprei no eBay), mas sempre existe a opção de cortar a ponta do cabo e colocar um conector diferente.

A Interface Elétrica e o Protocolo

Do ponto de vista elétrico temos apenas dois sinais (fora a alimentação e terra): Dado e Clock. Estes sinais são do tipo "coletor aberto", ou seja, normalmente estão flutuando (pullups garantem um nível alto) e podem ser colocados em nível baixo por qualquer um dos lados.

Na situação de repouso os dois sinais estão "soltos" e portanto em nível alto.

Para enviar um byte, o teclado inicia a transmissão conferindo que o clock está no nível alto e colocando o sinal de dados em nível baixo. A partir daí são enviados 11 bits (a começar por este zero); o sinal de dados é posicionado enquanto o clock está alto e depois o teclado força o clock para baixo para indicar que o bit pode ser lido. Os 11 bits correspondem a um start (sempre 0), 8 bits de dados, 1 bit de paridade (impar) e um stop (sempre 1).

O teclado envia, na maioria dos casos, um byte quando uma tecla é apertada (o scancode). Quando a tecla é solta, ele envia dois bytes: F0 e o scancode. Algumas teclas (extended keys) geram dois bytes quando apertadas (E0 scancode) e três quando soltas (E0,F0 scancode).

Para o PC (ou outro host) enviar dados para o teclado, ele deve primeiro segurar o clock em nível baixo, para inibir transmissões pelo teclado. Em seguida o sinal de dados deve ser colocado em nível baixo (start) e o sinal de clock deve ser solto. A partir daí a linha de clock será controlada pelo teclado, que a colocará em nível baixo quando o PC deve controlar a linha de dados. O PC enviará 11 bits (start, dados, paridade, stop) e em seguida aguardar um bit de confirmação do teclado.


Referência (e origem das figuras acima): https://www.avrfreaks.net/sites/default/files/PS2%20Keyboard.pdf

Software para o Arduino

Uma biblioteca simples para  tratar a conexão de um teclado PS/2 ao Arduino é descrita nem https://playground.arduino.cc/Main/PS2Keyboard, e o código mais recente está em https://github.com/PaulStoffregen/PS2Keyboard.

Uma biblioteca mais sofisticada pode ser vista em https://github.com/techpaul/PS2KeyAdvanced.

O princípio básico de ambas é o mesmo: o sinal de clock é conectado a um pino do Arduino capaz de gerar uma interrupção quando o sinal passar do nível alto para o baixo. Na PS2Keyboard é tratado apenas a recepção e a rotina de interrupção lê o sinal de dados e monta o código recebido. A PS2KeyAdavanced suporta comunicação bidirecional. Portanto a rotina de interrupção irá ler ou posicionar o sinal de dados conforme a operação.

Uma vez recebido um código do teclado, é preciso decodificá-lo, tomando o cuidado de manter o estado das teclas modificadoras (shift, caps lock, control e alt).

Para um teste rápido, vamos fazer as seguintes ligações entre um teclado PS/2 e um Arduino Uno ou Nano:
  • Clock do teclado no pino 2 do Arduino
  • Dado do teclado no pino 3 do Arduino
  • +5V do teclado no +5V do Arduino
  • GND do teclado no GND do Arduino
O código abaixo se limita a pegar e mostrar os bytes recebidos:
/*
 * Teste de interface com teclado PS/2
 * 
 * Dados do teclado no pino 2 e clock no pino 3
 */

// fila para os códigos recebidos
const byte tamfila = 16;
byte poe = 0;
byte tira = 0;
byte fila[tamfila];

void setup() {
  // iniciar os pinos
  pinMode (2, INPUT_PULLUP);
  pinMode (3, INPUT_PULLUP);
  pinMode (13, OUTPUT);
  digitalWrite(13, LOW);
  // vamos mostrar os códigos na serial
  Serial.begin(9600);
  Serial.println("Pronto");
  // assumir a interrupção do sinal de clock
  attachInterrupt(digitalPinToInterrupt(3), kbdint, FALLING);
}

void loop() {
  // verifica se tem algum código na fila
  if (poe != tira) {
    // mostra
    Serial.println (fila[tira], HEX);
    // tira da fila
    byte prox = tira+1;
    if (prox == tamfila) {
      tira = 0;
    } else {
      tira = prox;
    }
  }
}

// Trata a interrupção do teclado
void kbdint(void) {
  static byte cod = 0;
  static byte nbit = 0;

  // le o bit de dados
  int dado = digitalRead(2);

  // despreza start, paridade e stop
  if ((nbit > 0) && (nbit < 9)) {
    cod = cod >> 1;
    if (dado == HIGH) {
      cod |= 0x80;
    }
  }
  nbit++;
  if (nbit == 11) {
    // coloca na fila
    fila[poe] = cod;
    byte prox = poe+1;
    if (prox == tamfila) {
      prox = 0;
    }
    if (prox != tira) {
      poe = prox;
    }
    nbit = cod = 0;
  }
}

Nenhum comentário: