Interrupt edge detection
Matthias09
Posts: 47
Hey guys,
I am just about writing a program to read an encoder. To not miss a step I count both on falling and rising edges of Channel A, whenever Channel B is high. Therefore I switch the edge detection mode of Wakeup Byte WKED_B after every counted step from rising to falling and vise versa.
Now I encountered the problem, that when WKED_B = %00000000 it counts only the rising edge of Channel A (which sits on Bit 0), like it should. However, when I set the WKED_B = %11111111 it detects BOTH rising and falling edges, rather than only falling ones.
How come and what to do? [noparse]:)[/noparse]
Here is my interrupt routine. Please don't bother about the index channel part. That works fine. (At the startup, Ch A is disabled until the index channel fires the first time and the system hence has found it position. During turning I keep the index channel interrupt and reset the angle to the default value every time for higher accurancy.)
This is what I do at startup:
Thank you very much!
Matthias
Post Edited (Matthias09) : 8/12/2009 3:20:42 AM GMT
I am just about writing a program to read an encoder. To not miss a step I count both on falling and rising edges of Channel A, whenever Channel B is high. Therefore I switch the edge detection mode of Wakeup Byte WKED_B after every counted step from rising to falling and vise versa.
Now I encountered the problem, that when WKED_B = %00000000 it counts only the rising edge of Channel A (which sits on Bit 0), like it should. However, when I set the WKED_B = %11111111 it detects BOTH rising and falling edges, rather than only falling ones.
How come and what to do? [noparse]:)[/noparse]
Here is my interrupt routine. Please don't bother about the index channel part. That works fine. (At the startup, Ch A is disabled until the index channel fires the first time and the system hence has found it position. During turning I keep the index channel interrupt and reset the angle to the default value every time for higher accurancy.)
INTERRUPT ISR_Start: WKPND_B = int_storage 'Channel A Interrupt IF int_storage <> %0001 THEN Index_Channel 'count only, if A alone fires (other possible combinations: 0010: index fires, 0011: index + A fires) IF signal_B = 0 THEN ISR_Exit 'count only, if B is high toggle led1 IF edge = 0 THEN edge_0 'check the current edge detection mode, angle = angle + 1 'increment WKED_B = %11111111 'alter edge detection to falling edge on A edge = 0 'change edge detection mode variable to 0 for next interrupt toggle led3 GOTO ISR_Exit 'all done, leave edge_0: angle = angle - 1 'decrement WKED_B = %00000000 'alter edge detection to rising edge on A edge = 1 'change edge detection mode variable to 1 for next interrupt toggle led4 GOTO ISR_Exit 'all done, leave 'Index Channel Interrupt Index_Channel: 'if index fires (no mater if A also fires), then reset angle to 90 deg. WKEN_B = %11111100 'enable channel A interrupt (actually only has to be done after the first fire (as A is initally disabled). 'but to a routine would slow the process, which is not good. angle = 90 'reset angle Toggle Led2 ISR_Exit: WKPND_B = %00000000 'clear pending register, ALL bits RETURNINT 'end of interrupt handler
This is what I do at startup:
PROGRAM Start Start: WKPND_B = %00000000 'clear pending register WKED_B = %00000000 'rising edge detect on the whole B Byte. WKEN_B = %11111101 'but only enable Bit 1 for interrupt for startup. All other Bits (except 0) can be used as I/Os without creating an interrupt edge = 1 'first edge detection mode is rising edge (I define: rising edge = 1, falling edge = 0)
Thank you very much!
Matthias
Post Edited (Matthias09) : 8/12/2009 3:20:42 AM GMT
Comments
It is probably detecting a glitch and not really both rising and falling edges.
It might help to enable the schmitt trigger option for the pins.
What I would do is to run EACH input into 2 pins. Set one to interrupt on the rising edge, and the other to interrupt on the falling edge. Instead of trying to change the WKED_B register with every edge.
Bean
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Does that byte of memory hold "A", 65, $41 or %01000001 ?
Yes it does...
·
thanks for the idea!
I have implemented it an it basically works. However, I have the problem, that when turning the encoder in one direction (incrementing, interrupt %0010, see below), an interrupt occurs randomly which should only fire, when turning the encoder in the other one (the %0011 interrupt). It happens at all turning speeds. However, turning in the other direction works perfectly fine. How can this be? Is it the Schmitt Trigger thing? How can I activate it, if applicable?
Find my working code attached:
I am curious: what are the advantages to use two pins? so far I see the following ones:
- shorter interrupt routine, less interrupts are skipped therefore
- due to I now have two pins detecting the signal in different ports, that are alternating interrupting the program, the second interrupt is put into waitlist, when the first one is processed, rather than just ignored
Best,
Matthias
Post Edited (Matthias09) : 8/13/2009 3:09:27 AM GMT
Here again what is happening (copied from above): when turning the encoder in one direction (incrementing, interrupt %0010, see below), an interrupt occurs randomly which should only fire, when turning the encoder in the other one (the %0011 interrupt). It happens at all turning speeds. However, turning in the other direction works perfectly fine. How can this be? Is it the Schmitt Trigger thing? How can I activate it, if applicable?
Matthias
Skip all this interrupt on each line stuff. There is a much simpler way to decode quadrature encoders.
1 - Create a lookup table with 16 entries. The values should be the following:
2 - Create an RTCC interrupt. The frequency of the interrupt should be at least twice as high as the highest frequency clock generated by the encoder. For example, if you have an encoder with 100 pulses per revolution (PPR) and the fastest it will turn is 10 revs per second (RPS), then the encoder's fastest clock generated will be 100 PPR X 10 RPS = 1000 pulses per second. You would need to have the RTCC interrupting at a rate of at least 2000 Hz.
3 - When the RTCC interrupt occurs, your interrupt handler needs to sample the state of the two encoder lines. It then needs to take the state of the inputs from the previous interrupt (more about that in a moment), shift them two places to the left, and OR them with the current state.
4 - The value you just calculated will create an entry into the lookup table. The value from the lookup table will either by 1 (turned one count forward), -1 (turned one count backward), or 0 (no change).
5 - Finally, store the current value of the inputs for use next time. If you want, you can pre-shift the value two places to the left.
Assuming you have the encoder attached to bits 0 and 1 of Port A, the pseudo-code, it looks like this:
As long as you sample at a rate at least twice the highest input clock value, you'll never miss an encoder pulse. This technique also has the added bonus of counting every clock edge, so it increases the number of clocks from the encoder by 4. Finally, this technique eliminates any problems that occur if the encoder stops with the disk partially covering the optical sensor. This can create a condition where the clock toggles high/low, making it seem as though the encoder is moving when it’s simply the electronics being unable to settle on a logic state. Similar problems occur with contact bounce on a mechanical encoder. Anytime this happens, the lookup table will simply return zero, thus the encoder position variable doesn’t show a change in position.
Thanks,
PeterM
SCHMITT is covered in SX/B_Help, it's only usable with SX48.
Mechanical rotary encoder?· Maybe some external debounce circuitry (see attached)?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Greetings from Germany,
G
I appreciate your support!
PJMonty: looks up-in-the-sky and I tried and got it working! However: I monitored the variable CurrentPosition and it only has values between 0 and 1. What's wrong? My clue: I am not sure, if I can assign negative values to variables in SX/B. The compiler says ok, but I think I read somewhere that is not possible. Also your notation didn't work, so I adjusted, which was no problem but made me ask, if this code run on a SX28 before?
PJ Allen: it is an optical encoder, US Digital. Thanks for the hint with the SCHMITT command. SX 28 can use ST_B instead. However in the help file nothing is docummented about that. Any idea where I can find some information about that command?
Post Edited (Matthias09) : 8/15/2009 12:01:28 AM GMT
SCHMITT
Configures the internal Schmitt trigger for Pin on the SX48 or SX52. This command does not apply to the SX18,
SX20, or SX28 (use the ST_B and ST_C registers).
I cannot use it for my SX 28 Device. It says: NOT ALLOWED ON THIS DEVICE.
Post Edited (Matthias09) : 8/15/2009 12:05:45 AM GMT
Right!
Thanks for this tip! It works great, great, great!!
For all who are interested: SCHMITT command is possible with the SX28, but cannot be changed in the Program (after Start[noparse]:)[/noparse], like with the SX48. So declaration has to be in the beginning:
Matthias
I have used this algorithm on different processors before, but in C. Since I don't normally use SX/B, I'm just not familiar enough with its syntax, which is why I wrote it out in pseudo-code.
Have you verified that both outputs on your encoder are producing a signal? A oscilloscope is the easiest way to check, but you could also do it with a voltmeter if you turn the encoder very slowly to catch the signal going high and low. If one of your outputs is dead (or not hooked up right), then that would explain why you only get one direction. You can swap the A and B output leads from the encoder to the SX and see if you only get -1 and 0. If so, then you know something is up with one of the channels.
I have used US Digital optical encoders before. They are well made and won't need any debouncing.
BTW, you should move the table creation out of the interrupt loop. Build it once at the beginning of your code and then use the table from within the interrupt handler. Also, if you want, you can change the line:
PrevEncoderInput = PrevEncoderInput * 4
to read:
PrevEncoderInput = PrevEncoderInput SHL 2
This will explicitly tell the compiler to left shift the value two places rather than hoping the compiler optimizes it correctly.
Also, you can test if negative values are allowed in SX/B tables by writing a simple bit of code like this:
CurrentPosition = 100;
CurrentPosition = CurrentPosition + EncoderTable(2)
Here you are setting an initial value of 100 in the variable, and then adding a table entry to it that has a negative value. If "CurrentPosition" = 99 after that line is run, then you know SX/B allows negative values in tables.
Thanks,
PeterM
At start up, only the index channel interrupt is enabled, the system has to find it's reference point. once the index channel interrupt fires, both encoder channel interrupts will be enabled and the program starts to count the angle. Whenever the encoder bypasses the index channel again, the angle is 'reset' to the default value (90deg), making use of the additional information of having an absolute reference point.
Both edges of the A channel signal were detected, both on different pins, so one pin only detects rising, the other only falling edges.
Post Edited (Matthias09) : 8/15/2009 7:37:09 PM GMT
Thanks for the advice! As I got the encoder now run with minimal adjustment of my existing code (see above), I will stick to that. I checked if negative values can be assigned though. Other than stated before it is possible in SX/B. Setting a variable = -1 is stored as 255. Using the example below returns:
CurrentPosition = 100;
CurrentPosition = CurrentPosition + EncoderTable(2)
CurrentPosition = 100+255 = 99
Sincerely!
Matthias