Adafruit #570 in Inc 5000 & #11 in manufacturing - fastest-growing private companies in America - INC 5000 - Read more!

An other functions generator

by didier on Fri Jan 15, 2010 6:06 pm

The need for a functions generator to test an Arduino protoype drove me to design an other Arduino prototype :mrgreen:
I found a quite a lot of information on the net among which half was simply rubish, copy paste of rubish, mess or "I did not tested it but it should work". I also found a couple of really interesting papers!

The aim of this submission is to propose a fit for purpose functions generator, cheap and easy to build, pretty accurate and usable. DDS (Direct Digital Synthesis) is applied to this generator which produces all sorts of waves from a R2R 6 bits DAC (Digital to Analog Converter) http://didier.longueville.free.fr/public/images/DDSFunctionGenerator_01.png http://didier.longueville.free.fr/public/images/DDS_R2R_DAC_6B_Hardware.gif. On each clock cycle (derived from Timer/Counter2 programmed for fast mode), the M tuning word is added to the 32bits accumulator, from which the 8 MSB (Most Significant Bits) are used for extracting the signal intensity from lookup tables (signal ranging from 0 to 62 in digital).

NB: I addded a 7th bit to my DAC which is always set to 0 in order to have a full scale signal of 2.5 Volts. Reason being that I plan to buffer/invert/offset this signal with LM324 op amps that are not rail to rail! I also plan to generate -5V direct from Arduino using a diode-capacitor inverter to get a symetric signal.

And now the code

Code: Select all | TOGGLE FULL SIZE
/*
DDS Function generator using R2R DAC and Timer/Counter2 interrupts as clock counting pulses
2Rs are connected to PORTB PINB0 (LSB) to PINB5 (MSB), Rs are connected between the free terminals or R2Rs
the output frequency is set by a pot connected to the nalaog port 0.

no warranty, no claims, just fun

Didier Longueville invenit et fecit January 2010
*/

#define cbi(sfr,bitValue) (_SFR_BYTE(sfr) &= ~_BV(bitValue))
#define sbi(sfr,bitValue) (_SFR_BYTE(sfr) |=  _BV(bitValue))
#define tbi(sfr,bitValue) (_SFR_BYTE(sfr) ^=  _BV(bitValue))

// sine wave values 6 bits (in fact 0 to 62) in 256 steps
byte  sineWave[]  = {
31,32,33,33,34,35,36,36,37,38,39,39,40,41,41,42,43,44,44,45,46,46,47,48,48,49,50,50,51,51,52,52,
53,54,54,55,55,56,56,56,57,57,58,58,58,59,59,59,60,60,60,60,61,61,61,61,61,62,62,62,62,62,62,62,
62,62,62,62,62,62,62,62,61,61,61,61,61,60,60,60,60,59,59,59,58,58,57,57,57,56,56,55,55,54,54,53,
53,52,52,51,50,50,49,49,48,47,47,46,45,45,44,43,43,42,41,40,40,39,38,37,37,36,35,34,34,33,32,31,
31,30,29,28,28,27,26,25,25,24,23,22,22,21,20,19,19,18,17,17,16,15,15,14,13,13,12,12,11,10,10, 9,
 9, 8, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9,
10,10,11,11,12,12,13,14,14,15,16,16,17,18,18,19,20,21,21,22,23,23,24,25,26,26,27,28,29,29,30,31
};

/*
byte sawToothWave[] = {
 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8,
 8, 8, 8, 8, 9, 9, 9, 9,10,10,10,10,11,11,11,11,12,12,12,12,13,13,13,13,14,14,14,14,15,15,15,15,
16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,22,22,22,22,23,23,23,
23,23,24,24,24,24,25,25,25,25,26,26,26,26,27,27,27,27,28,28,28,28,29,29,29,29,30,30,30,30,31,31,
31,31,31,32,32,32,32,33,33,33,33,34,34,34,34,35,35,35,35,36,36,36,36,37,37,37,37,38,38,38,38,39,
39,39,39,39,40,40,40,40,41,41,41,41,42,42,42,42,43,43,43,43,44,44,44,44,45,45,45,45,46,46,46,46,
47,47,47,47,47,48,48,48,48,49,49,49,49,50,50,50,50,51,51,51,51,52,52,52,52,53,53,53,53,54,54,54,
54,54,55,55,55,55,56,56,56,56,57,57,57,57,58,58,58,58,59,59,59,59,60,60,60,60,61,61,61,61,62,62,
};
*/

