Quadrature encoder interface callback, does what, exactly?

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
User avatar
dbc
Posts: 89
Joined: Fri Aug 28, 2015 11:02 pm
Location: Sunnyvale, CA

Quadrature encoder interface callback, does what, exactly?

Post by dbc » Fri Aug 28, 2015 11:22 pm

If you set timer.channel(...mode=Timer.ENC_AB, callback=fun...), when is fun called? Is it called on encoder roll-over events?

What I'm looking at here is turning a 16 bit quadrature counter into a 32 bit counter, since ST in their infinite non-wisdom mapped timer2 and timer5 to conflicting I/O such that you can't use both of the 32 bit quadrature counters at the same time. If anyone has other ideas of how to handle the encoder roll-over I'd like to hear them.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Quadrature encoder interface callback, does what, exactly?

Post by dhylands » Sat Aug 29, 2015 7:14 am

The timer channel callback corresponds to input capture events (so rising/falling edges on the input lines).

The timer callback corresponds to timer rollover callbacks.

User avatar
dbc
Posts: 89
Joined: Fri Aug 28, 2015 11:02 pm
Location: Sunnyvale, CA

Re: Quadrature encoder interface callback, does what, exactly?

Post by dbc » Sat Aug 29, 2015 2:51 pm

So are you are saying that if you have the channel configured ENC_AB, you get a callback on every edge, or are you saying that if the channel is configured ENC_AB you get no call backs at all because it isn't in capture mode?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Quadrature encoder interface callback, does what, exactly?

Post by dhylands » Sat Aug 29, 2015 4:20 pm

Looking at the code, it would seem that when in ENC mode the channel callback is essentially a no-op:
https://github.com/micropython/micropyt ... 1282-L1313

If we were to enable it, the channel interrupt for the encoder would interrupt on each configured edge (rising, falling or both) on one or both of the channels. There wasn't a clean way to specify channel 1, channel 2, or both, which is why I didn't bother implementing it at the time.

When the timer is configured as ENC_AB, the channels are in fact in an input capture mode.

User avatar
dbc
Posts: 89
Joined: Fri Aug 28, 2015 11:02 pm
Location: Sunnyvale, CA

Re: Quadrature encoder interface callback, does what, exactly?

Post by dbc » Sat Aug 29, 2015 7:20 pm

OK, fair enough. Perhaps a documentation tweak is in order, I may take a stab at that. Could you point me at the C source file for the timer code in question? (The doc I was reading is in library/pyb.Timer.rst so I can update that with a couple of lines.)

As to how to define a callback for ENC_AB -- it doesn't really seem useful to me to have a callback on every edge, after all, the counter should be taking care of the counting. Seems to me only the roll-over event is interesting, but that is a bit dicey because quad decoders are notorious for roll-over fake-outs. It might be rocking back and forth across the roll-over and actually count over and then back again a few times before the interrupt handler even starts executing. So making sure you really *have* a carry before counting the carry is a little bit hairy.

--EDIT--

I had to blast out the door pretty quickly when I made the above post, and I just wanted to add a little bit to it now that I am back.

As to definition of callback for ENC_AB, since the point of using the hardware for encoding the quadrature signals is to avoid having to take a software interrupt for every edge, I don't see where edge interrupts make sense for ENC_AB mode. Especially in the case where you are looking at a motor with a high resolution encoder, an interrupt per edge could swamp the processor in interrupts. Reading the data sheet (and I must confess here that I haven't used ST parts much so I haven't dug deeply into ST's data sheets) it looks like you can configure the counters to generate interrupts only on roll-over events. So the hardware would at least support interrupt-on-carry-out callbacks.

Now, the trick is to make the carry signal reliable in the case where the counter is sitting on the edge rocking back and forth. My suggestion would be to add a function called carry() that returns 1, -1, or 0, based on the current MSB and the state of the MSB the last time carry() was called. The callback function could call carry() to get a reliable amount to add to the high-order bits of the counter carried in software, even when a high-speed encoder rocks back and forth several times before the Python interrupt handler gets started. So:

Code: Select all

def counter_carry_handler(aChannel):
    global high_bits
    high_bits += aChannel.carry()
Of course, this assumes that the handler (and carry() ) get executed before the counter gets half way around again, but I think that is reasonable.

An alternative might be defining an ENC_AB32 mode for the 16 bit counters that makes them appear to be a 32 bit counter for the purposes of quadrature decoding, and burying the details in the library implementation. That solves *my* specific problem, but may be less general and is not as closely tied to the actual hardware implementation.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Quadrature encoder interface callback, does what, exactly?

