/* Smooth Logarithmic Frequency Sweep (DDS) on DAC (SAMD21 XIAO) ------------------------------------------------------------- Generates a clean sine wave on DAC A0, sweeping *logarithmically* between freq_start and freq_stop over sweep_period milliseconds. Uses: - Direct Digital Synthesis (DDS) - Timer TC5 for precise DAC timing Hardware: Seeeduino XIAO (SAMD21) DAC output on A0 (D0) */ #include #include // ===== USER PARAMETERS ===== const float freq_start = 100.0; // Hz const float freq_stop = 1000.0; // Hz const unsigned long sweep_period = 5000; // ms (up + down) const uint16_t amplitude = 2047; // Max 4095 (12-bit DAC) // ===== INTERNAL CONSTANTS ===== const uint16_t TABLE_SIZE = 256; // Sine lookup table size const float SAMPLE_RATE = 48000.0; // DAC sample rate (Hz) volatile uint16_t sineTable[TABLE_SIZE]; volatile uint32_t phaseAcc = 0; // 32-bit phase accumulator volatile uint32_t phaseInc = 0; // Phase increment per sample // DAC pin const int dacPin = A0; // Sweep control unsigned long sweepStart = 0; float currentFreq = freq_start; // ===== TIMER ISR ===== void TC5_Handler(void) { // Clear interrupt flag TC5->COUNT16.INTFLAG.bit.MC0 = 1; // Increment phase accumulator phaseAcc += phaseInc; // Lookup sine sample uint16_t index = (phaseAcc >> 24) & 0xFF; // top 8 bits → 256-entry table analogWrite(dacPin, sineTable[index]); } // ===== SETUP FUNCTIONS ===== void setupSineTable() { for (uint16_t i = 0; i < TABLE_SIZE; i++) { float theta = (2.0f * PI * i) / TABLE_SIZE; sineTable[i] = amplitude + (uint16_t)(amplitude * sin(theta)); } } void setupTimer() { // Enable GCLK for TC5 GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(TC5_GCLK_ID) | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_CLKEN; while (GCLK->STATUS.bit.SYNCBUSY); // Disable timer TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Configure timer for match frequency mode TC5->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_MFRQ | TC_CTRLA_PRESCALER_DIV1; // Set compare value for desired sample rate uint32_t compareValue = (uint32_t)(SystemCoreClock / SAMPLE_RATE); TC5->COUNT16.CC[0].reg = compareValue; while (TC5->COUNT16.STATUS.bit.SYNCBUSY); // Enable interrupt NVIC_EnableIRQ(TC5_IRQn); TC5->COUNT16.INTENSET.bit.MC0 = 1; // Enable timer TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; while (TC5->COUNT16.STATUS.bit.SYNCBUSY); } void setup() { analogWriteResolution(12); pinMode(dacPin, OUTPUT); setupSineTable(); setupTimer(); sweepStart = millis(); } // ===== MAIN LOOP ===== void loop() { unsigned long elapsed = (millis() - sweepStart) % sweep_period; float half = sweep_period / 2.0; float ratio = freq_stop / freq_start; // Logarithmic sweep up and down if (elapsed < half) { float progress = elapsed / half; currentFreq = freq_start * pow(ratio, progress); // exponential up } else { float progress = (elapsed - half) / half; currentFreq = freq_stop * pow(1.0 / ratio, progress); // exponential down } // Update phase increment for DDS phaseInc = (uint32_t)((currentFreq * 4294967296.0) / SAMPLE_RATE); }