|
// FM DDS |
|
// Ed Nisley – KE4ZNU |
|
// 2017-04-19 Demo 1 |
|
|
|
#include <IntervalTimer.h> |
|
#include <ADC.h> |
|
#include <SPI.h> |
|
|
|
#define PIN_HEART 14 |
|
#define PIN_TIMER 15 |
|
#define PIN_ANALOG 16 |
|
#define PIN_GLITCH 17 |
|
|
|
#define PIN_AUDIO A9 |
|
|
|
#define PIN_DDS_FQUD 10 |
|
// data to DDS MOSI0 11 |
|
// no data from DDS MISO0 12 |
|
// DDS clock on SCK0 13 — also LED |
|
|
|
#define BUILTIN_LED 13 |
|
|
|
//——————— |
|
// Useful constants |
|
|
|
int SamplePeriod = 25; // microseconds per analog sample |
|
|
|
//——————— |
|
// Globals |
|
|
|
ADC *adc = new ADC(); |
|
|
|
IntervalTimer timer; |
|
|
|
volatile int AnalogSample; |
|
volatile int AudioMax = -4096; |
|
volatile int AudioMin = 4096; |
|
|
|
typedef struct { |
|
uint8_t Phase; |
|
uint32_t DeltaPhase; // DDS expects MSB first! |
|
} DDS; |
|
|
|
DDS DDSBuffer; |
|
|
|
double DDSClock = 180.0e6; // nominal DDS oscillator |
|
|
|
double CountPerHertz, HertzPerCount; // DDS delta-phase increments |
|
|
|
double Crystal = 20.0e6; // nominal DDS frequency |
|
double Deviation = 5.0e3; // nominal FM signal deviation (one-sided) |
|
|
|
double TestFreq; |
|
|
|
|
|
|
|
//——————— |
|
// Handy routines |
|
|
|
void FlipPin(int pin) { |
|
digitalWriteFast(pin,!digitalRead(pin)); |
|
} |
|
|
|
void PulsePin(int p) { |
|
FlipPin(p); |
|
FlipPin(p); |
|
} |
|
|
|
//——————— |
|
// Timer handler |
|
|
|
void timer_callback(void) { |
|
|
|
digitalWriteFast(PIN_TIMER,HIGH); |
|
|
|
digitalWriteFast(PIN_DDS_FQUD,HIGH); // latch previously shifted bits |
|
|
|
adc->startSingleRead(PIN_AUDIO, ADC_0); // start ADC conversion |
|
|
|
analogWriteDAC0(AnalogSample); // show previous audio sample |
|
|
|
digitalWriteFast(PIN_TIMER,LOW); |
|
|
|
} |
|
|
|
//——————— |
|
// Analog read handler |
|
|
|
void adc0_isr(void) { |
|
|
|
int Audio; |
|
|
|
digitalWriteFast(PIN_ANALOG,HIGH); |
|
|
|
AnalogSample = adc->readSingle(); // fetch just-finished sample |
|
Audio = AnalogSample – 2048; // convert to AC signal |
|
|
|
DDSBuffer.Phase = 0; |
|
|
|
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); |
|
digitalWriteFast(PIN_DDS_FQUD, LOW); |
|
|
|
SPI.transfer(DDSBuffer.Phase); |
|
|
|
DDSBuffer.DeltaPhase = (uint32_t)((((double)Audio / 2048.0) * Deviation + Crystal) * CountPerHertz); |
|
|
|
SPI.transfer((uint8_t)(DDSBuffer.DeltaPhase >> 24)); // MSB first! |
|
|
|
if (Audio > AudioMax) // ignore race conditions |
|
AudioMax = Audio; |
|
if (Audio < AudioMin) |
|
AudioMin = Audio; |
|
|
|
|
|
SPI.transfer((uint8_t)(DDSBuffer.DeltaPhase >> 16)); |
|
|
|
SPI.transfer((uint8_t)(DDSBuffer.DeltaPhase >> 8)); |
|
SPI.transfer((uint8_t)DDSBuffer.DeltaPhase); |
|
|
|
SPI.endTransaction(); // do not raise FQ_UD until next timer tick! |
|
|
|
digitalWriteFast(PIN_ANALOG,LOW); |
|
|
|
} |
|
|
|
//——————— |
|
// Hardware setup |
|
|
|
void setup(void) { |
|
|
|
pinMode(BUILTIN_LED,OUTPUT); // will eventually become SCK0 |
|
|
|
pinMode(PIN_HEART, OUTPUT); // show we arrived |
|
digitalWrite(PIN_HEART,LOW); |
|
PulsePin(PIN_HEART); |
|
PulsePin(PIN_HEART); |
|
|
|
pinMode(PIN_TIMER,OUTPUT); |
|
digitalWrite(PIN_TIMER,LOW); |
|
pinMode(PIN_GLITCH,OUTPUT); |
|
digitalWrite(PIN_GLITCH,LOW); |
|
|
|
pinMode(PIN_ANALOG,OUTPUT); |
|
digitalWrite(PIN_ANALOG,LOW); |
|
|
|
pinMode(PIN_AUDIO,INPUT); |
|
|
|
pinMode(PIN_DDS_FQUD,OUTPUT); |
|
digitalWriteFast(PIN_DDS_FQUD,HIGH); |
|
|
|
Serial.begin(115200); |
|
|
|
int waited = 0; |
|
while (!Serial && waited < 3000) { // fall out after a few seconds |
|
delay(1); |
|
waited++; |
|
if (! (waited % 50)) |
|
FlipPin(BUILTIN_LED); |
|
} |
|
|
|
Serial.printf("FM Modulated DDS\nEd Nisley KE4ZNU\n"); |
|
Serial.printf(" serial wait: %d ms\n\n",waited); |
|
|
|
SPI.begin(); |
|
SPI.usingInterrupt(255); // attached through analog IRQs |
|
|
|
adc->setAveraging(4); // choices: 0, 4, 8, 16, 32 |
|
adc->setResolution(12); // choices: 8, 10, 12, 16 |
|
|
|
adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); |
|
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); |
|
|
|
adc->enableInterrupts(ADC_0); |
|
|
|
if (!timer.begin(timer_callback, SamplePeriod)) { |
|
Serial.printf("Timer start failed\n"); |
|
while (true) { |
|
FlipPin(BUILTIN_LED); |
|
delay(75); |
|
} |
|
} |
|
|
|
CountPerHertz = (1LL << 32) / DDSClock; |
|
HertzPerCount = 1.0 / CountPerHertz; |
|
|
|
Serial.printf("DDS clock: %13.3f Hz\n",DDSClock); |
|
Serial.printf("CountPerHertz: %13.3f ct\n",CountPerHertz); |
|
Serial.printf("HertzPerCount: %13.3f Hz\n\n",HertzPerCount); |
|
|
|
TestFreq = Crystal; |
|
Serial.printf("Crystal: %13.3f Hz\n",Crystal); |
|
Serial.printf("Deviation: %13.3f Hz\n",Deviation); |
|
|
|
Serial.printf("\nSetup done\n"); |
|
|
|
} |
|
|
|
|
|
//——————— |
|
// Do things forever |
|
|
|
void loop(void) { |
|
digitalWrite(PIN_HEART,HIGH); |
|
|
|
Serial.printf(" %5d to %5d\n",AudioMin,AudioMax); |
|
AudioMax = 99*AudioMax/100; // ignore race conditions |
|
AudioMin = 99*AudioMin/100; |
|
|
|
digitalWrite(PIN_HEART,LOW); |
|
delay(500); |
|
|
|
} |