/* Smooth Frequency Sweep (DDS) on DAC (SAMD21 XIAO) ------------------------------------------------- Generates a smooth sine wave on DAC A0, sweeping up and down 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 for 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() { // Determine where we are in the sweep unsigned long elapsed = (millis() - sweepStart) % sweep_period; float half = sweep_period / 2.0; // Linear sweep up and down if (elapsed < half) currentFreq = freq_start + (freq_stop - freq_start) * (elapsed / half); else currentFreq = freq_stop - (freq_stop - freq_start) * ((elapsed - half) / half); // Update phase increment (DDS) // phaseInc = (currentFreq / SAMPLE_RATE) * 2^32 phaseInc = (uint32_t)((currentFreq * 4294967296.0) / SAMPLE_RATE); }