#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__ // SAMD21, like Ardino Zero // 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 #if COMPARATOR_BITS==6 // Not sure why you'd use 6-bit, other than debugging. // Reduced amplitude: clockTick() and getDutyCycle() do not scale the 6 up to 8-bits analogWriteResolution(8); #elif COMPARATOR_BITS == 8 || COMPARATOR_BITS == 10 analogWriteResolution(COMPARATOR_BITS); #else #error Unsupported resoltuion for DAC (expected 8 or 10) #endif analogWrite(A0, 0); #else // For AVRs // 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)) | _BV(WGM21) | _BV(WGM20); 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 // 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 = DDS_MAX_COMPARATOR-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. // 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 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) DDRC &= ~_BV(0); PORTC &= ~_BV(0); 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; #endif TCCR2B = 0; #endif } // Set our current sine wave frequency in Hz ddsAccumulator_t DDS::calcFrequency(unsigned short freq) { // Fo = (M*Fc)/2^N // M = (Fo/Fc)*2^N if(refclk == DDS_REFCLK_DEFAULT) { // Try to use precalculated values if possible if(freq == 2200) { return (2200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * DDS_MAX_ACCUMULATOR; } else if (freq == 1200) { return (1200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * DDS_MAX_ACCUMULATOR; } else if(freq == 2400) { return (2400.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * DDS_MAX_ACCUMULATOR; } else if (freq == 1500) { return (1500.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * DDS_MAX_ACCUMULATOR; } else if (freq == 600) { return (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * DDS_MAX_ACCUMULATOR; } } // Not a pre-calc frequency OR not using default REFCLK return DDS_MAX_ACCUMULATOR * freq / (refclk+refclkOffset); } // Degrees should be between -360 and +360 (others don't make much sense) void DDS::setPhaseDeg(int16_t degrees) { accumulator = degrees * (DDS_MAX_ACCUMULATOR/360.0); } void DDS::changePhaseDeg(int16_t degrees) { accumulator += degrees * (DDS_MAX_ACCUMULATOR/360.0); } // TODO: Clean this up a bit.. void DDS::clockTick() { /* if(running) { accumulator += stepRate; OCR2A = getDutyCycle(); } return;*/ if(running) { accumulator += stepRate; if(timeLimited && tickDuration == 0) { #ifdef __SAMD21G18A__ #ifdef DDS_IDLE_HIGH analogWrite(A0, DDS_MAX_COMPARATOR/2); #else analogWrite(A0, 0); #endif #else #ifndef DDS_PWM_PIN_3 OCR2A = 0; #else #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = DDS_MAX_COMPARATOR/2; #else // Set duty cycle to 0, effectively off OCR2B = 0; #endif #endif #endif running = false; accumulator = 0; } else { #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, DDS_MAX_COMPARATOR/2); #else analogWrite(A0, 0); #endif #else // Hold it low #ifndef DDS_PWM_PIN_3 OCR2A = 0; #else #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = DDS_MAX_COMPARATOR/2; #else // Set duty cycle to 0, effectively off OCR2B = 0; #endif #endif #endif } } //TODO: rename this function as it is more than just dutyCycle? //now it powers both a PWM dutyCycle as well as a DAC value on the Zero ddsComparitor_t DDS::getDutyCycle() { #if ACCUMULATOR_BITS - ACCUMULATOR_BIT_SHIFT > 8 // For larger sineTables which need more thn 8 bits. uint16_t phAng; #else // For standard sineTables which need 8 bits. uint8_t phAng; #endif if(amplitude == 0) // Shortcut out on no amplitude return DDS_MAX_COMPARATOR/2; // Lookup the value from the sin table. phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); #if DDS_LOOKUPVALUE_BITS > 8 // 16-bit lookup int16_t position = pgm_read_word_near(ddsSineTable + phAng); //>>(8-COMPARATOR_BITS); int32_t scaled = position; #else // 8-bit lookup int8_t position = pgm_read_byte_near(ddsSineTable + phAng); //>>(8-COMPARATOR_BITS); int16_t scaled = position; #endif // If there's an amplitude, scale it if(amplitude != 255) { // Amplitude is reduced, so do the full math scaled *= amplitude; // multiply by the amplitude, max 255 or an 8-bit shift scaled >>= 8; // bring it back 8 bits devide } // Scale to the number of bits available #if COMPARATOR_BITS > DDS_LOOKUPVALUE_BITS scaled <<= (COMPARATOR_BITS - DDS_LOOKUPVALUE_BITS); #elif COMPARATOR_BITS < DDS_LOOKUPVALUE_BITS scaled >>= (DDS_LOOKUPVALUE_BITS-COMPARATOR_BITS); #else // COMARATOR_BITS == DDS_LOOKUPVALUE_BITS -- no math needed. #endif //Move the sinewave up 1/2 scale into the positive range. scaled += DDS_MAX_COMPARATOR/2; return scaled; }