PSoC Quadrature Decode Problem

A catchall for PSoC Mixed-Signal Array (microcontroller) discussions not captured by the other forums.

Moderator: ericb

PSoC Quadrature Decode Problem

Postby Rodney@FIC on Sun Jun 18, 2006 4:24 pm

Hi All,

I have a project based on a CY8C27143-24PXI that reads a shaft encoder and reports the count on-demand via an I2C interface. Thanks to a previous thread on the topic, I have based my code on the algorithm proposed by baxsie with a few modifications to account for the fact that I am using different port pins.

The decode algorithm works fine when I replace the encoder by two switches and jiggle them manually. However, when the encoder is reinstalled and rotated slowly by hand, I am seeing far too many 2-state transitions (as evidenced by the LED I turn on every time one happens). I only expect to see the 2-state transitions occuring at very high speed, given the fact that the ISR containing the decode algorithm only takes 122 clock cycles to run.

I have carefully examined the outputs of my encoder and there is no bouncing happening whatsoever, so I am stumped as to what may be be causing the problem. I have cut and pasted my application code below - if anyone can spot something that I have done wrong I would appreciate it, otherwise I shall perhaps try another algorithm...


Code: Select all
;------------------------------------------------------------------------------
;   The following code segment initialises the application.  It should only
;    ever be executed after a hardware reset, such as initial power-up.  Tested
;    14 June 2006.
;------------------------------------------------------------------------------

InitialiseApplication:
    M8C_EnableGInt

;    mov     A,$2                                    ; Initialise I2C transmit
;    push    A                                       ;  buffer pointer.
;    mov     A,>encoder_count
;    push    A
;    mov     A,<encoder_count
;    push    A
;    lcall   I2CHW_1_InitRamRead
;    add     SP,-3

;    lcall   I2CHW_1_EnableSlave
;    lcall   I2CHW_1_EnableInt

    M8C_SetBank1                                    ; Set pin interrupt mode.
    mov     A,reg[PRT0IC0]
    or      A,(ENC_A|ENC_B)
    mov     reg[PRT0IC0],A

    mov     A,reg[PRT0IC1]
    or      A,(ENC_A|ENC_B)
    mov     reg[PRT0IC1],A
    M8C_SetBank0

    mov     A,reg[PRT0DR]                           ; Update port buffer.
    and     A,(ENC_A|ENC_B)                         ; Mask all but A and B.

    rlc     A                                       ; Rotate A state into CF.
    rlc     A
    rlc     A
    rlc     [encoder_state]                         ; Rotate CF into LSb.

    rlc     A                                       ; Rotate B state into CF.
    rlc     A
    rlc     [encoder_state]                         ; Rotate CF into LSb.

    mov     A,reg[PRT0IE]                           ; Enable pin interrupt.
    or      A,(ENC_A|ENC_B)
    mov     reg[PRT0IE],A

    mov     reg[INT_CLR0],$DF                       ; Clear GPIO interrupt.
    or      reg[INT_MSK0],$20                       ; Enable GPIO interrupt.
   

;------------------------------------------------------------------------------
;   The following code segment determines if an illegal two-state transition
;    has occurred or not.  If it has the ENC_LED is turned on, otherwise it is
;    turned off.
;------------------------------------------------------------------------------

TestEncoderState:
    mov     A,[flg]
    and     A,MAIN_ENCODER_STATE_ERROR
    jnz     TestEncoderStateFail

TestEncoderStatePass:
    mov     A,reg[PRT0DR]
    and     A,~ENC_LED
    mov     reg[PRT0DR],A
    jmp     TestEncoderReset

TestEncoderStateFail:
    mov     A,reg[PRT0DR]
    or      A,ENC_LED
    mov     reg[PRT0DR],A


;------------------------------------------------------------------------------
;   The following code segment determines if an encoder count reset has been
;    requested.  Tested 15 June 2006.
;------------------------------------------------------------------------------

TestEncoderReset:
;    mov     A,reg[PRT0DR]                           ; Reset?
;    and     A,ENC_RS
;    jz      TestI2C                                 ; No.

;    mov     [encoder_count],$0                      ; Yes.
;    mov     [encoder_count+0],$0


;------------------------------------------------------------------------------
;   The following code segment tests to see if the enocoder count has been read
;    by an external I2C master.  If it has the transmit buffer pointer must be
;    reset.  Tested
;------------------------------------------------------------------------------

TestI2C:
;    lcall   I2CHW_1_bReadI2CStatus                  ; I2C to-master complete?
;    and     A,I2CHW_RD_NOERR
;    jz      TestEncoderState                        ; No, keep waiting.

;    lcall   I2CHW_1_ClrRdStatus                     ; Yes, clear status bits.

;    mov     A,$2                                    ; I2C transmit buffer.
;    push    A
;    mov     A,>encoder_count
;    push    A
;    mov     A,<encoder_count
;    push    A
;    lcall   I2CHW_1_InitRamRead
;    add     SP,-3

    jmp     TestEncoderState


;------------------------------------------------------------------------------
;   Code exit.
;------------------------------------------------------------------------------

terminate:                                          ; Should never get to here.
    jmp     terminate


;------------------------------------------------------------------------------
;   The following code segment contains the GPIO interrupt service routine
;    (ISR) responsible for updating the encoder count.  Tested 15 June 2006.
;------------------------------------------------------------------------------

Main_GPIOISR::
    push    A
    mov     A,reg[PRT0DR]                           ; Update port buffer.
    and     A,(ENC_A|ENC_B)                         ; Mask all but A and B.

    rlc     A                                       ; Rotate A state into CF.
    rlc     A
    rlc     A
    rlc     [encoder_state]                         ; Rotate CF into LSb.

    rlc     A                                       ; Rotate B state into CF.
    rlc     A
    rlc     [encoder_state]                         ; Rotate CF into LSb.

    and     [encoder_state],$0F                     ; Clear high nibble.

    mov     A,[encoder_state]
    rlc     A
    jacc    EncoderStateTable

EncoderStateTable:
    jmp     Zero                                    ; 00 -> 00.
    jmp     OneMinus                                ; 00 -> 01.
    jmp     OnePlus                                 ; 00 -> 10.
    jmp     Two                                     ; 00 -> 11.

    jmp     OnePlus                                 ; 01 -> 00.
    jmp     Zero                                    ; 01 -> 01.
    jmp     Two                                     ; 01 -> 10.
    jmp     OneMinus                                ; 01 -> 11.

    jmp     OneMinus                                ; 10 -> 00.
    jmp     Two                                     ; 10 -> 01.
    jmp     Zero                                    ; 10 -> 10.
    jmp     OnePlus                                 ; 10 -> 11.

    jmp     Two                                     ; 11 -> 00.
    jmp     OnePlus                                 ; 11 -> 01.
    jmp     OneMinus                                ; 11 -> 10.
    jmp     Zero                                    ; 11 -> 11.

Zero:
    pop     A
    ret

OneMinus:
    and     [flg],~MAIN_ENCODER_STATE_ERROR         ; Clear two-state bit.

    sub     [encoder_count+1],$1                    ; Decrement count by one.
    sbb     [encoder_count],$0
    or      [flg],MAIN_ENCODER_REVERSE
    pop     A
    ret

OnePlus:
    and     [flg],~MAIN_ENCODER_STATE_ERROR         ; Clear two-state bit.

    add     [encoder_count+1],$1                    ; Increment count by one.
    adc     [encoder_count],$0
    and     [flg],~MAIN_ENCODER_REVERSE
    pop     A
    ret

Two:
    or      [flg],MAIN_ENCODER_STATE_ERROR          ; Set two-state bit.

    mov     A,[flg]
    and     A,MAIN_ENCODER_REVERSE
    jz      TwoPlus

TwoMinus:
    sub     [encoder_count+1],$2                    ; Decrement count by two.
    sbb     [encoder_count],$0
    pop     A
    ret

TwoPlus:
    add     [encoder_count+1],$2                    ; Increment count by two.
    adc     [encoder_count],$0
    pop     A
    ret

Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand

Postby graaja on Sun Jun 18, 2006 4:48 pm

Rodney,

Mostly, you get multiple GPIO triggers at slow speeds, even if the input looks clean. So, I would suggest you use small debounce delays in the GPIO ISR and see if that helps.
User avatar
graaja
PSoC Master
PSoC Master
 
Posts: 3084
Joined: Thu Dec 18, 2003 4:35 pm
Location: India

Postby lovell on Mon Jun 19, 2006 4:14 am

However, when the encoder is reinstalled and rotated slowly by hand, I am seeing far too many 2-state transitions

