Shop OBEX P1 Docs P2 Docs Learn Events
Syncing NCO output to trigger — Parallax Forums

Syncing NCO output to trigger

pgbpsupgbpsu Posts: 460
edited 2012-01-05 14:10 in Propeller 1
What's the best way (or any better way for that matter) to sync a 1.024Mhz counter generated square-wave output to an input signal?

I'm using Chip's counter code to setup a 1.024Mhz square wave output on a given pin. I want that waveform to start when a trigger signal arrives on another pin. I'm currently setting up the counter then waiting for the trigger then setting the 1.024Mhz pin as an output. My trigger is actually a 1 pulse-per-second from a GPS. I don't really care which PPS acts as the trigger, but I want it to trigger ON the PPS so I
1. startup with 5Mhz oscillator with PLL set to 16 (80Mhz system clock)
2. measure the number of system clocks in each of 10 seconds
3. average that to get the "actual" number of CNTS in one second
4. set the system clock to this value value
5. calculate CTRB and FRQB for my 1.024Mhz output signal and set them
6. wait for a PPS and grab system CNTS
7. predict when the next PPS will arrive, subtract from that the amount of time it takes to make pin an output.
8. wait for that system CNT to arrive
9. make pin output

This works okay. My output waveform is within a few hundred nanoseconds of my PPS although the first pulse is often a runt pulse- not a full pulse. I'm sure this has to do with the phase relationship between my 1.024Mhz output and when I set the pin to an output. So how can I do this better?

And secondly, I want to produce this same 1.024Mhz waveform on another board and have it look as close as possible (frequency and phase) to the first one for a few seconds. I believe checking the system clock against the a 1PPS should give me the actual system clock allowing me to get my 1.024Mhz frequency accurate. And syncing the output to the PPS I should be able to phase lock signal on different boards, at least for a few seconds. I understand there are temperature issues but for proof of concept I'm doing this on a workbench where there are few air currents and a stable temperature (less than 1 degree C) over seconds to minutes.

Thanks for reading and for any input.

Relevant code below.
PUB MAIN
  '' Setup PPS as input and measure system counts per second.  
  dira[PPS_PIN] := 0    ' set to input
  repeat idx from 0 to ARRAY_SIZE - 1
    waitpeq(0, constant(|<PPS_PIN), 0)        ' wait for pin to go low
    waitpne(0, constant(|<PPS_PIN), 0)        ' wait for pin to go high
    cntsPerSecond[idx] := cnt

  '' calculate mean value
  mean := 0
  repeat idx from 1 to ARRAY_SIZE - 1
    mean += (cntsPerSecond[idx]-cntsPerSecond[idx-1])

  mean := mean/(idx-1)

  '' make mean value new system clock value
  clkset($6F,mean) ' with to pll16x
  PAUSE_MS(1000) ' wait for it to take effect

  '' Synthesize frequencies on pin 0 and pin 1
  SynthFreq(MCLK_PIN, 1_024_000)           'determine ctr and frq for pin1
  CTRB := ctr                        'set CTRB
  FRQB := frq                        'set FRQB                   

  waitpeq(0, constant(|<PPS_PIN), 0)        ' wait for pin to go low
  waitpne(0, constant(|<PPS_PIN), 0)        ' wait for pin to go high
  startTime := cnt + mean - 820             '  remove CNTS required to make pin an output
  
  waitcnt(startTime)                       ' wait just less than one second 
  DIRA[MCLK_PIN]~~                         'make pin output

  repeat                                       ' stay alive so counters don't quit
    waitcnt(0)

Comments

  • pedwardpedward Posts: 1,642
    edited 2012-01-04 12:15
    I've noticed that Chip writes code so that he starts the counter first, then changes the pin to an output. I suspect the small delay allows time for the counter to settle before outputting a signal. This is also a good way to gate a signal.

    EG:
    test INA, 1 wc 'test pin 0
    muxc DIRA, 2 ' turn pin 1 into output or input depending on whether INA is high or low
    jmp #$-2
    
    
    

    This takes 12 clocks.
  • pgbpsupgbpsu Posts: 460
    edited 2012-01-04 12:30
    Hi pedward-

    Thanks for responding. I'm currently looking at moving this into a PASM cog to do the output generation. I think starting up the signal, then turning the pin on (as Chip has done as well) is giving me the runt pulse to start with. I'm not sure how big a deal this is, but I think I may be better able to sync this output signal each second to keep it in phase with the 1PPS if I move away from the counter and into a PASM cog doing waitcnt with an occasional waitpeq.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-01-04 13:27
    The way to do it in Spin or PASM is to set the 1.024 MHz pin to an output first. Then set frqx for the right frequency. Next prime phsx with a value that will give you the phase that you want out of the gate. Then do a waitpeq on the PPS pin, immediately followed by a command that sets ctrx to NCO mode. It will start right away (no settling needed for NCO) from the value programmed into the phase register. In PASM, there will be no uncertainty in the starting phase. In Spin, there will be a slight amount of uncertainty from the interpreter's hub access window.

    -Phil
  • jmgjmg Posts: 15,183
    edited 2012-01-04 14:48
    pgbpsu wrote: »
    I believe checking the system clock against the a 1PPS should give me the actual system clock allowing me to get my 1.024Mhz frequency accurate. And syncing the output to the PPS I should be able to phase lock signal on different boards, at least for a few seconds.

    If the 1pps is there on all boards this should not have "at least for a few seconds" issues ?
    Remember the 1.024MHz is also going to have its own jitter, which hopefully you are Ok with ?

    If you start at 80MHz, that divides by 78.125, & you will be 12.5ns quantized in phase & frequency,

    My maths gives
    Ta=78/80e6;Tb=79/80e6;A=7;B=1;1/((A*Ta+B*Tb)/(A+B))
    ans = 1024000

    so 7 cycles of Fa and one cycle of Fb is what your pin will actually deliver.
    Only over multiples of 8 cycles, will this average to exactly 1.024MHz

    Fa=80e6/78 Fa = 1025641.02564Hz
    Fb=80e6/79 Fb = 1012658.22784Hz
  • pgbpsupgbpsu Posts: 460
    edited 2012-01-05 08:54
    Hi Phil-

    Thanks for commenting on the right way to do this. Re-reading the Counter App note, it clearly states that the value of FRQx is added to the value of PHSx every clock cycle. Simple enough. However, when does this summing begin? Always, or only once I load CTRB with the NCO/pin info?

    Since I want bit 31 of the sum of PHSB and FRQB to be 1 right after I set the CTRB register, I tried presetting PHSB to
    PHSB := $8000_0000 - frq  ' frq is the frequency loaded into the FRQB register
    

    What I think should happen is on the first clock AFTER setting the CTRB, FRQB (which I set to: $0D1B_76C6) is added to PHSB (set to $72E4_893A) which results in $8000_0000. Bit 31 is 1 so APIN should be 1. Believe it or not, that not doing what I'd hoped. The rising edge of my 1.024Mhz output is delayed from the PPS (which I expected). The delay however varies more than I would like. Capturing a handful of images with the scope I get delays between my 1PPS and the start of the 1.024Mhz of 5-7uS. This seems a bit large to be hub window access. Secondly, my first pulse isn't as narrow as it should be. Based on the way the NCO mode works this must be caused by my choice of PHSB primer.

    The question now becomes how to preload the PHSB register with the correct value.

    -Peter
    The way to do it in Spin or PASM is to set the 1.024 MHz pin to an output first. Then set frqx for the right frequency. Next prime phsx with a value that will give you the phase that you want out of the gate. Then do a waitpeq on the PPS pin, immediately followed by a command that sets ctrx to NCO mode. It will start right away (no settling needed for NCO) from the value programmed into the phase register. In PASM, there will be no uncertainty in the starting phase. In Spin, there will be a slight amount of uncertainty from the interpreter's hub access window.

    -Phil
  • pgbpsupgbpsu Posts: 460
    edited 2012-01-05 09:07
    Hi jmg-

    Thanks for your post. You are correct about the width of individual pulses. I believe 7 short pulses and one long pulse will be acceptable, but to make things simpler for testing, I've shifted my output frequency down to 1.000Mhz. However, because the crystal isn't really 5Mhz (PLLed up to 80Mhz) my output waveforms still aren't perfect, but they are slightly improved.

    -Peter
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-01-05 09:14
    pgbpsu wrote:
    However, when does this summing begin? Always, or only once I load CTRB with the NCO/pin info?
    The summing begins immediately after the counter has been configured. Also, configuring the counter with a zero value stops the counting.

    BTW, you will get a symmetrical, jitterless waveform if you output at 1.25 MHz (80 / 64).

    -Phil
  • jmgjmg Posts: 15,183
    edited 2012-01-05 11:24
    pgbpsu wrote: »
    Capturing a handful of images with the scope I get delays between my 1PPS and the start of the 1.024Mhz of 5-7uS. This seems a bit large to be hub window access.

    With this sort of problem I often also output a port-pin pulse, so you know what the Pin-Sw Sync delays are, and can confirm how much of the the delay is counter-side and how much is capture-side related.
  • pgbpsupgbpsu Posts: 460
    edited 2012-01-05 14:10
    Although I haven't solved this problem I think I've learned enough that I know my approach isn't working. I was having trouble getting the counter generated version of my output signal to look the way I wanted. So I put together some PASM to wait for the PPS to arrive an then start toggling my output pin with the correct waitcnt instructions.
      iteration      := OUT_CLOCK_RATE * 2          ' how many highs and lows?
      mclkPulseWidth := ( clkfreq / iteration)
      ppsPinmask     := |<PPS_PIN 
      mclk2PinMask   := |<MCLK2_PIN
    '  iteration      += 51266   ' proto board
      iteration      += 51276   ' gg board
       
      waitpeq(0, constant(|<PPS_PIN), 0)        ' wait for pin to go low
      waitpne(0, constant(|<PPS_PIN), 0)        ' wait for pin to go high
    
      mclkCogId := cognew(@mclk_code,0)        'Launch new cog
    DAT
                  ORG       0                         ' Begin at cog RAM addr 0
    mclk_code
                  mov       time1, #1 wz              ' Z=0; put $01 into temp; Z=1 if #1=0
                  muxz      outa,  mclk2PinMask       ' Preset OUTA to LOW         "0"
                  muxnz     dira,  mclk2PinMask       ' Set    DIRA to HIGH=OUTPUT "1"
                  muxz      dira,  ppsPinMask         ' Set    DIRA to LOW=INPUT "0"
                  
    
    ' Sync this to rising PPS
    :sync_loop    mov       count, iteration          ' restock number of pulses in one second
                  sub       count, #1                 ' we want to sync at one less than full
                  waitpne   ppsPinMask, ppsPinMask    ' wait for PPS to go LOW; fall through if LRC_pin IS low
                  waitpeq   ppsPinMask, ppsPinMask    ' wait for PPS to go HIGH
    
                  muxnz     outa,  mclk2PinMask       ' RAISE MCLK2 pin
                  mov       time1, cnt                ' setup cnt
                  sub       time1, #9                 ' small adjustment to make first pulse correct size
                  add       time1, mclkPulseWidth     ' add pulse width in counts to time1
    :mclk_loop    waitcnt   time1, mclkPulseWidth     ' wait time1; set time1+=mclkPulseWidth
                  xor       outa,  mclk2PinMask       ' toggle pin
    
                  djnz      count, #:mclk_loop        ' Decrement loop count.
                  waitcnt   time1, mclkPulseWidth     ' wait time1; set time1+=mclkPulseWidth
                  muxz      outa,  mclk2PinMask       ' LOWER MCLK2 pin
    
                  jmp       #:sync_loop               ' jump back and do it again.
    
    'VARIABLES
    iteration      long      1
    ppsPinMask     long      0 
    mclk2PinMask   long      0 
    mclkPulseWidth long      0
    time1          res       1
    count          res       1
    
    
    This method has a faster response time and the waveform is more stable in the first few pulses.

    I've attached 2 plots showing the waveforms. In both figures the time base is 2.0uS/div and the vertical scale is 2.0V/div. The first figure (cntr.bmp) shows the 1PPS arriving (in yellow) about 5.2uS before the beginning of my counter-generated output (blue). The lag here varies a bit (I've seen as high as 7.5uS) and the first few cycles of this are not right. The second plot (asm.bmp) show the same story only using my PASM code to generate the square-wave output. You can see the lag from the PPS is much improved (stable at 50ns which is expected) and the waveform looks correct right from the get go.

    Although I'd like to do this with the counters because it would save me a cog, I'm satisfied (for now) that I can get this output wave synced to the PPS, but only at the start. The third figure 2asm.bmp shows the same code running on two independent boards. The only thing they share is a PPS pulse. The (external) trigger point (right in the center) is the PPS. Both boards then start putting out the PASM generated square wave. They start out in phase. The portion of the waveform from the left edge of the screen unto the trigger point is actually the end of the waveform from the previous 1PPS.

    There are two problems here. The first is that the waveform doesn't run all the way to the end of the second. The more critical problem for me is the two waves are out of phase. This can be seen by the mismatch between the two waveforms. But in reality it's worse than that. My PASM code has a loop structure that decrements each time through the loop. It is supposed to generate 2E6 toggles for each second. That doesn't work correctly. I have to add additional iterations to the loop (manually basically) to cycles all the way to the end of the second. Furthermore, I have a different number of cycles for the 2 different boards. In fact one board has 10 more cycles per second. So my error (both phase and frequency) is worse than what the above plot shows.

    Getting those first cycles synced has been solved. However keeping them synced through the whole second has not. I thought I could do this by measuring the performance of the crystal in use (using subsequent PPSs). Either this can't work because of error in the crystals or I'm just doing it wrong.

    I'm not convinced it can't be done. But I am convinced I'm not doing well enough.
Sign In or Register to comment.