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 <Arduino.h>
|
||||||
#include "DDS.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
|
// To start the DDS, we use Timer1, set to the reference clock
|
||||||
// We use Timer2 for the PWM output, running as fast as feasible
|
// We use Timer2 for the PWM output, running as fast as feasible
|
||||||
void DDS::start() {
|
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
|
// Use the clkIO clock rate
|
||||||
ASSR &= ~(_BV(EXCLK) | _BV(AS2));
|
ASSR &= ~(_BV(EXCLK) | _BV(AS2));
|
||||||
|
|
||||||
// First, the timer for the PWM output
|
// First, the timer for the PWM output
|
||||||
// Setup the timer to use OC2B (pin 3) in fast PWM mode with a configurable top
|
// Setup the timer to use OC2B (pin 3) in fast PWM mode with a configurable top
|
||||||
// Run it without the prescaler
|
// Run it without the prescaler
|
||||||
#ifdef DDS_PWM_PIN_3
|
#ifdef DDS_PWM_PIN_3
|
||||||
TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) |
|
TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) |
|
||||||
_BV(WGM21) | _BV(WGM20);
|
_BV(WGM21) | _BV(WGM20);
|
||||||
TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22);
|
TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22);
|
||||||
#else
|
#else
|
||||||
// Alternatively, use pin 11
|
// Alternatively, use pin 11
|
||||||
// Enable output compare on OC2A, toggle mode
|
// Enable output compare on OC2A, toggle mode
|
||||||
TCCR2A = _BV(COM2A1) | _BV(WGM21) | _BV(WGM20);
|
TCCR2A = _BV(COM2A1) | _BV(WGM21) | _BV(WGM20);
|
||||||
//TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) |
|
//TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) |
|
||||||
// _BV(WGM21) | _BV(WGM20);
|
// _BV(WGM21) | _BV(WGM20);
|
||||||
TCCR2B = _BV(CS20);
|
TCCR2B = _BV(CS20);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Set the top limit, which will be our duty cycle accuracy.
|
// Set the top limit, which will be our duty cycle accuracy.
|
||||||
// Setting Comparator Bits smaller will allow for higher frequency PWM,
|
// Setting Comparator Bits smaller will allow for higher frequency PWM,
|
||||||
// with the loss of resolution.
|
// with the loss of resolution.
|
||||||
#ifdef DDS_PWM_PIN_3
|
#ifdef DDS_PWM_PIN_3
|
||||||
OCR2A = pow(2,COMPARATOR_BITS)-1;
|
OCR2A = pow(2,COMPARATOR_BITS)-1;
|
||||||
OCR2B = 0;
|
OCR2B = 0;
|
||||||
#else
|
#else
|
||||||
OCR2A = 0;
|
OCR2A = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef DDS_USE_ONLY_TIMER2
|
#ifdef DDS_USE_ONLY_TIMER2
|
||||||
TIMSK2 |= _BV(TOIE2);
|
TIMSK2 |= _BV(TOIE2);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Second, setup Timer1 to trigger the ADC interrupt
|
// Second, setup Timer1 to trigger the ADC interrupt
|
||||||
// This lets us use decoding functions that run at the same reference
|
// 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
|
// We use ICR1 as TOP and prescale by 8
|
||||||
TCCR1B = _BV(CS10) | _BV(WGM13) | _BV(WGM12);
|
TCCR1B = _BV(CS10) | _BV(WGM13) | _BV(WGM12);
|
||||||
TCCR1A = 0;
|
TCCR1A = 0;
|
||||||
ICR1 = ((F_CPU / 1) / refclk) - 1;
|
ICR1 = ((F_CPU / 1) / refclk) - 1;
|
||||||
#ifdef DDS_DEBUG_SERIAL
|
#ifdef DDS_DEBUG_SERIAL
|
||||||
Serial.print(F("DDS SysClk: "));
|
Serial.print(F("DDS ICR1: "));
|
||||||
Serial.println(F_CPU/8);
|
Serial.println(ICR1, DEC);
|
||||||
Serial.print(F("DDS RefClk: "));
|
#endif
|
||||||
Serial.println(refclk, DEC);
|
|
||||||
Serial.print(F("DDS ICR1: "));
|
|
||||||
Serial.println(ICR1, DEC);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Configure the ADC here to automatically run and be triggered off Timer1
|
// 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)
|
ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used)
|
||||||
|
@ -60,14 +110,22 @@ void DDS::start() {
|
||||||
DIDR0 |= _BV(0);
|
DIDR0 |= _BV(0);
|
||||||
ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0);
|
ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0);
|
||||||
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0);
|
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0);
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void DDS::stop() {
|
void DDS::stop() {
|
||||||
|
#ifdef __SAMD21G18A__
|
||||||
|
TC_DISABLE();
|
||||||
|
TC_RESET();
|
||||||
|
analogWrite(A0, 0);
|
||||||
|
#else
|
||||||
// TODO: Stop the timers.
|
// TODO: Stop the timers.
|
||||||
#ifndef DDS_USE_ONLY_TIMER2
|
#ifndef DDS_USE_ONLY_TIMER2
|
||||||
TCCR1B = 0;
|
TCCR1B = 0;
|
||||||
|
#endif
|
||||||
|
TCCR2B = 0;
|
||||||
#endif
|
#endif
|
||||||
TCCR2B = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set our current sine wave frequency in Hz
|
// 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);
|
newStep = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//TODO: This doesn't work with the SAM21D... yet
|
||||||
newStep = pow(2,ACCUMULATOR_BITS)*freq / (refclk+refclkOffset);
|
newStep = pow(2,ACCUMULATOR_BITS)*freq / (refclk+refclkOffset);
|
||||||
}
|
}
|
||||||
return newStep;
|
return newStep;
|
||||||
|
@ -112,45 +171,65 @@ void DDS::clockTick() {
|
||||||
if(running) {
|
if(running) {
|
||||||
accumulator += stepRate;
|
accumulator += stepRate;
|
||||||
if(timeLimited && tickDuration == 0) {
|
if(timeLimited && tickDuration == 0) {
|
||||||
#ifndef DDS_PWM_PIN_3
|
#ifdef __SAMD21G18A__
|
||||||
OCR2A = 0;
|
#ifdef DDS_IDLE_HIGH
|
||||||
|
analogWrite(A0, pow(2,COMPARATOR_BITS)/2);
|
||||||
|
#else
|
||||||
|
analogWrite(A0, 0);
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#ifdef DDS_IDLE_HIGH
|
#ifndef DDS_PWM_PIN_3
|
||||||
|
OCR2A = 0;
|
||||||
|
#else
|
||||||
|
#ifdef DDS_IDLE_HIGH
|
||||||
// Set the duty cycle to 50%
|
// Set the duty cycle to 50%
|
||||||
OCR2B = pow(2,COMPARATOR_BITS)/2;
|
OCR2B = pow(2,COMPARATOR_BITS)/2;
|
||||||
#else
|
#else
|
||||||
// Set duty cycle to 0, effectively off
|
// Set duty cycle to 0, effectively off
|
||||||
OCR2B = 0;
|
OCR2B = 0;
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
running = false;
|
running = false;
|
||||||
accumulator = 0;
|
accumulator = 0;
|
||||||
} else {
|
} else {
|
||||||
#ifdef DDS_PWM_PIN_3
|
#ifdef __SAMD21G18A__
|
||||||
OCR2B = getDutyCycle();
|
analogWrite(A0, getDutyCycle());
|
||||||
#else
|
#else
|
||||||
|
#ifdef DDS_PWM_PIN_3
|
||||||
|
OCR2B = getDutyCycle();
|
||||||
|
#else
|
||||||
OCR2A = getDutyCycle();
|
OCR2A = getDutyCycle();
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// Reduce our playback duration by one tick
|
// Reduce our playback duration by one tick
|
||||||
tickDuration--;
|
tickDuration--;
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef __SAMD21G18A__
|
||||||
|
#ifdef DDS_IDLE_HIGH
|
||||||
|
analogWrite(A0, pow(2,COMPARATOR_BITS)/2);
|
||||||
|
#else
|
||||||
|
analogWrite(A0, 0);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
// Hold it low
|
// Hold it low
|
||||||
#ifndef DDS_PWM_PIN_3
|
#ifndef DDS_PWM_PIN_3
|
||||||
OCR2A = 0;
|
OCR2A = 0;
|
||||||
#else
|
#else
|
||||||
#ifdef DDS_IDLE_HIGH
|
#ifdef DDS_IDLE_HIGH
|
||||||
// Set the duty cycle to 50%
|
// Set the duty cycle to 50%
|
||||||
OCR2B = pow(2,COMPARATOR_BITS)/2;
|
OCR2B = pow(2,COMPARATOR_BITS)/2;
|
||||||
#else
|
#else
|
||||||
// Set duty cycle to 0, effectively off
|
// Set duty cycle to 0, effectively off
|
||||||
OCR2B = 0;
|
OCR2B = 0;
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t DDS::getDutyCycle() {
|
ddsComparitor_t DDS::getDutyCycle() {
|
||||||
#if ACCUMULATOR_BIT_SHIFT >= 24
|
#if ACCUMULATOR_BIT_SHIFT >= 24
|
||||||
uint16_t phAng;
|
uint16_t phAng;
|
||||||
#else
|
#else
|
||||||
|
@ -173,3 +252,4 @@ uint8_t DDS::getDutyCycle() {
|
||||||
scaled += 128>>(8-COMPARATOR_BITS);
|
scaled += 128>>(8-COMPARATOR_BITS);
|
||||||
return scaled;
|
return scaled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
DDS.h
33
DDS.h
|
@ -1,12 +1,19 @@
|
||||||
#ifndef _DDS_H_
|
#ifndef _DDS_H_
|
||||||
#define _DDS_H_
|
#define _DDS_H_
|
||||||
|
#include <Arduino.h>
|
||||||
#include <avr/pgmspace.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
|
// 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
|
// Quality on pin 3 is higher than on 11, as it can be clocked faster
|
||||||
// when the COMPARATOR_BITS value is less than 8
|
// when the COMPARATOR_BITS value is less than 8
|
||||||
|
#ifndef __SAMD21G18A__
|
||||||
#define DDS_PWM_PIN_3
|
#define DDS_PWM_PIN_3
|
||||||
|
#endif
|
||||||
|
|
||||||
// Normally, we turn on timer2 and timer1, and have ADC sampling as our clock
|
// 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 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
|
// Use a short (16 bit) accumulator. Phase accuracy is reduced, but speed
|
||||||
// is increased, along with a reduction in memory use.
|
// is increased, along with a reduction in memory use.
|
||||||
|
#ifndef __SAMD21G18A__
|
||||||
#define SHORT_ACCUMULATOR
|
#define SHORT_ACCUMULATOR
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SHORT_ACCUMULATOR
|
#ifdef SHORT_ACCUMULATOR
|
||||||
#define ACCUMULATOR_BITS 16
|
#define ACCUMULATOR_BITS 16
|
||||||
|
@ -35,10 +44,17 @@ typedef uint32_t ddsAccumulator_t;
|
||||||
// 8 = 62.5kHz PWM
|
// 8 = 62.5kHz PWM
|
||||||
// 7 = 125kHz PWM
|
// 7 = 125kHz PWM
|
||||||
// 6 = 250kHz 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
|
#define COMPARATOR_BITS 6
|
||||||
|
typedef uint8_t ddsComparitor_t;
|
||||||
#else // When using pin 11, we always want 8 bits
|
#else // When using pin 11, we always want 8 bits
|
||||||
#define COMPARATOR_BITS 8
|
#define COMPARATOR_BITS 8
|
||||||
|
typedef uint8_t ddsComparitor_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// This is how often we'll perform a phase advance, as well as ADC sampling
|
// 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)
|
// expense of CPU time. It maxes out around 62000 (TBD)
|
||||||
// May be overridden in the sketch to improve performance
|
// May be overridden in the sketch to improve performance
|
||||||
#ifndef DDS_REFCLK_DEFAULT
|
#ifndef DDS_REFCLK_DEFAULT
|
||||||
#define DDS_REFCLK_DEFAULT 9600
|
#ifdef __SAMD21G18A__
|
||||||
|
#define DDS_REFCLK_DEFAULT 44100
|
||||||
|
#else
|
||||||
|
#define DDS_REFCLK_DEFAULT 9600
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// As each Arduino crystal is a little different, this can be fine tuned to
|
// As each Arduino crystal is a little different, this can be fine tuned to
|
||||||
// provide more accurate frequencies. Adjustments in the range of hundreds
|
// provide more accurate frequencies. Adjustments in the range of hundreds
|
||||||
// is a good start.
|
// is a good start.
|
||||||
|
@ -61,7 +82,7 @@ typedef uint32_t ddsAccumulator_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Output some of the calculations and information about the DDS over serial
|
// 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
|
// When defined, use the 1024 element sine lookup table. This improves phase
|
||||||
// accuracy, at the cost of more flash and CPU requirements.
|
// accuracy, at the cost of more flash and CPU requirements.
|
||||||
|
@ -202,7 +223,7 @@ public:
|
||||||
return refclkOffset;
|
return refclkOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getDutyCycle();
|
ddsComparitor_t getDutyCycle();
|
||||||
|
|
||||||
// Set a scaling factor. To keep things quick, this is a power of 2 value.
|
// 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.
|
// Set it with 0 for lowest (which will be off), 8 is highest.
|
||||||
|
@ -222,7 +243,7 @@ private:
|
||||||
volatile ddsAccumulator_t stepRate;
|
volatile ddsAccumulator_t stepRate;
|
||||||
ddsAccumulator_t refclk;
|
ddsAccumulator_t refclk;
|
||||||
int16_t refclkOffset;
|
int16_t refclkOffset;
|
||||||
static DDS *sDDS;
|
//static _DDS *sDDS;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _DDS_H_ */
|
#endif /* _DDS_H_ */
|
||||||
|
|
Loading…
Reference in New Issue