?? Is this an optical encoder? If so then that's is expected. Opticals are for shaft position and speed. The lead-lag on the to output lines tells direction and the count how far the rotation. There is also an index pulse (every 360 degrees).
Bill Lovell
User avatar
lovell
The Big Cheese
The Big Cheese
 
Posts: 665
Joined: Sat Dec 20, 2003 6:15 am
Location: Maine, USA

Postby Rodney@FIC on Mon Jun 19, 2006 1:54 pm

I am not quite sure I understand what you mean Ganesh. Having scoped out the A and B lines of the encoder, not only do the transitions look clean, they ARE clean, with no bounce to within the resolution of my 150MHz scope. The only thing of interest I see is that the 0 to 5V transition takes about 2us, resembling a charging RC network.

I have to interpret your post then as saying that the PSoC itself generates multiple GPIO interrupts when it detects even the cleanest edge transition, and I find this very difficult to believe - not only because I have never experienced it before, but because it would represent a major design fault. That, and I am not sure how such a thing is actually possible - its not as if the interrupt controller has a FIFO buffer in which successive GPIO interrupts can be "stacked" (hopefully you know what I mean) - there is one GPIO interrupt and thats it. An interrupt from another source may occur at the same time as the GPIO interrupt, in which case the priority encoder will be used to decide which interrupt should be serviced first, but thats it - you cannot have more than one GPIO interrupt flagged.

As for your assertion lovell about such two-state transitions being expected, I strongly disagree. When operating a quadrature encoder within its stated speed range, there is no way that the enocder should skip a state - perhaps we have different definitions of what constitutes a state.

There are four possible states of the encoder A and B lines - 00 01 11 10 (where the first digit is the A line and the second is the B line). I have labelled these states 1 - 4. There is simply no physical way for the encoder to transition between two states (say 11 and 00) without passing through the intermediate state (01 or 10 depending on the direction). Its not the encoder producing the two-state transitions that I am seeing - its my code or the limitations of the PSoC hardware.

Now if I was rotating the encoder at such a speed that the state transitions were occuring with a period LESS than that of my GPIO ISR then I would expect to miss states, and this would lead to detecting two-state transitions. However, at the moment I am rotating the encoder by hand, and there is simply no way that I am rotating it at sufficient speed for this problem to occur.
Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand

Postby Rodney@FIC on Mon Jun 19, 2006 6:27 pm

I have run out of other ideas, I thought I would try reading the port within the GPIO ISR until I get a certain number of port reads the same. If two successive reads are different then the new port value is used as the comparison value for all successive port reads - the idea being that if there is "something" causing the port to bounce then the ISR will not be allowed to continue until it has settled down. Here is the new bit of code:

Code: Select all
Main_GPIOISR::
    push    A
    mov     A,reg[PRT0DR]
    and     A,(ENC_A|ENC_B)                         ; Mask all but A and B.
    mov     [int0],A                                ; Store port value.

    mov     [int1],PORT_TEST_THRESHOLD              ; Set port read count.

TestPortValue:
    mov     A,reg[PRT0DR]
    and     A,(ENC_A|ENC_B)
    cmp     A,[int0]                                ; Port value changed?
    jnz     PortValueChanged                        ; Yes.

    dec     [int1]                                  ; No, decrement count.
    jz      TestPortValuePass
    jmp     TestPortValue

PortValueChanged:
    mov     [int0],A                                ; Store new port value.
    mov     [int1],PORT_TEST_THRESHOLD              ; Reset port read count.
    jmp     TestPortValue

TestPortValuePass:
    mov     A,[int0]                                ; Load good port value.


I am sorry to report that this seems to of had no effect on reducing the number of two-state transitions detected whatsoever. Suffice to say that this is bad!
Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand

Postby GHamblin on Tue Jun 20, 2006 11:26 am

Hi Rodney,
Have you attempted using a small quick interrupt that simply assumes if you received an interrupt you must have a count and therefore
you only need to decide direction? Basically:

Interrupt on every change (both levels of both pins)
Keep a copy of the old value
Compare the low order bit of the old value with the high order bit of the new value. (shift the old value once to the left then bitwise AND)
If the compare is true you are moving in one direction.
If the compare is false you are moving in the opposite direction.
increment or decrement your counter accordingly .
Replace the old value with the new value.
Exit the interrupt.

