Sine Wave Library for the Due

There is a library available for the Due that creates sine waves on the fly and presents the output on a DAC. It has not been extensively tested so comments and corrections are welcome.

Due_2KHz.pdf (24.4 KB)

Hello there,

Looks like an interesting library.

It would help others greatly if you would fully comment the source code. That is, explain why there are various constants and why the calculation is done that way. This would make it quicker for other users to modify it for their own purposes.

For a quick example:
omega=2pif; //convert frequency in Hertz to angular frequency

This kind of library can be taken pretty far. For example produce various shape waves like triangle.
A more advanced version could produce sounds that sound like familiar instruments like piano, oboe, violin, etc.

Thanks for the feedback. I updated the code with some comments.

It is possible to create waveforms using the first few terms of a Fourier series. The one disadvantage to this method of sine creation is that the phase of the individual terms tend to drift so that the waveform shape gets jumbled. For audio applications this shouldn't be a big deal.

I played around with the creation of a class that would create the first three terms of an arbitrary series. But I don't see the interest to make it worthwhile.

Hi again,

I meant comment for functions as well as variables, what the variables are doing for us and how they fit into the picture :slight_smile:

But anyway, what do you mean that the phase of the terms tends to drift?

MrAl:
But anyway, what do you mean that the phase of the terms tends to drift?

If you create signal 1 of frequency f and another signal 2 of some multiple of f, the signals will initially align with the zero crossings of signal 1. Both signals initially cross zero at the same time. Over time, the zero crossing of signal 1 will not align with any zero crossing of signal 2. The term used in physics is the signals are not "coherent". The phases don't align over time because of phase drift even though they may start out aligned. You can't control the frequency exactly with this method.

charliesixpack:
If you create signal 1 of frequency f and another signal 2 of some multiple of f, the signals will initially align with the zero crossings of signal 1. Both signals initially cross zero at the same time. Over time, the zero crossing of signal 1 will not align with any zero crossing of signal 2. The term used in physics is the signals are not "coherent". The phases don't align over time because of phase drift even though they may start out aligned. You can't control the frequency exactly with this method.

Hi again,

I am having trouble understanding what you are talking about, mostly because i know i can generate a wave that is for a simple example:
y(t)=Asin(wt)+Bsin(2w*t)

and the second wave should have no problem keeping in sync with the first wave for the next 100 years :slight_smile:

At the very least, we can always start time 't' over again and thus there would be no way they could end up out of step with each other after 100 years if in fact they were in step to begin with.

What it sounds like to me what you are talking about is when generating waves that are NOT an integer multiple of each other (which would not really happen with a regular Fourier Series) such as:
y(t)=Asin(wt)+Bsin(5.1w*t)

where for this wave there would be a phase shift for the second term that would take about 10 cycles before it became in sync with the first term again.

If this is not what you were referring too, then please state the reason why you say integer multiple waves will become out of sync phase wise. You might also show an example wave with at least two components like i did, then say what causes them to become out of sync. This way i will understand what you are talking about better. Thanks :slight_smile:

This is pretty interesting to me because i have a lower frequency wave generator project in the works.
There are other ways to generate waves also. I planned to use this with an output buffer to test other circuits that need a specialized wave input for proper testing.

MrAl:
What it sounds like to me what you are talking about is when generating waves that are NOT an integer multiple of each other (which would not really happen with a regular Fourier Series) such as:
y(t)=Asin(wt)+Bsin(5.1w*t)

where for this wave there would be a phase shift for the second term that would take about 10 cycles before it became in sync with the first term again.

This is exactly what I am talking about. Take the simplest case where the integer multiple is 1 (you are trying to produce the same frequency).

If the frequencies match to one part in a million, after 1,000,000 cycles they will be 360 degrees out of phase. After 500,000 cycles they will be 180 degrees out of phase. For a 1 KHz signal that is 500 seconds to go completely out of phase.

You can never exactly match frequencies on independent oscillators. You need some means to sync the phase like generating the frequencies from the same clock in a way that maintains phase.

charliesixpack:
This is exactly what I am talking about. Take the simplest case where the integer multiple is 1 (you are trying to produce the same frequency).

If the frequencies match to one part in a million, after 1,000,000 cycles they will be 360 degrees out of phase. After 500,000 cycles they will be 180 degrees out of phase. For a 1 KHz signal that is 500 seconds to go completely out of phase.

You can never exactly match frequencies on independent oscillators. You need some means to sync the phase like generating the frequencies from the same clock in a way that maintains phase.

