Welcome to the Parallax Discussion Forums, sign-up to participate.

Mark_T
Posts: **1,566**

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:

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

I'll post some more details soon.

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

I'll post some more details soon.

## Comments

25 Commentssorted by Date Added Votes1,5660Vote UpVote Downat 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.

1,5660Vote UpVote DownI 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! ]

12,6320Vote UpVote DownLooking 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, TriBladeProp OS(also see Sphinx, PropDos, PropCmd, Spinix)Website: www.clusos.comProp Tools (Index) , Emulators (Index) , ZiCog (Z80)1,5660Vote UpVote Downa 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(!)

1880Vote UpVote Down"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.

2,7690Vote UpVote Down1,5660Vote UpVote DownI'm also cleaning up my python filter generation scripts for 'release' - they need more

comments to be intelligible ATM!

400Vote UpVote DownIt 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?

Invention is the Science of Laziness

21,0240Vote UpVote DownVery 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-Exupery1,5660Vote UpVote Downcoefficients are truncated to 10 bit which will affect that a little.

1,5660Vote UpVote Down21,0240Vote UpVote DownThe 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-Exupery1,5660Vote UpVote Downscaling 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

400Vote UpVote DownDivide 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.

Invention is the Science of Laziness

1,5660Vote UpVote Downjitter proportional to the least significant bit, 3 orders of magnitude less. This is why people bother

with sinusoidal DDS in the first place.

21,0240Vote UpVote Down-Phil

Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away.-Antoine de Saint-Exupery400Vote UpVote DownI 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

Invention is the Science of Laziness

21,0240Vote UpVote Downunless 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

10,2080Vote UpVote Downreset 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.

400Vote UpVote DownThe 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

Invention is the Science of Laziness

21,0240Vote UpVote DownHere'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:

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:

-Phil

910Vote UpVote DownMaybe worthwhile when Prop II arrives, with that hardware CORDIC!

21,0240Vote UpVote Down-Phil

400Vote UpVote DownFor "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.

Invention is the Science of Laziness

1,5660Vote UpVote Down4 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