Shop OBEX P1 Docs P2 Docs Learn Events
Interrupt edge detection — Parallax Forums

Interrupt edge detection

Matthias09Matthias09 Posts: 47
edited 2009-08-15 19:47 in General Discussion
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.)

 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

  • BeanBean Posts: 8,129
    edited 2009-08-12 11:12
    Matthias,
    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...


    ·
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-12 23:53
    Bean,

    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:

    
     Signal_A1     PIN RB.0 INPUT             'Channel A
     Signal_A2            PIN RB.1 INPUT            'Channel A too
     Signal_I         PIN RB.2 INPUT             'Index Channel
    
    
    INTERRUPT
    
     ISR_Start:
    
     WKPND_B = int_storage
    
     IF int_storage = %0011 THEN             'Channel A1 interrupts on rising edge: A2 then is 1. 
    
        IF signal_B = 1 THEN             'count only, if B is high
    
            angle = angle - 1         'decrement 
            toggle led4
    
        ENDIF
    
     ELSEIF int_storage = %0010 THEN        'Channel A2 interrupts on falling edge. A1 then is 0
    
        IF signal_B = 1 THEN             'count only, if B is high
    
            angle = angle + 1         'increment  
            toggle led3    
    
        ENDIF
    
     ELSE                        'Index Channel Interrupt (including combinations of Index and the A channels).
    
        WKEN_B  = %11111000            'enable channel A interrupt ports (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 to 90deg
        Toggle Led2                'show that
    
     ENDIF
    
    
     ISR_Exit:
     WKPND_B = %00000000                 'clear pending register, ALL bits
    
     RETURNINT                     'end of interrupt handler
    
    
    PROGRAM Start
    
    
     Start:
    
     WKPND_B = %00000000                 'clear pending register
     WKED_B  = %00000010                  'rising edge detect on the whole B Byte, except the second port of A channel
     WKEN_B  = %11111011                'but only enable Bit 2 (index channel) for initial interrupt at startup. (Info: All other Bits (except 0, 1) can be used as I/Os without creating an interrupt)
    
    
     END
    
    
    




    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
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-14 15:19
    Anybody any idea? That random problem drives me crazy [noparse]:)[/noparse]
    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
  • PJMontyPJMonty Posts: 983
    edited 2009-08-14 18:08
    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:

    0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
    



    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:

    'Create a lookup table
    EncoderTable[noparse][[/noparse]16] = 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
    
    ' Get the current encoder values and mask un-used bits
    CurrentEncoderInput = PortA AND %00000011 
    
    ' Create entry into lookup table.  NOTE: Multiplying times 4 is the same as left
    ' shifting two places.  Depending on the compiler, the leftshift may be faster
    TableIndex = CurrentEncoderInput OR (PrevEncoderInput * 4) 
    
    ' Update current position of encoder
    CurrentPosition = CurrentPosition + EncoderTable[noparse][[/noparse]TableIndex]
    
    ' Store current encoder inputs for next time
    PrevEncoderInput = CurrentEncoderInput 
    
    



    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
  • PJAllenPJAllen Banned Posts: 5,065
    edited 2009-08-14 18:13
    [noparse][[/noparse]I'm anybody, nominally.]

    SCHMITT is covered in SX/B_Help, it's only usable with SX48.

    Mechanical rotary encoder?· Maybe some external debounce circuitry (see attached)?
    394 x 243 - 11K
  • Guenther DaubachGuenther Daubach Posts: 1,321
    edited 2009-08-14 22:48
    Nope, this is not true - Schmitt can be used with all SX types on all ports except A.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Greetings from Germany,

    G
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-14 23:52
    Guys, thanks a lot!

    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?

     INTERRUPT    NOPRESERVE  6000
    
     '360*4 = 1440ppr. Max assumed speed: 2 rev/sec. max frequency = 2 rev/sec * 1440pulses/rev = 2880 pulses/sec. Nyquist: Scanning frequency
     '2x = 5760Hz == 6kHz
    
     ISR_Start:
    
    
    'Create a lookup table
    EncoderTable(0)  =  0 
    EncoderTable(1)  =  1
    EncoderTable(2)  = -1
    EncoderTable(3)  =  0
    
    EncoderTable(4)  = -1
    EncoderTable(5)  =  0 
    EncoderTable(6)  =  0
    EncoderTable(7)  =  1
    
    EncoderTable(8)  =  1
    EncoderTable(9)  =  0
    EncoderTable(10) =  0
    EncoderTable(11) = -1
    
    EncoderTable(12) =  0
    EncoderTable(13) = -1
    EncoderTable(14) =  1
    EncoderTable(15) =  0
    
    ' Get the current encoder values and mask un-used bits
    CurrentEncoderInput = RC AND %00000011 
    
    ' Create entry into lookup table.  NOTE: Multiplying times 4 is the same as left
    ' shifting two places.  Depending on the compiler, the leftshift may be faster
    PrevEncoderInput = PrevEncoderInput * 4
    TableIndex = CurrentEncoderInput OR PrevEncoderInput 
    
    ' Update current position of encoder
    CurrentPosition = CurrentPosition + EncoderTable(TableIndex)
    
    ' Store current encoder inputs for next time
    PrevEncoderInput = CurrentEncoderInput 
    
    

    Post Edited (Matthias09) : 8/15/2009 12:01:28 AM GMT
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-14 23:58
    @ Guenther: I think PJ Allen referred to the SX/B Help file where it says:
    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
  • JonnyMacJonnyMac Posts: 9,213
    edited 2009-08-15 00:22
    You can configure SCHMITT inputs on the SX28 using the PIN directive -- just add SCHMITT to the end of a pin definition.
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-15 00:29
    Just found out and was about to post [noparse]:)[/noparse]
    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:

    
     Signal_A1     PIN RB.0 INPUT         SCHMITT    'Channel A
     Signal_A2            PIN RB.1 INPUT        SCHMITT    '
    
    



    Matthias
  • PJMontyPJMonty Posts: 983
    edited 2009-08-15 00:38
    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
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-15 19:29
    and here the complete code for reading an US Digital 360PPR quad encoder. May it help people the same way I was helped.

    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.


    
    DEVICE SX28, OSCHS3, TURBO, STACKX, OPTIONX
    FREQ 50_000_000        
    
    
     Signal_A1 PIN RB.0 INPUT     SCHMITT    'Channel A
     Signal_A2    PIN RB.1 INPUT    SCHMITT    'Channel A too
     Signal_I     PIN RB.2 INPUT             'Index Channel
     Signal_B     PIN RC.0 INPUT             'Does not create an interrupt, is just read within the interrupt handler 
     angle         VAR BYTE             'saves the current angle
    
     INTERRUPT
     ISR_Start:
    
     WKPND_B = int_storage
    
    
     IF int_storage = %0001 THEN         'Channel A1 interrupts on rising edge. 
    
        IF signal_B = 1 THEN             'count only, if B is high
    
            angle = angle - 1         'decrement (or increment)
    
        ENDIF
    
     ELSEIF int_storage = %0010 THEN    'Channel A2 interrupts on falling edge.
    
        IF signal_B = 1 THEN             'count only, if B is high
    
            angle = angle + 1         'increment (or decrement) 
    
        ENDIF
    
     ELSE                        'Index Channel Interrupt (including combinations of Index and the A channels).
    
        WKEN_B  = %11111000            'enable channel A interrupt ports (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 to 90deg
    
     ENDIF
    
     ISR_Exit:
     WKPND_B = %00000000                 'clear pending register
    
     RETURNINT 
    
     PROGRAM Start
     Start:
    
    
     WKPND_B = %00000000                 'clear pending register
     WKED_B  = %00000010                  'rising edge detect on the whole B Byte, except the second port of A channel
     WKEN_B  = %11111011    
    
    
    
     angle = 0       'set initial random value, will be overwritten, once the index channel interrupt fires first interrupt. 
    
        DO
            'do your main program
        LOOP
    
     END
    
    
    

    Post Edited (Matthias09) : 8/15/2009 7:37:09 PM GMT
  • Matthias09Matthias09 Posts: 47
    edited 2009-08-15 19:47
    @ PJMonthy

    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
Sign In or Register to comment.