STM32F407 Timer+DAC+DMA输出任意波形(正弦波)
前言
我们知道STM32F407是有两个DAC的,也就是可以同时输出两个不同的信号。本文将介绍通过定时器触发DAC输出实现频率可控,同时利用DMA实现波形可控,充分利用外设,减轻CPU负担。
这里我们准备利用TIM4的update触发PA4上的DAC_CH1和DMA1_CH7_Stream5搬运DA
值输出正弦波。
DAC配置
DAC的配置需要着重注意触发方式的配置,也就是DAC_InitStructure.DAC_Trigger
,我们将它配置成DAC_Trigger_T4_TRGO
。
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体
DAC_InitTypeDef DAC_InitStructure; //DAC结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); //使能DAC时钟
//GPIO配置初始化 PA4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //PIN4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟模式
// GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //GPIO结构体初始化
//DAC配置初始化
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T4_TRGO; //定时器触发转换
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_Init(DAC_Channel_1, &DAC_InitStructure); //DAC1初始化
//DAC设置数据对齐
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //右对齐
DAC_Cmd(DAC_Channel_1,ENABLE); //DAC1 使能
Timer配置
定时器着重注意TIM_SelectOutputTrigger()
语句,我们将其配置为TIM_TRGOSource_Update
,意为当定时器更新时向外触发。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //TIM4 时钟使能
TIM_InternalClockConfig(TIM4);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //时基结构体
TIM_TimeBaseInitStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; //设置时钟分割
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_SelectOutputTrigger(TIM4,TIM_TRGOSource_Update); //更新溢出向外触发
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure); //时基初始化
TIM_Cmd(TIM4, ENABLE); //定时器使能
DMA配置
我们利用一个数组存储DA值,利用DMA将其搬运至DA输出的寄存器上。这里我存的一个周期24个点的正弦波。
#define DA1_Value_Length 24
const uint16_t DA1_Value[DA1_Value_Length] = {
0 ,
65 ,
262 ,
563 ,
953 ,
1410 ,
1890 ,
2379 ,
2824 ,
3209 ,
3506 ,
3687 ,
3747 ,
3678 ,
3484 ,
3183 ,
2791 ,
2340 ,
1855 ,
1369 ,
920 ,
534 ,
240 ,
57
};
DMA通过查询手册可知,我们需要配置DMA1的channel7和stream5。
DMA_InitTypeDef DMA_InitStructure; //DMA结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //使能DMA1时钟
//DMA配置初始化
DMA_InitStructure.DMA_Channel = DMA_Channel_7; //DMA_CH7
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //内存到外设
DMA_InitStructure.DMA_BufferSize = DA1_Value_Length; //传输次数:数组长度
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
//DMA_FIFO
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DA1_Value; //内存地址:输出DA值数组
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //16 bit
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存自增
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DAC->DHR12R1); //外设地址:DAC1地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //16 bit
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设不自增
DMA_Init(DMA1_Stream5,&DMA_InitStructure); //DMA1_Stream5 初始化
DMA_Cmd(DMA1_Stream5,ENABLE); //DMA1_Stream5 使能
DMA从内存到外设。内存地址DMA_InitStructure.DMA_Memory0BaseAddr
配置成我们设定的数组,写上我们数组的地址。外设地址DMA_InitStructure.DMA_PeripheralBaseAddr
为DA输出的寄存器地址也就是(uint32_t)&(DAC->DHR12R1)
。很明显,我们需要遍历数组的值搬运至DA输出的寄存器来输出波形,所以内存需要自增而外设不自增。12位精度的DA值,所以长度配置为16位HalfWord
。
可能会有人好奇为什么地址是(uint32_t)&(DAC->DHR12R1)
,其实我们可以看一下这个函数:
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data);
我们知道这个函数就是来设置输出的值的。点开看看,可以发现是在对数据的对齐进行一些处理操作,里面就出现了有关DHR12R1
的字眼。
或者更直接一点看手册,写得很明白。
记得打通DAC和DMA的通道:
DAC_DMACmd(DAC_Channel_1, ENABLE); //DAC1_DMA 使能
完整代码
这里奉上完整的代码,并且同时配置了两个通道,另外一个通道的配置方法相同。
DAC+DMA:
#include "DACDMA.h"
//!!!需TIM触发转换,请额外配置TIM
//此处默认DAC1:TIM4;DAC2:TIM5;
//输出频率 f = 84M/(TIM_Period * TIM_Prescaler * DA_Value_Length)
//输出的DA值数组
//PA4
const uint16_t DA1_Value[DA1_Value_Length] = {
0 ,
65 ,
262 ,
563 ,
953 ,
1410 ,
1890 ,
2379 ,
2824 ,
3209 ,
3506 ,
3687 ,
3747 ,
3678 ,
3484 ,
3183 ,
2791 ,
2340 ,
1855 ,
1369 ,
920 ,
534 ,
240 ,
57
};
//PA5
const uint16_t DA2_Value[DA2_Value_Length] = {
0 ,
65 ,
262 ,
563 ,
953 ,
1410 ,
1890 ,
2379 ,
2824 ,
3209 ,
3506 ,
3687 ,
3747 ,
3678 ,
3484 ,
3183 ,
2791 ,
2340 ,
1855 ,
1369 ,
920 ,
534 ,
240 ,
57
};
//DAC1; PA4; DMA_CH7; DMA1_Stream5; TIM4;
void DA1_Init(){
//结构体定义
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体
DAC_InitTypeDef DAC_InitStructure; //DAC结构体
DMA_InitTypeDef DMA_InitStructure; //DMA结构体
//时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); //使能DAC时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //使能DMA1时钟
//GPIO配置初始化 PA4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //PIN4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟模式
// GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //GPIO结构体初始化
//DAC配置初始化
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T4_TRGO; //!!!定时器触发转换 一次转移数组里的一个数据 TIM4
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_Init(DAC_Channel_1, &DAC_InitStructure); //DAC1初始化
//DAC设置数据对齐
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //右对齐
//DMA配置初始化
DMA_InitStructure.DMA_Channel = DMA_Channel_7; //DMA_CH7
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //内存到外设
DMA_InitStructure.DMA_BufferSize = DA1_Value_Length; //传输次数:数组长度
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
//DMA_FIFO
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DA1_Value; //内存地址:输出DA值数组
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //16 bit
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存自增
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DAC->DHR12R1); //外设地址:DAC1地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //16 bit
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设不自增
DMA_Init(DMA1_Stream5,&DMA_InitStructure); //DMA1_Stream5 初始化
DMA_Cmd(DMA1_Stream5,ENABLE); //DMA1_Stream5 使能
DAC_DMACmd(DAC_Channel_1, ENABLE); //DAC1_DMA 使能
DAC_Cmd(DAC_Channel_1,ENABLE); //DAC1 使能
}
//DAC1; PA5; DMA_CH7; DMA1_Stream6; TIM5;
void DA2_Init(){
//结构体定义
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体
DAC_InitTypeDef DAC_InitStructure; //DAC结构体
DMA_InitTypeDef DMA_InitStructure; //DMA结构体
//时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); //使能DAC时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //使能DMA1时钟
//GPIO配置初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PIN5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟模式
// GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //GPIO结构体初始化
//DAC配置初始化
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T5_TRGO; //!!!定时器触发转换 一次转移数组里的一个数据 TIM5
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_Init(DAC_Channel_2, &DAC_InitStructure); //DAC2初始化
//DAC设置数据对齐
DAC_SetChannel2Data(DAC_Align_12b_R, 0); //右对齐
//DMA配置初始化
DMA_InitStructure.DMA_Channel = DMA_Channel_7; //DMA_CH7
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //内存到外设
DMA_InitStructure.DMA_BufferSize = DA2_Value_Length; //传输次数:数组长度
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
//DMA_FIFO
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DA2_Value; //内存地址:输出DA值数组
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //16 bit
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存自增
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(DAC->DHR12R2); //外设地址:DAC2地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //16 bit
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设不自增
DMA_Init(DMA1_Stream6,&DMA_InitStructure); //DMA1_Stream6 初始化
DMA_Cmd(DMA1_Stream6,ENABLE); //DMA1_Stream6 使能
DAC_DMACmd(DAC_Channel_2, ENABLE); //DAC2_DMA 使能
DAC_Cmd(DAC_Channel_2,ENABLE); //DAC2 使能
}
//获取输出数据
uint16_t DA1_GetValue(){
return DAC_GetDataOutputValue(DAC_Channel_1);
}
uint16_t DA2_GetValue(){
return DAC_GetDataOutputValue(DAC_Channel_2);
}
#ifndef __DACDMA_H__
#define __DACDMA_H__
#include "stm32f4xx.h" // Device header
//!!!需TIM触发转换,请额外配置TIM
#define DA1_Value_Length 24
#define DA2_Value_Length 24
void DA1_Init();
void DA2_Init();
uint16_t DA1_GetValue();
uint16_t DA2_GetValue();
#endif
Timer:
#include "TimerBase.h"
//主频84M
//TIM4 16bit
void TIM4_Init(uint16_t arr, uint16_t psc){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //TIM4 时钟使能
TIM_InternalClockConfig(TIM4);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //时基结构体
TIM_TimeBaseInitStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; //设置时钟分割
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_SelectOutputTrigger(TIM4,TIM_TRGOSource_Update); //更新溢出向外触发
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure); //时基初始化
TIM_Cmd(TIM4, ENABLE); //定时器使能
}
//TIM5 32bit
void TIM5_Init(uint16_t arr, uint16_t psc){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); //TIM5 时钟使能
TIM_InternalClockConfig(TIM5);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //时基结构体
TIM_TimeBaseInitStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; //设置时钟分割
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_SelectOutputTrigger(TIM5,TIM_TRGOSource_Update); //更新溢出向外触发
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseInitStructure); //时基初始化
TIM_Cmd(TIM5, ENABLE); //定时器使能
}
#ifndef __TIMERBASE_H__
#define __TIMERBASE_H__
#include "stm32f4xx.h" // Device header
void TIM4_Init(uint16_t arr, uint16_t psc);
void TIM5_Init(uint16_t arr, uint16_t psc);
#endif
main:
#include "stm32f4xx.h" // Device header
#include "delay.h"
#include "usart.h"
#include "DACDMA.h"
#include "TimerBase.h"
int main(){
delay_init(168);
uart_init(9600);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
printf("Start !\r\n");
TIM4_Init(700-1, 1-1);//5KHz
DA1_Init();//PA4
TIM5_Init(350-1, 1-1);//10KHz
DA2_Init();//PA5
while(1){
}
}
频率计算
实际上,每次定时器的更新只会触发一个DA值的输出,所以输出频率会受到DA数组长度的影响。公式为:
输出频率 f = 84M/(TIM_Period * TIM_Prescaler * DA_Value_Length)
波形控制
根据原理,很容易知道可以通过改变数组DA1_Value[]
里面的值来达到控制波形的目的。
注意输出的电压与DA值有这样的关系:
Voltage = DA_Value*3.3V/4096
实践
来看一下输出的结果:
当:
TIM_Period = 700 - 1
TIM_Prescaler = 1-1
DA_Value_Length = 24
根据公式可知输出频率为 f=5KHz
当:
TIM_Period = 350 - 1
TIM_Prescaler = 1-1
DA_Value_Length = 24
根据公式可知输出频率为 f=10KHz
正弦波DA值制表
至于生成DA值数组的方法,应该有很多,这里可以浅浅介绍两种。
ADC采集法
直接利用ADC进行采集。方法可以参考一下这个文章:
Timer+ADC+DMA采集
函数生成法
利用函数生成。这里是生成的0到4095的正弦函数。
#include <stdio.h>
#include <math.h>
#define PI 3.14
#define Sine_Array_length 24 //数组长度,即生成的点数
unsigned int Sine_Array[Sine_Array_length] ={0};
void SineWave_Data(unsigned int cycle ,unsigned int*D,float Um)//周期:生成的点数;指针:数组的地址;幅度
{
unsigned int i;
for( i=0;i<cycle;i++)
{
D[i]=(unsigned int)((Um*sin(( 1.0*i/(cycle-1))*2*PI)+Um));
}
}
unsigned int j;
void main(){
SineWave_Data(Sine_Array_length, Sine_Array, 2048);
for ( j = 0; j < Sine_Array_length; j++)
{
printf("%d,", Sine_Array[j]);
}
}
结语
在配置过程中,要仔细查看手册,注意其各自对应关系。Timer和DAC注意触发的配置,DMA注意外设地址和内存地址的配置以及是否自增。同时还要注意输出正弦波频率的计算。最后最后,注意使能!!
ps.
以上内容均为本人个人理解和实践做出的总结。文笔较差,还望包涵 。如有错误或表达不当之处,欢迎指正! 感谢。