Best way to interface with rotary encoder

oracacle

Senior Member
I picked up some rotary encoders the other day. And am struggling for software ideas that will be fast enough for fast turning of the knob.
https://www.amazon.co.uk/XCSOURCE®-Encoder-Development-Arduino-TE173-Black/dp/B00XXEJ4VG/ref=sr_1_5?ie=UTF8&qid=1505914652&sr=8-5&keywords=rotary+encoder

if it is rotated right the clock goes low followed by the DT (presume short for detent) and the other way round for left turn. The faster its turn the short the duration between the each pin being going low.
My initial thought where to monitor the clock pin and then check to see if the DT pin is high or low. I'm not sure weather to use an interrupt for the monitoring or a tight loop.

Suggestions would be well appreciated. I am hoping this will be used for the RGB LED project, depends on how thing go.
 

oracacle

Senior Member
Thanks, i will get google to translate that as I have zero ability with french.
In the mean time i have had a bit of play around and come up with this

Rich (BB code):
symbol clk  = pinc.1
symbol dt   = pinc.0
#terminal 9600

init:
      let b0 = 125
main:
      if clk = 0 then
            if dt = 1 then
                  b0 = b0 + 1 max 255
                  sertxd ("Right",44,#b0,13,10)
            else if dt = 0 then
                  b0 = b0 - 1 min 0
                  sertxd ("Left",44,#b0,13,10)
            end if
            do
            loop while clk = 0
      end if
      goto main
Seems to work really well. I found that things were missed or direction miss read if i did have the do holding loop (checking for clk toreturn high) before leaving the inital if statement.
This is currently testing on a 20x2 and default clock 8mhz.
 
Last edited by a moderator:

hippy

Senior Member
I recall there has been quite a bit of discussion on the forum on using rotary encoders which may be useful to research.

One issue is determining if you actually have a quadrature encoder or something else. A link to the data sheet will help there as that affects which algorithms will work or not, which should give the best performance.

For fastest speed operation using the highest system speed is best. Adding anything in the loop or finite state machine used can slow things down quite a bit. There are tricks to use interrupts and output messages while still tracking turning which can be employed which have been previously discussed.

Deciding on what's best depends on what the encoder puts out, what speed of operation you require, what resolution of a turn you would like.
 

oracacle

Senior Member
It indicated a change 20 time a rotation apparently according to the description of the amazon link. The link also says its a KY-040 if that's any help
I haven't even looked for a data sheet as yet. I just hooked up my USB scope and watched traces change, took note of the order that they changed for each direction.
In all realism as it just simply a menu select type setup it doesn't matter if it misses a step here and there.
The board has 10K resistors pulling the output pins high, both are high to start with, if it turns right the clk pin goes low followed by the dt pin. for left it dt then clk.
The one thing that I did notice though is that the duration between each happening will vary in relation to the speed that its rotating (this application is for setting LED colours/brightness). It is also possible to get "stuck" between detents resulting in a runaway of the output.

I did a quick search on the forum but it did not bring up much - hens why I posted.
 

eggdweather

Senior Member
I believe nearly ever rotary encoder ever sold is based on the quadrature design with two switches that effectively form the inputs (D and CLK ) of a latch gate, such that when going CW, D will be (for example) 1 when the CLK transitions from 0-to-1 and when rotating CCW D will be 0 when the CLK transitions from 0-to-1 from which direction can be established and speed is determined by the CLK pulse duration.

Replicating a D-type latch in software is then best performed with an interrupt input connected to the CLK and the interrupt service routine for which, then tests an input pin connected to D for its state either 0 or 1 and all this can be achieved with minimal code and complexity. Perhaps 4 or 5 lines of code maximum. e.g. Enable edge triggered interrupts (1) and in the interrupt service routine acknowledge the interrupt(2) for clear and repeat, read the D pin state (3), then set a flag / byte for general use (4) and then return from interrupt (5).

In the foreground read the flag/byte and act on direction.
 

hippy

Senior Member
eggdweather describes the simplest and easiest solution which should work for any digital rotary encoder; interrupt or poll one pin and, when that activates, check the level of the other pin. That gives a step and a direction indicator.

For example, interrupting on the rising edge of A and then looking at B, we would get 0 when moving forward, 1 when moving backwards -

Code:
   |<-- forward -->| stop |<-- backwards -->|
      ___     ___             ___     ___
A ___|   |___|   |___________|   |___|   |___
        ___     ___         ___     ___
B _____|   |___|   |_______|   |___|   |_____
     :       :               :       :
     0       0               1       1
If there are 20 interrupts per revolution you have a maximum increase or decrease of 20 every revolution, a change every 18 degrees.

If you have a more complicated interrupt system which can interrupt on rising and falling edges of A you can double the maximum count per revolution halve the change angle to 9 degrees -

Code:
   |<-- forward -->| stop |<-- backwards -->|
      ___     ___             ___     ___
A ___|   |___|   |___________|   |___|   |___
        ___     ___         ___     ___
B _____|   |___|   |_______|   |___|   |_____
     :   :   :   :           :   :   :   :
     0   0   0   0           1   1   1   1
Getting more complicated still, interrupting on rising and falling edges of both A and B, you can double the maximum count per revolution again and halve the change angle to 4.5 degrees -

Code:
   |<-- forward -->| stop |<-- backwards -->|
      ___     ___             ___     ___
A ___|   |___|   |___________|   |___|   |___
        ___     ___         ___     ___
B _____|   |___|   |_______|   |___|   |_____
     : : : : : : : :       : : : : : : : :
     0 0 0 0 0 0 0 0       1 1 1 1 1 1 1 1
You can also apply 'computer mouse acceleration' so how quickly the count goes up increases the quicker the encoder is turned. That can be determined by how long ago the last change was, and if below a certain value add or subtract 2 instead of 1 and similar. Using 'fixed point numbers' which can be held as if integers you can have 1.5 multipliers which can make things smoother and more pleasing.

It's not easy to say what is needed for a particular project so it's probably best to start simple and then get more complicated if needed. The more you are doing, the more interrupts being handled, the faster the PICAXE will need to be to handle the interrupts themselves and the rest of the code.

Interrupting on rising and falling edge of A is usually a good starting point because interrupting on just a rising edge means having to wait for the A signal to disappear before returning to the main program, and one may end up stuck in the interrupt if you stop turning the sensor and it just happens to stop with that signal high.

The following simulates interrupting on both edges. The count increments every time C.4 (A) is set high or low, set C.3 (B) high to make that decrement -

Code:
#Picaxe 08M2
             ;  543210
Symbol IRQ_A = %010000 ; Signal A = pin C.4
Symbol PIN_B = pinC.3  ; Signal B = pin C.3

Symbol counter = w0    ; b1:b0
Symbol detect  = b2

Symbol thisCount = w2  ; b5:b4
Symbol lastCount = w3  ; b7:b6

MainProgram:
  Gosub Interrupt_Enable
  Do
    thisCount = counter
    If thisCount <> lastCount Then
      lastCount = thisCount
      If thisCount >= $8000 Then ; Negative
        thisCount = -thisCount
        SerTxd( "-", #thisCount, " " )
      Else
        SerTxd( "+", #thisCount, " " )
      End If
    End If
  Loop

Interrupt:
  counter = PIN_B ^ 1 * 2 - 1 + counter
  
Interrupt_Enable:
  detect = detect ^ IRQ_A
  SetInt detect, IRQ_A
  Return
Note the interrupt increments 'counter' and the main loop grabs 'counter' into 'thisCount'. This allows 'counter' to increment without messing up the values the main program is using.

In a real system it is not advisable to use a single SERTXD to output any display data because that takes considerable time and one can miss interrupts during that. It is best to use BINTOASCII and use SERTXD to output a character at a time as that allows interrupts to occur between each character output. Using HSEROUT is even better.

Ideally one wants to make the interrupt as fast as possible so one might allocate the B pin to fall on pinX.1, and swap A and B to alter direction indicated, to optimise the counting. Instead of -

counter = pinC.1 ^ 1 * 2 - 1 + counter

One can then use a faster -

counter = pinsC & %000010 - 1 + counter

Admittedly it's quite complicated and intricate but also quite straightforward once one has got to grips with it.

Added: Forgot to point out the demo only works with simulation, not a real rotary encoder. On a real rotary encoder B will be changing as A does and that needs to be catered for. It's a simple(ish) change in the interrupt counting knowing what 'detect' is at the time. Eg interrupting on a rising edge of A and B=0 is an increment, on falling edge of A then B=1 is the increment. I'll post a full example later.

More Added: Actually it's a simple change -

Code:
Interrupt:
  counter = detect Max 1 ^ PIN_B * 2 - 1 + counter
Just remember to cycle through toggling pinC.4 then pinC.3, then pinC.4, and pinC.3 again, or vice versa to get it incrementing as a real quadrature encoder would give.
 
Last edited:

oracacle

Senior Member
well that a bit to digest.
The project will be using a 20X2, with the encoder connected to hardware interrupt on port b, a little adaptation will be needed as its in the masking that will need to be altered.
Clock will be on B.0 and so make use of INT1.

Edit: I only need to detect a single notch at a time.
 
Last edited:

hippy

Senior Member
Handling the HINT pins makes things a little more complicated but not by too much ...

Code:
#Picaxe 20X2
             ;  21-
Symbol IRQ_A = %010    ; Signal A = pin B.0 HINT1
Symbol PIN_B = pinB.1  ; Signal B = pin B.1

Symbol counter = w0    ; b1:b0
Symbol detect  = b2

Symbol thisCount = w2  ; b5:b4
Symbol lastCount = w3  ; b7:b6

MainProgram:
  Gosub Interrupt_Enable
  Do
    thisCount = counter
    If thisCount <> lastCount Then
      lastCount = thisCount
      If thisCount >= $8000 Then ; Negative
        thisCount = -thisCount
        SerTxd( "-", #thisCount, " " )
      Else
        SerTxd( "+", #thisCount, " " )
      End If
    End If
  Loop

Interrupt:
  counter = detect / 16 Max 1 ^ PIN_B * 2 - 1 + counter
  
Interrupt_Enable:
  detect = IRQ_A * 16 ^ detect | IRQ_A
  flags = 0
  HintSetup detect
  SetIntFlags IRQ_A, IRQ_A
  Return
 

stan74

Senior Member
I found a basic lib for alps rotary encoder
' encoder routines for ALPS mechanical rotary encoder
' EC11B/ EC11E/ EC11G/ EC11J with push-Button
' all these have two different stable detents: A and B are High or Low
' followig graph will show:
'
' --:, :,---:, H
' A :| :| :|
' :'---:' :'--- L
' : : :
' ,-:--, : ,-:--, H
' B | : | : | : |
' ' : '-:--' : '- L
' : : :
' det1 det2 det1
'
' Because of two possible different states at any detent,
' you have to separate the detent positions from transient,
' in order to become 1 count between 2 detents.
' First transient (i.e. A from H to L) is counted,
' second (i.e. B from L to H) is not regarded. To get the direction
' of turning makes XOR between old and new position of A and B.
'
 

lewisg

Senior Member
Seems to work really well. I found that things were missed or direction miss read if i did have the do holding loop (checking for clk toreturn high) before leaving the inital if statement.
Works GREAT!

While Hippy's interrupt routine is probably better in a program that constantly monitors a encoder yours is very simple and works excellently for a small sub routine to modify variables. A rotary encoder is a nicer UI than three pushbuttons in some applications.

Thanks!
 

Buzby

Senior Member
Here is core of the code I used a while back for a rotary encoder.

It uses 'normal' interrupts, not the hardware ones, but this does not seem to cause any missed pulses.
If fact, the original code was not interrupt driven at all, and that worked fine for sensible rotary speeds, but got confused if you went too fast.

Althogh it looks like a lot of code, it is very quick, as only only a few lines get executed on each trigger, which is what you need for an interrupt. Setting the PICAXE speed higher also helps.

Cheers,

Buzby



Code:
#Picaxe 28X2
#no_table
#no_data


Symbol changes = b0 ' bit1 = C.1 changed,  bit2 = C.2 changed
Symbol pinmem  = b1 ' bit9 = C.1  bit10 = C.2
Symbol newint  = b2

Symbol C1cngd  = bit1
Symbol C2cngd  = bit2

Symbol C1pin   = bit9    
Symbol C2pin   = bit10

Symbol maskC1   = %00000010 ; C.1
Symbol maskC2   = %00000100 ; C.2
Symbol maskAll  = %00000110 ; Both

Symbol maskC1f  = %11111101 ; C.1 falling mask
Symbol maskC2f  = %11111011 ; C.2 falling mask

' ------------------------------------------------

' As the dial is turned the two quadrature signals 
' QA and QB switch state as shown here :  


' Rotating Right
' QA     ____      ____     ____
' ______|    |____|    |___|    |______
'
' QB       ____      ____      ____
'_________|    |____|    |____|    |___


' Rotating Left
' QA     ____      ____      ____
' ______|    |____|    |____|    |____
'
' QB  ____      ____      ____
'____|    |____|    |____|    |_______


' By checking the rising and falling edges with the state of
' the 'other pin' it is possible to determine the direction of rotation.
' e.g, if QA is rising and QB is low, then the direction is Right.    

' At each interrupt the code reads the pin states, does whatever function is needed,
' ( here just a sertxd ), then sets the flags to look for an interrupt from the 'other' pin. 

' Always setting the 'other' pin for the next trigger provides robust debounce, which has
' caused me problems in the past when the dial was jittering on an edge due to poor detents.


' Code starts here
' ----------------

sertxd( "Rotary Encoder both edges 1", CR, LF )

pinmem = pinsC ' preset pin memory  

' Set for either bit to interrupt to begin with
newint = pinsC & maskAll    ' get current pinstate
setint not newint, maskAll    ' trigger if pattern changes

Main:  ' Main loop
  Do
    Pause 10
  Loop


' Interrupt
' ---------

Interrupt:

 b4 = pinsC            ' Only read pins once, to avoid glitches
 changes = b4 ^ pinmem  ' xor to find changed bits
 pinmem = b4            ' remember pin states
 
 
  if C1cngd > 0 Then         ' C1 has changed
    if C1pin > 0 Then         ' C1 = 1
     if C2pin > 0 then      '       
        sertxd (" C1 - Fwd 11", cr, lf)  ' C2 = 1
        setint maskC2f, maskC2  ' Set interrupt for C2 falling 
        return ' exit interrupt    
    else
        sertxd (" C1 - Rev 01", cr, lf)  ' C2 = 0
        setint $FF, maskC2      ' Set interrupt for C2 rising
        return ' exit interrupt            
    endif
    else                 ' C1 = 0
     if C2pin > 0 then 
        sertxd (" C1 - Rev 10", cr, lf)  ' C2 = 1
        setint maskC2f, maskC2  ' Set interrupt for C2 falling 
        return ' exit interrupt    
    else
        sertxd (" C1 - Fwd 00", cr, lf)  ' C2 = 0
        setint $FF, maskC2      ' Set interrupt for C2 rising
        return ' exit interrupt    
    endif
    endif
  endif


  If C2cngd > 0 Then        ' C2 has changed     
    If C2pin > 0 Then         ' C2 = 1
     if C1pin > 0 then 
        sertxd (" C2 - Rev 11", cr, lf)   ' C1 = 1
        setint maskC1f, maskC1  ' Set interrupt for C1 falling
            return ' exit interrupt
     else
        sertxd (" C2 - Fwd 10", cr, lf)   ' C1 = 0
        setint $FF, maskC1  ' Set interrupt for C1 rising
            return ' exit interrupt        
    endif
    Else                 ' C2 = 0
     if C1pin > 0 then 
        sertxd (" C2 - Fwd 01", cr, lf)   ' C1 = 1
        setint maskC1f, maskC1  ' Set interrupt for C1 falling
            return ' exit interrupt            
    else
        sertxd (" C2 - Rev 00", cr, lf)   ' C1 = 0
        setint $FF, maskC1  ' Set interrupt for C1 rising
            return ' exit interrupt
    endif  
    endif  
  endif  

sertxd( "Should never get here ! ")
 
Last edited:

popchops

Active member
Seems to work really well. I found that things were missed or direction miss read if i did have the do holding loop (checking for clk toreturn high) before leaving the inital if statement.
This is currently testing on a 20x2 and default clock 8mhz.
Hi Guys,

I'm trying to do the same thing, but I am very new to basic and Picaxe. I'm using a Grayhill 62AG11 optical encoder as volume control input to a digital preamplifier (11.25 deg code span = 32 detents per revolution).
https://www.mouser.co.uk/datasheet/2/626/Opt_Encoder_62AG-335528.pdf

I see your code is 'clocking' in the B state using A input as clock. This is only using one edge in four (as Hippy mentioned). I have managed to hack together some code for 40X2 that detects every edge:

Rich (BB code):
#Picaxe 40X2
init: pause 1000 ; wait for display to initialise
b2 = 0 ;inital position
b1 = 0 ;prev_pos
b8 = 0 ;initial volume
serout B.0,N2400,(254,1) ; clear OLED display

main:
serout B.0,N2400,(254,128) ;set OLED cursor position

;Quad input A is on A.5 (pin8)
;Quad input B is on A.6 (pin9)
;Knob switch is on A.7 (pin10)

if pinA.5 = 0 then    ;code = AB
    if pinA.6=0 then     ;code = 00
        low D.3
        low D.2
        b2 = 1    ;position 1
    else             ;code = 01
        high D.3
        low D.2
        b2 = 4    ;position 4
    endif
else
    if pinA.6=0 then     ;code = 10
        low D.3
        high D.2
        b2 = 2    ;position 2
    else             ;code = 11
        high D.3
        high D.2
        b2 = 3    ;position 3
        debug
    endif
endif
;if prev_pos = pos - 1, then increment
;if prev_pos = pos + 1, then decrement

b3 = b2-1    ;CW state change
b4 = b2+1    ;CCW state change

if b1 = b3 then    ;increment
    inc b8
elseif b1 = b4 then    ;decrement
    dec b8
endif

bintoascii b2,b3,b4,b5
bintoascii pinA.5,b3,b4,b6
bintoascii pinA.6,b3,b4,b7
bintoascii b8,b3,b4,b9
serout B.0,N2400,("Pos (AB): ")
serout B.0,N2400,(b5," (",b6,b7,")")
serout B.0,N2400,(254,192)
serout B.0,N2400,("Volume: ")
serout B.0,N2400,(b3,b4,b9)
b1 = b2    ;define 'prev' value for delta test next iteration
goto main
I've still got a significant problem: the iterative code that polls A and B is far too slow. I want to be able to detect at least 32 edges per second (32 state changes) and I find that I am missing edges at high speed.

I realise after reading this page that I need: INTERRUPTS, but I don't know what HINT pins are in the post above.

If I understand correctly, there can be only one interrupt: sub-procedure in the code, so if I want to detect rising and falling edges on A and B pins, all these events will trigger the same interrupt code. So what do I need to put in my interrupt: sub-procedure to increment and decrement the counter appropriately for all four events? I guess the interrupt still needs to execute quickly or else I'll end up missing edges again if it's still processing the previous interrupt(?).

I will start with Hippy's 'A-only' ring and falling example, but as I have detents on every edge it would be misleading if only half of these give a state change.

I'll have a go with interrupts on A & B, rising and falling, and see what I can glean from the posts above. Any further hints and suggestions much appreciated!

Regards

Pops.
 

Attachments

Last edited:

AllyCat

Senior Member
Hi,

I've not read all through this thread (and never use X2s) but IMHO 32 edges/second shouldn't be difficult to achieve, with or without Interrupts. However, ......

The detection code could be written more efficiently ("ELSE"s tend to slow the code down) and you're not showing a SETFREQ to increase the clock rate. BUT, it appears the major issue is all those BINTOASCIIs and SEROUTs which will add an enormous overhead. See how many bytes of program code are being added by the BINTOASCIIs (do a syntax check with and without them) and you appear to be sending almost 50 bytes serially at around 4 ms/character. And note that SEROUT characters cannot be interrupted.

Cheers, Alan.
 

popchops

Active member
Can anybody help with the setint command?

Rich (BB code):
        setint OR %10000000,%11000000    ;detect state 1 or 3
Does this mean: pin7=1 OR pin6=0? That's what I need...

Thanks!

Pops.
 

AllyCat

Senior Member
Hi,

According to the Command Syntax , Yes.
But IMHO the comment is "suspicious", because an OR will detect 3 of 4 states (i.e. like the NOT {AND}). The best solution is to test it with the Simulator in the Program Editor.

Cheers, Alan.
 

inglewoodpete

Senior Member
Provided you use adjacent pins within one port, there is a much simpler way to read transitions of a rotary encoder. I developed the following code for an 08M2 but it will work fine on any current model of PICAXE. It works using changes of states of the chosen pins, rather than interrupts and bit-fiddling. With some experimenting, you could probably reduce the chip's clock speed.
Rich (BB code):
'Simple Rotary Encoder Demonstration Programme
'Written by inglewoodpete
'
#PICAXE 08M2
#Terminal 38400            'SerTxd at 32MHz
#No_Data                   'Comment this statement out if EEPROM data is to be initialised
'
'**** Hardware Assignments - 6 I/O Legs + 2 x power (Prefix i for nput, o for output)
'                                 Leg
'Symbol iEncoder0       = pinC.3 ' 4 Digital input - One bit of encoder
'Symbol iEncoder1       = pinC.4 ' 3 Digital input - One bit of encoder
'
' **** Variables b0 to b27 or w0 to w13 (Prefix b for byte; w for word)
'
Symbol bByteToShow      = b1     'w0 Used to display hexadecimal value (for debugging)
Symbol bCurrentState    = b2     'w1 Current copy of encoder output
Symbol bPreviousState   = b3     'w1 Previous copy of encoder output
Symbol bLatestTransition= b4     'w2 4-bit value, from Previous and Current states
Symbol bPrevTransition  = b5     'w2 Copy of previous transition, in upper 4-bits
Symbol bDirection       = b6     'w3 Validated indication of rotation direction
Symbol bTemp            = b7     'w3 Used within ShowHex routine
'
' ---------------------------------------------------------------------
Init: SetFreq M32
      PullUp %00011000
      Pause 1000              'Allows time for logging terminal to open after download
      SerTxd (CR, LF, "Rotary Encoder Demo", CR, LF)
      bPreviousState = PinsC And %00011000
      Do ' ****  M a i n    L o o p  ****
         bCurrentState = PinsC And %00011000
         If bCurrentState <> bPreviousState Then      'Has encoder been turned
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous and current encoder bits appearing in lower nibble
            bLatestTransition = bPreviousState * 4 Or bCurrentState / 8
            '                                              
            bDirection = bPrevTransition Or bLatestTransition
            bByteToShow = bDirection
            GoSub ShowHex                             'Show byte value as hex
            SerTxd(" ")
            Select Case bDirection
              Case $7E: SerTxd("CW ")
              Case $42: SerTxd("CCW ")
            End Select
            bByteToShow = bLatestTransition
            bPreviousState = bCurrentState            'Save for next comparison
            bPrevTransition = bLatestTransition * 16  'Copy transition into upper 4-bits
            SerTxd(CR, LF)
         EndIf
      Loop
'
' **** Subroutines *****************************
'
' ---- ShowHex: Send Hexadecimal value to PE Terminal -----------------------------------------
'
'        Send a  byte  as hex to terminal on the programming lead (ShowHex:)
'        Send a nibble as hex to terminal on the programming lead (SHexNibl:)
'        Alternate "D" entries cause a leading $ sign to be logged
'
'        Entry:   bByteToShow (byte) data to be transmitted
'        Exit:    bByteToShow (byte) unchanged
'        Used:    bTemp       (byte)
'
ShowHexD:SerTxd("$")                      'Alternate entry
ShowHex: bTemp = bByteToShow / 16         'Get high nibble
         GoSub Sh4bits                    'Show it
         Goto SHexNibl                    'Then do low nibble
ShowNibD:SerTxd("$")                      'Alternate entry
SHexNibl:bTemp = bByteToShow And %00001111
Sh4bits: bTemp = bTemp + "0"              'Convert to ASCII
         If bTemp> "9" Then               'Separate out and
            bTemp = bTemp + 7             ' correct over-
         EndIf                            ' decimal part.
         SerTxd(bTemp)                    'Display it
         Return
 

popchops

Active member
The detection code could be written more efficiently ("ELSE"s tend to slow the code down) and you're not showing a SETFREQ to increase the clock rate. BUT, it appears the major issue is all those BINTOASCIIs and SEROUTs which will add an enormous overhead. See how many bytes of program code are being added by the BINTOASCIIs (do a syntax check with and without them) and you appear to be sending almost 50 bytes serially at around 4 ms/character. And note that SEROUT characters cannot be interrupted.
Thanks Alan,
I might have to use a different pic for the display, if it slows down the decoder. I'll cut out the serout and see how that works.
It's true there is no setfreq because I have no external oscillator. I can add one if it's going to solve the problem.
Pops
 

popchops

Active member
Provided you use adjacent pins within one port, there is a much simpler way to read transitions of a rotary encoder. I developed the following code for an 08M2 but it will work fine on any current model of PICAXE. It works using changes of states of the chosen pins, rather than interrupts and bit-fiddling. With some experimenting, you could probably reduce the chip's clock speed.
That looks great inglewoodpete. I just discovered I can capture the state of both pins using pinsC, and perform operations on this byte. I am using adjacent C pins in the latest HW.
Pops
 

AllyCat

Senior Member
Hi,
I might have to use a different pic for the display, .....
It's true there is no setfreq because I have no external oscillator.
For much faster serial communications you might have to use a "PIC", because (all) PICaxe's serial input is inherently slow... Unless you use the "hardware" HSERIN and/or HSEROUT (which is possible with nearly all PICaxes). However, it's not necessary to use BINTOASCII and send over 30 bytes serially at 2400 baud, to update only a few displayed characters, you could just change your "reporting" code.

Ah, another reason why I don't use X2s :) , but it appears you can use SETFREQ up to M16 with a 40X2.

Also, using Pete's method, the pins wouldn't need to be adjacent (or probably even on the same port). It's possible to "test and adjust" the data from any input pins using various methods such as LOOKUP (or READ {TABLE} ) commands, etc., more quickly than the time it takes to Call and Return with an interrupt.

Cheers, Alan.
 

popchops

Active member
For much faster serial communications you might have to use a "PIC", because (all) PICaxe's serial input is inherently slow... Unless you use the "hardware" HSERIN and/or HSEROUT (which is possible with nearly all PICaxes). However, it's not necessary to use BINTOASCII and send over 30 bytes serially at 2400 baud, to update only a few displayed characters, you could just change your "reporting" code.
Thanks Alan. Not sure what you mean by "change reporting code"? I could avoid writing any unchanging characters, that's true.
Also - how can I avoid BINTOASCII for numerical output to serial OLED?

I will see if I can use setfreq without an oscillator... from reading the manuals I did not think this was possible.

Thanks again!
Pops
 

AllyCat

Senior Member
Hi,

Yes, only send characters that have changed. You can send numerical data serially simply with e.g. SEROUT B.0 , N2400 , (#b6) ; The # is basically the same as BINTOASCII, except that it "intelligently" chooses the (minimum) number of characters to send (e.g. no leading zeros). Thus you may need to append a " " (<space>) or two at the end of the number, to erase the previous digit(s) on the OLED. However, in some cases you appear to be sending only a single binary digit, so if the pin is read into b6 you can simply convert to ASCII with b6 = b6 + "0" (where "0" has the decimal value 48). It appears you can even write SEROUT B.0 , N2400 , (#pinA.5) directly.

The manual sometimes struggles to handle all possibilities of the different PICaxe chips, so often a "Syntax Check" and possibly running a test program in the Simulator is the best guide as to what is possible (with a #PICAXE 40X2 , or whatever, at the top of the test program).

Cheers, Alan.
 

popchops

Active member
Hi Guys,

I had some partial success. Increased speed using setfreq as AllyCat suggested, and improved the logic of the program. It is detecting every edge on A and B, rising and falling so I have 11.25deg resolution, but only up to about 5 edges / sec rotational speed (56 deg/s). That's not really good enough.
Rich (BB code):
init:
setfreq m16
;Quad input A is on C.6 (pin 25)
;Quad input B is on C.7 (pin 26)

;first, determine which state the knob is in (b2)
;set interrupts on A and B to detect the next CW and CCW transitons

b1 = 128    ;counter
let b2 = pinsC AND %11000000     ;current encoder state
let b3 = b2                ;init new encoder state
let b7 = 0                ;init INC/DEC delta

if b2 = %00000000 then            ;code = 00, state 1
    low D.2
    low D.3
    setint OR %11000000,%11000000    ;detect state 2 or 4
elseif b2 = %10000000 then        ;code = 01, state 4
    low D.2
    high D.3
    setint OR %01000000,%11000000    ;detect state 1 or 3
elseif b2 = %01000000 then         ;code = 10, state 2
    high D.2
    low D.3
    setint OR %10000000,%11000000    ;detect state 1 or 3
else                         ;code = 11, state 3
    high D.2
    high D.3
    setint OR %00000000,%11000000    ;detect state 2 or 4
endif


main:
;AB codes
;old new  INC/DEC
;00  00    0 (same state, no change)
;00  01   -1
;00  10   +1
;00  11    0 (skipped an edge)
;01  00   +1
;01  01    0 (same state, no change)
;01  10    0 (skipped an edge)
;01  11   -1
;10  00   -1
;10  01    0 (skipped an edge)
;10  10    0 (same state, no change)
;10  11   +1
;11  00    0 (skipped an edge)
;11  01   +1
;11  10   -1
;11  11    0 (same state, no change)

goto main ; loop back to start

interrupt:
let b3 = pinsC AND %11000000    ;capture the state of the C pins for later INC/DEC

if b3 = %00000000 then            ;code = 01, state 1
    low D.2
    low D.3
    if b2 = %10000000 then
        b7 = $01            ;lookup line 5, INC
    else b7 = $FF            ;assume DEC
    endif
    setint OR %11000000,%11000000    ;detect state 2 or 4
elseif b3 = %10000000 then        ;code = 01, state 4
    low D.2
    high D.3
    if b2 = %11000000 then
        b7 = $01            ;lookup line 14, INC
    else b7 = $FF            ;assume DEC
    endif
    setint OR %01000000,%11000000    ;detect state 1 or 3
elseif b3 = %01000000 then         ;code = 10, state 2
    high D.2
    low D.3
    if b2 = %00000000 then
        b7 = $01            ;lookup line 3, INC
    else b7 = $FF            ;assume DEC
    endif
    setint OR %10000000,%11000000    ;detect state 1 or 3
else                         ;code = 11, state 3
    high D.2
    high D.3
    if b2 = %01000000 then
        b7 = $01            ;lookup line 3, INC
    else b7 = $FF            ;assume DEC
    endif
    setint OR %00000000,%11000000    ;detect state 2 or 4
endif
b1 = b1+b7    ;if b7 = 255, then result is a decrement
debug b1
let b2 = b3    ;b2 = last code
return
This version sets up the initial interrupts in the init: part, performs the update in the interrupt: part and does nothing at all in the main: part. Any tips on how to improve this so that I could avoid missing edges at higher speed ~32 edges per second (1 rev/sec)? I know there's a lot of nested loops but I can't think of a faster way. I had a version using lookup with prev and current codes, but that was no faster.

inglewoodpete: thanks for your code, but I could not make it work. I could not see the count/integral result.

Many thanks

Pops
 

AllyCat

Senior Member
Hi,

IMHO the best solution would be to find out why Pete's code doesn't work for you.

Alternatively, since the "main:" isn't doing anything, then it might just as well poll the input pins and you can dispense with the interrupt structure, which wastes time (or to be precise it's particularly the RETURN).

But to "patch" what you've already written, probably that string of ELSEs is causing a significant delay. Here's a way that should be faster (untested) :
Code:
interrupt:
   b3 = pinsC AND %11000000    ; capture the state of the C pins for later INC/DEC
   lookdown b3 , (%00000000 , %10000000 , %01000000 , %11000000) , b4
   on b4 goto state1 , state4 , state2    ; Or fall into state 3
state3:                                  ; code = 11, state 3
   high D.2
   high D.3
   b7 = $FF                             ; assume DEC
   if b2 = %01000000 then
       b7 = $01                         ; lookup line 3, INC
   endif
   setint OR %00000000 , %11000000    ; detect state 2 or 4
   goto done
state1:
; Insert Equivalent code
state4:
; Insert Equivalent code
state2:
; Insert Equivalent code (no goto done required)
done:
    b1 = b1+b7            ; if b7 = 255, then result is a decrement
    debug b1
    b2 = b3              ; b2 = last code
return
The LOOKDOWN isn't really needed in this particular case; a simple b4 = b3 / 64 would do the same thing, if the labels in the ON .. GOTO were rearranged. I've never used or analysed a DEBUG B1 , so don't know how its timing compares with the normal SERTXD(#b1).

Cheers, Alan.
 

inglewoodpete

Senior Member
inglewoodpete: thanks for your code, but I could not make it work. I could not see the count/integral result.
While you don't need to use the exact same pins that I used my prototype, if you don't you'll have to make some code changes. These changes are in the line "bLatestTransition = bPreviousState * 4 Or bCurrentState / 8" - the comment lines above this describe the shifting back and forth.
First, modify the main loop as shown below: comment out most of the bit manipulation etc but add two lines of code near the top of the loop to show what PinsC look like every time the encoder is rotated. Let us know what is displayed and we can take you step-by-step through resolving the problem you are having. Your alternative code does look unnecessarily long and complicated and this often means slower execution!
Rich (BB code):
      Do ' ****  M a i n    L o o p  ****
         bCurrentState = PinsC And %00011000
         If bCurrentState <> bPreviousState Then      'Has encoder been turned
            bByteToShow = bCurrentState
            GoSub ShowHex                             'Show byte value as hex
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous, current encoder bits appearing in lower nibble
;            bLatestTransition = bPreviousState * 4 Or bCurrentState / 8
;            '                                              
;            bDirection = bPrevTransition Or bLatestTransition
;            bByteToShow = bDirection
;            GoSub ShowHex                             'Show byte value as hex
;            SerTxd(" ")
;            Select Case bDirection
;              Case $7E: SerTxd("CW ")
;              Case $42: SerTxd("CCW ")
;            End Select
            bPreviousState = bCurrentState            'Save for next comparison
            bPrevTransition = bLatestTransition * 16  'Copy transition into upper 4-bits
            SerTxd(CR, LF)
         EndIf
      Loop
 

popchops

Active member
Hi inglewoodpete - I made some modifications for my pin interfaces (see code comments) and captured some data:

Rich (BB code):
'Simple Rotary Encoder Demonstration Programme
'Written by inglewoodpete
'
#PICAXE 40X2
#Terminal 38400            'SerTxd at 32MHz
#No_Data                   'Comment this statement out if EEPROM data is to be initialised
'
'                                 Leg
'Symbol iEncoder0       = pinC.6 ' 25 Digital input - bit A of encoder
'Symbol iEncoder1       = pinC.7 ' 26 Digital input - bit B of encoder
'
' **** Variables b0 to b27 or w0 to w13 (Prefix b for byte; w for word)
'
Symbol bByteToShow      = b1     'w0 Used to display hexadecimal value (for debugging)
Symbol bCurrentState    = b2     'w1 Current copy of encoder output
Symbol bPreviousState   = b3     'w1 Previous copy of encoder output
Symbol bLatestTransition= b4     'w2 4-bit value, from Previous and Current states
Symbol bPrevTransition  = b5     'w2 Copy of previous transition, in upper 4-bits
Symbol bDirection       = b6     'w3 Validated indication of rotation direction
Symbol bTemp            = b7     'w3 Used within ShowHex routine
'
' ---------------------------------------------------------------------
Init: SetFreq M16
      ;PullUp %00011000
      Pause 1000         'Allows time for logging terminal to open after download
      SerTxd (CR, LF, "Rotary Encoder Demo", CR, LF)
      ;bPreviousState = PinsC And %00011000
    bPreviousState = PinsC And %11000000
      Do ' ****  M a i n    L o o p  ****
       ;bCurrentState = PinsC And %00011000
         bCurrentState = PinsC And %1100000
         If bCurrentState <> bPreviousState Then      'Has encoder been turned
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous and current encoder bits appearing in lower nibble
            ;bLatestTransition = bPreviousState * 4 Or bCurrentState / 8
        ;previous state is higher significance, new is LSB
        bLatestTransition = bCurrentState / 4 Or bPreviousState / 16
            '                                                                     
            bDirection = bPrevTransition Or bLatestTransition
            bByteToShow = bDirection
            GoSub ShowHex                             'Show byte value as hex
            SerTxd(" ")
            Select Case bDirection
              Case $7E: SerTxd("CW ")
              Case $42: SerTxd("CCW ")
        End Select
    debug
            bByteToShow = bLatestTransition
            bPreviousState = bCurrentState            'Save for next comparison
            bPrevTransition = bLatestTransition * 16  'Copy transition into upper 4-bits
            SerTxd(CR, LF)
         EndIf
      Loop
'
' **** Subroutines *****************************
'
' ---- ShowHex: Send Hexadecimal value to PE Terminal -----------------------------------------
'
'        Send a  byte  as hex to terminal on the programming lead (ShowHex:)
'        Send a nibble as hex to terminal on the programming lead (SHexNibl:)
'        Alternate "D" entries cause a leading $ sign to be logged
'
'        Entry:   bByteToShow (byte) data to be transmitted
'        Exit:    bByteToShow (byte) unchanged
'        Used:    bTemp       (byte)
'
ShowHexD:SerTxd("$")                      'Alternate entry
ShowHex: bTemp = bByteToShow / 16         'Get high nibble
         GoSub Sh4bits                    'Show it
         Goto SHexNibl                    'Then do low nibble
ShowNibD:SerTxd("$")                      'Alternate entry
SHexNibl:bTemp = bByteToShow And %00001111
Sh4bits: bTemp = bTemp + "0"              'Convert to ASCII
         If bTemp> "9" Then               'Separate out and
            bTemp = bTemp + 7             ' correct over-
         EndIf                            ' decimal part.
         SerTxd(bTemp)                    'Display it
         Return
Heres my log file from Terminal Simulation, starting in state '00xxxxxx' and turning clockwise (01xxxxxx, 11xxxxxx, 10xxxxxx, 00xxxxxx):
Code:
1
2  Rotary Encoder Demo
3  01
4  17
5  7E CW
6  E8
7  81
8  17
9  7E CW
10 E8
I get the 'CW' every 4 detents (edges). Is it working? Is there any increment/decrement function? It looks like it's only recognising 1/4 edges ($7E). I really want to detect every edge (rising and falling) on both pins A & B, CW and CCW.

I was able to program my 40X2 and log the dDirection before every turn of the encoder. As before I started in bCurrentState = '00xxxxxx':

bDirection (turning CW)
$E8, $01, $17, $7E
$E8, $81, $17, $7E
$E8, $81, $17, $7E
$E8 ...

bDirection (turning CCW)
$14, $02, $2B, $BD,
$D4, $D4, $2B, $BD,
$D4, $D4, $2B, $BD,
$D4 ...

I really don't understand what's going on (or what I should be seeing) in the CCW test. You can see there is some repetition!I never did see dDirection = $42 'CCW'.

Thanks - any help appreciated!!

Pops
 

popchops

Active member
Alternatively, since the "main:" isn't doing anything, then it might just as well poll the input pins and you can dispense with the interrupt structure, which wastes time (or to be precise it's particularly the RETURN).

But to "patch" what you've already written, probably that string of ELSEs is causing a significant delay. Here's a way that should be faster (untested)
Hi Alan - it works! and faster, I estimate maybe 8 edges per second reliably, and detecting every edge so gives me a new count with every detent. Here's the code:
Rich (BB code):
init:
setfreq m16
;Quad input A is on C.6 (pin 25)
;Quad input B is on C.7 (pin 26)

;first, determine which state the knob is in (b2)
;set interrupts on A and B to detect the next CW and CCW transitons

b1 = 128    ;counter
let b2 = pinsC AND %11000000     ;current encoder state
let b3 = b2                ;init new encoder state
let b7 = 0                ;init INC/DEC delta

if b2 = %00000000 then            ;code = 00, state 1
    low D.2
    low D.3
    setint OR %11000000,%11000000    ;detect state 2 or 4
elseif b2 = %10000000 then        ;code = 01, state 4
    low D.2
    high D.3
    setint OR %01000000,%11000000    ;detect state 1 or 3
elseif b2 = %01000000 then         ;code = 10, state 2
    high D.2
    low D.3
    setint OR %10000000,%11000000    ;detect state 1 or 3
else                         ;code = 11, state 3
    high D.2
    high D.3
    setint OR %00000000,%11000000    ;detect state 2 or 4
endif

main:
;AB codes
;old new  INC/DEC
;00  00    0 (same state, no change)
;00  01   -1
;00  10   +1
;00  11    0 (skipped an edge)
;01  00   +1
;01  01    0 (same state, no change)
;01  10    0 (skipped an edge)
;01  11   -1
;10  00   -1
;10  01    0 (skipped an edge)
;10  10    0 (same state, no change)
;10  11   +1
;11  00    0 (skipped an edge)
;11  01   +1
;11  10   -1
;11  11    0 (same state, no change)

goto main ; loop back to start

interrupt:
   b3 = pinsC AND %11000000    ; capture the state of the C pins for later INC/DEC
   b7 = $FF
   lookdown b3 , (%00000000 , %10000000 , %01000000 , %11000000) , b4
   on b4 goto state1 , state4 , state2    ; Or fall into state 3
state3:                                  ; code = 11, state 3
    ;high D.2
    ;high D.3
    if b2 = %01000000 then
        b7 = $01                ;lookup line 3, INC
    endif
    setint OR %00000000 , %11000000    ;detect state 2 or 4
    goto done
state1:
    ;low D.2
    ;low D.3
    if b2 = %10000000 then
        b7 = $01                ;lookup line 5, INC
    endif
    setint OR %11000000,%11000000        ;detect state 2 or 4
    goto done
state4:
    ;low D.2
    ;high D.3
    if b2 = %11000000 then
        b7 = $01                ;lookup line 14, INC
    endif
    setint OR %01000000,%11000000        ;detect state 1 or 3
    goto done
state2:
    ;high D.2
    ;low D.3
    if b2 = %00000000 then
        b7 = $01                ;lookup line 3, INC
    endif
    setint OR %10000000,%11000000        ;detect state 1 or 3
done:
    b1 = b1+b7            ; if b7 = 255, then result is a decrement
    debug b1
    b2 = b3              ; b2 = last code
return
Looks like I will not be able to use a single picaxe to do all my sensor detection and display. Maybe I'll need a single picaxe just to do the optical encoder interface, and then spit out a 'count' value to another chip.

Thanks again.

Pops.
 

AllyCat

Senior Member
Hi,

I still think that 30 steps/second should be possible with a single PICaxe, including sending a modest number of bytes to a serial display (i.e. updating the characters at a "readable" rate).

First, go to an M2 so that you can use SETFREQ m32 with the internal oscillator. That potentially doubles the detection rate.

Second, replace the interrupt with a simple "main" polling loop, which compares the "interrupt" searching mask with the present input pins state. Not only will that save the time Returning from a "useless" interrupt, but writing a simple "target" variable is faster than setting up an interrupt.

Third, send the serial data using HSEROUT, or better by POKESFRing single bytes directly to the serial hardware buffer. I don't know the detailed timing of DEBUG (since I have never used it), but it definitely uses "blocking" software serial timing code, and possibly at only 4800 baud. That's what I suspect is limiting the speed most severely.

Then, probably a few more refinements could be possible. For example "High D.2 : Low D.3" can be replaced by a single PINS command (I realise they're currently patched out). Dividing by 64 should be (slightly) faster than the LOOKUP and I suspect that the "direction" can be determined more efficiently. Interestingly, the 08M2 is marginally faster (perhaps 10%) than the other M2s (if it has sufficient pins), because it has less "hardware" to control.

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

OK, so I decided to measure how long that DEBUG b1 takes (by putting it in a tight loop), and the answer is about 125 ms , regardless of the clock frequency. So that's why you can only detect around 8 edges per second.

A SERTXD(#b1," ") with an 08M2 takes less than 10 ms (at its default SETFREQ M4) , since it only has to send 4 characters at 4800 baud, and that scales down to 2 ms with SETFREQ M16. Therefore, you should be able to easily detect 30 edges/second, probably with only a 4 or 8 MHz clock frequency.

That's why I've never used DEBUG ! ;)

Cheers, Alan.
 

popchops

Active member
OK, so I decided to measure how long that DEBUG b1 takes (by putting it in a tight loop), and the answer is about 125 ms , regardless of the clock frequency. So that's why you can only detect around 8 edges per second.
Thanks Alan, that's great news. I'll definitely ditch the debug.
I need a chip with HW SPI output (up to 1MHz) to control a PGA2311 digital attenuator, and only the X1 and X2 parts have this feature.
I can add an external resonator to get a higher setfreq... this would be worth doing to keep everything on one chip.
I know I'm not making proper use of the interrupts... so I'll try basic polling again with a few of those optimisations.

Thanks, Pops.
 

AllyCat

Senior Member
Hi,

16 MHz is very probably fast enough to detect 30 steps/sec from the encoder, so you should be able to use the internal clock oscillator of an X2.

But do you really need Hardware SPI ? The PGA2311 Data Sheet appears to specify a minimum clock frequency of "zero" Hz, and there seem to be no maximum data hold times required for the input signals. Unless you need to send vast amounts of data to the attenuator (what?) then I would expect bit-banging (software) to be fast enough. Bit-banging SPI should be much easier than I2C, which is also quite possible with a PICaxe (if rather slow), even with the simpler M2 chips.

Cheers, Alan.
 

inglewoodpete

Senior Member
Heres my log file from Terminal Simulation, starting in state '00xxxxxx' and turning clockwise (01xxxxxx, 11xxxxxx, 10xxxxxx, 00xxxxxx):
Code:
1
2  Rotary Encoder Demo
3  01
4  17
5  7E CW
6  E8
7  81
8  17
9  7E CW
10 E8
I get the 'CW' every 4 detents (edges). Is it working? Is there any increment/decrement function? It looks like it's only recognising 1/4 edges ($7E). I really want to detect every edge (rising and falling) on both pins A & B, CW and CCW.

I was able to program my 40X2 and log the dDirection before every turn of the encoder. As before I started in bCurrentState = '00xxxxxx':

bDirection (turning CW)
$E8, $01, $17, $7E
$E8, $81, $17, $7E
$E8, $81, $17, $7E
$E8 ...

bDirection (turning CCW)
$14, $02, $2B, $BD,
$D4, $D4, $2B, $BD,
$D4, $D4, $2B, $BD,
$D4 ...

I really don't understand what's going on (or what I should be seeing) in the CCW test. You can see there is some repetition!I never did see dDirection = $42 'CCW'.

Thanks - any help appreciated!!

Pops
Firstly, your encoder works quite differently to the ones I use. Mine goes through 4, 2-bit values for every detent 'click'. You mention using a simulator but this may not simulate a real encoder.

I can see that you're nearly there! Although that missing $42 in the CCW direction does not make sense to me.

Your data should look like this:
bDirection (turning CW)
$E8, $01, $17, $7E
$E8, $81, $17, $7E
$E8, $81, $17, $7E
$E8 ...

bDirection (turning CCW)
$14, $02, $2B, $BD,
$D4, $42, $2B, $BD,
$D4, $42, $2B, $BD,
$D4 ...
You will see that the right hand hex character becomes the left hand character of the following hex value.

Replace your entire Main loop with the following code and let us know what the result is.
Rich (BB code):
      Do ' ****  M a i n    L o o p  ****
         bCurrentState = PinsC And %11000000
         If bCurrentState <> bPreviousState Then      'Has encoder been turned
            bByteToShow = bCurrentState
            GoSub ShowHex                             'Show byte value as hex
            SerTxd (" ")
            ' At this point, bPreviousState = XY000000 and bCurrentState = xy000000
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' (/ 4) Move previous XY bits right 2 bit-positions: 00XY0000
            '  (Or) merge current xy bits:                       xyXY0000                    
            ' (/16) Move all 4 bits right by 4 bit positions:    0000xyXY
            ' This results in previous, current encoder bits appearing in lower nibble
            bLatestTransition = bPreviousState / 4 Or bCurrentState / 16
            '                                              
            bDirection = bPrevTransition Or bLatestTransition
            bByteToShow = bDirection
            GoSub ShowHex                             'Show byte value as hex
            SerTxd(" ")
            Select Case bDirection
              Case $7E, $E8, $81, $17: SerTxd("CW ")
              Case $42, $2B, $BD, $D4: SerTxd("CCW ")
            End Select
            bPreviousState = bCurrentState            'Save for next comparison
            bPrevTransition = bLatestTransition * 16  'Copy transition into upper 4-bits
            SerTxd(CR, LF)
         EndIf
      Loop
 

popchops

Active member
Hi inglewoodpete,

Firstly, your encoder works quite differently to the ones I use. Mine goes through 4, 2-bit values for every detent 'click'. You mention using a simulator but this may not simulate a real encoder.
OK I see - my optical encoder has a detent every edge, only 1 2-bit code per detent.

I changed this one line to put current code in the lower nibble:
Rich (BB code):
            ' This results in previous, current encoder bits appearing in lower nibble
            bLatestTransition = bCurrentState / 4 Or bPreviousState / 16
When I run the code in Simulation, and manipulate the input pins C.6 and C.7 in sequence, I get the correct decoding of CW and CCW edges:
1 C0 17 CW
2 80 7E CW
3 00 E8 CW
4 40 81 CW
5 C0 17 CW
6 40 7D
7 00 D4 CCW
8 C0 43
9 40 3D
10 00 D4 CCW
11 80 42 CCW
12 C0 2B CCW
13 40 BD CCW
14 00 D4 CCW


However, when I change nothing, but run the code on the PICAXE I get gibberish in the Terminal Window:
[00][87][00][87][00][F8]f0[80][F8][00][F8][1E]0~3[00][F8][E6][80][98][80]`[0F][00][87][00][F8][80][F8][06][0F][00][F8][1E]0~3[00][F8][E6][80][98][80][1E]0[00][87][00][F8][06][0F]~[0F][00][F8][1E]0~3[00][F8][E6][80][98][80][80][F8][00][87][00][F8]~[0F]f0[00][F8][1E]0~3[00][F8][E6][80][98][80][00][87][00][87][00][F8]f0[80][F8][00][F8][1E]0~3[00][F8][E6][80][98][80]`[0F][00][87][00][F8][80][F8][06][0F][00][F8][1E]0~3[00][F8][E6][80][98][80][1E]0[00][87][00][F8][06][0F]~[0F][00][F8][1E]0~3[00][F8][E6][80][98][80][80][F8][00][87][00][F8]~[0F]f0[00][F8][1E]0~3[00][F8][E6][80][98][80][00][87][00][87][00][F8]f0[80][F8][00][F8][1E]0~3[00][F8][E6][80][98][80]`[0F][00][87][00][F8][80][F8][06][0F][00][F8][1E]0~3[00][F8][E6][80][98][80][1E]0[00][87][00][F8][06][0F]~[0F][00][F8][1E]0~3[00][F8][E6][80][98][80][80][F8][00][87][00][F8]~[0F]f0[00][F8][1E]0~3[00][F8][E6][80][98][80][1E]0[00][87][00][F8]f0[18]0[00][F8][E6][80][98][80]`[0F][00][87][00][F8][18]0`0[00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][00][87][00][87][00][F8]`0`[0F][00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][80][F8][00][87][00][F8]`[0F][18][0F][00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][1E]0[00][87][00][F8][18][0F][18]0[00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80]`[0F][00][87][00][F8][18]0`0[00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][00][87][00][87][00][F8]`0`[0F][00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][80][F8][00][87][00][F8]`[0F][18][0F][00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][1E]0[00][87][00][F8][18][0F][18]0[00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80]`[0F][00][87][00][F8][18]0`0[00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][00][87][00][87][00][F8]`0`[0F][00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80][80][F8][00][87][00][F8]`[0F][18][0F][00][F8][1E]0[1E]0~3[00][F8][E6][80][98][80]

What's that all about? Thats a few CW turns and a few CCW turns. Tis a bit confusing. I was expecting some solid 'CW' & 'CCW' feedback, as in the Simulation Terminal Window. But - I added LED indicators to the CW and CCW states:
CSS:
Select Case bDirection
    Case $7E, $E8, $81, $17: SerTxd("CW ")    high D.2
    Case $42, $2B, $BD, $D4: SerTxd("CCW ") high D.3
... and now I get beautiful red LED blinking with each CW increment, and green LED for each CCW decrement (resetting to low at the start of the main loop). I still need to add the counter, but so far it looks like it's good for ~30 edges per second. It does miss the first turn after each change of direction though.

Thanks a lot!

Pops
 
Last edited:

popchops

Active member
Hi Alan,

But do you really need Hardware SPI ? The PGA2311 Data Sheet appears to specify a minimum clock frequency of "zero" Hz, and there seem to be no maximum data hold times required for the input signals. Unless you need to send vast amounts of data to the attenuator (what?) then I would expect bit-banging (software) to be fast enough. Bit-banging SPI should be much easier than I2C, which is also quite possible with a PICaxe (if rather slow), even with the simpler M2 chips.
Probably not... but I tried the HW SPI and it was so easy and so fast, and as you can see I'm struggling to find enough time to run my loop fast enough without that burden, so I would worry that bit-banging the SPI will take me a while to figure out and will just sap my resources. Alternatively the -X2 can bang out a 16-bit stereo volume instruction in 64 us (based on 250 kHz rate with 4 MHz clock) - I should be able to slot that into my loop without affecting the high-speed detection for the optical encoder. Then I can update the volume every detent, every increment or decrement.
A decent compromise might be to wait until the knob has stopped (??) and then update the volume with slow SPI, but I think this would be less satisfying.

Thanks again Alan.

Pops.
 

Buzby

Senior Member
"gibberish in the Terminal Window" is usually because the terminal is running at the wrong baud rate. Try 9600.
 

lbenson

Senior Member
gibberish in the Terminal Window
This usually means that the baud rate for the terminal doesn't match the baud rate the program is using. The typical cause of that is that you have increased the frequency with SETFREQ. You need to adjust the terminal baud rate to suit.
 

AllyCat

Senior Member
Hi,

Yes, of course bit-banging the SPI will take a few ms, but 30 edges/sec has a period of 33 ms and you could run an M2 twice as fast as an X2 using their respective internal oscillators. However, I still think there is something "wrong", if you can't get the code to detect 30 edges/sec "easily"......

SERTXD and SEROUT are inherently "slow" because they are generated by software which "blocks" any other processing. That enormous amount of apparent serial output looks very like an incorrect baud rate (as I see Buzby and Lance have just suggested), what rates are you using in the Program and the terminal? First, I would change that CCW to "A" (AntiClockwise) and the CW to "C". Not only will that save some time, but the single characters could be thrown directly at the Hardware Serial buffer and consume almost no (program) time at all.

I was a little concerned by that SELECT .. CASE structure, which I have sometimes found to be rather slow, but a side-by-side test with my preferred LOOKDOWN ran in almost exactly the same time, well within that 30 ms period, using only a 4 MHz clock, the execution time being dominated by whether 2 or 3 characters were being sent.

Cheers, Alan.
 

inglewoodpete

Senior Member
gibberish in the Terminal Window
(also similar by Buzby)
This usually means that the baud rate for the terminal doesn't match the baud rate the program is using. The typical cause of that is that you have increased the frequency with SETFREQ. You need to adjust the terminal baud rate to suit.
The offending code is in the directives that you have used. They are valid for the 08M2 that I was using @32MHz. You appear to be running your 40X2 at its default speed (8MHz).
Rich (BB code):
'Directives:
#PICAXE 40X2
#Terminal 38400            'SerTxd at 32MHz
#No_Data                   'Comment this statement out if EEPROM data is to be initialised
PICAXE Speed (SetFreq command)Terminal Speed (Baud Rate used in SerTxd)
4MHz4800
8MHz9600
16MHz19200
32MHz38400
64MHz76800

It does miss the first turn after each change of direction though.
Once you determine what the 'transitional' codes are, you can add them to the list for CW and CCW Case statements.

It looks like you're getting on top of the code for your project.
 
Last edited:

popchops

Active member
Thanks inglewoodpete,
The offending code is in the directives that you have used. They are valid for the 08M2 that I was using @32MHz. You appear to be running your 40X2 at its default speed (8MHz).
Top tip! I'm running at 16MHz, but I can fix this now.
 
Top