Shop OBEX P1 Docs P2 Docs Learn Events
shift out with CTR — Parallax Forums

shift out with CTR

Bobb FwedBobb Fwed Posts: 1,119
edited 2009-07-25 00:27 in Propeller 1
A while back I asked how it can be done, but I never got a straight answer.
I've looked at other peoples' code that have done it, but it is always somewhat hard to decipher CTRs (at least for me...I've never been good with them).

I am trying to shift out a 5-bit value. It should use both CTRs (one for the serial data, one for the clock), I am assuming.
Preferably, it would run at a specific speed (regardless of current clock speed -- but it has to be outputting at a max of about 2MHz).

Any help would be nice. About all I know is that the first CTR would be NCO mode, but controlling the speed and setting up the second CTR so it would output clock pulses between the first CTR's data changes....it escapes me.

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
April, 2008: when I discovered the answers to all my micro-computational-botherations!

Comments

  • lonesocklonesock Posts: 917
    edited 2009-07-24 19:36
    Hi.

    For me to understand counters, I had to think about the relationship of FRQx ("Frequency Register") and PHSx ("Phase Register") (I'll just use A for example). The entire job of counter A is to add FRQA to PHSA at certain times, and optionally control 0 or 1 or 2 output pins. Table 6 in the propeller datasheet shows when FRQA will be added to PHSA. In the NCO counter mode, the counter will ALWAYS add FRQA to PHSA (this means at each clock tick, so if you are using a 5MHz crystal and PLL16X, then the counter is doing the accumulation at 80MHz). But the second half of its job is to set the A pin (which you specify when setting the CTRA ("Control Register") register in bits 5..0) to whatever bit 31 is of PHSA.

    Now, the trick to shifting data out using the NCO counter mode is to set FRQA to 0, so PHSA never actually gets updated by the counter. However, the counter is still busy doing its job of making sure the A Pin is set to the high bit of the PHSA register. So, to shift out the value we want, we can just load PHSA up with our value, then ROR or ROL the PHSA register a bunch of times, and each time we move a new bit into the PHSA[noparse][[/noparse]31], the counter mirrors that on pin A.

    You need to make sure you keep your data line and your clock line in sync. I use both counters where one controls the CLK line and one controls the DataOut line to shift out one bit per instruction (using Propeller Assembly...Spin is too slow and non-deterministic for this to work). However, when you do it this way, you do not get to set the actual clock-out speed...it is a function of the clock speed of the propeller. So running at 80MHz means that the data will be clocked out at 20MHz. If you use PLL8X, then suddenly the data rate is now 10MHz. There isn't an easy way to throttle back the speed using the 2 counters technique.

    So you will probably want to use a combination of setting the clock line high and low using a single PASM command each, shift out the data using the NCO counter mode trick, then use a waitcnt to throttle back the speed. If you are running at 80MHz and you want to hit 2MHz clock output rate, then you get to use 10 instructions per bit out (20 MIPS / 2 MHz). You could easily do something like the following (untested) code:
    :loop
        or outa,maskCLK
        andn outa,maskCLK
        waitcnt timestamp,deltatime
        ror phsa,#1
        djnz bits_left,#:loop
    
    


    Note that this assumes you want to shift the data out LSB first, and that you set up the counter and phsa and the timing values ahead of time. There is most likely a way to use the video generator, but I have not looked into it at all.

    hth,
    Jonathan

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.

    Post Edited (lonesock) : 7/24/2009 7:42:12 PM GMT
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-07-24 20:29
    Thank you for the explaination, that makes more sense.

    You can't use the PLL divider on NCO (or am I not understanding how that works as well).

    Here is the code I am using now:
    '' input value is shifted all the way left so MSB of output is in bit[noparse][[/noparse]31] of val
    shift_out
                            SHL     val, #1         WC      ' shift output value and place bit 31 in C
                            MUXC    OUTA, DPin              ' set data pin to what value bit 31 was
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle                       
                            
                            DJNZ    Bits, #shift_out        ' cycle through the rest of the value
    


    Same number of instructions as your code (not using the CTRs).
    I just want a little faster. Maybe output at full speed as long as the clock pin is being controlled by the CTR.
    I realize now though that this code is 4MHz (at 80MHz clock). That's twice as fast as the datasheet says the MCP3208 should be able to handle...yet not a single problem; but hey, I always want to go faster! If it is running this fast at 3.3V, it should be able to go about 80% faster at 5V.

    Ultimately I could all but ignore CTRs if the propeller did a (nowadays, normal) single-cycle execution of it's ASM code.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2009-07-24 20:34
    Bob,

    By using a counter as a shift register, you eliminate the need for the MUXC. That saves one instruction. You can also unroll your loop to save the DJNZ. Lonesock's code was as long as yours, because he paced it with a waitcnt, which you did not do.

    -Phil
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-07-24 20:58
    Right, I saw that after I posted. I will mess with this and see what messes I can make.

    To clarify, I would do something like this:
                            MOV     CTRA, nco
                            MOV     PHSA, val
    
    shift_out
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle                       
                            
                            DJNZ    Bits, #shift_out        ' cycle through the rest of the value
    
    
    nco                     LONG    %00100 << 26
    val                     LONG    %11000 << 26           ' output value
    Bits                    LONG    5
    


    or
                            MOV     CTRA, nco
                            MOV     PHSA, val
    
    shift_out
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle                       
    
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle
    
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle
    
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle
    
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle                                                                                                
    
    nco                     LONG    %00100 << 26
    val                     LONG    %11000 << 26           ' output value
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-07-24 21:17
    OK...well I got it going here:
    entry
                            MOV     CTRA, nco
                            ADD     CTRA, DPin2
                            MOV     PHSA, val
                            
                            OR      DIRA, DPin
                            OR      DIRA, CPin
    
    shift_out
                            SHL     PHSA, #1                ' shift output value   
                                               
                            OR      OUTA, CPin              ' start clock cycle
                            ANDN    OUTA, CPin              ' end clock cycle                       
                            
                            DJNZ    Bits, #shift_out        ' cycle through the rest of the value
    
                            COGID   val
                            COGSTOP val         
    
    
    nco                     LONG    %00100 << 26
    val                     LONG    %11000 << 26           ' output value
    Bits                    LONG    5
    DPin                    LONG    |< 19
    DPin2                   LONG    19
    CPin                    LONG    |< 20
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2009-07-24 22:16
    Bob,

    I see you've gone back to the loop form, so apparently you don't need it to be any faster than it is. If you had, though, there's a way to send the clock pulse in one instruction, rather than two.

    I'm a little bit curious, though: why, if you're starting and stopping an entire cog just to send five bits, do you need the transfer itself to be so fast?

    -Phil
  • lonesocklonesock Posts: 917
    edited 2009-07-24 22:22
    You could set up one counter to shift the data out as described above,
    and the second counter to send out the clock waveform at 2MHz (for e.g.).
    Make sure your 1st data bit is ready to go in PHSA[noparse][[/noparse]31], then set the clock
    line high and start your CLK counter. Then in your loop:
    :loop
        waitpne maskCLK,maskCLK
        rol phsa,#1
        djnz bits_left,#:loop
    
    


    If I did that right, the cog will hang until your clock line goes low, then will
    move out the next data bit.

    Jonathan

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2009-07-24 22:49
    Another way is to set up CTRB as an NCO with a FRQB of 1. Then, when you need to output a pulse, write a number into PHSB equal to minus the number of processor clocks wide to want the pulse to be, viz:

    [b]CON[/b]
    
      [b]_clkmode[/b]      = [b]xtal1[/b] + [b]pll16x[/b]
      [b]_xinfreq[/b]      = 5_000_000
    
    [b]PUB[/b] Start
    
      [b]cognew[/b](@fast_ser, 0)
    
    [b]DAT[/b]
    
                  [b]org[/b]       0
    fast_ser      [b]mov[/b]       [b]ctra[/b],[b]ctra[/b]0              'CTRA is NCO output w/ FRQA == 0.
                  [b]mov[/b]       [b]phsa[/b],data               'Data is in PHSA.
                  [b]mov[/b]       [b]ctrb[/b],[b]ctrb[/b]0              'CTRB is NCO output w/ FRQB == 1.
                  [b]mov[/b]       [b]frqb[/b],#1
                  [b]mov[/b]       [b]dira[/b],#%11               'Set both pins to output.
    
    :loop         [b]ror[/b]       [b]phsa[/b],#1                 'Rotate data.
                  [b]neg[/b]       [b]phsb[/b],#3                 'Send a pulse 3 clocks long.
                  [b]jmp[/b]       #:loop
    
    [b]ctra[/b]0         [b]long[/b]      %00100 << 26 | 1        'Data on A1.
    [b]ctrb[/b]0         [b]long[/b]      %00100 << 26 | 0        'Clock on A0.
    
    data          [b]long[/b]      $aa55_aa55
    
    
    



    -Phil
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-07-24 22:58
    Phil Pilgrim (PhiPi) said...
    Bob,

    I see you've gone back to the loop form, so apparently you don't need it to be any faster than it is. If you had, though, there's a way to send the clock pulse in one instruction, rather than two.

    I'm a little bit curious, though: why, if you're starting and stopping an entire cog just to send five bits, do you need the transfer itself to be so fast?

    -Phil

    That was just me testing ... seeing if I could get something remotely what I wanted to come out.
    It is actually (now) already implemented in my MCP3XXX driver: obex.parallax.com/objects/488/ without loops...so ends up being 3 instructions per output bit.

    @Phil: That also sounds fast, and there are things I could use on the outgoing and incoming data.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!
  • lonesocklonesock Posts: 917
    edited 2009-07-24 23:00
    Phil Pilgrim (PhiPi) said...
    Another way is to set up CTRB as an NCO with a FRQB of 1. Then, when you need to output a pulse, write a number into PHSB equal to minus the number of processor clocks wide to want the pulse to be, viz:

    [b]CON[/b]
    
      [b]_clkmode[/b]      = [b]xtal1[/b] + [b]pll16x[/b]
      [b]_xinfreq[/b]      = 5_000_000
    
    [b]PUB[/b] Start
    
      [b]cognew[/b](@fast_ser, 0)
    
    [b]DAT[/b]
    
                  [b]org[/b]       0
    fast_ser      [b]mov[/b]       [b]ctra[/b],[b]ctra[/b]0              'CTRA is NCO output w/ FRQA == 0.
                  [b]mov[/b]       [b]phsa[/b],data               'Data is in PHSA.
                  [b]mov[/b]       [b]ctrb[/b],[b]ctrb[/b]0              'CTRB is NCO output w/ FRQB == 1.
                  [b]mov[/b]       [b]frqb[/b],#1
                  [b]mov[/b]       [b]dira[/b],#%11               'Set both pins to output.
    
    :loop         [b]ror[/b]       [b]phsa[/b],#1                 'Rotate data.
                  [b]neg[/b]       [b]phsb[/b],#3                 'Send a pulse 3 clocks long.
                  [b]jmp[/b]       #:loop
    
    [b]ctra[/b]0         [b]long[/b]      %00100 << 26 | 1        'Data on A1.
    [b]ctrb[/b]0         [b]long[/b]      %00100 << 26 | 0        'Clock on A0.
    
    data          [b]long[/b]      $aa55_aa55
    
    
    



    -Phil
    Sweet! I would not have thought of that! (For slow clock frequencies you won't get symmetrical waveforms, but probably not a big deal.)

    Jonathan

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
  • Bobb FwedBobb Fwed Posts: 1,119
    edited 2009-07-25 00:01
    That method worked great. It bumped my ADC driver up from 127ksps to 144ksps (almost 50% over spec at 5V, that's a good thing!) thanks!

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    April, 2008: when I discovered the answers to all my micro-computational-botherations!
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2009-07-25 00:27
    Bob,

    Don't forget to shut CTRB down when you're done sending data, or set FRQB to 0. Otherwise, after about 25 seconds, it will send a positive-going edge on its own.

    -Phil
Sign In or Register to comment.