Pages: [1]   Go Down
Author Topic: Dynamic generated sine wave  (Read 2552 times)
0 Members and 1 Guest are viewing this topic.
Groningen
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I have been trying to make a dynamic generated sine wave (sine wave is computed in a buffer) for a while now, however I keep ending up with a bit of jitter in my signal with no logical source. I am running the setup trough a 20khz low pass filter. I have checked the generated buffers and they display exactly what they need to. So could someone please help me out, included is a capture of the jitter on an oscilloscope.
So here is my code:
Code:
#include <avr/interrupt.h>   //add interrupts
#include <avr/io.h>          //no idea


float pi= 3.141592;
volatile int counter;

int buffersize = 200;
byte mainArray[201]; //should be one larger then buffersize
byte bufferArray[201];

volatile  boolean switchbuffer;  //buffer used to check if the buffer needs to be switched
boolean buffercheck;  //check if buffering is done, as to unload the CPU for buffering

int sinePhase; //integer that is there to make sure the sine wave his phase is transferred correctly between buffers


void setup(){
  Serial.begin(115200);
  noInterrupts();
 
  //preload a sine wave into the buffer and calc array
  for (int i = 0; i <= buffersize; i++){
    byte val = 127 + byte(127*sin(float(i)*(pi/50.0)));
    mainArray[i] = val;
    bufferArray[i] = val;
  }
 
  //setting up counters and checks
  counter=0;
  buffercheck=true;
  switchbuffer=false;
  sinePhase=0;

 
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20); //setting the prescaler (currently no prescaler)
  //TCCR2B = _BV(CS21);
  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable

 
   
 
 
  OCR2A = 180;        //set overflow compare register to a random value;
  OCR2B = 50;
  interrupts();
  Serial.println("i am");
 
}

ISR(TIMER2_OVF_vect){
if(counter > buffersize){
  counter = 0;
  OCR2A = bufferArray[0];
  switchbuffer = true;
 }else{
  OCR2A = mainArray[counter];
  counter++;
 }

}


void loop(){
  //Serial.println(counter);
  if(switchbuffer){
   for(int i = 0; i<=buffersize; i++){
     mainArray[i]=bufferArray[i];
   }
 
  switchbuffer = false;
  buffercheck = true; 
  }
 
  if(buffercheck){
    //generate shit
    generateSine(575);
  }
 
}


void printing(){ Serial.println(counter);}

void generateSine (int freq){
  float scale = 2*pi/float(62500/freq);
  int period = 62500/freq;
  int sinewaveTime = sinePhase;
 
  for(int i = 0; i<= buffersize; i++){
    if(sinewaveTime >= period){
     sinewaveTime=0;
    }else{
     sinewaveTime++;
    }
    byte val = 127 + byte(127*sin((float(i)+sinePhase)*scale));
   
    bufferArray[i] = val;
    //Serial.print(val);
   // Serial.print("\t");
  }
 // Serial.println();
  sinePhase=sinewaveTime;
  buffercheck=false;
  //Serial.print(mainArray[200]);
  //Serial.print("\t");
 // Serial.print(bufferArray[0]);
  //Serial.print("\t");
 // Serial.println(sinePhase);
}


* scope.jpg (263.47 KB, 1239x815 - viewed 63 times.)
Logged

New England
Offline Offline
Sr. Member
****
Karma: 7
Posts: 295
Natural Semiinductor
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

You can add program functions for diagnostics. The sine wave can run slowly while recording information about that discontinuity. Write an algorithm that will recognize that glitch and capture the state of all variables, arrays, and buffers before the event. That will let you slowly review the sequence of events that created the jitter.

My guesses:
The buffer does not have the right length to hold an integer number of cycles.
An integer overflowed.
A peripheral device needed service.
A Serial.print occurred and wrecked the phase.
Logged

I am going to get going.

USA
Online Online
Sr. Member
****
Karma: 17
Posts: 391
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It looks to me that there's more than one reason.  My favorite guess - and it's really only intuitive - is that your ISR demands data faster than the function generateSine() can create it.  Floating point is slow on the Arduino.  Trigonometric functions are painfully slow.

If that were the case, the interrupt would eventually get ahead of generateSine(), before the new data was ready, and would start delivering the previous calculation cycle's data.  That would introduce a phase shift in the output, which is what your tracing shows.

