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
/*
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)

1000Hz (clean)

5000Hz (still decent)

10000Hz (arglll we miss the speed

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