Post by dhylands » Sat Aug 29, 2015 9:11 pm

The source code for the timer code is in stmhal/timer.c

You should be able to make a little state machine which takes care of the rollover.

You can query the counter, and if it's between 0 and ARR/2 then you can assume that you rolled over from ARR to 0, and if the counter is betweem ARR/2 and ARR then you can assume you rolled over from 0 to ARR.

Doing a carry function doesn't really work without the state machine since you're full of race conditions (because the HW updates the counter on its own).

So the state machine basically only needs 2 states: Previously on the high half (ARR to ARR/2) and previously on the low half (0 to ARR/2).

This is off the top of my head, but it feels right to me:
If you were previously on the low half and the counter wrapped and is currently on the low half then you do nothing
If you were previously on the low half and the counter wrapped and is currently on the high half, then decrement your upper 16-bits
If you were previously on the high half and the counter wrapped and is currently on the low half then increment your upper 16-bits
If you were previously on the high half and the counter wrapped and is currently on the high half then you do nothing.

This makes the counter self correcting due to rocking.

Whenever you use a split counter like this (half SW and half HW) you should always query the HW twice in a row and make sure you're on the same side of the transition point, otherwise you can still have a race condition. So something like this:

Code: Select all

loop:
    query HW
    query SW
    query HW
    if HW didn't wrap between the 2 queries then you know HW and SW are consistent with each other
    else repeat.

User avatar
dbc
Posts: 89
Joined: Fri Aug 28, 2015 11:02 pm
Location: Sunnyvale, CA

Re: Quadrature encoder interface callback, does what, exactly?

Post by dbc » Sat Aug 29, 2015 9:51 pm

Your comments make me think that all of the work of creating a wider QEI with software should be pushed into the library and encapsulated as much as possible. How about this:

* Define an ENC_AB32 mode for channels of those 16 bit counters that support QEI
* Both ENC_AB and ENC_AB32 ignore the callback parameter
* ENC_AB32 synthesizes a 32 bit QEI in software
* the .counter() method returns a 32 bit value instead of a 16 bit value when mode is ENC_AB32
* the .counter([value]) method extends [value] to 32 bits and sets the synthetic counter, adjusting hardware as appropriate.

This creates an abstraction of a 32 bit QEI with minimal disruption to the API. It pushes all the nasty end cases into the library. On the downside, it isn't a 1:1 mapping onto the hardware registers, if that is a downside.

--EDIT--
So after further reflection while driving up and down highway 85, I think simply defining the callback for ENC_AB to be roll over is sufficient. The roll-over state machine can be implemented adequately in Python in the callback. Reading an extended-precision counter requires more Python code to implement the idiom you describe, but while non-obvious it isn't nasty. Adequate documentation should spare people tears in making long quadrature decoders work. I'll see if I can cook up a patch.

--EDIT 2--
Am I over-thinking this? The Counter object callback gets called on overflow for other modes, so isn't that same interrupt also generated for ENC_AB mode over/under flows? So it seems instead of hanging a callback off the Channel set to ENC_AB mode, an ordinary callback hung off the Counter is what I've been looking for all along. Correct?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Quadrature encoder interface callback, does what, exactly?

Post by dhylands » Sun Aug 30, 2015 5:26 am

There is both a timer callback and a timer channel callback

The timer callback would deal with counter overflow

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: Quadrature encoder interface callback, does what, exactly?

Post by SpotlightKid » Sun Aug 30, 2015 11:11 am

I wrote this library to read encoders:

https://github.com/SpotlightKid/micropy ... er/encoder

It uses external pin interrupts and gray code error checking and works very reliably without hardware or software debouncing. IMHO there's no need to fuss with timers and ENC mode. Also, you can use more encoders, since there are 16 GPIO external interrupt lines. You need two for each encoder.

User avatar
dbc
Posts: 89
Joined: Fri Aug 28, 2015 11:02 pm
Location: Sunnyvale, CA

Re: Quadrature encoder interface callback, does what, exactly?

Post by dbc » Sun Aug 30, 2015 4:21 pm

It uses external pin interrupts and gray code error checking and works very reliably without hardware or software debouncing. IMHO there's no need to fuss with timers and ENC mode
Sure, that works for low precision encoders and low motor speeds. I've done that before with other micro controllers. The point of using ENC_AB is to reduce the interrupt rate.

The Counter call back should do what I need here, I somehow got inappropriately focused on the Channel call back. As I said above, I haven't used ST parts before, so I'm still climbing the learning curve on ST philosophy and ST data sheet organization, and of course the pyb module as well.

Post Reply