Hi again,

You must really be talking about something very different than i am.

Who said anything about independent oscillators? We are talking about generating two waves from within the SAME processor, with the same math expressions for both waves. They cant be different. Why would we want to use two different oscillators? We want to generate these waves using one program and one ARM chip, so I dont understand what you are trying to accomplish here.

In the real world sin(wt) and sin(2w*t) in many cases (but not all) will be real, separate oscillators, but within the ARM chip these two are generated from the same clock and same math expressions. We can do them perfect in the real world too if we use digital logic to generate the base frequencies, then filtering into almost pure sine waves. There might be some phase shift there, but it will be constant for the next trillion years or until the power is turned off, whichever comes first :slight_smile:

If this still isnt what you are talking about that's fine, just let me know, maybe a little more information about what you are trying to do and how you intend to do it so i can understand you better.

I used the example of two oscillators at the same frequency to simplify the problem. If you are talking about generating signals of frequency f and 2f the principle is the same. You will not get ideal frequencies. There will be some error even using the same processor. The 2f frequency will not be exactly twice the f frequency.

If you use my algorithm to generate sine waves you can see this effect. If you generate the first three Fourier terms of a square wave you will initially see a textbook result. Over a few seconds you will see something completely different because the frequency multiples are not exact even for the same processor and the same algorithm. The phases drift. I don't know how else to make the point.

charliesixpack:
I used the example of two oscillators at the same frequency to simplify the problem. If you are talking about generating signals of frequency f and 2f the principle is the same. You will not get ideal frequencies. There will be some error even using the same processor. The 2f frequency will not be exactly twice the f frequency.

If you use my algorithm to generate sine waves you can see this effect. If you generate the first three Fourier terms of a square wave you will initially see a textbook result. Over a few seconds you will see something completely different because the frequency multiples are not exact even for the same processor and the same algorithm. The phases drift. I don't know how else to make the point.

Hi again,

Ok i think i see what you are trying to say here, and i think it is a good point. If the second wave error in time accumulates then it will show up as a phase shift. But that's if the second wave error accumulates.

Knowing this might happen, we can force synchronization cant we?
For example, keep a running time variable t (if needed) and use a cycle time tc:

y=sin(wtc)+sin(2w*tc)

We will usually start out with tc=0, and at the end of the cycle time tc set it back to zero:
tc=0

This means if the phase error over the first cycle is 1 part in 1 million then the phase error will be 1 part in 1 million for the second cycle as well.

We might want to keep a running time variable too though:
t=t+tc

just so we know where we are in real time.

Note this works with integer multiples, but some non integer multiples could be done too as long as they dont result in a cycle time that is too long.

But then again, doesnt the Due do (ha ha, "Due do", say it out loud, but only once) double floats? Does it use this for the trig functions too? I'd have to check, but that might mean we get a longer time period before we see significant phase shift (you could check into this if you like).

We would also have to check to see how the trig functions work for this processor. Some may use the modulus of the frequency so that we only operate on the difference between the actual argument and a multiple of 2pi (ie principle angle).
Short table:
2
pi=>0
3pi=>pi
4
pi=>0
5*pi=>pi
This would mean only the initial phase shift (which would be quite small) would be retained over any number of wave cycles.

Check this out and see what you think.

We could also try this numerically on the PC without using the Due just to see the numerical results.

Hello again,

You may wish to read the post just before this one which explains this in more detail.

Using another language i tested the idea that there may be a phase shift when we try to generate two waves at the same time from within the same processor using the same math package for both waves. A phase shift in one of the waves (and not the other or a different phase shift in the other) would cause wave shape distortion (even though all the right harmonics are still present) mainly because the Fourier reconstruction depends not only on the harmonic amplitude but the phases of the individual harmonics as well. If this problem occurred, it may be ok for audio, but for wave shape generation (something i am interested in doing very soon) if we want to create a signal out of pure sine waves of different frequencies and that signal has to be a certain wave shape (such as triangle) then we must have a way to generate sine waves that have at the very least constant phase shift relative to each other.

As it turns out, this criterion seems to be satisfied without going through too much trouble, at least up to the precision of the math operators, but that's a different story which i'll get to in a minute.

First, what really happens when there is a numerical error in the Nwt portion of the wave is BOTH waves change phase, so the sin(wt) wave and the sin(2wt) wave change phase, but they stay in step with each other. So if wave 1 changes phase by 20us then the other wave changes by 20us too, so they still cross zero at the same point, just not in sync with the original zero crossing. So we might say that they change frequency by a very small amount.

