Sine wave generator

Hi everyone, I am very new to the propeller, assembly, and this forum. I am trying to build a function generator and so I am trying to output a sin wave on 8 output pins to go to a D/A converter. I have successfully output a ramp, so I know my LSB and MSB are in the right place and for that function the output pins were performing as expected. I am trying to use the sine table with no success. I was hoping someone could point me in a better direction. With some embarrassment I give you what I have so far:

sin_90 long $0800
sin_180 long $1000
sin_table long $E000 >>1
sin long 0
ptr long 0
ctr long 360
address long @address

I am uncertain I am accessing the array correctly, or even declaring it correctly. On an oscilloscope I get a periodic but non-coherent output from the DAC. I am also fairly sure I am not calling get_sin correctly as when I comment the entire routine out, my output is the same. Any help would be appreciated. Also, does anyone know more about programming the propeller in C? That would be easier for me for sure. Also any tips for debugging propeller asm?

Comments

First: please surround your code with [ code ] and [ /code ], (without the spaces) so that we see it in a code box. You can also select the code and press the # button in the editor.

If I understand your code right you want to read the angle array in the PASM cog and write the resulting sine value to P0..P7.
For that you need to pass the address of angle[0] to the PASM cog in the PAR register:

In the PASM cog you need to move the pointer in par to the variable address, then you can increment this later (PAR is not changeable)

begin mov address, par
mov dira, #$FF

You don't need all the indirect addressing with movs, you can just read the phase with rdbyte sin, address, and then increment address:

rdbyte sin, address
add address, #1
call #getsin

Now you get the sin value in the range -$FFFF...+$FFFF from the sine table in ROM. to fit this to 8 bits you can shift it by 17:

sar sin, #17
mov outa, sin

do that in a loop 360 times and with the ctr variable as you have done already and then move address back to the first array element: address=par.

Andy

Edit: I just forget that the sin routine needs the angle not from 0 to 359, but from 0 to 8191 for a full circle, so your Spin code must declare a word array for angle and fill it with
angle[index] := index *8192 / 360
Then in the PASM code you read the array with rdword instead od rdbyte and increment the address by 2.

yes, the cognew misses still the array address as second parameter. This second parameter value lands in the PAR register, now it will be 1 and your rdword's access the hubram from that address.

Then in the Spin code, make index a long, a byte will not go to 360.
You don't need the dira[0..7]~~, you set the dira in the cog anyway, and the Spin cog gets terminatedat the end of the main methode

In the PASM code:
- I think the rdlong in getsin must be a rdword, the sin table is stored in words.
- I guess you have connected a DAC at P7..P0, then you should add an offset to the sin value, so that zero is in the middle of the DAC range ($80). The sin value from the table is a signed 16bit value, so you should shift right with SAR and not SHR:

sar sin, #17
add sin, #$80
mov outa, sin

and the delay may be a bit short, I dont remember what is the minimal value so that waitcnt not hangs, but 20 should be safe.

When you have this delay-per-sample working correctly, which will have zero jitter but some granularity, another variant approach I've thought of (on paper) for Sine generation, is to config a COG Counter in NCO mode, and run a fast-polling loop on the upper 12 bits of PHSx value, and apply those bits as the Sine-table index (with the same quadrant shuffle you do now)

These bits will follow a sawtooth, the same as your present scan counter does.
Your sine out will have NCO frequency precision, and will scale naturally over frequency.

At very low NCO output rates, you will read the same PHSx value multiple times, as polling is faster than INC, and at higher Fout, your polling will skip some values, but still give a valid sine.
I think the cross-over point is ~9765.625Hz, and your frequency granularity is 18.6 milli hertz.

Above this, the scan loop and sawtooth may 'beat', but that will change the sample 'dots' along the sine wave, and any LPF will smooth that.

The next step would be to add a Clocked video i2s out, and use a stereo DAC chip..

There is room in a COG to support both Gen modes.
The hard-step size may have benefits where super low jitter matters, and the NCO tracker would be easy to sweep.

Pulling the getsin in-line once you have it working, will shave a couple of cycles off the loop.

I wrote a tutorial program using a free running cog counter to generate the phase of a sine wave, where a loop (pasm or spin, slowly) samples the phase, looks up the sine in the table, and transfers it to the output, which in the example is a second cog counter in duty mode. It is a different approach to look at.

Whoa! That was way back in the 2006 thread on "Spin code examples for the beginner". The version of that program that runs 8 sine wave generators to the leds on the demo board or the quick start is still mesmerizing.

Nice example.
{ Just need it some orders of magnitude faster to feed an i2c DAC ... }

What kind of speed are you looking for?

There is an old object I have in my personal library dated back in 2006 I think that Chip wrote originally that uses PWM and updates the duty with the correct phase position of the desired sinewave. The original file had an update rate of about 250kHz.
I modified and cleaned that file up so that the update rate to the PWM duty cycle is now over 500kHz.

Note: The 3dB roll off with the selected components is at about a 90kHz sinewave

Feel free to use the attached files however you wish. I think there are enough notes within the DEMO file and the driver file to get you started. If not let me know and I will try to help.

EDIT: I do like Tracy Allen's 'sinewave3.spin'... it seems to be smoother at higher frequencies than what I just posted. I might see If I can get his to work using only one I/O pin. Attached is a second version implementing Tracy Allen's sinewave using only one pin. See the 'cat' variable at the bottom of the Sinewave_v2.spin file. The sample rate was improved from 524kHz to 1.25MHz from the previous version 1 to the new version 2.

