pi-pico-pio-quadrature-encoder icon indicating copy to clipboard operation
pi-pico-pio-quadrature-encoder copied to clipboard

Reading the position sometimes returns values near 2^32

Open firelizzard18 opened this issue 1 year ago • 7 comments

I'm trying to use this project to read an encoder with an Adafruit Feather RP2040. Here's my test program:

#include "hardware/pio.h"
#include "quadrature.pio.h"

PIO pio = pio0;
uint sm;

void setup() {
  uint offset = pio_add_program(pio, &quadrature_program);
  sm = pio_claim_unused_sm(pio, true);
  quadrature_program_init(pio, sm, offset, D10, D9);

  Serial.begin(115200);
  while (!Serial);
  Serial.println("Hello!");
}

void loop() {
  static uint pos = 0;
  pio_sm_exec_wait_blocking(pio, sm, pio_encode_in(pio_x, 32));
  uint newPos = pio_sm_get_blocking(pio, sm);
  if (pos != newPos) {
    Serial.print("Pos = ");
    Serial.print(newPos);
    Serial.println();
    pos = newPos;
  }
}

It generally works but as I rotate the encoder I see weird values:

Pos = 157
Pos = 158
Pos = 159
Pos = 4294967136
Pos = 159
Pos = 160
Pos = 159

That value (4294967136) is 2^32 - 160, so I think there's some weird range or sign error or something.

firelizzard18 avatar Apr 30 '23 02:04 firelizzard18

it initializes to 0, so if you go counterclockwise, you'll get a negative number. Change your "uint" to "int" and I think that will make it show as the negative number if you want that.

jamon avatar Apr 30 '23 16:04 jamon

The issue occurs when I’m rotating it clockwise. The number will suddenly jump then go back. Looking at the PIO code I’m pretty sure it’s because the fetch instruction gets executed in between the mov x, !x operations

firelizzard18 avatar Apr 30 '23 16:04 firelizzard18

ah--that makes sense. Maybe I'll add a DMA enabled version where it pushes an update on every change and you just DMA it to a memory location that you can read from code whenever you like.

jamon avatar Apr 30 '23 17:04 jamon

there's a much easier fix: don't invert the X register and use Y to do the increment:
mov Y, !X
jmp Y--, next
mov X, !Y

This way the value in X is always good to be read.

Also, the BUG comment in the PIO code really is a bug: the code likely only increments 1 per 4 steps instead of 1 per 2 steps, because in half the inc/dec operations the code runs through both and leaves X with the previous value.

pmarques-dev avatar Jun 02 '23 01:06 pmarques-dev

I've switched to using the shift register:

.program quadrature0
.wrap_target
    wait 0 PIN 0
    in PINS, 2
    wait 1 PIN 0
    in PINS, 2
.wrap

.program quadrature1
.wrap_target
    wait 0 PIN 1
    in PINS, 2
    wait 1 PIN 1
    in PINS, 2
.wrap

Thus all my code needs to do is read the FIFOs. At one point I was using ISRs instead but Arduino has its own special interrupt code and I didn't care enough to figure out how to get that to play nice with the PIO interrupts.

firelizzard18 avatar Jun 02 '23 16:06 firelizzard18

@firelizzard18 --that's definitely a simpler solution, but for the sake of others reading the issue--the main difference in implementation is that the one in this repo does not require active work by the processor--it is fully asynchronous and happens in the background. The one mentioned in the comment above requires that the processor read it frequently to avoid skipping steps, and is time-sensitive (not a knock on the solution, just writing this so that less experienced readers understand the reasons for the complexity)

jamon avatar Jun 02 '23 16:06 jamon

I'm just sharing my code in case it's useful to someone else. My use case is sending USB keyboard events when the encoder ticks so I have no need for a counter.

firelizzard18 avatar Jun 02 '23 16:06 firelizzard18