There is a secondary problem however that creeps in. That's the numerical truncation once we get to very high values of time 't'. When this happens, the multiplication wt becomes less and less accurate, and although the phase for the two waves stays the same, we dont get a sine wave any more for each one because the required precision for the calculation involving the sin() function is no longer met due to the numerical truncation of the product wt (with t very very large).
Of course the way around that is to continually update 't' to be a different variable that resets every major cycle. If this isnt done, then there would be a limit as to how long we can run this without seeing the sine waves turn into square waves. For a 1Hz wave this is not too much of a concern, because it might run for over 1500 years that way, but as soon as we start to get up in frequency it may start to becomes more significant (i havent calculated the effect at say 1Mhz). The DAC in the Due ARM does not allow changing too fast anyway i think, although i dont know what the max frequency is i'd have to look that up.

So the best bet is to use a fictitious time 't' for the sine calculations and that seems to work the best all around. The drawback is we would probably have to do one more calculation to figure out the cycle time before we start to generate the wave.

I'll be looking into this further once i start the wave generator 'project' (just a pet project really i want to use for testing other stuff).

If you still disagree, simply show me an example of a wave that somehow screws up after so many cycles and i'll look at that carefully. I can post the plot for the tests i did if anyone is interested in seeing them.
Hey, can we upload a file here...

If phase really matters, you could restart the signals when a zero crossing is expected. You expect a zero crossing at an integer multiple of the period from the time of start. The initial conditions force a zero for the first sample.

charliesixpack:
If phase really matters, you could restart the signals when a zero crossing is expected. You expect a zero crossing at an integer multiple of the period from the time of start. The initial conditions force a zero for the first sample.

That's what i told you.
Either that or just keep subtracting 2pi whenever the angle gets over 2pi.

I did a plot to show that this works.

I may do it a different way anyway for my particular project, but this is still good to know.

Will this library work on an Arduino nano?.. Or a digispark attiny85 dev board?

Please could you give examples of wiring setup for the example givien with the library.

MrAl:
That's what i told you.
Either that or just keep subtracting 2pi whenever the angle gets over 2pi.

I did a plot to show that this works.

I may do it a different way anyway for my particular project, but this is still good to know.

Dear folks,

KISS.

may I recommend to try that extremely simple code?:

int x; // Sinus wave
int y; // Cosinus wave
int i = 0;  // just to record the number of steps
int n = 4; // 8 =~ 50 steps per wave, 57=ca 1° steps (360 steps per wave)  mit n=553 0,1° steps (3600 steps per wave)
int m; //Signal to DAC


void setup()
{
  // put your setup code here, to run once:
  Serial.begin(9600);
  analogWriteResolution(12);
  y = 10000;
  x = 0;

}

void loop()
{
  // put your main code here, to run repeatedly:

  y = y - (x / n);  
  x = x + (y / n);
  ++i;
//m=x>>4)+2048;      // uncomment to debug to serial plotter
//Serial.println(m); // uncomment to debug to serial plotter/
//while (i > 128) ;  // uncomment to get one shot w 128 values
analogWrite(DAC0X / 16)+2048);
}

Just try it and watch the miracle!

Tweaking it somewhat, I could get a sinusoide at ~14KHz, with 8-9 samples pro cycle.

//High Speed Sinus/Cosinus generation for Arduino Due.


int x; // Sinus wave
int y; // Cosinus wave
int i = 0;  // just to record the number of steps
int n = 2;// Changing n will change the sine frequency and its precision.
//           2   = 8-9 steps per wave 14kHz on the due
//           8   =~ 50 steps per wave 
//           57  =~ 1° steps (360 steps per wave, best precision 0,1% distorsion only
//           553 = 0,1° steps (3600 steps per wave)
//           25  = ~1kHz on the due
int m; //Signal to DAC
boolean t;
long microsec;


void setup()
{
  Serial.begin(9600);
  analogWriteResolution(12);
  y = 1800;  // 10800 to get 0,77V on the due.
  x = 0;
  pinMode(4,OUTPUT);
  noInterrupts();
  }

void loop()
{
  y -= x / n;
  x += y / n;
  ++i ;
  //t = !t;             // uncomment to measure the sample speed on D4
  //digitalWrite(4,t);
  m= x + 1880;     
  analogWrite(DAC0, m);
}