#include <math.h>

#include <stdio.h>

int main( int argc, char **argv ) {

double const pi = 3.14159;

double const two_pi = pi * 2.0; // two * Pi radians per cycle (circle) == 360 degrees if you really must...

double const radian_frequency_omega = 1000.0 * two_pi; // 1000 cycles / second radian frequency

double const dac_scale = 127.0; // for +127.0 to -127.0 output range. Suitable for an 7-bit magnitude + sign bit DAC. Change if needed.

double const dac_offset = 0.0; // for a bipolar type DAC. Offset to mid-span sample value or whatever if using a unipolar DAC.

#define STEPS_PER_CYCLE 256 // this many samples are encoded along the circle of one cycle of the waveform frequency. So 256kHz is needed as a sample clock with this set to 256 and 1000Hz wave frequency being generated.

double radian_phase_shift_per_step = two_pi / ((double) STEPS_PER_CYCLE); // Every new step the circle phase changes by 2PI/N amount so at the end of STEPS_PER_CYCLE steps we're back at the beginning and the story never ends....

signed int sample_array[STEPS_PER_CYCLE]; // The look up table for N samples in an integer format that is useful for whatever DAC you want to use. Could be changed to an 8-bit variable type for 8-bit DACs or 32 bit variable type for 24 or 32 bit DACs... signed, unsigned, whatever is suitable.

// Generate the look up table data. Calculate the value at every discrete step around the circle.

unsigned int step_index;

for( step_index = 0; step_index < STEPS_PER_CYCLE; step_index++ ) {

double const theta = ((double) step_index) * radian_phase_shift_per_step; // the angular position at this step.

double const sin_theta = sin(theta); // the sin value of the current angle. Could add a phase shift initialization if it mattered...

signed int const sample = (double) ( ( (double)(sin_theta * dac_scale) ) + dac_offset); // sin() returns +1 to -1 range. Create a sample of the range and integer type needed for the DAC from the +1 to -1 range double.

sample_array[step_index] = sample; // Store this LUT value.

// Proceed to the next sample entry if there is one...

}

// Now play that waveform endlessly....

// Interrupts and/or DMA could be used for this...

for(step_index = 0;;) {

dac_output( sample_array[step_index] );

step_index = step_index + 1;

if( step_index >= STEPS_PER_CYCLE ) {

step_index = 0;

}

delay_until_next_sample_time();

}

} // main

And yes you could store only 1/2 or 1/4 of the circle's values and do some quadrant angle related calculations to derive the other quadrant's values from the stored quadrant due to symmetry.

Or you could just calculate sin(theta) and scale it at every time step even if the time steps are at known but irregularly spaced intervals. And there are sin(theta) approximation algorithms taylor series, CORDIC, and many others. HW witha FPU can probably even calculate double sin(double theta) quickly..... Then again that's not using a LUT.

https://en.wikipedia.org/wiki/CORDIChttps://en.wikipedia.org/wiki/SineHey there's even a WP article on LUT of sine.

https://en.wikipedia.org/wiki/Lookup_table#Computing_sinesFIxed minor erratum. Also you'd typically calculate the LUT on the development machine and hard-code the DAC values into firmware FLASH in the application binary so you don't have to calculate the table entries on the embedded host. Yeah you COULD calculate the table data at start-up on the embedded target if you want.

You could also make an oscillator with an IIR type filter if that is interesting / useful. The Goertzel algorithm was used for instance to make resonators that were used to decode DTMF among other tone detection applications. Lots of ways to make a sine wave.

I have a PIC32 and want to use SPI to send data to a MCP4901 DAC to generate a 1 KHz sine wave.

Can someone point me to a link that explains how lookup tables are used to generate sine waves? Or explain how it works? I've searched online but all I have found are complex formulas.

I would like to use a table with 256 values and just need to know how often to step through the table. I guess the idea is to take each value and send it to the MCP4901.