/*
byte triangleWave[] = {
 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,12,12,13,13,14,14,15,15,
16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,30,30,31,
31,31,32,32,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42,43,43,44,44,45,45,46,46,
47,47,47,48,48,49,49,50,50,51,51,52,52,53,53,54,54,55,55,56,56,57,57,58,58,59,59,60,60,61,61,62,
62,62,61,61,60,60,59,59,58,58,57,57,56,56,55,55,54,54,53,53,52,52,51,51,50,50,49,49,48,48,47,47,
47,46,46,45,45,44,44,43,43,42,42,41,41,40,40,39,39,38,38,37,37,36,36,35,35,34,34,33,33,32,32,31,
31,31,30,30,29,29,28,28,27,27,26,26,25,25,24,24,23,23,22,22,21,21,20,20,19,19,18,18,17,17,16,16,
16,15,15,14,14,13,13,12,12,11,11,10,10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0
};
*/

volatile byte upperCountingValue = 255;      // change value for setting different reference frequency

double outputFrequency = 1000;               // default frequency value
double referenceFrequency = 62500;           // cpu_frequency/prescaler/upperCountingLimit
// Variables inside interrupt
volatile byte phaseAccumulatorMBSs;          // accumulator counter 8 MSBs
volatile unsigned long phaseAccumulator;     // phase accumulator, will automatically overflow at &FFFFFFFF
volatile unsigned long tuningWordM;          // dds tuning word m

void setup(){
  // Set output ports
  DDRB  |=  B00111111;
  PORTB  =  B00000000;
  // Set main clock
  setTimerCounter2();
}

void loop(){
  delay(250);
  setFrequency();
}

/* interrupt service run when Timer/Counter2 reaches OCR2A */
ISR(TIMER2_COMPA_vect){
  // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulator += tuningWordM;
  // use the 8 MSBs from phase accumulator as frequency information
  phaseAccumulatorMBSs = phaseAccumulator >> 24;
  // Set R2R DAC using lookup table, comment/uncomment as appropriate
  PORTB = sineWave[phaseAccumulatorMBSs];
  // PORTB = sawToothWave[phaseAccumulatorMBSs];
  // PORTB = triangleWave[phaseAccumulatorMBSs];
 }

/* Set frequency and calculate related variables */
void setFrequency() {
  unsigned long lastOutputFrequency = outputFrequency;
  // Read potentiometer value on analog port
  outputFrequency = analogRead(0)*10+100;
  // Update output frequency if needed
  if (lastOutputFrequency != outputFrequency) {
    // Disable Output Compare Match A Interrupt
    cbi(TIMSK2, OCIE2A);
    // Calculate tuning word value
    tuningWordM = (unsigned long) ((pow(2,32) * outputFrequency) / referenceFrequency);
    // Enable Output Compare Match A Interrupt
    sbi(TIMSK2, OCIE2A); 
  }
}

/* Set Timer/Counter2 parameters */
void setTimerCounter2() {
  // Disable interrupts while setting registers
  cli(); 
  // Reset control registers
  TCCR2A = 0;
  TCCR2B = 0;
  // Set Timer/Counter2 to Compare Match (CTC) Mode
  sbi(TCCR2A, WGM21);
  // Prescaler x1
  sbi(TCCR2B, CS20);
  // Set compared value
  OCR2A = upperCountingValue;
  // Use system clock for Timer/Counter2
  cbi(ASSR,AS2);
  // Reset Timer/Counter2 Interrupt Mask Register
  TIMSK2 = 0;
  // Enable Output Compare Match A Interrupt
  sbi(TIMSK2, OCIE2A);
  // Enable interrupts once registers have been updated
  sei();
}


Performances
100Hz (easy)
Image

1000Hz (clean)
Image

5000Hz (still decent)
Image

10000Hz (arglll we miss the speed :oops: )
Image

