diff --git a/DDS.cpp b/DDS.cpp index 27129aa..a4e13c0 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -13,19 +13,30 @@ void DDS::start() { #ifdef DDS_PWM_PIN_3 TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) | _BV(WGM21) | _BV(WGM20); + TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); #else // Alternatively, use pin 11 - TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) | - _BV(WGM21) | _BV(WGM20); + // Enable output compare on OC2A, toggle mode + TCCR2A = _BV(COM2A1) | _BV(WGM21) | _BV(WGM20); + //TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) | + // _BV(WGM21) | _BV(WGM20); + TCCR2B = _BV(CS20); #endif - TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); // Set the top limit, which will be our duty cycle accuracy. // Setting Comparator Bits smaller will allow for higher frequency PWM, // with the loss of resolution. +#ifdef DDS_PWM_PIN_3 OCR2A = pow(2,COMPARATOR_BITS)-1; OCR2B = 0; +#else + OCR2A = 0; +#endif +#ifdef DDS_USE_ONLY_TIMER2 + TIMSK2 |= _BV(TOIE2); +#endif + // Second, setup Timer1 to trigger the ADC interrupt // This lets us use decoding functions that run at the same reference // clock as the DDS. @@ -50,16 +61,18 @@ void DDS::stop() { void DDS::setFrequency(unsigned short freq) { // Fo = (M*Fc)/2^N // M = (Fo/Fc)*2^N - if(refclk == DDS_REFCLK_DEAULT) { + if(refclk == DDS_REFCLK_DEFAULT) { // Try to use precalculated values if possible if(freq == 2200) { - stepRate = (2200.0 / DDS_REFCLK_DEAULT) * pow(2,ACCUMULATOR_BITS); + stepRate = (2200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } else if (freq == 1200) { - stepRate = (1200.0 / DDS_REFCLK_DEAULT) * pow(2,ACCUMULATOR_BITS); + stepRate = (1200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + } else if (freq == 600) { + stepRate = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } else { - // Do the actual math instead. - stepRate = (freq / refclk) * pow(2,ACCUMULATOR_BITS); + // BUG: Step rate isn't properly calculated here, it gets the wrong frequency + stepRate = (freq/(refclk+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } @@ -68,28 +81,40 @@ void DDS::clockTick() { if(running) { accumulator += stepRate; if(timeLimited && tickDuration == 0) { +#ifndef DDS_PWM_PIN_3 + OCR2A = 0; +#else #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = pow(2,COMPARATOR_BITS)/2; #else // Set duty cycle to 0, effectively off OCR2B = 0; +#endif #endif running = false; accumulator = 0; } else { +#ifdef DDS_PWM_PIN_3 OCR2B = getDutyCycle(); +#else + OCR2A = getDutyCycle(); +#endif } // Reduce our playback duration by one tick tickDuration--; } else { // Hold it low +#ifndef DDS_PWM_PIN_3 + OCR2A = 0; +#else #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = pow(2,COMPARATOR_BITS)/2; #else // Set duty cycle to 0, effectively off OCR2B = 0; +#endif #endif } } diff --git a/DDS.h b/DDS.h index edab90a..8115dcc 100644 --- a/DDS.h +++ b/DDS.h @@ -4,8 +4,14 @@ #include // Use pin 3 for PWM? If not defined, use pin 11 +// Quality on pin 3 is higher than on 11, as it can be clocked faster +// when the COMPARATOR_BITS value is less than 8 // #define DDS_PWM_PIN_3 +// Normally, we turn on timer2 and timer1, and have ADC sampling as our clock +// Define this to only use Timer2, and not start the ADC clock +// #define DDS_USE_ONLY_TIMER2 + // Use a short (16 bit) accumulator. Phase accuracy is reduced, but speed // is increased, along with a reduction in memory use. #define SHORT_ACCUMULATOR @@ -27,16 +33,29 @@ // 8 = 62.5kHz PWM // 7 = 125kHz PWM // 6 = 250kHz PWM +#ifdef DDS_PWM_PIN_3 #define COMPARATOR_BITS 6 +#else // When using pin 11, we always want 8 bits +#define COMPARATOR_BITS 8 +#endif // This is how often we'll perform a phase advance, as well as ADC sampling // rate. The higher this value, the smoother the output wave will be, at the // expense of CPU time. It maxes out around 62000 (TBD) -#define DDS_REFCLK_DEAULT 38400 +#define DDS_REFCLK_DEFAULT 38400 +// As each Arduino crystal is a little different, this can be fine tuned to +// provide more accurate frequencies. Adjustments in the range of hundreds +// is a good start. +#define DDS_REFCLK_OFFSET 0 + +#ifdef DDS_USE_ONLY_TIMER2 +// TODO: Figure out where this clock value is generated from +#define DDS_REFCLK_DEFAULT 48800 +#endif // When defined, use the 1024 element sine lookup table. This improves phase // accuracy, at the cost of more flash and CPU requirements. -#define DDS_TABLE_LARGE +// #define DDS_TABLE_LARGE #ifdef DDS_TABLE_LARGE // How many bits to keep from the accumulator to look up in this table @@ -131,7 +150,9 @@ static const uint8_t ddsSineTable[256] PROGMEM = { class DDS { public: - DDS(): refclk(DDS_REFCLK_DEAULT), accumulator(0), running(false) {}; + DDS(): refclk(DDS_REFCLK_DEFAULT), accumulator(0), running(false), + timeLimited(false), tickDuration(0), amplitude(0) + {}; // Start all of the timers needed void start(); diff --git a/examples/DDS/DDS.ino b/examples/DDS/DDS.ino new file mode 100644 index 0000000..245bb79 --- /dev/null +++ b/examples/DDS/DDS.ino @@ -0,0 +1,32 @@ +#include +#include + +DDS dds; + +void setup() { + pinMode(2, OUTPUT); + pinMode(3, OUTPUT); + pinMode(11, OUTPUT); + dds.start(); + dds.setFrequency(440); + dds.on(); + delay(5000); +} + +void loop() { + dds.setFrequency(2200); + delay(5000); + dds.setFrequency(1200); + delay(5000); +} + +#ifdef DDS_USE_ONLY_TIMER2 +ISR(TIMER2_OVF_vect) { + dds.clockTick(); +} +#else // Use the ADC timer instead +ISR(ADC_vect) { + TIFR1 = _BV(ICF1); // Clear the timer flag + dds.clockTick(); +} +#endif