Here is the code with the latest corrections. I get some output at P0..P7 but have no DAC and no Scope connected.
There was a bug with the shift of the sine output, we need to shift it by 9 and not 17, that's my fault, sorry!

I went through this same process several years ago when the Prop first came out. I needed a nice clean sine wave to drive a voice-coil motor. After many days spent trying to get the propeller to do what I needed using a D2A converter... and still not QUITE being there... I purchased a DDS development board from Analog devices. It was a MUCH better way of doing what I needed. It was very easy to interface to, I got an amazingly clean sine wave at any frequency from zero hertz up to several megahertz. I ran the signal through a digital potentiometer to do scaling. It was MUCH easier, gave an amazing signal, used fewer pins, and once designed onto a board only cost a few dollars.

The evaluation board from digikey is: EVAL-AD9833SDZ-ND A bit pricy, (~$70) but the chips are in the $10 range. You might want to see if it fits your application like it did mine.

I am such a novice I think I only 50% understand your post, : / I think you are discussing an approach I would like to take though, I know it must be possible to use the system counter modules to control the frequency of a sine wave and that frequencies higher than the 2 kHz I was able to get with this code. I ultimately want to be able to control the frequency with two knobs, one for course adjustment which would set course frequency according to a priority encoder and then a fine adjustment knob using a potentiometer and an ADC. Right now I am trying to get the samples from the sin table into cog memory so that I can cycle through them rapidly using a counter loop as I believe you are describing. I am getting a triangle wave now which makes little sense to me. Here is my code:

I know it must be possible to use the system counter modules to control the frequency of a sine wave and that frequencies higher than the 2 kHz I was able to get with this code.

Above some ceiling, you are going to need to reduce the number of 'dots' in your Sine, and once you have an adder-version working, you can use a Counter in NCO mode (which is just an adder ) and use the upper bits as your quadrant index. Top 2 bits would be Quadrant choice, and next 12 bits are the phase inside that quadrant.

Beau gives Sinewave_v2.spin, which is a PASM version that does exactly this.
Did you try that ?

If you want smooth/clickless updates of new Freq values, calculated elsewhere, you would need to move the
rdlong frqa, pointer
inside the loop, which will slow it down slightly.

You may want to run both Software choices, as a skipping adder will always deliver points on a sine wave, but not always the same points every cycle, and if you no not smooth with a LPF that slight difference might be noticed.

I have yet to dissect sinewave_V2. I intend to as I think understanding it fully will improve my novice status. The only thing is, it seems that it puts out the sin wave on one pin whereas I am looking to put it out onto 8. In my current code I thought I was successfully writing the values from the sin table into cog memory because my original loop modified to have the instruction mov outa,cogmem produced a sine wave on the scope. Then I had it run only once through the angles, get the sine values and write them to memory, I thought. But then when I loop through the memory I thought I wrote to I get a triangle wave.

Nifty, uses a smaller COG based Full Sine table, which allows the scan loop to shrink to 5 lines.
Gives more X-Dots at high frequencies, at the cost of less X-dots at low frequencies, but the Y dots can be higher precision, is a DAC is available to use it. They are stored as 32 bit values.

It uses DUTY mode output to produce a pretty good sine wave up to about 500 kHz. Beyond that, things start to fall apart.

Not too surprising, at 400KHz you have 10 samples in the X axis, (so are taking stretched strides thru that 256 full sine table), but the DAC only has 20 clocks to build a Y axis value, before a new one arrives.

If this is insufficient for anyone, and they have deep pockets, this news is topical :

Although it would be possible to shorten the loop a little, it would come at the expense of frequency resolution.

It is already only 5 lines long - not much fat there !

I think Frequency resolution is determined more by the Adder, the loop affects the 'Dots on the Graph', not so much the underlying frequency (or frequencies, as I prefer to think of the NCO output )

I could see that making the Table 512 x 16, rather than 256 x 32 could give a useful gain of more X axis dots, for little real Y axis cost (not in a Prop DAC anyway)

A direct table allows any waveform to be synth'd, but as most change slowly, one way to get more resolution, would be to store a signed delta in the table.

Edit : oops, just dawned on me that delta storage is only possible in a no-skips/no repeats readout, so it would be of no use here, as an adder index design has both skips and repeats, depending on frequency.

The good thing about a table, is you can take as long as you like creating it, and read-back is very fast.

As you can see, inside the endless loop I've incorporated a few lines of code to modify phsa.
As it sits, it runs perfectly fine.
But if I comment out add temp, foo
and uncomment add temp, fum
it breaks.
What in the world am I doing wrong?

Hmm, a mystery. counter A (or rather explicit manipulation of PHSA) is controlling the instruction setting
FRQB from the 256 entry table. All looks reasonable, and fum should always be identical to foo.

This isn't some kind of name clash for 'fum' on some other part of the code?

Yup. That was the problem, Tracy. I mindlessly followed what Phil had done. Of course what he had actually done was recycle a register that had served its purpose and was no longer needed. I'm easily fooled by subtle acts of cleverness. ;-)

The number after the res tells how much to advance the assembly pointer, i.e. res 1 reserves one long and advances the assembly pointer by one. I guess res 0 doesn't advance the assembly pointer at all, so you ended up with three variables, acc, fum, and temp, all pointing to the same location in the cog. The output just sits at 1.65 volts.

Keep in mind that at 100kHz, phsa advances by 5_368_709 (frqa0) at each clock tick, once every 12.5 nanoseconds. That makes it 800 steps to traverse the phase accumulator.