The links I liked (non exhaustive list)
Timer/Counters: http://arcfn.com/2009/07/secrets-of-arduino-pwm.html
DDS principle: http://www.analog.com/library/analogdia ... 8/dds.html
Function generator using PWM: http://interface.khm.de/index.php/lab/e ... generator/
Function generator using PWM: http://blog.wingedvictorydesign.com/200 ... ion/all/1/
R2R ladders: http://en.wikipedia.org/wiki/Resistor_ladder
Last edited by didier on Mon Feb 01, 2010 10:23 am, edited 5 times in total.
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am

Re: An other functions generator

by adafruit on Fri Jan 15, 2010 8:57 pm

nice work, whats the maximum frequency you got out of the sine wave?
User avatar
adafruit
 
Posts: 11663
Joined: Thu Apr 06, 2006 3:21 pm
Location: nyc

Re: An other functions generator

by didier on Sat Jan 16, 2010 3:51 am

Honestly, I would not use it above 10kHz under the defined conditions because of the 'slow' time base 6 bits DAC. At least twice the fequency and 2 more bits would surely help a lot, but I did not retain this option because I wanted to keep it very simple. However it works perfectly from almost nothing up to 5 Khz which is perfect for my application (Automotive).
In addition to adding buffer/interverter/offset and attenutation circuitry, I will also pay some interest to frequency control, playing with the OCR2A and disabling other interrupts or twiting the counter. The question of latency is also not fully clear to me.
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am

Re: An other functions generator

by didier on Thu Feb 04, 2010 5:06 pm

Here we go for the improved version. This one allows generation of a very decent signal thanks to
- Removal of disturbances from other TimerCounters than n°2
- Faster time base (x2) [easy one]

In addition this version which still works in standalone allows you to change wave style (Sine, square, triangle, saw teeth +/-) by toggling a push button: Designed and tested using an Adafruit proto shield fitted with a mini breadboard.

Enjoy

Code: Select all | TOGGLE FULL SIZE
/*
DDS Function generator using R2R DAC and Timer/Counter2 interrupts as clock counting pulses
Designed on Arduino Diecimila and Adafruit proto shield fitted with mini breadbord
2Rs are connected to PORTB PINB0 (LSB) to PINB5 (MSB), Rs are connected between the free terminals or R2Rs
Pressing a push button connected on PIND2 (Ground true) has two effects:
- Short click validates frequency as set with pot connected to the analog port 0. frequency ranges from 100 to 10340 Hz
- Long click toggles wave style (Sine, Square, Triangle, Saw Teeth)
Status led on PORTD3

no warranty, no claims, just fun

Didier Longueville invenit et fecit February 2010
*/

const int stepsPerCycle = 256;               // number of samples per wave cycle
byte vData[stepsPerCycle];                   // vector containing wave profile
int waveStyle = 0;                           // 0=sine, 1=square, 2=triangle, 3 and 4=saw teeth
int peakToPeakIntensity = 63;                // Maw wave intensity
byte timer0Mask = 0;
int upperCountingValue = 127;                // change value for setting different reference frequency
double outputFrequency = 1000;               // default frequency value
double referenceFrequency = 16000000>>7;     // cpu_frequency/upperCountingValue/prescaler
volatile unsigned long phaseAccumulator;     // phase accumulator, will automatically overflow at &FFFFFFFF
volatile unsigned long tuningWordM;          // dds tuning word m

void setup(){
  // Set output ports
  // PORTB R2R ladder
  DDRB  |=  ((1<<PINB5) | (1<<PINB4) | (1<<PINB3) | (1<<PINB2) | (1<<PINB1) | (1<<PINB0)); // set PORTB pins to output
  PORTB = 0;            // turn off PORTB pins
  // PORTD status led and command switch 
  DDRD  &= ~(1<<PIND2); // set PIND2 as input
  PORTD |=  (1<<PIND2); // bias PIND2
  DDRD  |=  (1<<PIND3); // set PIND3 as output
  PORTD &= ~(1<<PIND3); // turn PIND3 off
  // DDRD  |=  (1<<PIND4); // set PIND4 as output for diagnostics purpose only
  // blink status led
  for (int i=0; i<10; i++) {
    PORTD ^=  (1<<PIND3); // toggle PIND3
    delay(100);
  }
  // Generate defaut set of wave data
  generateWaveData(waveStyle);
  // Set default frequency
  setFrequency(outputFrequency);
  // Disable unused timers in order to get a much more stable TimerCounter2
  timer0Mask = TIMSK0; // Record TimerCounter0 register content
  TIMSK0 = 0;
  TIMSK1 = 0;
  // Start time base clock
  setTimerCounter2();
}

