Using the DAC with DMA on Teensy 3.1

It took me some time to figure out how to control the DAC output through DMA. I've started with Paul's Audio library (AudioOutputAnalog to be exact) and then tried to replicate that functionality with the high level functions provided by DMAChannel. In the end I got it working and you can find the source-code at the end of this message. During my experiments I found that these are the crucial steps to make it work:

  • the buffer used as source has to be declared as static volatile uint16_t, but not DMAMEM,
  • if you want the buffer to wrap over at the end and start at the beginning, use DMAChannel.xxxxBuffer(...),
  • and when using &DAC0_DAT0L as destination it has to be cast to *(volatile uint16_t *).

The attached code generates a 1 kHz sine-wave using a sample-rate of 128 kHz.

File pdb.h
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified 2015, Ferdinand Keil, ferdinandkeil@gmail.com
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef pdb_h_
#define pdb_h_

#include "kinetis.h"

// Multiple input & output objects use the Programmable Delay Block
// to set their sample rate.  They must all configure the same
// period to avoid chaos.

#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT | PDB_SC_PDBIE | PDB_SC_DMAEN)

#if F_BUS == 60000000
  #define PDB_PERIOD (468-1)
#elif F_BUS == 56000000
  #define PDB_PERIOD (437-1)
#elif F_BUS == 48000000
  #define PDB_PERIOD (375-1)
#elif F_BUS == 36000000
  #define PDB_PERIOD (281-1)
#elif F_BUS == 24000000
  #define PDB_PERIOD (187-1)
#elif F_BUS == 16000000
  #define PDB_PERIOD (125-1)
#else
  #error "Unsupported F_BUS speed"
#endif

#endif

File dac_test.ino
Code:
#include <DMAChannel.h>
#include "pdb.h"

DMAChannel dma(false);

static volatile uint16_t sinetable[] = {
   2047,    2147,    2248,    2348,    2447,    2545,    2642,    2737,
   2831,    2923,    3012,    3100,    3185,    3267,    3346,    3422,
   3495,    3564,    3630,    3692,    3750,    3804,    3853,    3898,
   3939,    3975,    4007,    4034,    4056,    4073,    4085,    4093,
   4095,    4093,    4085,    4073,    4056,    4034,    4007,    3975,
   3939,    3898,    3853,    3804,    3750,    3692,    3630,    3564,
   3495,    3422,    3346,    3267,    3185,    3100,    3012,    2923,
   2831,    2737,    2642,    2545,    2447,    2348,    2248,    2147,
   2047,    1948,    1847,    1747,    1648,    1550,    1453,    1358,
   1264,    1172,    1083,     995,     910,     828,     749,     673,
    600,     531,     465,     403,     345,     291,     242,     197,
    156,     120,      88,      61,      39,      22,      10,       2,
      0,       2,      10,      22,      39,      61,      88,     120,
    156,     197,     242,     291,     345,     403,     465,     531,
    600,     673,     749,     828,     910,     995,    1083,    1172,
   1264,    1358,    1453,    1550,    1648,    1747,    1847,    1948,
};

void setup() {
  dma.begin(true); // allocate the DMA channel first
  
  SIM_SCGC2 |= SIM_SCGC2_DAC0; // enable DAC clock
  DAC0_C0 = DAC_C0_DACEN | DAC_C0_DACRFS; // enable the DAC module, 3.3V reference
  // slowly ramp up to DC voltage, approx 1/4 second
  for (int16_t i=0; i<2048; i+=8) {
    *(int16_t *)&(DAC0_DAT0L) = i;
    delay(1);
  }
  
  // set the programmable delay block to trigger DMA requests
  SIM_SCGC6 |= SIM_SCGC6_PDB; // enable PDB clock
  PDB0_IDLY = 0; // interrupt delay register
  PDB0_MOD = PDB_PERIOD; // modulus register, sets period
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG; // reset and restart
  PDB0_CH0C1 = 0x0101; // channel n control register?
  
  dma.sourceBuffer(sinetable, sizeof(sinetable));
  dma.destination(*(volatile uint16_t *)&(DAC0_DAT0L));
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
  dma.enable();
}

void loop() {
  // do nothing here
}
 
thank you, Ms ferdinandkeil
this post contain exactly what I look for.

but I just would like to know:
how can anyone to save the code with .h extension?
 
Arduino: 1.8.2 (Windows 10), TD: 1.36, Board: "Teensy 3.2 / 3.1, Serial, 96 MHz (overclock), Faster, US English"

C:\Users\user\Desktop\LUT_WITH_DMA\LUT_WITH_DMA.ino:2:17: fatal error: pdb.h: No such file or directory

compilation terminated.

Error compiling for board Teensy 3.2 / 3.1.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
 
Hello people
what is the parts of this code that control the amplitude and freq. of this sine wave ?
another question : do I have to learn deeply about microcontroller to be able to use teensy board?
 
they told me that it is very simple and easy to use for children!

It is. The Teensy processors can be programmed using the Arduino IDE and everything is done for the Teensy to understand the simple Arduino programming language which is a subset of (C / C++). You might get an overview here: https://www.arduino.cc/reference/en/

The Teensyduino plugin which you install into the Arduino IDE comes with plenty of Teensy specific libraries and examples which make even the ways more complex and powerful hardware of the Teensy (compared to the Arduino UNO) easily accessible.
 
Hello people
what is the parts of this code that control the amplitude and freq. of this sine wave ?
another question : do I have to learn deeply about microcontroller to be able to use teensy board?

This defines the waveform using the maximal amplitude and resolution of the DAC.
Code:
static volatile uint16_t sinetable[] = {
   2047,    2147,    2248,    2348,    2447,    2545,    2642,    2737,
   2831,    2923,    3012,    3100,    3185,    3267,    3346,    3422,
   3495,    3564,    3630,    3692,    3750,    3804,    3853,    3898,
   3939,    3975,    4007,    4034,    4056,    4073,    4085,    4093,
   4095,    4093,    4085,    4073,    4056,    4034,    4007,    3975,
   3939,    3898,    3853,    3804,    3750,    3692,    3630,    3564,
   3495,    3422,    3346,    3267,    3185,    3100,    3012,    2923,
   2831,    2737,    2642,    2545,    2447,    2348,    2248,    2147,
   2047,    1948,    1847,    1747,    1648,    1550,    1453,    1358,
   1264,    1172,    1083,     995,     910,     828,     749,     673,
    600,     531,     465,     403,     345,     291,     242,     197,
    156,     120,      88,      61,      39,      22,      10,       2,
      0,       2,      10,      22,      39,      61,      88,     120,
    156,     197,     242,     291,     345,     403,     465,     531,
    600,     673,     749,     828,     910,     995,    1083,    1172,
   1264,    1358,    1453,    1550,    1648,    1747,    1847,    1948,
};
The frequency is controlled by your setting of PDB_PERIOD which has to be calculated depending on your desired output frequency, your selected cpu clock frequency (which will set the bus clock behind the stages). For example, if you need a frequency of 2kHz and the LUT has 128 entries, that means that you need 128 x 1000 = 128000 triggers per second. Running a Teensy 3.2 at 96MHz makes its internal bus clock to 48MHz. Divide these 48MHz by 128000 and you get 375 as the divider. To set the PDB_PERIOD, you have to subtract 1 because the PDB includes also a full cycle for a 0 value. Thus, #define PDB_PERIOD (375-1) is the solution for that CPU speed.
Code:
#if F_BUS == 60000000
  #define PDB_PERIOD (468-1)
#elif F_BUS == 56000000
  #define PDB_PERIOD (437-1)
#elif F_BUS == 48000000
  #define PDB_PERIOD (375-1)
#elif F_BUS == 36000000
  #define PDB_PERIOD (281-1)
#elif F_BUS == 24000000
  #define PDB_PERIOD (187-1)
#elif F_BUS == 16000000
  #define PDB_PERIOD (125-1)
 
another question : do I have to learn deeply about microcontroller to be able to use teensy board?
....
they told me that it is very simple and easy to use for children!

The normal way to do things involves using already-made libraries. In this case, the audio library provides an easy way to create sine waves and lots of other waveform stuff. There's a detailed tutorial available.

https://www.pjrc.com/store/audio_tutorial_kit.html

So if you want simple and easy, you could buy that $60 kit, or buy some of the parts and build the rest yourself. Then print the 31 page PDF and go through the tutorial. Or you could skim the PDF while watching the 45 minute video where Alysia and I demonstrate the whole thing. But of course actually doing the tutorial yourself is far better for learning than just watching a video. It takes most people 3 to 4 hours. It took us 17 hours of filming and 2 solid days of video editing to make that youtube video! All that work and thousands of hours developing the library code was all done to try to make this easy for you.

If you've actually done the tutorial, or merely read the PDF or watched the video, I believe you can see creating a sine wave with the library is very easy. Even creating many of them simultaneously is pretty simple, just drag several onto the canvas in the design tool, combine them through mixers and connect to your audio output, and fill in some pretty simple code (copy & paste from the oscillators tutorial) to configure for the waveforms you wanted. Hopefully you can see how easily you could create all sorts of other audio projects, just by designing different systems and filling in small amounts of Arduino code to control them.

However, many people want to do the hard stuff themselves, rather than use the libraries. Sometimes they want features (like higher sample rates) which are not offered by the easy-to-use libraries. Sometimes they just want to learn how to do all the steps themselves, rather than use a library. Sometimes we even get people who mistakenly believe doing things from scratch would be easier. It most certainly is not! If you step off the well worn path of using libraries, you do indeed need to learn deeply about microcontrollers. It does take substantial programming skill, and it's usually pretty hard for adults, not to mention children.

This is true of Teensy and Arduino and Raspberry Pi and all others too. All these platforms are only easy when you make use of the libraries and software features designed to help you easily use them. When you want to do stuff that's outside the scope of what the libraries provide, or if you just want to do all the hard stuff yourself rather than use libraries and easy functions, there is indeed a world of very complex technical details.
 
Last edited:
This defines the waveform using the maximal amplitude and resolution of the DAC.
The frequency is controlled by your setting of PDB_PERIOD which has to be calculated depending on your desired output frequency, your selected cpu clock frequency (which will set the bus clock behind the stages). For example, if you need a frequency of 2kHz and the LUT has 128 entries, that means that you need 128 x 1000 = 128000 triggers per second. Running a Teensy 3.2 at 96MHz makes its internal bus clock to 48MHz. Divide these 48MHz by 128000 and you get 375 as the divider. To set the PDB_PERIOD, you have to subtract 1 because the PDB includes also a full cycle for a 0 value. Thus, #define PDB_PERIOD (375-1) is the solution for that CPU speed.
Code:
#if F_BUS == 60000000
  #define PDB_PERIOD (468-1)
#elif F_BUS == 56000000
  #define PDB_PERIOD (437-1)
#elif F_BUS == 48000000
  #define PDB_PERIOD (375-1)
#elif F_BUS == 36000000
  #define PDB_PERIOD (281-1)
#elif F_BUS == 24000000
  #define PDB_PERIOD (187-1)
#elif F_BUS == 16000000
  #define PDB_PERIOD (125-1)

thank you very much for replying
your answer is very useful to me!
its very interesting to know that!

but
I just would like to know if the values of PDB_PERIOD are changeable so I can change them as I want ?

for example if I want to get a 10KHz sine wave:
I need 1280000 triggers per second
48000000/1280000 = 37.5
PDB _PERIOD should be (37.5-1)
right?


another question:
how can I set the CPU frequency? or at least how to know what is it, since there are many choices?
 
The normal way to do things involves using already-made libraries. In this case, the audio library provides an easy way to create sine waves and lots of other waveform stuff. There's a detailed tutorial available.

https://www.pjrc.com/store/audio_tutorial_kit.html

So if you want simple and easy, you could buy that $60 kit, or buy some of the parts and build the rest yourself. Then print the 31 page PDF and go through the tutorial. Or you could skim the PDF while watching the 45 minute video where Alysia and I demonstrate the whole thing. But of course actually doing the tutorial yourself is far better for learning than just watching a video. It takes most people 3 to 4 hours. It took us 17 hours of filming and 2 solid days of video editing to make that youtube video! All that work and thousands of hours developing the library code was all done to try to make this easy for you.

If you've actually done the tutorial, or merely read the PDF or watched the video, I believe you can see creating a sine wave with the library is very easy. Even creating many of them simultaneously is pretty simple, just drag several onto the canvas in the design tool, combine them through mixers and connect to your audio output, and fill in some pretty simple code (copy & paste from the oscillators tutorial) to configure for the waveforms you wanted. Hopefully you can see how easily you could create all sorts of other audio projects, just by designing different systems and filling in small amounts of Arduino code to control them.

thank you for replying
teensy look very interesting to learn
I am obsessed with electronics and I wold like to learn everything about them!
 
I'm new to the Teensy's DMA features, so this is really helpful! Thanks :)

I was wondering, what if I wanted to change the DAC's output while the script was running, for example change the frequency of the output sine wave, or output a different waveform entirely? I know I'd have to have a separate lookup table ready, but how could I change the DMA setting?
 
To change just the frequency, you could adapt the PDB mod value. To switch over to a different wave table, you’d just create a second DMA transfer descriptor (tcd) and switch over.
 
Oh cool, switching wave tables is really simple! I'm a bit confused how to change the PCB mod value on the fly though. For example I tried using

PCB_PERIOD = 36;

while in the main loop, but found that it's not a valid expression.
 
PCB_PERIOD is a pre-compile #define which is used to preload the PBB mod register and which can naturally not be modified at runtime. You’ll have to calculate a new mod value and to write it as a uint16_t to the PDB0_MOD register. See the processor’s reference manual, PDB section, for details.
 
Hello!
I have a question. I would like to use your code for generating a sinewave for a resolver excitation. Is there any way that I could implement a interrupt that would fire when for example the DAC reads the 32 entry (or any other entry by my choice) in the LUT and execute an ISR?

Thank you!
 
Back
Top