From fdaa6024013719f4ca636bb04e301acc7277f662 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Sun, 16 Aug 2015 20:17:36 -0400 Subject: [PATCH] Very rough draft of DDS supporting the Arduio Zero and it's built-in DAC. --- DDS.cpp | 174 +++++++++++++++++++++++++++++++++++++++++--------------- DDS.h | 33 +++++++++-- 2 files changed, 154 insertions(+), 53 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index 8772fc7..e025993 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -1,41 +1,95 @@ #include #include "DDS.h" +#ifdef __SAMD21G18A__ + +// The SimpleAudioPlayerZero sample project found at: +// https://www.arduino.cc/en/Tutorial/SimpleAudioPlayerZero +// is an execellent reference for setting up the Timer/Counter +#define TC_ISBUSY() (TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY) +#define TC_WAIT() while (TC_ISBUSY()); +#define TC_ENABLE() TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; TC_WAIT(); +#define TC_RESET() TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; TC_WAIT(); \ + while (TC5->COUNT16.CTRLA.bit.SWRST); +#define TC_DISABLE() TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; TC_WAIT(); + +#endif + // To start the DDS, we use Timer1, set to the reference clock // We use Timer2 for the PWM output, running as fast as feasible void DDS::start() { + +#ifdef DDS_DEBUG_SERIAL + // Print these debug statements (commont to both the AVR and the SAMD21) + Serial.print(F("DDS SysClk: ")); + Serial.println(F_CPU/8); + Serial.print(F("DDS RefClk: ")); + Serial.println(refclk, DEC); +#endif + +#ifdef __SAMD21G18A__ + // Enable the Generic Clock Generator 0 and configure for TC4 and TC5. + // We only need TC5, but they are configured together + GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ; + while (GCLK->STATUS.bit.SYNCBUSY); + + TC_RESET(); + + // Set TC5 16 bit + TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16; + + // Set TC5 mode as match frequency + TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; + TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 | TC_CTRLA_ENABLE; + TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / DDS_REFCLK_DEFAULT - 1); + TC_WAIT() + + // Configure interrupt + NVIC_DisableIRQ(TC5_IRQn); + NVIC_ClearPendingIRQ(TC5_IRQn); + NVIC_SetPriority(TC5_IRQn, 0); + NVIC_EnableIRQ(TC5_IRQn); + + // Enable TC5 Interrupt + TC5->COUNT16.INTENSET.bit.MC0 = 1; + TC_WAIT(); + + //Configure the DAC + analogWriteResolution(8); + analogWrite(A0, 0); +#else // Use the clkIO clock rate ASSR &= ~(_BV(EXCLK) | _BV(AS2)); // First, the timer for the PWM output // Setup the timer to use OC2B (pin 3) in fast PWM mode with a configurable top // Run it without the prescaler -#ifdef DDS_PWM_PIN_3 - TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) | + #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 + TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); + #else // Alternatively, use pin 11 // 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 + 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 // 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_PWM_PIN_3 + OCR2A = pow(2,COMPARATOR_BITS)-1; + OCR2B = 0; + #else + OCR2A = 0; + #endif -#ifdef DDS_USE_ONLY_TIMER2 - TIMSK2 |= _BV(TOIE2); -#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 @@ -43,15 +97,11 @@ void DDS::start() { // We use ICR1 as TOP and prescale by 8 TCCR1B = _BV(CS10) | _BV(WGM13) | _BV(WGM12); TCCR1A = 0; - ICR1 = ((F_CPU / 1) / refclk) - 1; -#ifdef DDS_DEBUG_SERIAL - Serial.print(F("DDS SysClk: ")); - Serial.println(F_CPU/8); - Serial.print(F("DDS RefClk: ")); - Serial.println(refclk, DEC); - Serial.print(F("DDS ICR1: ")); - Serial.println(ICR1, DEC); -#endif + ICR1 = ((F_CPU / 1) / refclk) - 1; + #ifdef DDS_DEBUG_SERIAL + Serial.print(F("DDS ICR1: ")); + Serial.println(ICR1, DEC); + #endif // Configure the ADC here to automatically run and be triggered off Timer1 ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) @@ -60,14 +110,22 @@ void DDS::start() { DIDR0 |= _BV(0); ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0); ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0); + +#endif } void DDS::stop() { +#ifdef __SAMD21G18A__ + TC_DISABLE(); + TC_RESET(); + analogWrite(A0, 0); +#else // TODO: Stop the timers. -#ifndef DDS_USE_ONLY_TIMER2 - TCCR1B = 0; + #ifndef DDS_USE_ONLY_TIMER2 + TCCR1B = 0; + #endif + TCCR2B = 0; #endif - TCCR2B = 0; } // Set our current sine wave frequency in Hz @@ -89,6 +147,7 @@ ddsAccumulator_t DDS::calcFrequency(unsigned short freq) { newStep = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } else { + //TODO: This doesn't work with the SAM21D... yet newStep = pow(2,ACCUMULATOR_BITS)*freq / (refclk+refclkOffset); } return newStep; @@ -112,45 +171,65 @@ void DDS::clockTick() { if(running) { accumulator += stepRate; if(timeLimited && tickDuration == 0) { -#ifndef DDS_PWM_PIN_3 - OCR2A = 0; +#ifdef __SAMD21G18A__ + #ifdef DDS_IDLE_HIGH + analogWrite(A0, pow(2,COMPARATOR_BITS)/2); + #else + analogWrite(A0, 0); + #endif #else -#ifdef DDS_IDLE_HIGH + #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 + #else // Set duty cycle to 0, effectively off - OCR2B = 0; -#endif + OCR2B = 0; + #endif + #endif #endif running = false; accumulator = 0; } else { -#ifdef DDS_PWM_PIN_3 - OCR2B = getDutyCycle(); +#ifdef __SAMD21G18A__ + analogWrite(A0, getDutyCycle()); #else + #ifdef DDS_PWM_PIN_3 + OCR2B = getDutyCycle(); + #else OCR2A = getDutyCycle(); + #endif #endif } // Reduce our playback duration by one tick tickDuration--; } else { +#ifdef __SAMD21G18A__ + #ifdef DDS_IDLE_HIGH + analogWrite(A0, pow(2,COMPARATOR_BITS)/2); + #else + analogWrite(A0, 0); + #endif +#else // Hold it low -#ifndef DDS_PWM_PIN_3 + #ifndef DDS_PWM_PIN_3 OCR2A = 0; -#else -#ifdef DDS_IDLE_HIGH + #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 + OCR2B = pow(2,COMPARATOR_BITS)/2; + #else + // Set duty cycle to 0, effectively off + OCR2B = 0; + #endif + #endif #endif } } -uint8_t DDS::getDutyCycle() { +ddsComparitor_t DDS::getDutyCycle() { #if ACCUMULATOR_BIT_SHIFT >= 24 uint16_t phAng; #else @@ -173,3 +252,4 @@ uint8_t DDS::getDutyCycle() { scaled += 128>>(8-COMPARATOR_BITS); return scaled; } + diff --git a/DDS.h b/DDS.h index 57cf40b..1498b9d 100644 --- a/DDS.h +++ b/DDS.h @@ -1,12 +1,19 @@ #ifndef _DDS_H_ #define _DDS_H_ - +#include #include +// Just a little reminder +#ifndef __SAMD21G18A__ +#warning Experimental support for ArduinoZero. Not yet complete +#endif + // 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 +#ifndef __SAMD21G18A__ #define DDS_PWM_PIN_3 +#endif // 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 @@ -14,7 +21,9 @@ // Use a short (16 bit) accumulator. Phase accuracy is reduced, but speed // is increased, along with a reduction in memory use. +#ifndef __SAMD21G18A__ #define SHORT_ACCUMULATOR +#endif #ifdef SHORT_ACCUMULATOR #define ACCUMULATOR_BITS 16 @@ -35,10 +44,17 @@ typedef uint32_t ddsAccumulator_t; // 8 = 62.5kHz PWM // 7 = 125kHz PWM // 6 = 250kHz PWM -#ifdef DDS_PWM_PIN_3 +#ifdef __SAMD21G18A__ +//TODO: 10 bit resolution for the Zero's DAC. +//Doesn't work just yet, so keep 8-bit for now. +#define COMPARATOR_BITS 8 +typedef uint8_t ddsComparitor_t; +#elif defined(DDS_PWM_PIN_3) #define COMPARATOR_BITS 6 +typedef uint8_t ddsComparitor_t; #else // When using pin 11, we always want 8 bits #define COMPARATOR_BITS 8 +typedef uint8_t ddsComparitor_t; #endif // This is how often we'll perform a phase advance, as well as ADC sampling @@ -46,8 +62,13 @@ typedef uint32_t ddsAccumulator_t; // expense of CPU time. It maxes out around 62000 (TBD) // May be overridden in the sketch to improve performance #ifndef DDS_REFCLK_DEFAULT -#define DDS_REFCLK_DEFAULT 9600 + #ifdef __SAMD21G18A__ + #define DDS_REFCLK_DEFAULT 44100 + #else + #define DDS_REFCLK_DEFAULT 9600 + #endif #endif + // 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. @@ -61,7 +82,7 @@ typedef uint32_t ddsAccumulator_t; #endif // Output some of the calculations and information about the DDS over serial -//#define DDS_DEBUG_SERIAL +#define DDS_DEBUG_SERIAL // When defined, use the 1024 element sine lookup table. This improves phase // accuracy, at the cost of more flash and CPU requirements. @@ -202,7 +223,7 @@ public: return refclkOffset; } - uint8_t getDutyCycle(); + ddsComparitor_t getDutyCycle(); // Set a scaling factor. To keep things quick, this is a power of 2 value. // Set it with 0 for lowest (which will be off), 8 is highest. @@ -222,7 +243,7 @@ private: volatile ddsAccumulator_t stepRate; ddsAccumulator_t refclk; int16_t refclkOffset; - static DDS *sDDS; + //static _DDS *sDDS; }; #endif /* _DDS_H_ */