I don't know if your encoder is for motion control positioning or a user interface. Or how much noise or bounce you have but this scheme seems to work fine for me in UI application. Also if it is a control knob with detents. some of these controls are made to operate at 1X instead of 4X resolution. In the 1X case each detent will provide multiple edges and the 1X scheme is much easier to decode.
If you understand it,
then it's obsolete!
User avatar
GHamblin
The Big Cheese
The Big Cheese
 
Posts: 291
Joined: Mon Dec 22, 2003 3:57 pm
Location: Tucson,AZ

Postby Rodney@FIC on Tue Jun 20, 2006 2:25 pm

Hello,

Whilst being familar with the algorithm you suggest, I am unwilling to migrate to it without figuring out whats causing problems with the one I have. As I need full 4X decoding at reasonable speeds (its a motion control application) I would add zero-state and two-state detection just to be safe, and I think that this would result in me experiencing the same problems just with a different algorithm. If I have not figured it out by the end of today I may do that, but not just yet.
Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand

Postby alager on Tue Jun 20, 2006 3:08 pm

Rodney,

I've delt with this problem many times. 2us is a lifetime to the PSoC. Your rise/fall time of your signal is simply too long to use directly. As the signal transitions from 0 to 5v, it passes through the schmitt trigger levels to change states. However there is only 60mV of hysteresis, and I guaruntee that if you had a higher bandwidth scope and you zoomed in on just the transition you'd see more than 60mV of noise. Don't forget that your scope probe is adding capacitive bypassing to the signal under investigation, so you can't totaly believe what you are seeing.

With that in mind, you need to put a small delay in your ISR to make sure that the voltages have finished transitioning. A 150us to 200us delay should be nothing compared to the time of hand rotation on the encoder. Place the delay before you read the IO pins and you should be fine.


Aaron
User avatar
alager
The Big Cheese
The Big Cheese
 
Posts: 506
Joined: Tue Mar 15, 2005 2:26 pm
Location: Peteluma, CA

Postby Jeff_Birt on Wed Jun 21, 2006 5:35 am

Check out app note 2145 by Edwin Olson. I've used this method and it works good and the code is really easy.
Jeff Birt
University of Missouri - Rolla
User avatar
Jeff_Birt
Bite-Size Cheese
Bite-Size Cheese
 
Posts: 19
Joined: Tue Jan 03, 2006 8:50 pm

Postby Rodney@FIC on Wed Jun 21, 2006 3:52 pm

The closer I look at this the stranger it gets. I think I have found what is causing the multiple two-state transitions I am seeing, and its not noise on the line or anything you would expect at all - its reading the damn port value in the application code.

In order to see what was going on, I moved the 4X decode algorithm out of the ISR and in with the rest of the application code. With everything bar the 4X decode algorithm removed from main.asm it worked great, and I could not get a single two-state transition to occur - even when I shoved the encoder in a power drill and gave it full noise!

I have since been adding-back all the code segments I took out, and low and behold, the first time I tried to read the port in the main code loop I started getting two-state transitions happening even at very low speeds. Take out the port read and the problem goes away, put it back and it returns. Pah!

Furthermore, I have just tried inserting my 4X decode algorithm back into the GPIO ISR from whence it came and guess what - the same thing happens. The code runs perfectly until I add a port read in the main application loop. Now I am convinced that this has something to do with the fact that my encoder A and B lines are configured as interrupt-on-change. Having said that however you MUST be able to read the port without this sort of flim-flam happening - I mean whats going to happen when I need to use the I2CHW interface that shares the same port?

If anyone has encountered such a problem before I would love to hear from you, as without being able to read the port I am pretty much sunk.
Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand

Postby mrzee on Wed Jun 21, 2006 7:34 pm

I've never had this issue and I use the change from last read on two projects at speeds of up to 10kHz.

Might I suggest two things:
1) burn a chip and run it in case its ICE related
2) Change the following code
Code: Select all
M8C_SetBank1                                    ; Set pin interrupt mode.
    mov     A,reg[PRT0IC0]
    or      A,(ENC_A|ENC_B)
    mov     reg[PRT0IC0],A

    mov     A,reg[PRT0IC1]
    or      A,(ENC_A|ENC_B)
    mov     reg[PRT0IC1],A
    M8C_SetBank0


to
Code: Select all
M8C_SetBank1                                    ; Set pin interrupt mode.

    mov     A,(ENC_A|ENC_B)
    mov     reg[PRT0IC0],A

    mov      A,(ENC_A|ENC_B)
    mov     reg[PRT0IC1],A
    M8C_SetBank0

