📚 CMPE2250: Lesson — Digital to Analog Converter (DAC) on STM32G0
🎯 Learning Objectives
By the end of this lesson, students will be able to:
1️⃣ Introduction to the DAC
While the ADC (Analog-to-Digital Converter) allows our microcontroller to “read” the physical world, the DAC (Digital-to-Analog Converter) allows it to “speak” to it. We use the DAC to generate varying voltage levels, which can be used for audio generation, motor control references, or building a custom function generator.
-
Resolution: Our STM32 features a 12-bit DAC. This means we can output 212=4096 distinct voltage levels.
-
Voltage Range: The output voltage scales linearly between 0V and our analog reference voltage (VREF+), which is typically 3.3V.
2️⃣ The Problem with Software Triggers
The simplest way to use a DAC is to write a value directly to its data register inside a while(1) loop.
-
The Issue: The timing is entirely dependent on how fast the CPU executes instructions. Any interrupts (like SysTick) will pause the loop, causing the output waveform to stutter and jitter.
-
The Solution: Hardware Triggers. We can connect a hardware timer (like TIM6) directly to the DAC. The DAC will wait for a Trigger Output (TRGO) signal from the timer before updating its analog output. This guarantees microsecond-perfect timing without CPU jitter.
3️⃣ Taking it Further: DAC with DMA
-
In Chapter 7, we explored how to use Direct Memory Access (DMA) to send strings over USART without tying up the CPU. We can apply that exact same concept to the DAC to generate complex, high-frequency waveforms.
-
Instead of sending ASCII characters to a serial port, we use the DMA to send an array of 12-bit voltage values (like a sine wave lookup table) directly to the DAC’s data register.
-
Circular Mode: This is the key difference from our UART implementation. By enabling DMA Circular Mode, the DMA controller will automatically wrap around to the beginning of the array once it reaches the end.
-
Zero CPU Overhead: Once configured, the Timer triggers the DAC, and the DAC triggers the DMA to fetch the next value. A continuous, perfect sine wave is generated endlessly while the CPU is 100% free to execute other tasks.
4️⃣ Step-by-Step: Configuring the STM32G0 DAC
To get the DAC up and running, we need to follow a specific sequence of initialization steps. We must route power to the peripheral, prepare the physical pin, configure the DAC’s internal behavior, and finally, enable the output.
Step 1: Enable the Peripheral Clocks
-
Before we can write to any DAC or GPIO registers, we must enable their clocks in the Reset and Clock Control (RCC) register.
-
Enable the clock for the GPIO port you are using (e.g., GPIOAEN).
-
Enable the clock for the DAC peripheral (e.g., DAC1EN in APBENR1).
Step 2: Configure the GPIO Pin for Analog Mode
-
The DAC output is hardwired to specific pins (for DAC1_CH1, this is PA4).
-
Set the corresponding bits in the GPIO MODER register to Analog Mode (11).
-
Note for students: While the DAC will technically force its output to the pin even if left in digital mode, setting it to Analog Mode is required to disable the pin’s digital input buffer (the Schmitt trigger). Leaving the digital buffer on while outputting an analog sine wave causes rapid, erratic digital switching, which burns excess power and injects noise into your clean analog signal!
Step 3: Configure the DAC Output Buffer
-
he STM32 DAC includes an internal operational amplifier acting as an output buffer.
-
Modify the DAC Mode Control Register (MCR).
-
Ensure the buffer is enabled (this is usually the default state, MODE1 = 000) so the DAC can drive external loads without the voltage dropping.
Step 4: Select the Trigger Source (Hardware vs. Software)
-
How will the DAC know when to update its output voltage?
-
For Software Control (ICA #7 Part 1): Disable the trigger enable bit (TEN1 = 0). The DAC will immediately output whatever value your code writes into its data register.
-
For Hardware Control (ICA #7 Part 2): Enable the trigger bit (TEN1 = 1). Then, set the Trigger Selection bits (TSEL1) to listen to your specific timer (e.g., 0101 for TIM6 TRGO). The DAC will now wait for the timer’s signal before updating.
Step 5: Enable the DAC
- Set the Enable bit (EN1 = 1) in the DAC Control Register (CR). The DAC is now live and holding control of the physical pin.
Step 6: Write the Digital Value
-
Write a 12-bit value (0 to 4095) into the DAC Data Holding Register (DHR12R1).
-
If using a software trigger, the voltage changes immediately. If using a hardware trigger, the value sits in the holding register until the Timer fires.
5️⃣ Step-by-Step: Automating the DAC with DMA
Once the DAC is configured to accept a hardware trigger, we can use Direct Memory Access (DMA) to automatically feed it data. This requires configuring a Timer to act as a pacemaker, routing the DAC’s request through the DMAMUX, and setting up the DMA channel to handle the transfer.
Step 1: Enable the DMA and DMAMUX Clocks
Just like the DAC and GPIO, the DMA controller and the DMAMUX are asleep by default.
- Enable the clock for the DMA controller (e.g., DMA1EN in AHBENR). On this architecture, enabling the DMA clock automatically enables the DMAMUX clock.
Step 2: Configure the Timer (The Pacemaker)
-
The DMA needs a signal to know when to move the next piece of data. We will use a timer (like TIM6) to generate this signal.
-
Set the timer’s Prescaler (PSC) and Auto-Reload Register (ARR) to achieve your target sample rate.
-
Configure the Master Mode Selection (MMS) bits in the timer’s control register to output its Update Event as a Trigger Output (TRGO).
Step 3: Route the Signal (The DMAMUX)
-
Unlike older architectures where peripherals were hardwired to specific DMA channels, the DMAMUX acts as a digital switchboard. We must manually connect the DAC to an available DMA channel.
-
Look up the DMAMUX Request ID for the DAC. For example, DAC1_CH1 is Request ID 8.
-
Write this ID into the DMAMUX Channel Configuration Register (e.g., DMAMUX1_Channel0->CCR = 8;) to link the DAC to DMA1 Channel 1.
Step 4: Configure the DMA Channel
-
Now we tell the DMA controller exactly how to move the data. Before configuring, ensure the DMA channel is disabled (EN = 0).
-
Direction: Set the transfer direction to read from Memory and write to the Peripheral.
-
Data Size: The DAC holding register expects 12-bit data, so configure both the memory and peripheral data sizes to 16-bit (Half-word).
-
Pointers: Set the Memory Address (CMAR) to the start of your waveform array (e.g., sine_lookup) and the Peripheral Address (CPAR) to the DAC Data Holding Register (&(DAC1->DHR12R1)).
-
Incrementing: Tell the DMA to automatically increment the memory pointer after each transfer (MINC = 1) so it steps through the array. Do not increment the peripheral pointer (PINC = 0), as we always want to write to the exact same DAC register.
-
Length: Put the total number of samples in your array into the Number of Data Register (CNDTR).
-
Circular Mode: Enable Circular Mode (CIRC = 1). This is the magic bullet! When the DMA transfers the last sample in the array, it will automatically reset its memory pointer back to the beginning and start over, generating a continuous, endless waveform.
Step 5: Link the DAC to the DMA
-
The DMA is ready, but the DAC still needs permission to talk to it.
-
Set the DMA Enable bit (DMAEN1 = 1) in the DAC Control Register (CR).
Step 6: Start the Engine
-
With the plumbing fully connected, you can start the system.
-
Enable the DMA Channel.
-
Enable the DAC.
-
Enable the Timer. The hardware will now generate your waveform completely independent of the CPU!