# choosing best n points on sine wave for wave generation

I want to generate a sine wave using DAC+DMA. Currently, I have an example that generates a sine wave using DAC+DMA from an array of 60 points, at 8KHz. I want to be able to increase the frequency up to 12KHz, in increments of 100Hz. To do this without losing precision, I might need to lower the number of points per wave as the frequency increases. I wanted to ask the following:

1. Can you recommend a function (preferable in C, but pseudo code is also good, I just don't know the technical terms to search for it) to calculate the following function:

void get_best_n_points_for_sine_wave_plot(int n, uint32* array);

Which gets an input value n, and writes into array the n numbers that give the best n points to plot the wave on DAC (I suppose the lowest and highest points must be there, and the rest are scattered evenly before, between and after them? this is just a guess and I prefer your educated input)

1. any better way you could suggest to do this?

Thanks!

• As long as you meet the nyquist criteria (to the level that only you can decide) you can choose wherever you want those points to be with impunity. – Andy aka Feb 20 at 10:31
• How fast is your DAC? – user253751 Feb 20 at 10:36
• sure its an unsigned integer pointer ? – Sorenp Feb 20 at 10:48
• I will check the DAC speed a bit later, can you pleas etel me why it's important? – Ilans Feb 20 at 10:55
• yes, the sine wave starts at 0. but even if the output array goes +-, it's just adding a constant value to the entire array to get it to what I want, so it doesn't matter – Ilans Feb 20 at 10:56

I'm interpreting this as your sample rate is 60*8=480kHz.

You'll be hard pressed to find a convenient sample set that gives you linear accuracy, since changing the frequency of a sampled signal is geometric. Depending on your accuracy requirements, your table could become enormous.

Depending on your processor speed, you could probably implement a 2-pole IIR. Put the poles right on the unit circle in the Z plane, at the angles representing the frequency of interest, and keep the coefficients for each frequency. In your IIR, you can then simply switch in the coefficients for that frequency as required.

This requires a set of coefficients for each frequency, rather than what would almost be a sine table for each frequency. It's computationally far less taxing than calculating sin(x) for each sample. If you wanted to go fully continuous, you could calculate the coefficients each time the frequency is changed. The per-sample IIR calculations can be batched to your DMA size for efficiency.

If this isn't an option, or if you have arbitrarily large amounts of memory, you might want to include tolerances for your output frequency that would help us suggest table optimizations.

• Thanks for your answer, but it will require me to learn and test new things, something which sadly I currently have no time to do. Memory is not an issue. I prefer the easiest and fastest solution. I just want a function / pseudo-code to do what I defined or something equally simple. – Ilans Feb 20 at 15:27
• Then what are your tolerances on the frequencies? If you want them exact, you'll essentially need 21 independent tables. Even though they might have points in common, the indexing wouldn't be worth it. – Cristobol Polychronopolis Feb 20 at 16:07

See if you can break the rules for the sample rate. I expect you can arbitrarily change the sample rate to scale your output frequency, same table, same data points.

I interpret your requirement is to generate all frequencies on 100Hz steps from 8kHz to 12kHz.

The simplest way to do this is with a single sine table, and a sampling rate that would generate 100Hz if you single stepped through it. For 200Hz, you would skip through 2 at a time, for 8kHz you'd skip through 80 at a time. This will as a byproduct give you all multiples of 100Hz, up to the Nyquist rate.

One possible choice would be a 480 entry table, and a sample rate of 48kHz, which is a nice audio standard, and also a simple division from your 144MHz clock. Your top frequency is only 25% of the sample rate, so the anti-alias filter design is relatively straightforward. For least consumption of resources, your DMA must be able to wrap numbers modulo 480, or your CPU be fast enough to do that. The relatively low sample rate means there are plenty of CPU cycles to do the trivial operations needed. See the last paragraph for how to use a single stepping DMA.

Another choice would be a 512 long table, with a 51.2kHz sample rate, with a saving in the modulo arithmetic, at the expense of a trickier sample rate.

Pseudocode to generate the table

TABLE_LEN = 480   # or 512, or whatever
for index in (0 to TABLE_LEN-1):
datum = sin(2*pi*index/TABLE_LEN)


Pseudocode to step through the table with the CPU, with a binary table length, assuming you get an interrupt every tick of the sample rate

int RESOLUTION = 100
int tab_pointer = 0
int incr = freq/RESOLUTION

interrupt_function:
tab_pointer += incr
tab_pointer &= TABLE_LEN-1     # mask so there's no carry out to higher bits
dac = sine_table[tab_pointer]


Pseudocode to step through the table with the CPU, with an arbitrary table length, binary or not, assuming you get an interrupt every tick of the sample rate

int RESOLUTION = 100
int tab_pointer = 0
int incr = freq/RESOLUTION

interrupt_function:
tab_pointer += incr
if tab_pointer >= TABLE_LEN:
tab_pointer -= TABLE_LEN

dac = sine_table[tab_pointer]


If you still want to DMA to the DAC, and the DMA will only handle increments of a single address, then there's still a fairly quick way to do it. Build a table 480 long with the samples in the correct order, by using the second 'generation' function, but outputting to your new table rather than the DAC. If you need the speed between frequency changes, then read from your first 100Hz table. If you don't need the speed and want to economise on memory, then lose the intermediate 100Hz table, and build it directly from the sin function.