what's your best pasm code for a gray code quadrature encoder?
Has anyone a compact pasm code to sense direction and step on a 2 bit gray code encoder?
Is there anything better than converting to binary and substracting fron the last value?
Thanks in advance
Alex
Is there anything better than converting to binary and substracting fron the last value?
Thanks in advance
Alex

Comments
For those speed demons, I've thought that a small PLD can clock two counters, and the difference is the location.
I think that needs 3 channels, two as counters, and one to generate the PLD state engine clock.
Edit : The generic design would need 3 Counter, but I think the Prop allows this to shrink to 2 Counters (1 COG) if you use
CTRMODE %01011 POSEDGE detector w/ feedback INC@[A1 & !A2] BPIN: !A1
- those BPIN sigs then drive the PLD as ack-style handshakes, which can create a ack-clk, to advance the pld quad phase lock.
That uses 4 pins per Quad Counter : CLK_U, CLK_D, QFBn_U, QFBn_D
It may be possible to drop that to 3, if we flip to
CTRMODE %01111 NEGEDGE detector w/ feedback INC @[!A1 & A2] BPIN: !A1
- and map both BPIN to the same IO, and use the OR feature ?
Pins are now : CLK_U, CLK_D, PINB == (QFBn_U OR QFBn_D)
I'm guessing here that two CTRs can drive (OR) a single pin in FB mode ?
if CLKin is mostly HI, FB out will be mostly low, allowing OR to work.
Here, 2 Quad 'support' channels can fit into a SPLD, and you need 1 COG Counter pair, per HW Quad supported.
The PLD does the edge conditioning, and Phase-follow, and the Prop does the 32 bit dual (difference) counting. Maximum Quad Speed should be >20MHz-40MHz region.
Spinning Up Fun With Encoders
Duane J
Thanks for the idea, anyway the counters are used in the cog I'll use.
Alex
Thanks Duane, I bowsed the doc however I found it too verbose and inefficient.
I found on another site the clue I was looking for:
- After the usual step detection your direction is given by (pcode):
If New(A) == Old(B) then going down
else going up
The following example (untested) should do. Whenever I find a little time to hook up the test I'll try it out.
:ReadInput mov newscan, ina 'Get input shr newscan, basepin 'move encoder inputs to bits 1&0 and newscan, # $ 3 'Mask input xor newscan, oldscan 'Detect changes tjz newscan, #:ReadInput 'No change? -> keep reading xor newscan, oldscan 'Restore newscan test newscan, # $ 2 WC 'C= New A bit test oldscan, # $ 1 WZ 'Z= not old B bit mov oldscan, newscan 'Save input for next change mov posadj, resolution 'Init value to increment position 'If C<>Z that means New(A) == Old(B) 'If New(A) == Old(B) THEN we are going DOWN ' Else we are going UP if_C_ne_Z neg posadj, resolution 'If New A == Old B -> going down adds Position, posadj jmp #:ReadInputIt's so simple I just can't imagine something better. :cool:If you'd like to say that you are going UP instead of DOWN for New(A) == Old(B) then just swap the test pattern on the test instructions.
Alex
The classic Quad system uses 2 old values, and 2 new values to give 16 choices, 4 are hold, 4 are illegal, and 4 each for Up and Dn.
In Change Pseudo code that is usually along the lines of
IF A_Changed THEN IF NewA == NewB THEN DEC(QuadValue) ELSE INC(QuadValue) ELSIF B_Changed THEN IF NewA <> NewB THEN DEC(QuadValue) ELSE INC(QuadValue) added: and if you cannot guarantee poll rates, and/or want to trap illegal conditions you can add IF (A_Changed AND B_Changed) THEN StickyFlagError // Both signals should not change in one poll pass ELSIF A_Changed THEN IF NewA == NewB THEN DEC(QuadValue) ELSE INC(QuadValue) ELSIF B_Changed THEN IF NewA <> NewB THEN DEC(QuadValue) ELSE INC(QuadValue) // Implicit 4th option is Nothing changed == do nothingYour code is asymmetrical. The first conditional is also true if both change; the second, only if B changes and not A. You have to make a decision whether two changes entail a double jump due to an inadequate sampling rate or constitute an illegal condition.
-Phil
Correct, it is merely the counting code, not the error handling needed if you cannot guarantee you polling is faster than the edge rate.
In a fully working system, it is good practice to include code that catches and sticky-flags an illegal condition.
That way you know if you are not having illegal events. I've added that as an expanded example.
You should always catch +/-1step, but If the mechanical system's speed is near the speed limit of you SW loop, you could catch a double jump with:
if (newsample XOR oldsample) == $3 Flag sticky errorAnyway you shouldn't treat that as a double jump since you can't know for sure in which direction that was (if 2 bits changed then you are exactly at the opposite end of the table not knowing through which path you got there)Alex
If you want all 4 edges to count properly then I think you you do need more tests, but if you only want one count per 4 edges, then one ==, <> is ok.
Basically you can feed a Quad pair into a CLK.DIRN counter for one count per 4 edges.
There's no need for that once you know the inputs changed (with XOR between new_sample and old_sample)
With a single " IF new(A) == old(B) " you will count correctly on every edge. See the attached spreadsheet.
encoder.xls
Alex
Interesting.
If this was really 100% coverage, my PLD optimizer should give the same reduction.
Here is what the PLD optimizer says, spaced for clarity, note Qfb is old(B), Qia is new(A)
CLK_Dn.d => CLK_Dn & Qfb & Qia & Qib & !Qfa # CLK_Dn & !Qfb & !Qia & Qib & !Qfa # CLK_Dn & Qfb & Qia & !Qib & Qfa # CLK_Dn & !Qfb & !Qia & !Qib & Qfa Illegal => !Qfb & !Qia & Qib & Qfa << passes IF new(A) == old(B) test # Qfb & Qia & !Qib & !Qfa << passes IF new(A) == old(B) test # !Qfb & Qia & Qib & !Qfa # Qfb & !Qia & !Qib & QfaIt does not reduce to match your example, and the clue as to why more qualifiers are needed, is in the Illegal case.
Notice two of those illegal branches, pass your single test of " IF new(A) == old(B) "
So the correct coverage is given by first excluding the Illegal case, in a second test
If (Inputs_Changed AND Inputs_Legal) THEN
IF new(A) == old(B) THEN INC() ELSE DEC()
In the case of Prop+PASM, you may be able to use the unique feature that CY is Parity result, as No Change and Illegal are both even parity, and one-only-changed are odd-parity ?
entry mov combined, #0 :loop mov look, lookyup ' load look up table mov state, ina ' Read inputs and state, pinmask ' Mask shl combined,#2 ' Shift old bits left or combined,state ' combine to provide 4 bit word and combined,#15 ' mask off really old bits mov shift,combined ' Make ready to use as index shl shift,#1 ' Shift to multiply by two shl look,shift ' Use to shift look up table bits to bits 30,31 sar look,#30 ' Shift to bits 0 and 1 keeping the sign adds count,look ' Add the result to the count jmp #:loop pinmask long %0011 count long 0 combined long 0 ' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 lookyup long %00_01_11_00_11_00_00_01_01_00_00_11_00_11_01_00 state res 1 look res 1 shift res 1http://forums.parallax.com/showthread.php?89954-quadrature-encoders/page2
That's a brilliant piece of code! -- and from 2007, too, when we were all still learning the ins and outs of the Prop!
-Phil
Graham
-Phil
Exactly! That's what I meant with " once you know the inputs changed (with XOR between new_sample and old_sample)"
Result of current_input XOR previous_input
00
Nothing changed => loop and keep reading
01
1 step detected => PROCESS
10
1 step detected => PROCESS
11
2 steps detected => ILLEGAL
Results of 00 and 11 don't get processed
Results 01 and 10 signal that we received a single step, now we need only to determine it's direction. You go through the single "IF new(A)==old(B) THEN..." for both cases and you get the right direction. (only this last step is modeled in the excel file above)
Alex
It may be what you meant, but it not what you actually said, or what your code does.
You need to re-word that test to be
once you know the inputs legally changed
but the 11 (both changed) really does need to be caught, before you apply the "IF new(A)==old(B) THEN" test.
I'm not sure I'd call that "does not get processed" ?
If you fail to test/catch that case, your INC/DEC coverage is different from a classic Quadrature design, in that you will (wrongly) change on an illegal case.
I think in PASM, a IF_Z_OR_NC test is able to exit on (NoChange OR Illegal )
Alex
entry mov old, #0 :loop mov state, ina ' Read inputs and state, #%11 ' Mask xor state, old WC 'detect pin changes C=0 if 00/11 xor state, old 'restore state if_nc jmp #:skip 'no changes or ILLEGAL -> ignore test state, #%01 WC 'C = new(B) test old, #%10 WZ 'Z = not old(A) if_C_eq_Z add count, #1 'Going UP if_C_ne_Z sub count, #1 'Going DOWN :skip mov old, state 'save for next round jmp #:loop count long 0 old long 0 state res 1If I did correctly the math It's even 5 longs shorter and 1 instruction faster than Graham's code.Alex
YES! Thanks for reminding me of NR, I use it so little I always forget it's there. So now we shaved another long. :cool:
On the sum[?] I'm not so sure. The key is to add (or substract) when one bit on the new sample is the same as the other bit of the previous sample. It doesn't matter if it's 0 or 1, just that it be the same. How can we do that with sum[?] ?
Alex
test status, #%10 wc muxc old, #%10 wc sumc count, #1Bit shifting and masking, for me at least, can be hard to follow. I know that this is not native PASM but the PropBASIC compiler that I'm currently evaluating produces PASM and at any time, there should be no
more than approximately 10 instructions executing.
At this point, I didn't bother with the incrementing/decrementing of the counter variable but would like opinions as to whether this looks feasible.
Edit: Just added the counter Inc/Dec stuff.
Cheers!
Mickster
Absolutely COOL :cool:. hand tested modifying the excel above shows it should work. I'll try it tonight.
We keep shaving longs...
I'll post the final tests tonight (I hope)
Alex
If you want to push down in size, a JMP to another JMP never 'looks good', and I like to speed the fastest path, and then use that time for optional 'other stuff'.
eg It is nice to have an (optional?) line that can somehow flag illegal cases, (either toggle a pin, or a sticky pin ) as that can indicate the SW is not keeping up.
This should be possible without increasing the longest path time ?
Roughly sketched :
:ChkValid if_nc_and_nz 'Pin opcode. Optional Signal illegal with Toggle or Set :Reload mov old, state ' save for next round :loop mov state, ina ' Read inputs and state, #MaskQaQb ' Mask just two pins. xor state, old WC, nr ' detect pin changes C=0 if 00/11, Z=0 if 00 if_nc jmp #:ChkValid ' no changes or ILLEGAL -> ignore ' action blockSW pace issues can sneak up as users add more code to the loop (Chips time sliced COG is going to be great...)
- or they might splice on multiple Quad channels.
Here's an encoder simulator with the PASM code under test and the results seen
Encoder 1 - Alex.spin
Have Fun!
Alex