I think that you also get some jitter because you're outputting one too many samples.  I think that you're repeating a sample each time you switch buffers, and you get some minor jitter.  You might not be able to see it if you're filtering the output.

To test my "outrunning the function" theory, try changing the argument to generateSine() to 625, which would give you exactly two full cycles of data in the buffer.  See if you still get jitter.  Then, load both buffers in setup with identical data, and don't call generateSine() at all, and see if the jitter goes away.

I also think you might want to do these things, though they're also intuitive, and you'll want to examine them to see if you agree:
  • Use a 200-byte buffer, rather than 201.  Fill it the same way.  The first sample will be zero, and the last will be almost zero, heading for zero.  As it stands now, you start at zero, run around to zero, output another zero, and then start again.  You have a 201th of a cycle of jitter.
  • Change your tests to match the change in the buffer size.  For example, change this
Code:
for (int i = 0; i <= buffersize; i++){
to this
Code:
for (int i = 0; i < buffersize; i++){
And, I'd recommend saying this in generateSine(), instead of what you have:
Code:
  for(int i = 0; i<= buffersize; i++){
    sinewaveTime++;
    if(sinewaveTime >= period){
     sinewaveTime=0;
    }
I'll leave the rest to you.


Finally, you seem to be doing this the hard way.  See this link for a direct digital synthesis project using the Arduino Uno:
http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/.  If you read that project, read skeptically; they do some odd things.  My favorite oddity is that they disable the timer interrupt, do some floating point math, and reenable it.  I think that it would be better to do the math, disable the interrupt, store the result, and reenable.  See what you think.
Logged

Offline Offline
God Member
*****
Karma: 6
Posts: 524
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes, the best and fastest method is to precalculate a full phase sinewavetable.
Then feed that to the DAC.

If memspace is a concern, just calculate 1/4 of the full phase and use 2-complement logic for the rest 3/4.
Logged

Groningen
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks guys for the replies, the calculation time of the buffer was indeed a problem, it did several tests as suggested but to seed how long it really took to fill the buffer i decided to stop the intterupts when loading te buffer and re-enabling them when the buffer was filled.  Apparently it took almost five times as long to fill the buffer compared to the play time as you can see in the attached image. I wanted to do this so i could have various calculation during the loop, so i could make a lowpass filter or add several waves together (it is meant to become a full fledged synthesizer with digital oscillators). So just to all the people out there who want a wavetable lookup wave generator here is my code for now. (The whole wavetable isn't in there, because the whole post exceeded the maximum amount of characters, so if you want to use the same code use a programm like excel to generate a sinewae table with 3125 samples).
Code:
#include <avr/interrupt.h>   //add interrupts
#include <avr/io.h>          //no idea
#include <avr/pgmspace.h>    //to store values in progmem

//just to be crazy load 3125 samples im the progmem memory, why? 20hz sine wave, thats why. (I know overkill, will be adapted in future versions, sample size could be divided by 3 without losing fidellity)
prog_uchar sineArray[] PROGMEM = {127,127,128,}; // deleted all the values otherwise the post was to long, just use excel to generate your own wavetable.


volatile float scale;  //used to store the scale in which the counter should increment (as to get a specific frequency)
volatile float counter;  //global counter variable used in the timer interrupt
void setup(){
  
  Serial.begin(115200);
  //disable interrupts
  noInterrupts();
  counter = 0;
  
  float freq=1200;  //input the frequency that you want here
  scale=3125/(62500/freq); //formula to calculate how the counter should increment (3125 is the size of the whole array for one 20 hz sine wave, 62500 is the sampling rate, so 62500 samples per second)
  
  //enabling the outputs for TIMER2
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  
  //setting up timer 2
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);  //setting the timer to fast pwm mode with OCR2A/B as top
  TCCR2B = _BV(CS20); //setting the prescaler to 1
  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  OCR2A = 180;        //set overflow compare register to a random value, this one will be used for the wave generation
  OCR2B = 50;
  interrupts();
}

//////////////////TIMER interrupt\\\\\\\\\\\\\\\\\\\\
ISR(TIMER2_OVF_vect){
  
  counter= counter + scale;    //increment the counter with the scale
  if (counter>=3125){
    //reset counter if it reaches the end of the total sinewave array
    counter=0;
  }
  
   byte val = pgm_read_byte_near(sineArray + int(counter)); // read the value out of PROGMEM
   OCR2A = val;      // write value to the compare register
  
}

void loop(){

  
}

For now it is really basic, future plans are to link the frequency to midi and to add several other waves and use OCR2B as an ARSD envelope generator, feeding into an opamp, which serves as a voltage controlled amplifier.


* scope canceling interrupts.jpg (50.49 KB, 540x454 - viewed 41 times.)
Logged

USA
Online Online
Sr. Member
****
Karma: 17
Posts: 391
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I see trouble with doing floating point math inside the ISR.  The ISR runs about 135 CPU cycles or so without taking accounting for calls to floating point routines; I'd be surprised if the ISR could complete before the next Timer2 interrupt fires.  What kind of results are you getting from this code?

There's a reason why the DDS project uses long integers to keep track of phase and frequency - the math is much faster.
Logged

Groningen
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

As to the question if it wouldn't take up to much calculation time, the answer is no. It runs fine with the current code (tiny mistake i made, you have to take out the comment above the ISR routine other wise the compiler will give you an error). So to test the calculation limitations I have added several other waves (seven in total), the results are actually quite good, no jitter, some distortion (probably because it is a low fidelity wave), no stability issues. You can view the code here and I will also upload a screenshot of the final wave.

Code:
#include <avr/interrupt.h>   //add interrupts
#include <avr/io.h>          //no idea
#include <avr/pgmspace.h>    //to store values in progmem

//just to be crazy load 3125 samples im the progmem memory, why? 20hz sine wave, thats why. (I know overkill, will be adapted in future versions, sample size could be divided by 3 without losing fidellity)
prog_uchar sineArray[] PROGMEM = {127,127,128,128,128,128,129,129,129,129,130,130,130,130,131,131,131,131,132,132,132,132,133,133,133,133,134,134,134,134,135,135,135,135,136,136,136,136,137,137,137,137,138,138,138,138,139,139,139
}; // this list is incomplete so if you want to run the code you have to input a sinewavetable of 3125 samples long.


volatile float scale;  //used to store the scale in which the counter should increment (as to get a specific frequency)
volatile float counter;  //global counter variable used in the timer interrupt
volatile float squarecounter;
volatile float squarescale;
volatile float counter2;
volatile float counter3;
volatile float counter4;
volatile float counter5;
volatile float counter6;
volatile float scale2;
volatile float scale3;
volatile float scale4;
volatile float scale5;
volatile float scale6;
void setup(){
 
  Serial.begin(115200);
  //disable interrupts
  noInterrupts();
  counter = 0;
  squarecounter = 0;
 
  float freq=880;  //input the frequency that you want here
  scale=3125/(62500/freq); //formula to calculate how the counter should increment (3125 is the size of the whole array for one 20 hz sine wave, 62500 is the sampling rate, so 62500 samples per second)
 
  scale2=3125/(62500/1046.50);
  scale3 =3125/(62500/1318.51);
  scale4 =3125/(62500/1760);
  scale5 =3125/(62500/1567.98);
  scale6 =3125/(62500/3520);
  float squarefreq = 440;
  squarescale = 3125/(62500/squarefreq);
  //enabling the outputs for TIMER2
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
 
  //setting up timer 2
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);  //setting the timer to fast pwm mode with OCR2A/B as top
  TCCR2B = _BV(CS20); //setting the prescaler to 1
  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  OCR2A = 180;        //set overflow compare register to a random value, this one will be used for the wave generation
  OCR2B = 50;
  interrupts();
}


ISR(TIMER2_OVF_vect){
 
  counter= counter + scale;    //increment the counter with the scale
  if (counter>=3125){
    //reset counter if it reaches the end of the total sinewave array
    counter=0;
  }
  counter2 = counter2 +scale2;
  if(counter2>=3125){
    counter2=0;
  }
  counter3 = counter3 + scale3;
  if(counter3>=3125){
    counter3=0;
  }
  counter4 = counter4 + scale4;
  if(counter4>=3125){
    counter4=0;
  }
  counter5 = counter5 + scale5;
  if(counter5>=3125){
    counter5=0;
  }
  counter5 = counter6 + scale6;
  if(counter6>=3125){
    counter6=0;
  }
 
  squarecounter = squarecounter + squarescale;
  if(squarecounter >=3125){
    squarecounter = 0;
  }
 
   byte sinval = pgm_read_byte_near(sineArray + int(counter)); // read the value out of PROGMEM
   byte sinval2= pgm_read_byte_near(sineArray + int(counter2));
   byte sinval3 = pgm_read_byte_near(sineArray + int(counter3));
   byte sinval4 = pgm_read_byte_near(sineArray + int(counter4));
   byte sinval5 = pgm_read_byte_near(sineArray + int(counter5));
   byte sinval6 = pgm_read_byte_near(sineArray + int(counter6));
   byte squareval;
   byte squaretemp = pgm_read_byte_near(sineArray + int(squarecounter)); // read the value of the sine array into the counter
   
   if (squaretemp>=127){squareval = 255;} else{squareval=0;}
   byte val = sinval/7 + sinval2/7 + sinval3/7 + sinval4/7 + sinval5/7 + sinval6/7 + squareval/7;
   OCR2A = val;      // write value to the compare register
 
}

void loop(){

 
}


* seven waves.jpg (67.96 KB, 540x454 - viewed 35 times.)
Logged

USA
Online Online
Sr. Member
****
Karma: 17
Posts: 391
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Looking at the assembly listing for this code, under IDE 1.5.2, it looks to me that the ISR takes over 548 cycles to execute, assuming that every test in the ISR fails, and again ignoring calls to floating point routines.  The interrupt fires every 256 cycles.  I don't see how the program can keep pace.

Since it looks like you have an oscilloscope, I'll recommend a quick test:  Pick an unused output pin, set it when the ISR starts, clear it when the ISR exits, and measure the pulse length on your scope.  I think that it will be considerably longer than 16 microseconds, the time between interrupts.  And, that won't include the time it takes to push and pop the processor state.

Logged

Groningen
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I tested it, and it seems that it works. Green is the actual signal and red the pin i toggle (pin smiley-cool. It appears to fall within the operating range and i don't see how the calculation could be cut off, because it sends out different values each pass. Here's the code:
Code:
#include <avr/interrupt.h>   //add interrupts
#include <avr/io.h>          //no idea
#include <avr/pgmspace.h>    //to store values in progmem

//just to be crazy load 3125 samples im the progmem memory, why? 20hz sine wave, thats why. (I know overkill, will be adapted in future versions, sample size could be divided by 3 without losing fidellity)
prog_uchar sineArray[] PROGMEM = {};


volatile float scale;  //used to store the scale in which the counter should increment (as to get a specific frequency)
volatile float counter;  //global counter variable used in the timer interrupt
volatile float squarecounter;
volatile float squarescale;
volatile float counter2;
volatile float counter3;
volatile float counter4;
volatile float counter5;
volatile float counter6;
volatile float scale2;
volatile float scale3;
volatile float scale4;
volatile float scale5;
volatile float scale6;
void setup(){
  
  Serial.begin(115200);
  //disable interrupts
  noInterrupts();
  pinMode(8,OUTPUT);
  
  counter = 0;
  squarecounter = 0;
  
  float freq=880;  //input the frequency that you want here
  scale=3125/(62500/freq); //formula to calculate how the counter should increment (3125 is the size of the whole array for one 20 hz sine wave, 62500 is the sampling rate, so 62500 samples per second)
  
  scale2=3125/(62500/1046.50);
  scale3 =3125/(62500/1318.51);
  scale4 =3125/(62500/1760);
  scale5 =3125/(62500/1567.98);
  scale6 =3125/(62500/3520);
  float squarefreq = 440;
  squarescale = 3125/(62500/squarefreq);
  //enabling the outputs for TIMER2
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  
  //setting up timer 2
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);  //setting the timer to fast pwm mode with OCR2A/B as top
  TCCR2B = _BV(CS20); //setting the prescaler to 1
  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  OCR2A = 180;        //set overflow compare register to a random value, this one will be used for the wave generation
  OCR2B = 50;
  interrupts();
}


ISR(TIMER2_OVF_vect){
  digitalWrite(8,HIGH);
  
  counter= counter + scale;    //increment the counter with the scale
  if (counter>=3125){
    //reset counter if it reaches the end of the total sinewave array
    counter=0;
  }
  counter2 = counter2 +scale2;
  if(counter2>=3125){
    counter2=0;
  }
  counter3 = counter3 + scale3;
  if(counter3>=3125){
    counter3=0;
  }
  counter4 = counter4 + scale4;
  if(counter4>=3125){
    counter4=0;
  }
  counter5 = counter5 + scale5;
  if(counter5>=3125){
    counter5=0;
  }
  counter5 = counter6 + scale6;
  if(counter6>=3125){
    counter6=0;
  }
  
  squarecounter = squarecounter + squarescale;
  if(squarecounter >=3125){
    squarecounter = 0;
  }
  
   byte sinval = pgm_read_byte_near(sineArray + int(counter)); // read the value out of PROGMEM
   byte sinval2= pgm_read_byte_near(sineArray + int(counter2));
   byte sinval3 = pgm_read_byte_near(sineArray + int(counter3));
   byte sinval4 = pgm_read_byte_near(sineArray + int(counter4));
   byte sinval5 = pgm_read_byte_near(sineArray + int(counter5));
   byte sinval6 = pgm_read_byte_near(sineArray + int(counter6));
   byte squareval;
   byte squaretemp = pgm_read_byte_near(sineArray + int(squarecounter)); // read the value of the sine array into the counter
  
   if (squaretemp>=127){squareval = 255;} else{squareval=0;}
   byte val = sinval/7 + sinval2/7 + sinval3/7 + sinval4/7 + sinval5/7 + sinval6/7 + squareval/7;
   OCR2A = val;      // write value to the compare register
  
  digitalWrite(8,LOW);
}

void loop(){

  
}


* scope pic.jpg (72.71 KB, 540x454 - viewed 41 times.)
Logged

USA
Online Online
Sr. Member
****
Karma: 17
Posts: 391
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Check the frequency of your interrupts.  I count 20 between 500 us and 4 ms, for a frequency of 20/3.5 ms =~ 5700 Hz, for a period of about 175 us.  Timer2 is set up in normal mode, with a prescaler of 1, so it overflows at a rate of 16000000 Hz/256 = 62500 Hz, with a period of 16 us. 

I think that an additional ten Timer2 overflows occur while the ISR executes, so it's only processing about one out of every eleven that occur.  The waveshape looks right, but the timescale is much longer than one would expect from the arithmetic of the scales and counters. 
Logged

Pittsburgh, PA, USA
Offline Offline
Faraday Member
**
Karma: 98
Posts: 4815
I learn a bit every time I visit the forum.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I see a lot about sine wave generator circuits, one based on a dual op amp chip, resistors and a cap or two. I wonder if using a digital pot for one of the resistors would allow frequency control?
Logged

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

Groningen
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for the reply guys, you are indeed correct when saying the interrupts lasts longer, because of processes going on. I tried it out with some optimized code (without floating points) and unfortunately the maximum amount of waves is 1, when you don't use a divider for the clock.
For now i am trying to generate several waves (3 in total), with a clock divider of 8. This would theoretically give me the possibility to run 8 waves simultaneously. But because I need the extra cpu calculation time for other processes like reading inputs and doing some other calculations.

As for what I am going to do in the future. First step for me is to build a decent low pass filter, because the frequentie at which the pwm toggles is now in an audible frequency. On top of that if I try to filter it out with a standard passive low pass filter I get to much interference with the actual signal. After tackling this I will try to implement midi input, so I can actually play it. When this works I want to experiment with timer 1, on 10-bit mode, so I can try to stack the waves, which in return gives higher fidelity and lower distortion waves.

On the topic of using pwm to control a transistor which would in turn allow for control of an external circuit. It can be easily done, the reason why I do not want to is because I want to have a very stable oscillator as the starting point of my circuit. On top of that if you use timer 2 for the pwm output, it will only give you 256 kinds of values, which is not enough to do pitch bends or compensate for tuning drift. If you want to do that I would advise using the 16-bit timer 1 one the arduino uno.

If you have any question about my current project, feel free to ask. If you have any solid remarks or tips feel free to pass them along I am sure I can use them.
Logged

Pages: [1]   Go Up
Jump to: