terça-feira, maio 11, 2021

Raspberry Pi Pico: Usando o PIO para Interfacear um Teclado PS/2

Uma das características mais interessantes da PiPico é o PIO - Programmable Input/Output, que permite executar pequenos programas de manipulação de entrada e saída digital em paralelo à execução dos núcleos ARM Cortex M0+.

Resolvi estudar o PIO fazendo a interface com um teclado PS/2, que já examinamos aqui no blog. Neste post anterior nós usamos um Arduino e os dados eram lidos bit a bit em uma interrupção gerada pelo sinal de clock. Com a PIO o processador vai receber direto o dado completo.

Sou sujinho mas funciono, viu?


A descrição da PIO está no datasheet do RP2040 (o microcontrolador usado na PiPico). Resumindo as informações que estão lá:

  • O RP2040 possui dois blocos PIO
  • Cada bloco PIO possui quatro máquinas de estado que rodam de forma independente
  • Cada bloco PIO possui uma memória de programa com capacidade de 32 instruções, esta memória é compartilhada pelas quatro máquinas de estado
Cada máquina de estado da PIO
  • Possui dois registradores de deslocamento de 32 bits
  • Tem acesso aos 32 pinos de GPIO para entrada e saída digital
  • Tem acesso a duas filas de 4 posições de 32 bits para interface bidirecional com o processador, que podem ser reconfiguradas como uma única fila de 8 posições unidirecional. Estas filas podem interagir com a interrupção e o DMA.
Os programas para a PIO utilizam somente 9 instruções: JMP, WAIT, IN, OUT, PUSH, PULL, MOV, IRQ, e SET.  Mesmo assim, pode ser complexo construir um programa a partir do zero. Felizmente a Raspberry Foundation fornece vários exemplos em https://github.com/raspberrypi/pico-examples/tree/master/pio.

No nosso caso, o que interessa é o exemplo "clocked input": leitura de bits indicados por pulsos de clock. Neste exemplo, o programa na PIO são apenas três instruções (wrap_target e wrap são configurações):
    wrap_target()
    wait 0 pin 1
    wait 1 pin 1
    in pins, 1
    wrap()
O que isso faz é esperar o sinal de clock ir para o nível 0, esperar o clock ir para o nível 1, ler o dado, colocar no registrador de deslocamento e voltar ao começo (cortesia das configurações feitas por wrap).

O resto é tratado na configuração da máquina de estados. Lá são especificados os pinos de dado e clock, a direção do deslocamento e o número de bits a receber para considerar o dado completo e colocá-lo na fila.

Para tratar o teclado PS/2 precisamos fazer pequenos ajustes neste exemplo:
  • O estado de repouso do clock é o nível alto, o dado deve ser pego na borda de descida
  • Temos um total de 11 bits (start, dados, paridade e stop).
Antes de partir para o C/C++, resolvi fazer um teste usando MicroPython. A boa notícia é que existe suporte ao PIO no MicroPython, a má é que a documentação é um pouco escassa. O manual do Python SDK tem algumas coisas, existem exemplos no github e tem mais informações no site do Micro Python. Juntando tudo, cheguei ao seguinte código:
import time
import rp2
from machine import Pin

# in_shiftdir=rp2.PIO.SHIFT_RIGHT -> deslocar os bits recebidos para a direita
# autopush=True, push_thresh=11   -> colocar na fila de recepção a cada 11 bits recebidos
# fifo_join=rp2.PIO.JOIN_RX       -> juntar a fila de transmissão na de recepção
@rp2.asm_pio(in_shiftdir=rp2.PIO.SHIFT_RIGHT, autopush=True, push_thresh=11, fifo_join=rp2.PIO.JOIN_RX)
def rdKbd():
    wrap_target()
    wait (1, pin, 1)
    wait (0, pin, 1)
    in_ (pins, 1)
    wrap()

# Configura os pinos de entrada
pin14 = Pin(14, Pin.IN, Pin.PULL_UP)
pin15 = Pin(15, Pin.IN, Pin.PULL_UP)

# O clock de 100KHz para a SM é adequado para um clock de cerca de 10KHz do teclado
# Os pinos de entrada começam no pino 14
sm = rp2.StateMachine(0, rdKbd, freq=100000, in_base=pin14)

# Ativa a State Machine
sm.active(1)

while True:
    # sm.get() espera ter um valor na fila de recepção e o pega
    # O shift e o and jogam fora start, paridade e stop e alinham o scancode à direita
    lido = (sm.get() >> 22) & 0xFF
    print ('Lido = {:02X}'.format(lido))
A montagem para teste ficou assim:
  • A alimentação do teclado vem do VBus do PiPico
  • O terra do teclado vai para qualquer pino GND do Pi Pico
  • O sinal de dado do teclado vai para um resistor de 22K cuja outra ponta está ligada ao GPIO 14 do PiPico e a um resistor de 33K com a outra ponta em GND. Os resistores formam um divisor de tensão para reduzir os 5V do teclado para os 3V que o PiPico aguenta.
  • O sinal de clock do teclado vai para um resistor de 22K cuja outra ponta está ligada ao GPIO 15 do PiPico e a um resistor de 33K com a outra ponta em GND.

Executando o código através da IDE Thonny podemos observar os "scancodes" recebidos do teclado. Por exemplo, apertando F9 é gerado o scancode 01, soltando o F9 são gerados os scancodes F0 01.

01/05/22: Revisão geral, inclusão da figura da montagem

Nenhum comentário: