Quadrature encoder interface callback, does what, exactly?
Quadrature encoder interface callback, does what, exactly?
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.
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.
Re: Quadrature encoder interface callback, does what, exactly?
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.
The timer callback corresponds to timer rollover callbacks.
Re: Quadrature encoder interface callback, does what, exactly?
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?
Re: Quadrature encoder interface callback, does what, exactly?
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.
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.
Re: Quadrature encoder interface callback, does what, exactly?
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:
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.
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()
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.
Re: Quadrature encoder interface callback, does what, exactly?
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:
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.
Re: Quadrature encoder interface callback, does what, exactly?
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?
* 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?
Re: Quadrature encoder interface callback, does what, exactly?
There is both a timer callback and a timer channel callback
The timer callback would deal with counter overflow
The timer callback would deal with counter overflow
-
- Posts: 463
- Joined: Wed Apr 08, 2015 5:19 am
Re: Quadrature encoder interface callback, does what, exactly?
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.
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.
Re: Quadrature encoder interface callback, does what, exactly?
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.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
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.