13.1 介绍一下
数字量转换成模拟量的过程叫做数模转换,完成这种功能的电路叫做数模转换器。说白了就是将离散的数字信号转换为连续变量的模拟信号的一种器件。
这玩意主要由数字寄存器、模拟电子开关、位权网络、求和运算放大器和基准电压源(或恒流源)等组成的。主要用存于数字寄存器的数字量的各位数码,分别控制对应位的模拟电子开关,使数码为1的位在位权网络上产生与其位权成正比的电流值,再由运算放大器对各电流值求和,并转换成电压值 。
换成图也就是下面这个小东西:
13.2 STM32f429 DAC结构
STM32F429内部含有两个12 位电压输出数模转换器。DAC 可以按 8 位或 12 位模式进行配置,而且也是可以和DMA配合使用的,在12位模式中,数据可以是左对齐也可以是右对齐。
DAC 有两个输出通道,每个通道各有一个转换器。
两个 DAC 转换器:各对应一个输出通道:
12 位模式下数据采用左对齐或右对齐
生成噪声波、生成三角波
DAC 双通道单独或同时转换
每个通道都具有 DMA 功能
通过外部触发信号进行转换
DAC 各个引脚功能定义:
13.3 STM32f429 DAC功能
1、DAC 转换
两个DAC分别对应一个独立的通道,当使用时需要将 DAC_CR 寄存器中的相应 ENx 位置 1,即可使能对应 DAC 通道。
在使能DAC通道之后,可以通过写DAC_DHRx 寄存器或通过触发信号来启动一次DAC转换。
(1)写DAC_DHRx 寄存器启动DAC转换
如果未选择硬件触发(DAC CR中的TENx位复位),那么经过一个APB1时钟周期后,DAC_DHRx中存储的数据将自动转移到DAC_DORx。当DAC_DORx加载了DAC_DHRx内容时,模拟输出电压将在一段时间t_SETTING后可用,具体时间取决于电源电压和模拟输出负载。
DAC_DORx 无法直接写入,任何数据都必须通过加载DAC_DHRx 寄存器才能传输到 DAC 通道x。
(2)硬件触发启动DAC转换
如果选择硬件触发(置位 DAC_CR 寄存器中的 TENx 位)可通过外部事件(定时计数器、外部中断线)触发转换。
当触发条件到来,将在三个APB1时钟周期后,将DAC_DHRx 寄存器的内容转移到DAC_DORx寄存器。
DAC外部触发源:
2、DAC 数据格式
1)对于 DAC 单通道 x,有三种可能的方式:
8位右对齐:软件必须将数据加载到 DAC_DHR8Rx [7:0] 位(存储到DHRx[11:4] 位)。
12位左对齐:软件必须将数据加载到 DAC_DHR12Lx [15:4] 位(存储到DHRx[11:0] 位)。
12 位右对齐:软件必须将数据加载到 DAC_DHR12Rx [11:0] 位(存储到DHRx[11:0] 位)。
DAC单通道数据对齐方式(下图):
2)对于 DAC双通道 x,有三种可能的方式:
8 位右对齐:将 DAC 1 通道的数据加载到 DAC_DHR8RD [7:0] 位(存储到DHR1[11:4] 位),将 DAC 2 通道的数据加载到 DAC_DHR8RD [15:8] 位(存储到 DHR2[11:4] 位)
12 位左对齐:将 DAC 1 通道的数据加载到 DAC_DHR12RD [15:4] 位(存储到 DHR1[11:0] 位),将 DAC 2 通道的数据加载到 DAC_DHR12RD [31:20] 位(存储到 DHR2[11:0] 位)
12 位右对齐:将 DAC 1 通道的数据加载到 DAC_DHR12RD [11:0] 位(存储到 DHR1[11:0] 位),将 DAC 2 通道的数据加载到 DAC_DHR12RD [27:16] 位(存储到 DHR2[11:0] 位)
3、DMA 请求
每个 DAC 通道都具有 DMA 功能。两个 DMA 通道用于处理 DAC 通道的 DMA 请求。
4、生成噪声
将 WAVEx[1:0] 置为 “01”即可选择生成噪声,使用 LFSR(线性反馈移位寄存器)可以生成可变振幅的伪噪声。
LFSR中的预加载值为0xAAA。
5、生成三角波
将 WAVEx[1:0] 置为“10”即可选择 DAC 生成三角波。
6、DAC 双通道转换
DAC控制器有三个双寄存器:DHR8RD、DHR12RD 和 DHR12LD,可以访问一个寄存器同时驱动两个 DAC 通道,从而有效利用两个 DAC 通道总线带宽。通过两个DAC 通道和这三个双寄存器可以实现 11 种转换模式:
独立触发(不产生波形)
独立触发(生成单个 LFSR)
独立触发(生成不同 LFSR)
独立触发(生成单个三角波)
独立触发(生成不同三角波)
同步软件启动
同步触发(不产生波形)
同步触发(生成单个 LFSR)
同步触发(生成不同 LFSR)
同步触发(生成单个三角波)
同步触发(生成不同三角波)
13.4 DAC典型应用步骤
1、使能DAC时钟:
开启PA口时钟和ADC1时钟,设置PA1为模拟输入。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
使能模拟信号输入GPIO时钟,假设是GPIOA,根据实际情况调整:
RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE);
2、初始化模拟信号输入的GPIO引脚为模拟方式:
GPIO_Init();
3、初始化DAC通道参数。
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct) ;
4、使能DAC通道
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
5、启动DAC转换
(1)通过写DAC_DHRx 寄存器启动DAC转换:
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data) ;
或void DAC_SetChannel2Data(uint32_t DAC_Align, uint16_t Data) ;
或void DAC_SetDualChannelData(uint32_t DAC_Align, uint16_t Data2, uint16_t Data1) ;
或者通过触发信号启动DAC转换
13.5 常用库函数
DAC相关的函数和宏都被定义在两个文件中:
头文件:stm32f4xx_dac.h
源文件:stm32f4xx_dac.c
1、DAC初始化函数
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
参数1:uint32_t DAC_Channel,是DAC通道宏定义,定义在stm32f4xx_dac.h中:
#define DAC_Channel_1 ((uint32_t)0x00000000) //通道1
#define DAC_Channel_2 ((uint32_t)0x00000010) //通道2
参数2:DAC_InitTypeDef* DAC_InitStruct,是DAC初始化结构体指针,自定义的结构体定义在stm32f4xx_dac.h中:
typedef struct
{
uint32_t DAC_Trigger; //设置触发方式
uint32_t DAC_WaveGeneration; // 设置波形生成
uint32_t DAC_LFSRUnmask_TriangleAmplitude; //设置LFSR掩码值或三角波最大振幅
uint32_t DAC_OutputBuffer; //设置输出缓冲器
}DAC_InitTypeDef;
例如:
DAC_InitTypeDef DAC_InitStructure;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;//不使用触发方式,软件启动转换
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; //不使用波形发生器
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; //不使用DAC输出缓冲
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;//屏蔽幅值
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
2、DAC使能函数
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
参数1:uint32_t DAC_Channel,DAC通道宏定义。
参数2:FunctionalState
NewState,使能或禁止ADC,定义如下:
ENABLE:使能ADC;
DISABLE:禁止ADC。
3、单通道输出,写DAC通道1输出数据寄存器函数
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data) ;
参数1:uint32_t DAC_Align, DAC数据对齐方式,定义如下:
#define DAC_Align_12b_R ((uint32_t)0x00000000)//12位右对齐形式
#define DAC_Align_12b_L ((uint32_t)0x00000004) //12位左对齐形式
#define DAC_Align_8b_R ((uint32_t)0x00000008) //8位右对齐形式
参数2:uint16_t Data,DAC输出的数据。
4、单通道输出,写DAC通道2输出数据寄存器函数
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data) ;
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
5、双通道输出,写DAC输出数据寄存器函数
void DAC_SetDualChannelData(uint32_t DAC_Align, uint16_t Data2, uint16_t Data1) ;
参数1:同单通道输出定义形式。
参数2和参数3:DAC通道2和DAC通道1输出数据。
6、DAC软件触发函数
void DAC_SoftwareTriggerCmd(uint32_t DAC_Channel, FunctionalState NewState)
参数1:uint32_t DAC_Channel,DAC通道宏定义。
参数2:FunctionalState NewState,使能或禁止ADC。
13.6 应用
使用DAC通道1生成一个频率为1KHz的正弦波。
使用定时器T2,在每次定时溢出中断中采样数组数据,并写入DAC_DHRx,定时溢出频率为20KHz。
DAC使用单通道模式,输出不使用触发,使能输出缓冲功能。在每次将数据写入DAC_DHRx后,经过一个APB1 时钟周期,DAC_DHRx 寄存器中存储的数据将自动转移到DAC_DORx,经输出缓冲器,在PA4引脚上产生对应输出电压。
1、编程要点
(1)、使能DAC和复用引脚GPIO的工作时钟。
(2)、初始化DAC通道1相关GPIO引脚为模拟方式。
(3)、根据要求,初始化ADC1。
(4)、初始化定时器
(5)、使能DAC。
(6)、在定时器一处中断中写DAC_DHRx。
2、DAC初始化功能
#include //因为要使用sin函数生成正弦表,需要包含math.h头文件
#define Pi 3.1415926
#define f 1000//正弦波频率 1KHz
#define fs 20000//采样频率 20KHz
uint16_t Sine_Table[20];
void DAC_Config (void)
{
uint16_t i = 0;
/* 生成正弦波形数据,右对齐*/
for (i = 0; i < 20; i++)
{
Sine_Table [i] = (uint16_t )((float)4095*(1+sin(2iPi* f /fs))/2);
}
DAC_Mode_Init ();//配置DAC功能
TIM_Config();//配置定时器功能
}
3、DAC配置程序
static void DAC_Mode_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
/ -------------------第1步-------------------- /
/* 使能GPIOA时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/* 使能DAC时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/ -------------------第2步-------------------- /
/* DAC的GPIO配置,模拟 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/ -------------------第3步-------------------- /
/* 配置DAC 通道1 */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;//不使用触发
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; //不使用波形发生器
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; //使用DAC输出缓冲
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0; //屏蔽幅值 设置
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
/ -------------------第4步-------------------- /
/* 使能通道1 由PA4输出 */
DAC_Cmd(DAC_Channel_1, ENABLE);
}
4、定时器配置程序
static void TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 使能TIM2时钟,TIM2CLK 为90M */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* TIM2基本定时器配置 */
// TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 4500-1; //采样频率 = 20KHz
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频,不分频 90M / (0+1) = 90M
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时钟分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* 配置TIM2中断 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);
/* 使能TIM2 */
TIM_Cmd(TIM2, ENABLE);
}
5、定时器中断服务程序
void TIM2_IRQHandler(void)
{
static u16 Index;
if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
if(Index <19)//采样边界控制
Index ++;
else
Index =0;
//写DAC通道1的12位右对齐数据寄存器,从而启动一次DAC转换
DAC_SetChannel1Data(DAC_Align_12b_R, Sine_Table [Index]);
}
}
具体的可以参考其他官方或个人优秀的代码。