Quadrature encoders are a clever way of encoding positions by phase offset binary signals. Wanted to try these out to build a motor controller in a SAMD51-based control board but to do that wanted to be able to first test this so make an output generator, with internal testing for e.g. timing. Ohai rp2040 w/PIO.
Short version - the output over a full cycle over two GPIO pins wants to take the form of 00 / 01 / 11 / 10 - which is easily encoded in a single byte, output two bits at a time. Then want to write a program to test the timing for this on the same chip, so use one state machine to output the signal and a second to count time to make sure it looks right.
from machine import Pin
import rp2
import time
# this tells the PIO program we have two output pins
@rp2.asm_pio(out_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW))
def square():
mov(osr, isr)
out(pins, 2)
mov(x, y)
label("a")
jmp(x_dec, "a")
nop()
out(pins, 2)
mov(x, y)
label("b")
jmp(x_dec, "b")
nop()
out(pins, 2)
mov(x, y)
label("c")
jmp(x_dec, "c")
nop()
out(pins, 2)
mov(x, y)
label("d")
jmp(x_dec, "d")
@rp2.asm_pio()
def count_high():
mov(x, invert(null))
jmp(x_dec, "wait")
label("wait")
wait(1, pin, 0)
nop()
label("high")
jmp(x_dec, "next")
label("next")
jmp(pin, "high")
mov(isr, x)
push()
p0 = machine.Pin(0, machine.Pin.OUT)
p1 = machine.Pin(1, machine.Pin.OUT)
sm0 = rp2.StateMachine(0, square, out_base=p0)
sm0.put(62_500_000 - 4)
sm0.exec("pull()")
sm0.exec("mov(y, osr)")
sm0.put(0b01111000 << 24)
sm0.exec("pull()")
sm0.exec("mov(isr, osr)")
# very important in_base and jmp_pin point to the same pin
sm1 = rp2.StateMachine(1, count_high, jmp_pin=p1, in_base=p1)
sm1.active(1)
sm0.active(1)
for j in range(100):
count = sm1.get()
print("%d ns" % int((0xFFFFFFFF - count) * 16.0))
sm1.active(0)
sm0.active(0)
This is obviously a fairly trivial use of rp2040 PIO engines, but does achieve what I wanted: a measurable clock with an overall cycle time of 2s and 1s high on each case - the output consisted of a lot of 1000000000 ns
- faster is correct but the Python code to sm1.get()
is problematic - so tested on a ‘scope. Using DMA to read out to an array would solve this.
Key points for anyone trying to get started with this kind of thing:
jmp_pin
and in_base
to be definedout_base
to point at the first then out_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW)
to correspond to the correct number of output pinsThese are things I had forgotten since I had not played much with the PIO blocks for a few months. I suspect building a quadrature decoder with the PIO blocks would also be in play.