Author Topic: Sine wave lookup table  (Read 5258 times)

0 Members and 1 Guest are viewing this topic.

rcbuck

• Regular Contributor
• Posts: 134
• Country:
Sine wave lookup table
« on: April 28, 2017, 03:17:30 am »
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.

ebclr

• Super Contributor
• Posts: 1838
• Country:
Re: Sine wave lookup table
« Reply #1 on: April 28, 2017, 03:50:30 am »
Basically, you define on an array of variables from 0 to 90 degree precalculated and play with inversion for the signal for others degrees, in other words, you need to define only 1/4 of the cycle, the remaining 3 are easy inversion or 1 subtraction or both.

You can define the full cycle, will be a little faster but will take more memory, after that a simple do-while loop, will simple change the index from the table to the AD ( or 4  for the  reduce memory ) will do the job

evb149

• Super Contributor
• Posts: 1666
• Country:
Re: Sine wave lookup table
« Reply #2 on: April 28, 2017, 04:06:40 am »
#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/CORDIC
https://en.wikipedia.org/wiki/Sine

Hey there's even a WP article on LUT of sine.
https://en.wikipedia.org/wiki/Lookup_table#Computing_sines

FIxed 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.
« Last Edit: April 28, 2017, 06:42:41 am by evb149 »

The following users thanked this post: ebclr

BrianHG

• Super Contributor
• Posts: 2961
• Country:
Re: Sine wave lookup table
« Reply #3 on: April 28, 2017, 06:28:57 am »
Now if you want to see a discussion of how to go about generating a sine table, either integer/floating with and without the sin(x) function, it's been thoroughly  over here:
https://www.eevblog.com/forum/microcontrollers/code-used-for-sine-table/
__________
BrianHG.

hamster_nz

• Super Contributor
• Posts: 2035
• Country:
Re: Sine wave lookup table
« Reply #4 on: April 28, 2017, 08:01:41 am »
Best thing to try.

Use a 32-bit phase accumulator and step size
uint_32 phase, step;

Use the top8 bits to index your table:

sample = table[phase>>24];
phase += step;

Frequency resolution is then (sample_rate/2^32) Hz.

'step' can be calculated as 2^32/sample_rate*desired_freq.
E.g.a step value of 1 would take 2^32 samples before 'phase' overflows back to zero.
or a step value of 2^20 would take 1024 samples to walk through the table back to zero.

It is quick, simple to code and hard to get wrong. :-)

Also, when making you lookup table think about if table entry 'i' is the value at i/n (where n is the size of the table, and I is from 0 to n-1) or is it the value at (2i+1)/2n, which is the middle of the sample period? One gives you a more symtrical table.
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.

amspire

• Super Contributor
• Posts: 3763
• Country:
Re: Sine wave lookup table
« Reply #5 on: April 28, 2017, 08:28:58 am »
The approach that probably hasn't been discussed much is to ditch the dac, and use a PWM using Don Lancaster's Magic Sinewaves. This is a PWM stream that has harmonic cancellation - so for example with 6 PWM pulses per quadrant, you can have no harmonics below the 25th harmonic.

http://www.tinaja.com/msquant/retry_m/xxxx.shtml

With a 16MHz clock, if you can get the cpu's PWM to output the right pulses, you can achieve 1Khz with 0.108% distortion - as long as you have a filter to cut out 25Khz and above. All without a DAC.

A little bit of work to turn one quadrant into 4 quadrants, but it all can be done. Not as easy or as versatile as a DAC, but for some applications, it can be perfect.

Don's calculator is written in Javascript, so you can capture the page and run it offline. The more pulses per quadrant (for a fixed clock), the higher you can push the first harmonic at the cost of distortion. With 2 pulses per quadrant, you can reduce the distortion to 0.04%, but the first harmonic is the 9th. If you have a fixed frequency, the ideal filter will have a notch (zero) at the frequency of the first harmonic - something like an elliptical filter. The first uncancelled harmonic is always really big.
« Last Edit: April 28, 2017, 08:53:57 am by amspire »

The following users thanked this post: ebclr

Phoenix

