Making sinewaves with XMEGA DAC

The XMEGA is quite a leap from the “classic” AVRs. Some of the interesting features are the DAC and DMA. When combined, they can be used to generate all kinds of useful signals in the audio range.

This example uses the DAC, DMA, timer and event system to generate 1200 Hz and 2200 Hz sinewaves. I’ll show how to make a Bell 202 modem (think: APRS and AX.25) in another post.

How it all works

I use an ATxmega32A4U. Several components have to be brought together to make a signal generator:

  • Array of sinewave samples (or another desired signal)
  • DAC – obviously to output the signal
  • DMA engine – moves the samples to the DAC without any CPU usage
  • timer – decides when (how fast) a new sample should be output
  • event system – connects the timer with the DMA

Why is it so “complicated”? Because otherwise the AVR CPU would have to execute code to load every sample and have little time to do anything else. With this setup the CPU and software is only needed to start the generator. Afterwards the CPU can do other tasks or sleep. Software is only needed to change the frequency (“speed”) of the signal or to stop the generator.

Sinewave samples

Let’s begin with the sinewave samples to be played. Public interface of the sinewave module is very simple – basically just an array of uint8_t numbers and an init function. This function copies samples from flash to RAM. This is needed because DMA can only move data between RAM and peripherals (it can’t access flash). I could skip the PROGMEM attribute (and let the startup code do the copying), but an explicit copy function allows me to adjust the amplitude of the signal.

The interface

The data

The init function is pretty obvious. Magic numbers were generated using the following python script. I found out that a small offset was needed because the DAC could not reach 0V and clipped the signal.

The generator

The generator is prepared to output 1200Hz and 2200Hz sinewaves, so there is just a shift function to switch between these two frequencies. First element to initialize is the timer that has to count up to a particular top value, reset and start again (CTC mode in the manual). Calculation of the top value is described in the comments.

Second element to initialize is the event system. It allows to transmit interrupt-like events between peripherals, so that no CPU action is required. Here, the event channel 0 is set to be triggered by timer overflow. Next the DMA is set up. This may look complex, but it boils down to:

  • source address – sinewave table (lines 58-60)
  • target address – DAC output register (lines 55-57)
  • which of the addresses should be incremented (only the sample address), by what amount and if the transfer should be repeated continuously (line 52)
  • how many transfers should take place (line 61)
  • when should a transfer happen – on event channel 0 (line 54)

After initialization, the dds_start function enables the DAC, configures reference voltage (full-scale DAC output can be either Vcc or 1V), loads factory DAC calibration data and starts the timer. Now – every timer overflow an event will be triggered, that will make the DMA update sample value in DAC output register.

While the generator is running the overflow value of the timer can be adjusted on the fly to change output frequency and this is done by the dds_shift function.