Using the DAC

Can we use the 12 bit  DAC of the Teensy 3.1  to generate AFSK (Audio Frequency Shift Keying) signals? Lets try.  First, we try to generate sine waves with frequencies of 1200 or 2200 Hz. If we could quickly toggle between those two frequencies,  it would support generating AFSK.

The idea is to use a sampling frequency of 16 times the signal frequency and change the sampling frequency to change the signal frequency.

Most microcontrollers have hardware timers that can generate interrupts periodically. ChibiOS/RT offers GPTs (Generic Programable Timers) which abstracts hardware timers and make them easy to use. We set up a timer with a clock frequency of 211.2 KHz which is the least common multiplier of 16×1200 Hz and 16×2200 Hz.  When starting the timer with a periodic clock tick, we set a counter that decrements each tick. When it reaches 0, the counter is reset and a handler function is invoked.  For 2200 Hz, the counter is 6, for 1200 Hz the counter is 11.

We try the following handler function to generate a sine wave. The variable phase starts with 0 and for each of the 16 steps we add 2*pi/16. We multiply with 2000 and add 2050 to maximise the use of the 12 bit DAC.

static void sinewave() {
   register uint16_t
   amplitude = (uint16_t) (sin(phase) * 2000 + 2050);
   analogWrite(amplitude);
   phase += STEP;
   if (phase >= 2 * PI) phase = 0;
}

We connect the scope to the DAC output pin on the Teensy 3.1 board and see a sine wave as expected. But wait, there is a glitch there. To see this clearer, we add a digital output and toggle it at the start of the handler function and connect channel B of the scope to this output. Now the sinewave is displayed on top of a square pulse (where each transition represent a call of the function).

xxx13

There is a delay before the computed amplitude is output on the DAC pin. This is very small when phase = 0, but significant otherwise. It also seems to grow somewhat as phase grows. This indicates that the sin function is computationally intensive. It seems to take up to about 40 microseconds. Not much, but too much when doing this 35200 times per second.  It may also delay the next callback from the timer which is problematic.

The simple solution is of course to precompute the sine wave and put the numbers into a table. The sinewave function is just a simple lookup into this table.

static uint8_t i = 0;
static uint16_t sine[16] = 
  { 2059, 2815, 3464, 3898, 4050, 3898, 3464, 2815, 
    2050, 1285, 636, 202, 50, 202, 636, 1285 };

static void sinewave() {
  analogWrite(sine[i++]);
  if (i >= 16) i=0;
}

Now things are running quite smooth and we can also see on the scope that the delay of the DAC is neglectable.

xxx16

That said,  it is possible to optimize the implementation of such computations. The microcontroller in the Teensy 3 actually has a decent set of DSP instructions that could be used. The Teensy audio library for example exploits this and is quite impressing.