STM32 F4 DAC DMA Waveform Generator
Goal: generating an arbitrary periodic waveform using a DAC with DMA and TIM6 as a trigger.
Agenda:
- Modeling a waveform in MATLAB and getting the waveform data
- Studying the DAC, DMA, and TIM6 to see how it can be used to generate a waveform
- Coding and testing a couple of functions
%% Generating an n-bit sine wave
% Modifiable parameters: step, bits, offset
close; clear; clc; points = ; % number of points between sin() to sin(*pi)
bits = ; % -bit sine wave for -bit DAC
offset = ; % limiting DAC output voltage t = :((*pi/(points-))):(*pi); % creating a vector from to *pi
y = sin(t); % getting the sine values
y = y + ; % getting rid of negative values (shifting up by )
y = y*((^bits-)-*offset)/+offset; % limiting the range (+offset) to (^bits-offset)
y = round(y); % rounding the values
plot(t, y); grid % plotting for visual confirmation fprintf('%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, \n', y);
There's a trade-off between the sine wave resolution (number of points from sin(0) to sin(2*pi)), output frequency range, and precision of the output frequency (e.g. we want a 20kHz wave, but we can only get 19.8kHz or 20.2kHz because the step is 0.4kHz). The output frequency is a non-linear function with multiple variables. To complicate it further, some of these variables must be integers within 1 to 65535 range which makes it impossible to output certain frequencies precisely.
Although precise frequency control is terribly hard (if not impossible), one feature does stand out - ability to generate a periodic waveform of any shape.
Below is the code for mediocre range/precision/resolution but excellent versatility in terms of shaping the output waveform.
@input - uint16_t function[waveform_resolution]
@output - PA4 in analog configuration
@parameters - OUT_FREQ, SIN_RES
To tailor other parameters, study the DAC channel block diagram, electrical characteristics, timing diagrams, etc. To switch DAC channels, see memory map, specifically DAC DHRx register for DMA writes.
#include <stm32f4xx.h>
#include "other_stuff.h" #define OUT_FREQ 5000 // Output waveform frequency
#define SINE_RES 128 // Waveform resolution
#define DAC_DHR12R1_ADDR 0x40007408 // DMA writes into this reg on every request
#define CNT_FREQ 42000000 // TIM6 counter clock (prescaled APB1)
#define TIM_PERIOD ((CNT_FREQ)/((SINE_RES)*(OUT_FREQ))) // Autoreload reg value const uint16_t function[SINE_RES] = { , , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , , , ,
, , , , , , , }; static void TIM6_Config(void);
static void DAC1_Config(void); int main()
{
GPIO_InitTypeDef gpio_A; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); gpio_A.GPIO_Pin = GPIO_Pin_4;
gpio_A.GPIO_Mode = GPIO_Mode_AN;
gpio_A.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &gpio_A); TIM6_Config();
DAC1_Config(); while ()
{ } } static void TIM6_Config(void)
{
TIM_TimeBaseInitTypeDef TIM6_TimeBase; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseStructInit(&TIM6_TimeBase);
TIM6_TimeBase.TIM_Period = (uint16_t)TIM_PERIOD;
TIM6_TimeBase.TIM_Prescaler = ;
TIM6_TimeBase.TIM_ClockDivision = ;
TIM6_TimeBase.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM6, &TIM6_TimeBase);
TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update); TIM_Cmd(TIM6, ENABLE);
} static void DAC1_Config(void)
{
DAC_InitTypeDef DAC_INIT;
DMA_InitTypeDef DMA_INIT; DAC_INIT.DAC_Trigger = DAC_Trigger_T6_TRGO;
DAC_INIT.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_INIT.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_Init(DAC_Channel_1, &DAC_INIT); DMA_DeInit(DMA1_Stream5);
DMA_INIT.DMA_Channel = DMA_Channel_7;
DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t)DAC_DHR12R1_ADDR;
DMA_INIT.DMA_Memory0BaseAddr = (uint32_t)&function;
DMA_INIT.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_INIT.DMA_BufferSize = SINE_RES;
DMA_INIT.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_INIT.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_INIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_INIT.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_INIT.DMA_Mode = DMA_Mode_Circular;
DMA_INIT.DMA_Priority = DMA_Priority_High;
DMA_INIT.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_INIT.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_INIT.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_INIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA1_Stream5, &DMA_INIT); DMA_Cmd(DMA1_Stream5, ENABLE);
DAC_Cmd(DAC_Channel_1, ENABLE);
DAC_DMACmd(DAC_Channel_1, ENABLE);
}
Using the code above we are supposed to get a 5kHz sine wave constructed with 128 points (for better quality, consider using more points).
Here's a picture of what we actually get (off by 25Hz, not too bad).
And here's the cool sinc(x) function. To generate other functions, model it in MATLAB, cast to 12-bit, STM32F4 does the rest.