void loop(){
  if (~(PIND>>PIND2) & 0x01){ // loop until button is pressed
    TIMSK0 = timer0Mask;  // Re-enable timer 0 in order to enable millis function
    PORTD |=  (1<<PIND3); // turn on status led
    long buttonDown=millis(); // Record 'button down' time
    while (~(PIND>>PIND2) & 0x01);
    long buttonUp = millis();
    if (buttonUp - buttonDown >=500) { // Long click
      // Change wave style
      waveStyle++;
      waveStyle = waveStyle % 5;
      generateWaveData(waveStyle);
    }
    else if(buttonUp - buttonDown >=5) { // Short click
      // Set frequency
      setFrequency(0);
    }
    // Shorter clicks are rejected (probably bounces)
    PORTD &= ~(1<<PIND3); // turn off status led
    TIMSK0 = 0; // Disable timer 0
  }
}

// set frequency;
void setFrequency(int frequency){
  if (frequency==0) { // if frequency variable = 0, frequency is computed from pot value on analog port 0
    outputFrequency = analogRead(0)*10+100;
  }
  // Calculate tuning word value
  tuningWordM = (unsigned long) ((pow(2, 32) * outputFrequency) / referenceFrequency);
}

// interrupt service run when Timer/Counter2 reaches OCR2A
ISR(TIMER2_COMPA_vect){
  // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulator += tuningWordM;
  // use the 8 MSBs from phase accumulator as frequency information
  byte phaseAccumulatorMSBs = phaseAccumulator >> 24;
  // Set R2R DAC using lookup table
  PORTB = vData[phaseAccumulatorMSBs];
  // PORTD ^= (1<<PIND4); // Diag pupose only
}

// Set Timer/Counter2 parameters
void setTimerCounter2() {
  // Disable interrupts while setting registers
  cli(); 
  // Reset control registers
  TCCR2A = 0;
  TCCR2B = 0;
  // Clear Timer on Compare Match (CTC) Mode
  TCCR2A |= (1<<WGM21);
  // Prescaler x1
  TCCR2B |= (1<<CS20);
  // Set compared value
  OCR2A = upperCountingValue;
  // Use system clock for Timer/Counter2
  ASSR &= ~(1<<AS2);
  // Reset Timer/Counter2 Interrupt Mask Register
  TIMSK2 = 0;
  // Enable Output Compare Match A Interrupt
  TIMSK2 |= (1<<OCIE2A);
  // Enable interrupts once registers have been updated
  sei();
}

// Generate wave data in global vector
void generateWaveData(int waveType){
  // Disable Output Compare Match A Interrupt
  TIMSK2=0;
  switch(waveType){
  case 0: // Sine
    for (int i=0; i<stepsPerCycle; i++)  vData[i] = (peakToPeakIntensity*(sin((i*6.2831)/stepsPerCycle)+1))/2;
    break;
  case 1: // Square
    for (int i=0; i<stepsPerCycle/2; i++) vData[i] = 0;
    for (int i=stepsPerCycle/2; i<stepsPerCycle; i++) vData[i] = peakToPeakIntensity;
    break;
  case 2: // Triangle
    for (int i=0; i<stepsPerCycle/2; i++) vData[i] = (i*peakToPeakIntensity*2)/stepsPerCycle;
    for (int i=stepsPerCycle/2; i<stepsPerCycle; i++) vData[i] = ((stepsPerCycle-i)*peakToPeakIntensity*2)/stepsPerCycle;
    break;
  case 3: // Saw tooth positive slope
    for (int i=0; i<stepsPerCycle; i++) vData[i] = (i*peakToPeakIntensity)/stepsPerCycle;
    break;
  case 4: // Saw tooth negative slope
    for (int i=0; i<stepsPerCycle; i++) vData[i] = ((stepsPerCycle-i)*peakToPeakIntensity)/stepsPerCycle;
    break;
  }
  // Enable Output Compare Match A Interrupt
  TIMSK2 |= (1<<OCIE2A);
}
Last edited by didier on Thu Feb 04, 2010 6:11 pm, edited 1 time in total.
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am

