Dual DDS for controlling phase and amplitude of signal

I have been playing with ways of using the Propeller to aid in handling SSB signal generation,
and in particular have come up with what seems to be a novel approach (which is well suited
to the Propeller, whose DSP capabilities are otherwise severely restricted by a lack of hardware
multiply).

The basic idea is to sum the output of two identical DDS units with distinct phase-offsets to
generate an output of controllable amplitude and phase. (DDS = 'direct digital synthesis')

Normally a DDS unit outputs a fixed amplitude sinewave by table-lookup from a phase accumulator.
The phase accumulator is stepped on every cycle by an increment that represents the frequency
of the output wave (w.r.t. the DDS clock).

The Prop can run a DDS loop fairly tightly (> 1MHz rate) since only addition and table lookup
are needed (alas the ROM sine table is not suitable as its only a single quadrant). And the counters'
FRQx and PHSx registers can do some of the lifting.

But multiplying the output to 16 bit accuracy would slow things down to perhaps 250kHz at best.
This can be improved by using mutiple interleaved cogs, but cogs are a very finite resource.

However summing two values of equal amplitude and frequency but different phase gives
a result with arbitrary amplitude - running two DDS loops and one additional ADD is not much slower than
one DDS loop, and takes only one cog.

So my basic ASM loop is:

:topofloop	mov     off1, PHSA        ' basic dual DDS loop, 16 instructions, all hub instructions aligned
		mov	off2, PHSB        ' use the counters' NCO mode as our DDS accumulator
		shr	off1, #18	  ' shift for the table (which has 2^13 word entries)
		add	off1, wave_table  

		rdword	off1, off1        ' read the 16 bit sine value
		shr	off2, #18         ' do the second DDS too
		add	off2, wave_table

		rdword	off2, off2
		add	off1, off2	  ' sum the DDS outputs, a 17 bit unsigned output value suitable for
		mov	OUTA, off1        ' R-2R DAC on pins 16:0  (although 12 or 14 bits is enough)

		nop	      		  ' keep exactly 64 clocks per loop...
		nop
		djnz	n, #:innerloop    ' loop for all but first 6 times which are unrolled to allow extra work

There's a lot of complication I'm not showing around updating the FRQx registers according to externally
provided (changing) phase angles - done using several unrolled copies of this loop and replacing the NOPs
and DJNZ with some tricky stuff.

Anyway the upshot is that I can generate (currently using a 9 bit R-2R resistor ladder), SSB at frequencies upto
about 500kHz from an audio signal source (currently sine or square wave).

My test harness uses a CORDIC cog to generate the signal, I then have a cog implementing a complex IIR
digital filter to produce band-limited quadrature audio signal from this.

The quadrature signals are passed to a cog that calculates the phase(z) (using atan2, again CORDIC), and arcsin(magnitude(z))
via log/antilog and arcsin lookup tables. This gives two angle values to drive the above DDS system.

Square wave test, LO = 87kHz
P1110100.JPG

I'll post some more details soon.

Comments

  • 25 Comments sorted by Date Added Votes
  • BTW in the 'scope pic you can clearly see the image of the fundamental (and 3rd harmonic maybe)
    at about 40dB down, and the characteristic decrease in amplitude of the 3rd/5th/7th/etc harmonics.
    There is some even harmonic content as I'm simply generating a sampled square wave without
    any aliasing filtering, so there is effectively jitter - hence the gaps between odd harmonics
    don't go down as far as would be expected.
  • The complex filter is a 5 pole elliptic one, with about 5kHz pass band and 50dB stopband atten,
    I believe.

    [ I use the Python scipy.signal library and matplotlib for filter design, its wonderful! For a complex
    filter I just convert to poles/zeros, rotate the poles and zeros in the complex plane and that's it! ]
  • This is amazing! Congratulations!

    Looking forward to see how this project goes.

    BTW Phil has done some amazing things along these lines with the prop.
    My Prop boards: P8XBlade2, RamBlade, CpuBlade, TriBlade
    Prop OS (also see Sphinx, PropDos, PropCmd, Spinix)
    Website: www.clusos.com
    Prop Tools (Index) , Emulators (Index) , ZiCog (Z80)
  • I didn't see very much recent when I searched the forum about modulation techniques, but I knew
    a few people might well be interested or inspired!

    What's rejuvenated my interest in rf techniques has been purchasing a spectrum analyser, something
    that has become affordable these days (well yes you can get secondhand bargains, but they weigh
    a ton and take up so much room).

    The current project was inspired by reading about the HeyPhone, a low frequency cave radio unit
    designed for UK cave rescue teams, and which is open sourced, but is all analog domain and uses
    about 20 opamps for lots of active filter circuitry as well as requiring a custom quartz crystal frequency.

    Basically the 15 year old design looked already 15 years out of date when it appeared and it
    looked ripe for a more modern version with smaller BoM, which seemed like
    an interesting challenge.

    Also the idea of transmitting through 500m of solid rock is basically cool (and obviously
    a life saver at times).

    So anyway as it uses 87kHz I got to thinking about using the Prop and I2S DACs/ADCs and
    how to avoid needing a 5.568MHz xtal.

    Another topic that might be of interest is how I code-generate IIR digital filters in PASM directly
    in Python (there is a FIR system already I note, a search will turn it up), since that is more widely
    applicable than SSB modulation(!)
  • MarkT,
    "Another topic that might be of interest is how I code-generate IIR digital filters in PASM directly in Python"
    Digital Filters using the Prop1are a topic of interest to some of us.
    We would appreciate if you post some of your work.
  • Mark this looks really interesting and I look forward to having a play. The amplitude control via phase shift is nifty.
  • OK, I'll look at packaging a version soon.

    I'm also cleaning up my python filter generation scripts for 'release' - they need more
    comments to be intelligible ATM!
  • Mark_T wrote: »
    Anyway the upshot is that I can generate (currently using a 9 bit R-2R resistor ladder), SSB at frequencies up to
    about 500kHz from an audio signal source (currently sine or square wave).
    Nice work!

    It should be possible to calculate 4 samples in parallel and use the video generator to clock them out. That would limit the output to 8 bit resolution, and the sine table would have to be designed with phase shifts for a specific frequency.

    If you can tolerate nasty harmonics and a varying DC offset, you should be able to get similar amplitude resolution using the counter NCO outputs. Since your output is at ~1/900 the Propeller frequency, ORing the two outputs gives PWM at ~88kHz with ~450 amplitude levels dependent on the phase difference. I've done a lot of PWM harmonic calculations with the goal of improving rpitx. The calculations I've done looked good, but the code has been one of many projects I don't have time to do. Normal PWM with a period of 900 would give 900 amplitudes at DC, but are using the fundamental frequency. Due to symmetry we loose half of the amplitude levels. Same fundamental amplitude, different phase. So, the limited 50-100% duty cycle of ORing NCOs is of no concern here. Would the power amplifier benefit from a PWM signal?
    James https://github.com/SaucySoliton/

    Invention is the Science of Laziness
  • Mark,

    Very interesting topic! How many dB do you estimate you're getting for carrier and opposite sideband suppression?

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Perhaps 25--30dB carrier, 50dB opposite SB. The complex elliptic filter is specified for -55dB stopband, but the
    coefficients are truncated to 10 bit which will affect that a little.
  • If you can tolerate nasty harmonics and a varying DC offset, you should be able to get similar amplitude resolution using the counter NCO outputs. Since your output is at ~1/900 the Propeller frequency, ORing the two outputs gives PWM at ~88kHz with ~450 amplitude levels dependent on the phase difference. I've done a lot of PWM harmonic calculations with the goal of improving rpitx. The calculations I've done looked good, but the code has been one of many projects I don't have time to do. Normal PWM with a period of 900 would give 900 amplitudes at DC, but are using the fundamental frequency. Due to symmetry we loose half of the amplitude levels. Same fundamental amplitude, different phase. So, the limited 50-100% duty cycle of ORing NCOs is of no concern here. Would the power amplifier benefit from a PWM signal?
    I'll have to think about that a bit - my first reaction is that NCO will have inacceptable phase noise compared to DDS.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 21,124
    edited July 18 Vote Up0Vote Down
    Mark_T wrote:
    I'll have to think about that a bit - my first reaction is that NCO will have inacceptable phase noise compared to DDS.
    Your intuition is correct -- unless you just happen to hit a fundamental frequency that's power-of-two fraction of the crystal frequency.

    The other thing you can do, short of a DDS, is to control a VCO with a filtered DUTY-mode output and program a long-period DPLL to keep the frequency on target.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Initial version of the python filter compiling stuff I mentioned - the generated spin lacks automatic amplitude
    scaling and driving code ATM - so you have to call the stages in turn with suitable SAR or SHL instructions to
    scale the intermediate results best

  • Mark_T wrote:
    I'll have to think about that a bit - my first reaction is that NCO will have inacceptable phase noise compared to DDS.
    Your intuition is correct -- unless you just happen to hit a fundamental frequency that's power-of-two fraction of the crystal frequency.
    The NCO should be able to divide the main clock by N without adding phase noise. Make that 2N if you insist on 50% duty cycle. Of course, beware of the roundoff error for FRQx.

    Divide by 900 for 88.889kHz
    Divide by 901 for 88.790kHz
    The difference is 98.66Hz. Frequencies between the integer steps should appear as PWM between the two. Pifm did this intentionally to get better frequency resolution. By operating at such a low frequency you have .4 degree phase resolution. For voice you probably won't be able to tell the difference. Digital modes might have a problem. Since you already use the counters as DDS accumulators it should be very easy to test.

    Here's another idea for generating SSB at higher frequencies: Run CTRA in PLL mode at 2x the output frequency (or more.) Connect the video generator output to R-2R ladder or DAC. Feed the video generator with colors 128+Amplitude and 128-Amplitude, pixels %10101010. This could be considered an 8 bit extension of broadcast mode. Modulate FRQA as needed. The problem with this is the counter phase noise gets worse at higher frequencies. The PLLs don't really filter it that well.
    James https://github.com/SaucySoliton/

    Invention is the Science of Laziness
  • The NCO should be able to divide the main clock by N without adding phase noise
    But NCO has jitter power proportional to the peak-to-peak signal amplitude, whereas DDS has
    jitter proportional to the least significant bit, 3 orders of magnitude less. This is why people bother
    with sinusoidal DDS in the first place.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 21,124
    edited July 20 Vote Up0Vote Down
    The NCO should be able to divide the main clock by N without adding phase noise.
    The Prop's counters do not do a divide-by-N. Their NCO modes are more like a multiply by a fraction of 2**32. And the counters do not reset upon rollover. So, depending upon the value of FRQx, there can be quite a bit of phase jitter.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Well, division and multiplication are equivalent. The problem is we must multiply by an integer that does not exactly represent the division we want. We could reset the counters manually every so often to hide the roundoff error.

    I put together a quick simulation and found that the NCO will run for seconds before deviating from a true divide-by-N. I always round up. This is likely not optimal. Some of the factors with a reset interval less than 1 may have an interval greater than 1 when FRQx is rounded down.


    n=4 frq=1073741824 (1073741824.000000)
    n=6 frq=715827883 (715827882.666667) out=1 ref=0 phs=6442450944 glitch_at_clk=2147483648 max_reset_interval=26.843546
    n=8 frq=536870912 (536870912.000000)
    n=10 frq=429496730 (429496729.600000) out=1 ref=0 phs=2147483648 glitch_at_clk=1073741824 max_reset_interval=13.421773
    n=12 frq=357913942 (357913941.333333) out=0 ref=1 phs=4294967298 glitch_at_clk=536870915 max_reset_interval=6.710886
    n=14 frq=306783379 (306783378.285714) out=0 ref=1 phs=1 glitch_at_clk=429496731 max_reset_interval=5.368709
    n=16 frq=268435456 (268435456.000000)
    n=18 frq=238609295 (238609294.222222) out=1 ref=0 phs=6442450950 glitch_at_clk=306783386 max_reset_interval=3.834792
    n=20 frq=214748365 (214748364.800000) out=1 ref=0 phs=6442450945 glitch_at_clk=1073741829 max_reset_interval=13.421773
    n=22 frq=195225787 (195225786.181818) out=1 ref=0 phs=6442450952 glitch_at_clk=238609304 max_reset_interval=2.982616
    n=24 frq=178956971 (178956970.666667) out=1 ref=0 phs=6442450945 glitch_at_clk=536870915 max_reset_interval=6.710886
    n=26 frq=165191050 (165191049.846154) out=1 ref=0 phs=2147483648 glitch_at_clk=1073741824 max_reset_interval=13.421773
    n=28 frq=153391690 (153391689.142857) out=1 ref=0 phs=2147483650 glitch_at_clk=178956973 max_reset_interval=2.236962
    n=30 frq=143165577 (143165576.533333) out=0 ref=1 phs=4294967301 glitch_at_clk=306783389 max_reset_interval=3.834792
    n=32 frq=134217728 (134217728.000000)
    n=34 frq=126322568 (126322567.529412) out=1 ref=0 phs=2147483648 glitch_at_clk=268435456 max_reset_interval=3.355443
    n=36 frq=119304648 (119304647.111111) out=1 ref=0 phs=2147483656 glitch_at_clk=134217737 max_reset_interval=1.677722
    n=38 frq=113025456 (113025455.157895) out=1 ref=0 phs=6442450944 glitch_at_clk=134217728 max_reset_interval=1.677722
    n=40 frq=107374183 (107374182.400000) out=1 ref=0 phs=2147483653 glitch_at_clk=178956979 max_reset_interval=2.236962
    n=42 frq=102261127 (102261126.095238) out=1 ref=0 phs=2147483656 glitch_at_clk=113025464 max_reset_interval=1.412818
    n=44 frq=97612894 (97612893.090909) out=1 ref=0 phs=2147483654 glitch_at_clk=107374189 max_reset_interval=1.342177
    n=46 frq=93368855 (93368854.260870) out=0 ref=1 phs=4294967303 glitch_at_clk=126322577 max_reset_interval=1.579032
    n=48 frq=89478486 (89478485.333333) out=0 ref=1 phs=4294967306 glitch_at_clk=134217743 max_reset_interval=1.677722
    n=50 frq=85899346 (85899345.920000) out=1 ref=0 phs=2147483648 glitch_at_clk=1073741824 max_reset_interval=13.421773
    n=52 frq=82595525 (82595524.923077) out=1 ref=0 phs=6442450945 glitch_at_clk=1073741837 max_reset_interval=13.421773
    n=54 frq=79536432 (79536431.407407) out=1 ref=0 phs=6442450944 glitch_at_clk=134217728 max_reset_interval=1.677722
    n=56 frq=76695845 (76695844.571429) out=1 ref=0 phs=2147483655 glitch_at_clk=178956987 max_reset_interval=2.236962
    n=58 frq=74051161 (74051160.275862) out=0 ref=1 phs=4294967301 glitch_at_clk=102261133 max_reset_interval=1.278264
    n=60 frq=71582789 (71582788.266667) out=0 ref=1 phs=19 glitch_at_clk=97612919 max_reset_interval=1.220161
    n=62 frq=69273667 (69273666.064516) out=0 ref=1 phs=25 glitch_at_clk=74051187 max_reset_interval=0.925640
    n=64 frq=67108864 (67108864.000000)
    n=66 frq=65075263 (65075262.060606) out=1 ref=0 phs=6442450974 glitch_at_clk=69273698 max_reset_interval=0.865921
    n=68 frq=63161284 (63161283.764706) out=1 ref=0 phs=2147483652 glitch_at_clk=268435473 max_reset_interval=3.355443
    n=70 frq=61356676 (61356675.657143) out=1 ref=0 phs=2147483656 glitch_at_clk=178956994 max_reset_interval=2.236962
    n=72 frq=59652324 (59652323.555556) out=1 ref=0 phs=6442450956 glitch_at_clk=134217755 max_reset_interval=1.677722
    n=74 frq=58040099 (58040098.594595) out=0 ref=1 phs=1 glitch_at_clk=143165579 max_reset_interval=1.789570
    n=76 frq=56512728 (56512727.578947) out=0 ref=1 phs=4294967304 glitch_at_clk=134217747 max_reset_interval=1.677722
    n=78 frq=55063684 (55063683.282051) out=1 ref=0 phs=2147483672 glitch_at_clk=76695878 max_reset_interval=0.958698
    n=80 frq=53687092 (53687091.200000) out=0 ref=1 phs=4294967308 glitch_at_clk=67108879 max_reset_interval=0.838861
    n=82 frq=52377650 (52377649.951220) out=1 ref=0 phs=2147483648 glitch_at_clk=1073741824 max_reset_interval=13.421773
    n=84 frq=51130564 (51130563.047619) out=1 ref=0 phs=2147483684 glitch_at_clk=53687129 max_reset_interval=0.671089
    n=86 frq=49941481 (49941480.186047) out=0 ref=1 phs=19 glitch_at_clk=61356699 max_reset_interval=0.766959
    n=88 frq=48806447 (48806446.545455) out=1 ref=0 phs=6442450957 glitch_at_clk=107374211 max_reset_interval=1.342178
    n=90 frq=47721859 (47721858.844444) out=1 ref=0 phs=2147483652 glitch_at_clk=306783404 max_reset_interval=3.834793
    n=92 frq=46684428 (46684427.130435) out=1 ref=0 phs=6442450956 glitch_at_clk=53687105 max_reset_interval=0.671089
    n=94 frq=45691142 (45691141.446809) out=0 ref=1 phs=10 glitch_at_clk=82595543 max_reset_interval=1.032444
    n=96 frq=44739243 (44739242.666667) out=1 ref=0 phs=6442450949 glitch_at_clk=134217743 max_reset_interval=1.677722
    n=98 frq=43826197 (43826196.897959) out=0 ref=1 phs=3 glitch_at_clk=429496759 max_reset_interval=5.368709
    n=100 frq=42949673 (42949672.960000) out=1 ref=0 phs=2147483649 glitch_at_clk=1073741849 max_reset_interval=13.421773
    n=102 frq=42107523 (42107522.509804) out=1 ref=0 phs=2147483650 glitch_at_clk=85899350 max_reset_interval=1.073742
    n=104 frq=41297763 (41297762.461538) out=1 ref=0 phs=2147483673 glitch_at_clk=76695891 max_reset_interval=0.958699
    n=106 frq=40518560 (40518559.396226) out=1 ref=0 phs=2147483648 glitch_at_clk=67108864 max_reset_interval=0.838861
    n=108 frq=39768216 (39768215.703704) out=0 ref=1 phs=4294967304 glitch_at_clk=134217755 max_reset_interval=1.677722
    n=110 frq=39045158 (39045157.236364) out=1 ref=0 phs=6442450960 glitch_at_clk=51130584 max_reset_interval=0.639132
    n=112 frq=38347923 (38347922.285714) out=1 ref=0 phs=6442450981 glitch_at_clk=53687143 max_reset_interval=0.671089
    n=114 frq=37675152 (37675151.719298) out=1 ref=0 phs=2147483648 glitch_at_clk=134217728 max_reset_interval=1.677722
    n=116 frq=37025581 (37025580.137931) out=0 ref=1 phs=19 glitch_at_clk=42949695 max_reset_interval=0.536871
    n=118 frq=36398028 (36398027.932203) out=1 ref=0 phs=6442450944 glitch_at_clk=536870912 max_reset_interval=6.710886
    n=120 frq=35791395 (35791394.133333) out=1 ref=0 phs=2147483697 glitch_at_clk=41297819 max_reset_interval=0.516223

    James https://github.com/SaucySoliton/

    Invention is the Science of Laziness
  • I think you're missing the point. When the counter's output switches state, it does not reset PHSx. So the values in PHSx on rollover can be different every time, unless the value of FRQx is a power of two. What this means is that each cycle can be starting with a different internal phase. This translates on subsequent rollovers to phase jitter on the outputs. Although the average frequency will be quite accurate, the phase jitter renders the counters unsuitable for generating RF for transmission, due to the resulting "birdies."

    Running the NCO through the internal PLL helps a little, but not enough, since the PLL's time constant is pretty short.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • jmgjmg Posts: 10,343
    We could reset the counters manually every so often to hide the roundoff error.

    I put together a quick simulation and found that the NCO will run for seconds before deviating from a true divide-by-N. I always round up. This is likely not optimal. Some of the factors with a reset interval less than 1 may have an interval greater than 1 when FRQx is rounded down.
    There will be some practical limit to when you can reset the counters manually every so often to hide the roundoff error ?
    What is the smallest N where this can be a general solution ?

    This also consumes more CPU resource, in order to watch and reset the counter, but for some that /N equivalent divider will be worth it.

  • SaucySolitonSaucySoliton Posts: 42
    edited July 25 Vote Up0Vote Down
    I think you're missing the point. When the counter's output switches state, it does not reset PHSx. So the values in PHSx on rollover can be different every time, unless the value of FRQx is a power of two. What this means is that each cycle can be starting with a different internal phase. This translates on subsequent rollovers to phase jitter on the outputs. Although the average frequency will be quite accurate, the phase jitter renders the counters unsuitable for generating RF for transmission, due to the resulting "birdies."
    My point is that it takes many many clock cycles for the starting phase to accumulate to the point where it affects the output. If we don't reset the counter we will experience a 12.5nS phase shift every few seconds. I don't think you'll see it on a spectrum analyzer.

    jmg wrote: »
    There will be some practical limit to when you can reset the counters manually every so often to hide the roundoff error ?
    What is the smallest N where this can be a general solution ?
    The table shows that n=6 will experience a phase shift at 26.8 seconds. That's 3 clocks high, 3 clocks low. I only investigated even dividers so far. Generally, the need to reset PHSx is greater at large N. It makes sense that a smaller value of FRQx will be proportionally more affected by rounding. The lower bound is 53.86/N seconds. Note that 53.86709 = (2^32)/80e6, the number of seconds to overflow a 32 bit counter at 80 MHz. Since we are dividing by N, we must keep the accumulation less than (2^32)/N to prevent it from affecting the output.

    At large N, there is more need to reset PHSx. But the 12.5nS phase shift that happens is less significant. At large N it is also reasonable to do divide-by-N using waitcnt. I think Mark could get an acceptable signal from the NCO. What I like about the DDS approach is the clean signal output with constant DC offset and low harmonics.



    n=900 frq=4772186 (4772185.884444) out=1 ref=0 phs=2147483658 glitch_at_clk=41297849 max_reset_interval=0.516223 safe_bound=0.059844
    n=902 frq=4761605 (4761604.541020) out=1 ref=0 phs=6442450960 glitch_at_clk=10374352 max_reset_interval=0.129679 safe_bound=0.059712
    n=904 frq=4751071 (4751070.017699) out=1 ref=0 phs=2147483821 glitch_at_clk=4836851 max_reset_interval=0.060461 safe_bound=0.059580
    n=906 frq=4740583 (4740582.004415) out=0 ref=1 phs=329 glitch_at_clk=4761935 max_reset_interval=0.059524 safe_bound=0.059448
    n=908 frq=4730141 (4730140.193833) out=0 ref=1 phs=43 glitch_at_clk=5867495 max_reset_interval=0.073344 safe_bound=0.059317
    n=910 frq=4719745 (4719744.281319) out=0 ref=1 phs=4294967469 glitch_at_clk=6567469 max_reset_interval=0.082093 safe_bound=0.059187
    n=912 frq=4709394 (4709393.964912) out=0 ref=1 phs=4294967310 glitch_at_clk=134218127 max_reset_interval=1.677727 safe_bound=0.059057
    n=914 frq=4699089 (4699088.945295) out=0 ref=1 phs=11 glitch_at_clk=85899547 max_reset_interval=1.073744 safe_bound=0.058928
    n=916 frq=4688829 (4688828.925764) out=1 ref=0 phs=6442450953 glitch_at_clk=63161405 max_reset_interval=0.789518 safe_bound=0.058799
    n=918 frq=4678614 (4678613.612200) out=1 ref=0 phs=2147483764 glitch_at_clk=12064814 max_reset_interval=0.150810 safe_bound=0.058671
    n=920 frq=4668443 (4668442.713043) out=1 ref=0 phs=6442450945 glitch_at_clk=16268819 max_reset_interval=0.203360 safe_bound=0.058543
    James https://github.com/SaucySoliton/

    Invention is the Science of Laziness
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 21,124
    edited July 25 Vote Up0Vote Down
    My point is that it takes many many clock cycles for the starting phase to accumulate to the point where it affects the output. If we don't reset the counter we will experience a 12.5nS phase shift every few seconds. I don't think you'll see it on a spectrum analyzer.
    Here's a spectral shot of a Propeller NCO generating 20 MHz. It's clean because FRQx is a power of two:

    20_000_000Hz.gif

    Here's a shot of the same NCO generating 20.1 MHz. It's rife with birdies due to phase jitter, since FRQx has many one bits:

    20_100_000Hz.gif

    These birdies make the Prop counters unsuitable for generating RF that will go out into the ether.

    Here's the program I used to generate the traces:
    CON
    
      _clkmode      = xtal1 + pll16x
      _xinfreq      = 5_000_000
    
    PUB  start
    
      frqa := frq_val(20_000_000)
      ctra := %00100 << 26
      dira[0]~~
      repeat
    
    PRI frq_val(frq) : r
    
    '' Compute the required value of frqx to obtain the frequency frq.
     
      repeat 32
         frq <<= 1
         r <<= 1
         if frq => clkfreq
            frq -= clkfreq
            r++
    

    -Phil


    800 x 480 - 25K
    800 x 480 - 25K
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • I toyed with the idea of using an Si570 for the propeller clock, doing extra math, then cherry picking output frequencies and varying the clock frequency for clean signals. LOTS of bother for something done more easily in other ways.

    Maybe worthwhile when Prop II arrives, with that hardware CORDIC!
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 21,124
    edited July 25 Vote Up0Vote Down
    I've played with using the LC oscillator in an SA600-series mixer, along with a varactor controlled by a DUTY-mode output from the Prop. The frequency was fed back into the Prop, which I programmed as a DPLL. It worked pretty well: the output was clean, and as a bonus, the output was a sine wave, rather than a harmonic-laden square wave.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Thanks for posting the plots. That 20.1MHz plot is unfortunate. Also unfortunate that the PLL usually does little to clean up the signal. It is not surprising because 20.1 != 80/N, where N is an even integer. Try FRQA=715827883 decimal (13.333333MHz, N=6.) I thought the whole debate was whether N needs to be a power of 2, or whether an (even) integer is good enough.

    For "random" frequencies the spurious emissions will decrease as the output frequency is lowered. At 88kHz, where OP is operating, the output should be close to acceptable. My simulation showed the spurs 58dB down.
    James https://github.com/SaucySoliton/

    Invention is the Science of Laziness
  • Just to make it clear in a concrete way why the Prop's NCO is jittery for non-power of two, imagine its
    4 bits, not 32:

    FRQ = 4:
    PHS:0,4,8,12,0,4,....
    OUTPUT 0,0,1,1,0,0,1,1,etc

    FRQ = 3
    PHS: 0,3,6,9,12,15,2,5,8,11,14,1,4,7,10,13,0....
    OUTPUT 0,0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0,...

    ie successive cycles are:
    000111
    00111
    00011
    repeat. Lots of big spurs. Add 28 trailing zeros and you can see exactly the same problem for 32 bit
    FRQ/PHS.

    The way the Prop does its NCO means it has uniform frequency resolution of 0.0186Hz (assuming 80MHz system clock),
    whereas divide-by-N would have a resolution strongly depending on frequency, about 1.25e-8Hz resolution at 1Hz,
    1.25e-2 Hz resolution at 1kHz and only ~12.5kHz resolution at 1MHz
Sign In or Register to comment.