Using the PIO to Interface a PS/2 Keyboard
One of the most intriguing features of the RP2040 used in the Raspberry Pi Pico is the PIO - Programmable Input/Output. It allows us to execute small programs that handle digital input and output independently to the ARM Cortex M0+ Cores.
For my first study of the PIO I decided to interface a PS/2 keyboard. In the past I have interfaced one of these keyboards to a Arduino Nano, reading the bits one by one in an interrupt generated by the clock signal. Using the PIO, the main processor will receive a complete byte in one go.
![]() |
| An old but working PS/2 keyboard |
The figure bellow shows the pinout of the PS/2 connector:
- The RP2040 has two PIO blocks
- Each PIO block has four independently running state machines.
- Each PIO block has a program memory, shared by the four state machines, with room for 32 PIO instructions.
Each PIO state machine has:
- Two 32 bit shift registers
- Access to all 30 GPIO pins for digital input and output (four of these bits are used for connecting the Flash memory and should not be messed)
- Access to two 32 bits, four position, queues for bidirectional communication with the ARM cores. and DMA. If you are communicating only in one direction, you can reconfigure them to a single 8 position queue.
There are only nine PIO instructions: JMP, WAIT, IN, OUT, PUSH, PULL, MOV, IRQ, e SET. Writing a program from scratch can be daunting. Thankfully the Raspberry Foundation gives many examples in https://github.com/raspberrypi/pico-examples/tree/master/pio.
In our case the can start from the "clocked input" example: reading bits signaled by clock pulses. In this example the PIO program has only three instructions (wrap_target and wrap affect the configuration):
wrap_target()
wait 0 pin 1
wait 1 pin 1
in pins, 1
wrap()
- The rest state for the clock is high, the data is sampled in the falling edge
- We have a total of 11 bits
import time
import rp2
from machine import Pin
# in_shiftdir=rp2.PIO.SHIFT_RIGHT -> shift received bits to right
# autopush=True, push_thresh=11 -> push to receive queue when 11 bits are shifted
# fifo_join=rp2.PIO.JOIN_RX -> join tx queue into rx queue
@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()
# Configure input pins
pin14 = Pin(14, Pin.IN, Pin.PULL_UP)
pin15 = Pin(15, Pin.IN, Pin.PULL_UP)
# A 100kHz clock for the State Machine is enough for the (around) 10kHz clock of the keyboard
# Input pin numbers for the State Machine start at pin 14
sm = rp2.StateMachine(0, rdKbd, freq=100000, in_base=pin14)
# Activate the State Machine
sm.active(1)
while True:
# sm.get() waits for a value in the rx queue and returns it
# The shift and and discard the start, parity and stop bits and align the scan code to the right
key = (sm.get() >> 22) & 0xFF
print ('Key = {:02X}'.format(key))




Comments
Post a Comment