Re: An other functions generator

by didier on Thu Feb 04, 2010 5:40 pm

I have a question:
Is there a way (or another) to lacth PORTB and PORTD bits in the same time? :?:

Reason is that increasing the number of bits for DAC will require that the upper bits from PORTD shall be used. In addition, I forsee a loss of time due to the masking of these bits :oops: Any idea?
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am

Re: An other functions generator

by mtbf0 on Thu Feb 04, 2010 9:53 pm

didier wrote:Is there a way (or another) to lacth PORTB and PORTD bits in the same time? :?:


no.

do you need the uart? portd is an eight bit port, though you'll probably need to switch the resistors out of the circuit for programming.
"i want to lead a dissipate existence, play scratchy records and enjoy my decline" - iggy pop, i need more
User avatar
mtbf0
 
Posts: 1645
Joined: Fri Nov 09, 2007 11:59 pm
Location: oakland ca

Re: An other functions generator

by didier on Fri Feb 05, 2010 8:37 am

mtbf0 wrote: do you need the uart?

No and yes :mrgreen:
My original specification is "stand alone" generator, so that I am not suposed to use the serial port.
But it is soooo usefull for debuging :wink: and I also want to keep the option of setting parameters from an application using a command processor.
An 8 D latches chip would do the job too. But I am a found of these tricky (sometimes awesome) solutions, and Arduino is such a nice play ground for that :lol:

Back to ADC: toggling the LSBs after MSBs may not be perceptible. This has to be checked.
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am

Re: An other functions generator

by mtbf0 on Fri Feb 05, 2010 5:57 pm

build it on a minipov3. 8 bit portb. uart on pd0 and pd1. crystal on pa0 and pa1. don't have to worry about turning off timer0.
"i want to lead a dissipate existence, play scratchy records and enjoy my decline" - iggy pop, i need more
User avatar
mtbf0
 
Posts: 1645
Joined: Fri Nov 09, 2007 11:59 pm
Location: oakland ca

Re: An other functions generator

by didier on Tue Feb 09, 2010 5:14 pm

