Shop OBEX P1 Docs P2 Docs Learn Events
Fast Quadrature Encoder — Parallax Forums

Fast Quadrature Encoder

Carlos D. LopezCarlos D. Lopez Posts: 9
edited 2009-12-03 21:57 in Propeller 1
Hi,

I'm trying to create a Quadrature signal for a A pulse frequency of 83.565 kHz.
Here is the code for what I'm trying to do. I have a pulse signal on the Pin 20, for each pulse, I modify the pins[noparse][[/noparse]16..19] to look like a quadrate signal, where Pin[noparse][[/noparse]16]= A, Pin[noparse][[/noparse]17]= -A, Pin[noparse][[/noparse]18] = B (phased 90 degrees), and Pin[noparse][[/noparse]19] = -B.

{{Output.spin

Toggles two pins, one after another.}}
VAR
  byte myCounter
  byte myPulse
   
PUB Main
  Toggle(clkfreq/31_100)     'Toggle P16 ten times, 1/4 s each

PUB Toggle(Delay)| Time
{{Toggle Pin, Count times with Delay clock cycles in between.}}

  dira[noparse][[/noparse]16..20]~~             'Set I/O pin to output direction
  Time:=cnt
  repeat       'Repeat for Count iterations                
    waitcnt(Delay + cnt) ' Wait for Delay cycles
    !myPulse
    outa[noparse][[/noparse]20]:=myPulse
    
    if myPulse <> 0
     case myCounter
        0:outa[noparse][[/noparse]16..19] := %1001        '  Toggle I/O Pin
        1:outa[noparse][[/noparse]16..19] := %0101    '  Toggle I/O Pin
        2:outa[noparse][[/noparse]16..19] := %0110    '  Toggle I/O Pin
        3:outa[noparse][[/noparse]16..19] := %1010        '  Toggle I/O Pin
     myCounter++
     if myCounter>3
        myCounter:=0




My problem is that the waitcnt command has a limit for the "Delay" value of 381. Is there away around this so that I can generate a pulse signal of 334 Khz?
thanks

Carlos Lopez

Comments

  • evanhevanh Posts: 16,146
    edited 2009-12-02 22:36
    First thing is drop the -A and -B signals. You should use a real line driver chip for those parts. Either that or you don't need the inverted signals at all.
  • TubularTubular Posts: 4,717
    edited 2009-12-03 00:19
    Hi Carlos,

    I'm not an expert on spin timing, but I don't think that loop (in SPIN at least) is going to go around fast enough for you to generate 334 kHz. Just try running without your delay and see what frequency you get out

    The good news, however is that two counters can be set up to do quadrature in the background for you. They can also do the inverted -A and -B signals if you want them. It works best (no jitter) with dividing by powers of two, so if you have a 5Mhz crystal and your prop is running at 80 Mhz, you can easily derive 78.125 khz (divide by 80 Mhz by 1024).
  • lonesocklonesock Posts: 917
    edited 2009-12-03 00:46
    Tubular is exactly right. Assuming you are running your prop at 80MHz, 83.565 kHz requiring 4 state changes means you have a budget of only ~240 clocks per change. To put that in perspective, the overhead of just the repeat command is ~225 clocks, and that is likely the fastest part of the Spin code. Using the recommended 2 counters can get you very close to what you want (is a little bit of jitter acceptable?), with the added benefits of not requiring another cog and being fairly low power.

    Let us know how you decide to proceed and I'm sure we can give you a little help, if needed.

    Jonathan

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
  • VIRANDVIRAND Posts: 656
    edited 2009-12-03 06:44
    You can get quadrature by XOR 2 bits of a counter, the result being the quadrature of
    the higher bit, which you save before doing the XOR and output both at the same time.
    Getting the last 2 phases should be obvious.
  • Carlos D. LopezCarlos D. Lopez Posts: 9
    edited 2009-12-03 18:27
    Hi,

    This is another code that I tried just to see how fast I could make it go.

    PUB Main
    {Launch cog to toggle P16 endlessly}
      dira[noparse][[/noparse]16..18]~              'Set I/O pin to output direction
    cognew(@Toggle, 0) 'Launch new cog
    
    DAT
    {Toggle P16}
            org 0 'Begin at Cog RAM addr 0
    Toggle  mov dira, Pin18 'Set Pin to output
    :loop  
            mov outa, %1001
            add Counter, #1
            mov outa, %0101
            add Counter, #1
            mov outa, %0110
            add Counter, #1
            mov outa, %1010
            jmp #:loop 'Loop endlessly
            
    Pin18   long  %11110000000000000000'|< 18 'Pin number
    Time    res   1 'System Counter Workspace
    Counter long  0
    
    



    I'm creating a delay by adding the add counter command. Also with this code, for some reason I'm not getting pin 18 to respond to change, it is copying from pin 19, which does not happen with the original spin code I had.

    The Period of the signal that I'm getting is around 3us (micro), around 333kHz.
    How can I change this code so that I can get the desired frequency, 83.565kHz, and I need this to be programmable in the sense that I need to change the frequency, this being the fastest.
    83.565 kHz - 80 ft/min
    73.119 kHz - 70 ft/min
    62.673 kHz - 60 ft/min
    - 50 ft/min
    - 40 ft/min
    - 30 ft/min
    - 20 ft/min
    - 10 ft/min

    Is it possible to interface this with another programming language, VBA or C++ or something external to the Propeller tool?

    Thanks.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-12-03 19:44
    You set the pins as outputs in your assembly but you are not aligning your codes (%1001 etc) with the correct bits. You are also not declaring them as literals, it should be #%1001 etc.

    Rather than mov into outa you should or with outa, so
    or outa, #%1001
    
    



    Would set bits 0 and 3 high but that is not quite what you want. You could put the 1001 in a variable and shift it left:
    mov     out_var, %1001
    shl       out_var,  pin              'pin is the first pin of the four you are using.
    
    


    This would set the bit defined by pin high as well as the bit three above it. This can be done before the loop.

    To change the frequency you would need to add one or many waitcnts in the loop, there delay would be set by getting a variable from hub ram (using rdlong). Alternatively your main program could set one of the pins oscillating at the required frequency using a counter module. Then this cog could use the waitpne type instructions changing state when the pin pulsed. The main program could then easy change the frequency simply by changing FRQA.

    You can communicate to a PC through serial via the same lead you program the device through.

    Graham
  • lonesocklonesock Posts: 917
    edited 2009-12-03 20:14
    I would do something like this (untested):

    VAR
      long pause_counts
      byte quad_cog
      
    PUB start
      stop
      set_Hz( 83_565 )
      quad_cog := cognew( @quad_entry, @pause_counts ) + 1
    
    PUB stop
      if quad_cog
        cogstop( quad_cog~ - 1 )
    
    PUB set_Hz( Hz )
      ' the update rate is 4x faster than the frequency
      pause_counts := ((clkfreq >> 2) + (Hz >> 1)) / Hz  
    
    DAT
    ORG 0 'Begin at Cog RAM addr 0
    
    {{
            This will be run in its own cog, meaning:
            - we need our own copy of dira
            - we get our own copy of outa
            - I need to get the frequency info from the Hub RAM
              (address is in the PAR register)
    }}
    quad_entry
            mov outa,all_quad_vals_twice
            mov dira,pin_mask
            mov timestamp,#511
            add timestamp,cnt
    
    loop_forever
            rdlong delay_counts,par
            waitcnt timestamp,delay_counts
            rol outa,#4
            jmp loop_forever
    
    {===== PASM constants and parameters =====}
    pin_mask                long %1111 << 16
    all_quad_vals_twice     long %1001_0101_0110_1010____1001_0101_0110_1010
    
    {===== PASM scratch variables =====}
    ' Remember, all RES variables _MUST_ come after all LONG variables 
    delay_counts            res 1
    timestamp               res 1
    
    ' make sure this whole thing fits inside a single cog
    FIT 496
    



    You can update the frequency by calling the set_Hz function, once you've called start. This works since it's running in its own cog, you have your own outa register, so you can store all the patterns you want, and just rotate them "through" the window of output bits set in dira.

    Jonathan

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-12-03 20:55
    Nice!
  • Carlos D. LopezCarlos D. Lopez Posts: 9
    edited 2009-12-03 21:10
    Is there a reason why the all_quads_val_twice, why two times the series of quads?
    Why is the initial timestamp = #511?
    And what about specifying the frequency parameter from another external program? using VBA or C++?

    Thanks for your help.
  • lonesocklonesock Posts: 917
    edited 2009-12-03 21:33
    Well, the variables in PASM are all longs, meaning 32 bits. If you have 4 x 4-bit values, that only takes up 16 bits...the other 16 bits would be all 0's. So by duplicating the sequence, you can rotate the variable left by 4 bits each time and it will have the correct behavior (A B C D A B C D), otherwise you'd get (A B C D 0 0 0 0) repeating.

    The initial 511 is added to the current cnt value, so I'm just making sure it starts some time in the future (it should be at least 27, 22 max for the rdlong, and 5 for the waitcnt, but 511 is extra safe, and really doesn't take much time (511/80e6 seconds) once at cog-load. I do it in the order set offset, then add cnt because that way the cnt value is the least stale possible.

    You could use another program on the PC to specify the frequency. I would send down the frequency over a serial port (look into Full Duplex Serial, a.k.a. FDS). This would require you parsing the serial data, then calling the set_Hz function. Serial communication under unextended C++ may be a bit difficult. I've never tried under VBA. You can do it fairly easily under VB.net 2008, and there's a free Express Edition available.

    Finally, this method will not give you the _exact_ frequency you need...if you end up needing more precision, we could use counters, but those are a bit harder to explain...I'd save that for rev 2, if this works for you.

    Jonathan

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-12-03 21:35
    In case Jonathan is busy (OK he posted before me but I'll leave it in case it helps too):

    1. He puts two copies of the set of quads into the outa register and then uses rol to scroll through them.

    outa:

    1001_0101_0110_1010____1001_0101_0110_1010

    rol outa,#4

    outa now:

    0101_0110_1010____1001_0101_0110_1010_1001_

    So the next set of values will be on your output pins.

    2. Initial time stamp of 511 to provide some time in which to read the actual delay time from hub memory the very first time.

    3. I suggested using serial via the USB programming link, you can get serial objects for VBA and C++
  • Carlos D. LopezCarlos D. Lopez Posts: 9
    edited 2009-12-03 21:57
    OK thanks for the help.
    If this were the program that called the code posted previously:

    OBJ
      QuadCog : "QuadCog"
    
    PUB Main
      dira[noparse][[/noparse]16..19]~
    
      QuadCog.start
    
    



    Would these need to be loaded the EEPROM instead of RAM?
    I have Visual Studio.NET 2005, so I could use vb.net or c#. But how would you send the frequency parameter?
Sign In or Register to comment.