[/code]

This will eliminate any other pins possibly causing an interrupt since your ISR assumes only these two lines can cause the GPIO it is the correct way to set up the interrupt register anyway.

Remember that all other ports GPIO functions should be disabled too since your ISR assumes that only ENC_A and ENC_B lines will ever generate a GPIO ISR.
MrZee
User avatar
mrzee
The Big Cheese
The Big Cheese
 
Posts: 384
Joined: Mon Jan 05, 2004 5:59 pm
Location: Virginia, USA

Postby Rodney@FIC on Wed Jun 21, 2006 8:48 pm

Thanks for your reply Mr Zee. I have already eliminated the code segment you mentioned and have set the interrupt mode through the PSoC Designer IDE. With regards to it being an ICE issue, I have programmed a part and tried it with the same result. I am going to try starting a brand new project and see if its something I am overlooking or not.
Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand

Postby jiml on Thu Jun 22, 2006 7:01 am

I think you are correct that you cannot read a port that is configured for "interrupt on change from last read" from main code and have the ISR feature work reliably. I ran into this with another project (not encoder related). You can work around this feature/problem by placing the interrupt on change pins on a port where no other GPIO inputs are defined.

The basic problem is that the change detectors on the port pins get reset by any read operation ....whether it comes from within the ISR or from the application code.

jiml
jiml
The Big Cheese
The Big Cheese
 
Posts: 440
Joined: Tue Sep 27, 2005 2:35 pm

Postby Mr_E on Thu Jun 22, 2006 8:29 am

I think we've had issues with "Interrupt on Change" as well. As I recall, the port must be read inside the ISR to latch the new state (or the next state will be missed). As Jiml noted, reading the port in the main loop as well will affect the interrupt state.

If I needed the port value in main, I would use the ISR to read in the port value into a global variable, then let main check that variable.

Best of luck,
Mr_E
User avatar
Mr_E
The Big Cheese
The Big Cheese
 
Posts: 297
Joined: Wed Aug 11, 2004 7:08 am
Location: Oklahoma, USA

Postby jiml on Thu Jun 22, 2006 9:15 am

Mr. E's comments are consistent with my own experience. If your encoder is being used for a user input wheel then reading inside the ISR will not work for getting the state of the other pins since the wheel will seldom be in motion! In this case, if at all possible you should try and remap the GPIO pins so you can have the change on interrupt pins in a port where all the other pins are used as global i/o or perhaps outputs.

jiml
jiml
The Big Cheese
The Big Cheese
 
Posts: 440
Joined: Tue Sep 27, 2005 2:35 pm

Postby wrightpc on Thu Jun 22, 2006 7:30 pm

Rodney,

My advice is to NOT read the port in the background, otherwise you may lose a state-change. Also, when entering the interrupt, read the port ONCE, then save it in a register that is checked later by the interrupt.

The issue is that if it is read at the start of the interrupt and then again later, if it changes state between then, not only do you cancel the next GPIO interrupt, but you may start to read corrupt data, which you are seeing as a 2-state transition.

Example:
Starting State 00

State Change to 01

Interrupt - Read 01
Same interrupt check bit0 = 1

State change to 10

Same Interrupt check bit1 = 1
Interrupt result is state 11, which is incorrect.
exit interrupt.
(no second interrupt)



If the state is captured at the start of the first interrupt and buffered then the software would get the correct result, and a second interrupt would immediately trigger after the first to process the next state-change.

Hope this is what you are after. Hope the example is clear enough.

Paul. :)
User avatar
wrightpc
The Big Cheese
The Big Cheese
 
Posts: 736
Joined: Thu Jul 28, 2005 7:56 pm
Location: Christchurch, New Zealand

Postby Rodney@FIC on Mon Jun 26, 2006 12:50 pm

Hi All,

After starting my project again completely from scratch I can report that the problem is definitely being caused by my reading of the port register outside of the GPIO ISR. In order to be able to use the port value in my application code I ended up creating a port variable in RAM that I update every time I enter the GPIO ISR. The code now works pretty-much as expected, but it strikes me as a bit of a kludge. Thanks to everyone for their help.
Rodney@FIC
Cheese Cube
Cheese Cube
 
Posts: 56
Joined: Thu May 19, 2005 7:30 pm
Location: New Zealand


Return to PSoC1 General

Who is online

Users browsing this forum: Bing [Bot] and 1 guest

cron