Finally...
I tested the option of 6 bits taken from PORTB and 2 bits from PORTD. But the bit masking business is taking too much time.
I also tested the option of 8 bits from PORTD. Wave profiles at low frequencies are smoother, but that does not improve profiles at high frequencies :( (Not a big surprise)

Speed is the issue! I resigned from improving the time base above 125 KHz, which gives honnest results up to 12 Khz (Giving 10 samples per cycle). A well choosen capacitor does the smooth alright.

Here is the latest version wich makes the Arduino remotely controlled. I contains standard waves + a noise generator and a custom wave that keeps recorded in EEPROM. Frequency is manually or digitally adjusted. Feel free to improve it and please pass your comments :wink:

HTH

Code: Select all | TOGGLE FULL SIZE
/*
DDS Function generator using R2R DAC and Timer/Counter2 interrupts as clock counting pulses
Designed on Arduino Diecimila and Adafruit proto shield fitted with mini breadbord
2Rs are connected to PORTB PINB0 (LSB) to PINB5 (MSB), Rs are connected between the free terminals or 2Rs
All function driven by commands
- a: toggle frequency adjust mode: adjustment pot is active while led is on
- f: read (no argument) or set frequency
- e: export and display indiviudal custom wave data
- i: import  and display indiviudal custom wave data the argument must comply to the format (Address+1)*(maxIntensity+1)+Data
- w: set wave style argument can be 0=sine, 1=square, 2=triangle, 3 and 4=saw teeth, 5=random, 6=custom
Status led on PORTD7
Optional synch pin on PORTD6

no warranty, no claims, just fun

Didier Longueville invenit et fecit February 2010
*/

#include <EEPROM.h>

const int stepsPerCycle = 256;    // Number of samples per wave cycle
byte vWaveDataGbl[stepsPerCycle]; // Vector containing wave profile
int maxIntensity = 63;            // Max wave intensity

int nbrCounts = 127;                         // Change value for setting different reference frequency
double refFrequency =16000000/(nbrCounts+1); // CPU_frequency/nbrCounts/prescaler
volatile unsigned long phaseAccumulator;     // Phase accumulator, will automatically overflow at &FFFFFFFF
volatile unsigned long tuningWordM;          // DDS tuning word m

int waveStyleGbl = 0;     
int frequencyGbl = 10000;
byte freqAdjModeGbl = 0;

void setup(){
  Serial.begin(115200);
  // Set output ports
  // R2R ladder
  DDRB  |=  ((1<<PINB5) | (1<<PINB4) | (1<<PINB3) | (1<<PINB2) | (1<<PINB1) | (1<<PINB0)); // set PORTB pins to output
  // status led 
  DDRD  |=  (1<<PIND7); // set PIND7 as output
  PORTD &= ~(1<<PIND7); // turn PIND7 off
  // synch pin
  DDRD  |=  (1<<PIND6); // set PIND6 as output
  // blink status led
  for (int i=0; i<10; i++) {
    PORTD ^=  (1<<PIND7); // toggle PIND7
    delay(100);
  }
  // Generate defaut set of wave data
  generateWaveData(waveStyleGbl);
  // Set default frequency
  setFrequency(frequencyGbl);
  // Start time base clock
  setTimerCounter2();
  // Reset Timer/Counter0 & 1
  TIMSK0 = 0;
  TIMSK1 = 0;
  Serial.println("Ready");
}

void loop(){
  commandsProcessor();
  PORTD  = ((PORTD & ~(1<<PIND7)) | (freqAdjModeGbl<<PIND7)); // turn status led
  if (freqAdjModeGbl==1) {
    frequencyGbl = setFrequency(0);
  }
}

// set frequency;
int setFrequency(int frequency){
  // Disable interrupts while setting registers
  if (frequency==0) { // if frequency variable = 0, frequency is computed from pot value on analog port 0
    frequency = analogRead(0)*10+100;
  }
  // Calculate tuning word value
  tuningWordM = (unsigned long) ((pow(2, 32) * frequency) / refFrequency);
  return frequency;
}

// interrupt service run when Timer/Counter2 reaches OCR2A
ISR(TIMER2_COMPA_vect){
  // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulator += tuningWordM;
  // Use the 8 MSBs from phase accumulator as frequency information
  byte phaseAccumulatorMSBs = phaseAccumulator >> 24;
  // Set R2R DAC using lookup table
  PORTB = vWaveDataGbl[phaseAccumulatorMSBs];
  // PORTD ^= (1<<PIND6);
}

// Set Timer/Counter2 parameters
void setTimerCounter2() {
  // Disable interrupts while setting registers
  cli(); 
  // Reset control registers
  TCCR2A = 0;
  TCCR2B = 0;
  // Clear Timer on Compare Match (CTC) Mode
  TCCR2A |= (1<<WGM21);
  // Prescaler x1
  TCCR2B |= (1<<CS20);
  // Set compared value
  OCR2A = nbrCounts;
  // Use system clock for Timer/Counter2
  ASSR &= ~(1<<AS2);
  // Reset Timer/Counter2 Interrupt Mask Register
  TIMSK2 = 0;
  // Enable Output Compare Match A Interrupt
  TIMSK2 |= (1<<OCIE2A);
  // Enable interrupts once registers have been updated
  sei();
}

// Generate wave data in global vector
void generateWaveData(int style){
  TIMSK2 = 0; // Disable timer/counter2
  switch(style){
  case 0: // Sine
    for (int i=0; i<stepsPerCycle; i++)  vWaveDataGbl[i] = (maxIntensity*(sin((i*6.2831)/stepsPerCycle)+1))/2;
    break;
  case 1: // Square
    for (int i=0; i<stepsPerCycle/2; i++) vWaveDataGbl[i] = 0;
    for (int i=stepsPerCycle/2; i<stepsPerCycle; i++) vWaveDataGbl[i] = maxIntensity;
    break;
  case 2: // Triangle
    for (int i=0; i<stepsPerCycle/2; i++) vWaveDataGbl[i] = (i*maxIntensity*2)/stepsPerCycle;
    for (int i=stepsPerCycle/2; i<stepsPerCycle; i++) vWaveDataGbl[i] = ((stepsPerCycle-i)*maxIntensity*2)/stepsPerCycle;
    break;
  case 3: // Saw tooth positive slope
    for (int i=0; i<stepsPerCycle; i++) vWaveDataGbl[i] = (i*maxIntensity)/stepsPerCycle;
    break;
  case 4: // Saw tooth negative slope
    for (int i=0; i<stepsPerCycle; i++) vWaveDataGbl[i] = ((stepsPerCycle-i)*maxIntensity)/stepsPerCycle;
    break;
  case 5: // Random (Noise)
    // randomSeed(millis());
    for (int i=0; i<stepsPerCycle; i++) vWaveDataGbl[i] = random(0, maxIntensity);
    break;
  case 6: // Custom wave
    for (int i=0; i<stepsPerCycle; i++) vWaveDataGbl[i] = EEPROM.read(i);
    break;
  }
  TIMSK2 |= (1<<OCIE2A); // Ensable timer/counter2
}

// Process commands
void commandsProcessor(void) {
  if (Serial.available()) {
    byte keyWord = Serial.read();
    byte vArgBuffer[16];
    int nbrArgumentBytes=0;            // set default variable
    while (Serial.available()) {       // keep reading buffer until not empty
      byte incomingByte=Serial.read(); // read each received byte
      if (incomingByte<=0x20) {
        Serial.flush();                       
        break; // discard extra bytes in buffer when receiving non printable character
      }
      else {
        nbrArgumentBytes++; // else store byte in buffer
        vArgBuffer[nbrArgumentBytes-1]=incomingByte;
      }
    }
    // Compute argument value starting from the lsb
    long argValue=0;
    long factor=1;
    for (int i=nbrArgumentBytes; i>=1; i--) {
      argValue += (vArgBuffer[i-1]-0x30)*factor;
      factor *= 10;
    }
    switch (keyWord) { // action depends on keyword
    case 'a': // toggle frequency adjustment mode
      freqAdjModeGbl ^= B00000001;
      break;
    case 'e': // Export wave data
      argValue = constrain(argValue, 0, (stepsPerCycle-1));
      Serial.print("Address: ");
      Serial.print(argValue, HEX);
      Serial.print(" contains: ");
      Serial.print(EEPROM.read(argValue), HEX);
      Serial.println("");
      break;
    case 'f': // Frequency
      if (nbrArgumentBytes >= 1) {
        argValue = constrain(argValue, 0, 16000);
        frequencyGbl = setFrequency(argValue);
      }
      Serial.print("Frequency: ");
      Serial.print(frequencyGbl, DEC);
      Serial.println("");
      break;
   case 'h': // Test
      Serial.println("Hello world!");
      break;
    case 'i': // Import wave data
      argValue = constrain(argValue, 0, 16447);
      EEPROM.write((argValue>>6)-1, (argValue & B00111111));
      Serial.print("Address: ");
      Serial.print((argValue>>6)-1, HEX);
      Serial.print(" contains: ");
      Serial.print(argValue & B00111111, HEX);
      Serial.println("");
      break;
    case 'w': // Toggle wave style
      if (nbrArgumentBytes >= 1) {
        argValue = constrain(argValue, 0, 6);
        waveStyleGbl = argValue;
        generateWaveData(waveStyleGbl);
      }
      Serial.print("Wave style: ");
      switch(waveStyleGbl){
      case 0: // Sine
        Serial.print("Sine");
        break;
      case 1: // Square
        Serial.print("Square");
        break;
      case 2: // Triangle
        Serial.print("Triangle");
        break;
      case 3: // Saw tooth positive slope
        Serial.print("Saw tooth positive slope");
        break;
      case 4: // Saw tooth negative slope
        Serial.print("Saw tooth negative slope");
        break;
      case 5: // Random (Noise)
        Serial.print("Random");
        break;
      case 6: // Custom wave
        Serial.print("Custom");
        break;
      }
      Serial.println("");
      break;
    }
  }

Last edited by didier on Wed Feb 10, 2010 5:26 am, edited 1 time in total.
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am

Re: An other functions generator

by didier on Sun Jun 20, 2010 2:33 am

For those who are intrested in the performances obtained with the described DDS, you are kindly invited to visit http://didier.longueville.free.fr/arduinoos/?p=240 :wink:
didier
 
Posts: 115
Joined: Mon Nov 17, 2008 5:14 am