Problems with PIO implementation of quadrature encoder #13528
Replies: 7 comments 14 replies
-
Are you carefully filtering the edges from the encoder, so that they have clean transitions, and do you have the inputs in Schmitt-trigger mode? |
Beta Was this translation helpful? Give feedback.
-
How large is your PIO program? I faintly recall from an analysis sometime ago that programs are loaded into the state machine memory top-down. If your code is smaller than the state machine memory size, the computed jumps may go to a different address than intended. But then the code should fail. |
Beta Was this translation helpful? Give feedback.
-
When having a look at my similar program: # Quadrature encoder for RPi 2040 PIO
#
# Resolution: 4 in-/decrements per cycle.
#
# Original version (c) 2021 pmarques-dev @ github
# (https://github.com/raspberrypi/pico-examples/blob/master/pio/quadrature_encoder/quadrature_encoder.pio).
# Adapted and modified for micropython 2022 by rkompass.
#
# Quadrature encoding uses a state table in form of a jump table
# which is fast and has no interrupts.
# This program was reduced to take 'only' 25 of 32 available PIO instructions.
# The jump table has to reside at the beginning of PIO program memory, therefore
# the instructions are filled up with 7 nop()'s.
#
# The counter x is permanently pushed nonblockingly to the FIFO.
# Reading the actual value requires emptying the FIFO then waiting for and getting the next pushed value.
# The worst case sampling loop takes 14 cycles, so this program is able to read step
# rates up to sysclk / 14 (e.g., sysclk 125MHz, max step rate = 8.9 Msteps/sec).
#
# SPDX-License-Identifier: BSD-3-Clause
#
#
# --------------------------------------------------------
#
# Example of usage:
# -----------------
# #
# # pinA = Pin(8, Pin.OUT) # Pins for generation of the quadrature signal, if you do not want to use switches.
# # pinB = Pin(9, Pin.OUT) # Then connect pin 6 with pin 8, and pin 7 with pin 9 on the Raspi Pico.
# # qgen = QGen_Pio((pinA, pinB), 250, freq=60_000)
#
# pinX = Pin(6, Pin.IN, Pin.PULL_UP)
# pinY = Pin(7, Pin.IN, Pin.PULL_UP)
# qenc = QEnc_Pio_4((pinX, pinY), freq=125_000_000)
#
# # qgen.start()
# for i in range(200):
# print('x:', qenc.read())# , qgen.running())
# sleep_ms(100)
#
# --------------------------------------------------------
from rp2 import PIO, StateMachine, asm_pio
# ----- Quadrature encoder class, using RPI2040 PIO. Resolution: 4 in-/decrements per cycle --------
class QEnc_Pio_4:
def __init__(self, pins, sm_id=0, freq=10_000_000):
if not isinstance(pins, (tuple, list)) or len(pins) != 2:
raise ValueError('2 successive pins required')
pinA = int(str(pins[0]).split(')')[0].split('(')[1].split(',')[0])
pinB = int(str(pins[1]).split(')')[0].split('(')[1].split(',')[0])
if abs(pinA-pinB) != 1:
raise ValueError('2 successive pins required')
in_base = pins[0] if pinA < pinB else pins[1]
self.qenc = StateMachine(sm_id, self.sm_qenc, freq=freq, in_base=in_base)
self.qenc.exec("set(x, 0)") # instead of sm_qenc.restart()
self.qenc.exec("in_(pins, 2)")
self.qenc.active(1)
@asm_pio(in_shiftdir=PIO.SHIFT_LEFT, out_shiftdir=PIO.SHIFT_RIGHT)
def sm_qenc(): # AB AB !!! *** this logic is wrong, it's BA, the lower pin always is on the right side
jmp("read") # 0000 : from 00 to 00 = no change
jmp("decr") # 0001 : from 00 to 01 = backward
jmp("incr") # 0010 : from 00 to 10 = forward
jmp("read") # 0011 : from 00 to 11 = error
jmp("incr") # 0100 : from 01 to 00 = forward
jmp("read") # 0101 : from 01 to 01 = no change
jmp("read") # 0110 : from 01 to 10 = error
jmp("decr") # 0111 : from 01 to 11 = backward
jmp("decr") # 1000 : from 10 to 00 = backward
jmp("read") # 1001 : from 10 to 01 = error
jmp("read") # 1010 : from 10 to 10 = no change
jmp("incr") # 1011 : from 10 to 11 = forward
jmp("read") # 1100 : from 11 to 00 = error
jmp("incr") # 1101 : from 11 to 01 = forward
label("decr")
jmp(x_dec, "read") # 1110 : from 11 to 10 = backward !!! we cannot change all incr <-> decr because
label("read") # 1111 : from 11 to 11 = no change !!! this next read has to be here after a decrement
mov(osr, isr) # save last pin input in OSR
mov(isr, x)
push(noblock)
out(isr, 2) # 2 right bits of OSR into ISR, all other 0
in_(pins, 2) # combined with current reading of input pins
mov(pc, isr) # jump into jump-table at addr 0
label("incr") # increment x by inverting, decrementing and inverting
mov(x, invert(x))
jmp(x_dec, "here")
label("here")
mov(x, invert(x))
jmp("read")
nop()
nop()
nop()
nop()
nop()
nop()
nop()
def read(self):
for _ in range(self.qenc.rx_fifo()):
self.qenc.get()
n = self.qenc.get()
return -n if n < (1<<31) else (1<<32)-n # !!! *** therefore we change the polarity of counting here I notice that the saving of the pin input before writing the counter to isr, and restoring it, seems essential:
Could it be that this is missing in your code? Your code is more complicated than mine in this respect, so I could not exclude that at a first inspection. Feel free to try this one out. Note the fill-up with |
Beta Was this translation helpful? Give feedback.
-
@JeffreyUitbeijerse You might like to read this doc on the general principles. |
Beta Was this translation helpful? Give feedback.
-
@JeffreyUitbeijerse
and the OSR is not used otherwise.
sequence is not justified. So my next guess is that
that first removes the older results before getting the actual one. |
Beta Was this translation helpful? Give feedback.
-
Hi all, Thank you for all the suggestions already, I will provide some answers to them below.
|
Beta Was this translation helpful? Give feedback.
-
I previously asked if the pins were in Schmitt-trigger mode. Are they set up this way? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi all,
For my graduation assignment I need to build a test setup which contains 6 quadrature encoders. I want to use the TI LAUNCHXL-F28379D to run a control algorithm, but this board can only read up to 3 quadrature encoders. Therefore my proposed solution was to read the 6 encoders using the state machines on the Raspberry Pi Pico, and send the counts to the F28379D through SPI.
I am new to the Raspberry Pi Pico, MicroPython and state machines, but using several examples online I managed to write my own program, which reads a quadrature encoder using the A and B channel, and also resets the counter once the Index channel is high for the first time. At first glance the program seems to do its job perfectly, but upon further inspection I encountered the following problem:
I tested the algorithm using a miniature positioning stage, which contains an encoder (grey), a voice coil motor and some flexures, as shown below:
Using the mechanism, I can oscillate the encoder with a fixed amplitude and frequency. Doing so results in the following encoder readings on the F28379D:
compared to the readings from my algorithm on the Pico:
It seems to me that the algorithm increments the counter faster than it decrements the counter, even though I implemented delay cycles such that each possible action (increment, decrement, no update) takes 12 clock cycles. I added the code down below for reference. Does anybody have an idea of what could be the issue here? Thanks in advance.
Beta Was this translation helpful? Give feedback.
All reactions