• Frequent Contributor
• Posts: 291
• Country:
Re: Sine wave lookup table
« Reply #6 on: April 28, 2017, 08:32:07 am »
It is quick, simple to code and hard to get wrong. :-)

I have had granuality problems generating sine with straight lookup, with some slightly more complex linear interpolation you can get a much cleaner sine wave. Great for arbitrary carrier/lookup frequency vs fundemental frequency.

• Super Contributor
• Posts: 1875
• Country:
• Reactor Operator SSN-583, Retired EE
Love Cypress PSOC, ATTiny, Bit Slice, OpAmps, Oscilloscopes, and Analog Gurus like Pease, Miller, Widlar, Dobkin, obsessed with being an engineer

The following users thanked this post: ebclr

rcbuck

• Regular Contributor
• Posts: 134
• Country:
Re: Sine wave lookup table
« Reply #8 on: April 28, 2017, 04:59:07 pm »
Thanks everyone for the suggestions. I will see if I can make one of them work. The PIC is running at 80 MHz so speed isn't an issue.

Quote
The approach that probably hasn't been discussed much is to ditch the dac
This is just a temporary thing to verify SPI code for the DAC is working. I will not be using the sine wave code in my project.

The project will take an analog signal into ADC channel 0. I will send the upper 8 bits to the DAC over SPI. I should hear the same audio out of the DAC that goes into the ADC. The problem I have is the ADC is working but I cannot get anything out of the DAC. It must be some kind of issue with the SPI communication to the DAC. The SPI works with a 25LC256 so I don't know why the DAC doesn't work. I figured the best way to solve the DAC problem was to just generate a tone and send that to the DAC.

I am doing all this on one board just for initial confirmation that everything works. Once I can get the ADC to DAC working on one board I will know my code is correct. The final step is to have the ADC output sent over Ethernet to a second board. The second board will receive the Ethernet data and send it to the DAC. This will be a 2 way link. Board one's ADC output will be reproduced at board two's DAC and vice-versa. I already have the client/server code working and can pass text message back and forth.

evb149

• Super Contributor
• Posts: 1666
• Country:
Re: Sine wave lookup table
« Reply #9 on: April 28, 2017, 05:31:39 pm »
Main common reasons why a SPI peripheral may not work:

* You are not generating the proper CHIP SELECT (and any other needed ENABLE) signals for the part and leaving them asserted and unasserted when needed.  The appropriate MCU port bits must also be configured to be able to OUTPUT those signals by SW or under control of the SPI peripheral block's automation.

* The MCU SPI CLOCK may not be generated at a rate that is compatible with the device's clock capability.

* The MCU CLOCK POLARITY / CLOCK PHASE may not be correct relative to the SPI clock polarity / phase the peripheral needs.

And since it is a DAC:
* Does it have proper POWER / GROUND connections and working supplies in the right levels?

* Does it use a VREF voltage or similar to set the output level and is it proper?

* Are you buffering and offsetting and scaling  the DAC output properly relative to its output impedance and current / voltage output capabilities?

rcbuck

• Regular Contributor
• Posts: 134
• Country:
Re: Sine wave lookup table
« Reply #10 on: April 28, 2017, 07:22:02 pm »
Quote
You are not generating the proper CHIP SELECT (and any other needed ENABLE) signals for the part

Sometimes the dumb things bite you in the ass. I verified the Vdd and Vss voltages were correct. This is a 4 layer board with internal power and ground planes so I didn't think that was the problem.

While verifying Vdd and Vss, I noticed the SDI pin of the MCP4901 was lifted off of its solder pad. I then remembered (how could I forget??) that I had done that. When I first brought the board up I had a problem with the PIC SPI engine communicating with the 25LC256. So I lifted the SDI pin of the DAC thinking it might have been interfering with the EEPROM chip. I got the 25LC256 working with software adjustments. But it was a couple of days later before I started working with the ADC/DAC code. By then I had forgotten the small detail about the DAC pin.

I just soldered the pin back down. Now when I inject a 1KHz tone into the ADC and send the ADC output bytes to the DAC, I hear the tone in my monitor speaker. The DAC is driving an onboard MCP6001 which then drives an external amplifier.

Like Hannibal, I like it when a plan comes together.

Smf