Very rough draft of DDS supporting the Arduio Zero and it's built-in DAC.
This commit is contained in:
		
							parent
							
								
									c679bec886
								
							
						
					
					
						commit
						fdaa602401
					
				
							
								
								
									
										172
									
								
								DDS.cpp
								
								
								
								
							
							
						
						
									
										172
									
								
								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
 | ||||
|  | @ -44,14 +98,10 @@ void DDS::start() { | |||
|   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 | ||||
|   #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