Very rough draft of DDS supporting the Arduio Zero and it's built-in DAC.
This commit is contained in:
		
							parent
							
								
									c679bec886
								
							
						
					
					
						commit
						fdaa602401
					
				
							
								
								
									
										174
									
								
								DDS.cpp
								
								
								
								
							
							
						
						
									
										174
									
								
								DDS.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -1,41 +1,95 @@
 | 
			
		|||
#include <Arduino.h>
 | 
			
		||||
#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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										33
									
								
								DDS.h
								
								
								
								
							
							
						
						
									
										33
									
								
								DDS.h
								
								
								
								
							| 
						 | 
				
			
			@ -1,12 +1,19 @@
 | 
			
		|||
#ifndef _DDS_H_
 | 
			
		||||
#define _DDS_H_
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <avr/pgmspace.h>
 | 
			
		||||
 | 
			
		||||
// 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_ */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue