# Lesson 011: DAC

In today’s lesson we will be covering the DAC module. Like the ADC module, the speed and resolution of the DAC module is just OK. I was able to generate a 24.6kHz sine wave with a 32 point sample without resorting to more hardware direct calls (IE: skipping the SSP calls).

A 32 point sample size gives a 25.7mV resolution (3.3V * 32/4096). This equates to a 5-bit resolution at this frequency. By decreasing the frequency you can get better resolution. At 4096 sample points, I get about 193Hz.

So, if you need a high resolution DAC that works at moderate frequencies, then you either need to look elsewhere or do some serious code optimization and reduce the SSP overhead.

Now on to the code!

Create a new Synergy project and add both a DAC driver on r_dac and Timer Driver on r_gpt.

Configure the Timer Driver with a name g_timer, Channel 0, set the GPT0 COUNTER OVERFLOW ICU to Priority 15, and add a callback function with the name g_timer_callback.

Configure the DAC Driver with a name of g_dac and Channel 0.

We are going to use the sine function from the math library and use a series of defines for our frequency and lookup table.

Add the following code just below the first #include:

```#include

#define PI 3.14159265358979323846

#define FREQUENCY 12000
#define SAMPLE_POINTS 32
#define DT ((1.0) / (FREQUENCY * SAMPLE_POINTS))
#define DT_NS ((DT)*(1E9))

dac_size_t dataPoints[SAMPLE_POINTS];

uint16_t dataIndex;```

Set the FREQUENCY and SAMPLE_POINTS to something reasonable. The code above will create a 12kHz sine wave with 32 sample points.

The DT define is the time delta between two adjacent points and DT_NS is the DT time in nano seconds.

Next we create a variable for our lookup table as well as a global variable that holds the current index inside our table.

All the magic in the code happens inside the Timer Driver interrupt. Here, we will set our DAC output and increment our data index.

```void g_timer_callback(timer_callback_args_t * p_args)
{
SSP_PARAMETER_NOT_USED(p_args);

g_dac.p_api->write (g_dac.p_ctrl, dataPoints[dataIndex]);

// Increment or reset data index
dataIndex = (dac_size_t)(dataIndex + 1 >= SAMPLE_POINTS ? 0 : dataIndex + 1);
}```

In our hal_entry function, we will fill our lookup table with data based on the frequency and sample points, then open our DAC and Timer. Next, we will configure the timer interrupt to trigger based on our frequency.

```void hal_entry(void)
{
double t = 0;
timer_size_t dt_ns = (timer_size_t)(DT_NS < 1 ? 1 : DT_NS);

// Pre Generate Data Points and use as a lookup table
for (uint16_t i = 0; i < SAMPLE_POINTS; i++) { dataPoints[i] = (dac_size_t) (((sin (2 * PI * FREQUENCY * t) + 1) / 2.0) * 4095); t += (dt_ns/1E9); } // Open DAC g_dac.p_api->open (g_dac.p_ctrl, g_dac.p_cfg);
g_dac.p_api->start (g_dac.p_ctrl);

// Open Timer
g_timer.p_api->open (g_timer.p_ctrl, g_timer.p_cfg);

// Set Period
g_timer.p_api->periodSet(g_timer.p_ctrl, dt_ns, TIMER_UNIT_PERIOD_NSEC);

// Start the timer
g_timer.p_api->start(g_timer.p_ctrl);

while (true)
{
// Do nothing here. Let the timer interrupts do the work.
}
}```

A few notes…

First, there is quite a bit of room to save some instruction overhead. Inside the timer callback function, we use the SSP_PARAMETER_NOT_USED statement just to avoid a compiler warning.

Next, we do some overflow checking on our dataIndex and either increment or reset it’s value. Instead, you can use the overflow ‘feature’ of C by using a datatype that can hold the number of samples (IE: if using 4096 samples, a uint16_t would work and hold 65536 samples). In order to do this, the max value for the data type must be divisible by our sample size, with no remainder. So, for 32 samples, 65536/32 = 2048.0, so this would work. If we used 33 samples, 65536/33 = 1985.939, so this wouldn’t work (actually it would, but our output wave form would ‘glitch’ in value. Try it out and see for yourself).

Using the overflow, we basically fill out our data table repeating data until we use up all the numbers in our sample size. Continuing with the above example, we would copy the 32 sample points 2048 times to fill up our 65536 values. Then, instead of checking for overflow, we could just continuously increment our dataIndex and it will overflow back to 0 and continue on our merry way.

The second way to remove overhead is to dig down inside the dac->write function and find the actual hardware call necessary. This ends up being:

`((R_DAC_Type *) 0x4005E000UL)->DADRn[channel] = value;`

Since we are using channel 0, the code we would replace

`g_dac.p_api->write (g_dac.p_ctrl, dataPoints[dataIndex]);`

with

`((R_DAC_Type *) 0x4005E000UL)->DADRn[0] = value;`

Using just the second solution, at 4096 points, our frequency goes from 193Hz to 311.6Hz, a 61.4% improvement!

At 32 points, the frequency goes from 24.6kHz to 39.5kHz, a 60.6% increase.

And the third way is to use the compiler optimization flag. The above numbers were at Optimize None (so the compiler would not optimize my variables out and I could debug). With Optimize Most as the option, at 32 points, I got a frequency of 84.45kHz with the second optimization in place (replacing the Dac->write function).

I’ll leave it to you to implement the first optimization.

Here’s a screenshot of the oscilloscope at a 12kHz frequency and 32 sample points:

As always, code is posted up on GitHub.

## 3 thoughts on “Lesson 011: DAC”

1. Hi Eric, Any I2S codec tutorial. Many thanks.

Like

2. This is great stuff to see. Nice step by step procedure. It seems to be intentionally generic, but I’m wondering what hardware you tested it with?

Like

1. Eric says: