DAC双通道输出电压实验

更多交流欢迎关注作者抖音号:81849645041

目的

        了解DAC数模转换工作原理,利用DAC两个通道输出电压,结合ADC读取引脚电压。

原理

        DAC 为数字/模拟转换模块,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与 ADC 相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而 ADC 把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由 DAC 输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。

        STM32F4 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。DAC 可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。DAC 工作在 12 位模式时,数据可以设置成左对齐或右对齐。DAC 模块有 2 个输出通道,每个通道都有单独的转换器。在双 DAC 模式下,2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2 个通道的输出。DAC 可以通过引脚输入参考电压 Vref+(通 ADC 共用)以获得更精确的转换 结果。

STM32F4 的 DAC 模块主要特点有:

        ① 2 个 DAC 转换器:每个转换器对应 1 个输出通道

        ② 8 位或者 12 位单调输出

        ③ 12 位模式下数据左对齐或者右对齐

        ④ 同步更新功能

        ⑤ 噪声波形生成

        ⑥ 三角波形生成

        ⑦ 双 DAC 通道同时或者分别转换

        ⑧ 每个通道都有 DMA 功能

DAC模块框图:

        整个 DAC 模块围绕框图下方的“数模转换器”展开,它的左边分别是参考电源的引脚:VDDA 、VSSA 及 Vref+,其中 STM32 的 DAC 规定了它的参考电压 输入范围为 2.4~3.3V。“数模转换器”的输入为 DAC 的数据寄存器“DORx”的数字编码,经过它转换得的模拟信号由图中右侧的“DAC_OUTx”输出。而数据寄存器“DORx”又受“控制逻辑”支配,它可以控制数据寄存器加入一些伪噪声信号或配置产生三角波信号。图中的左上角为 DAC 的触发源,DAC 根据触发源的信号来进行 DAC 转换,其作用就相当于 DAC 转换器的开关,它可以配置的触发源为外部中断源触发、定时器触发或软件控制触发。如本章实验中需要控制正弦波的频率,就需要定时器定时触发 DAC 进行数据转换。

1. 参考电压

        与 ADC 外设类似,DAC 也使用 VREF+引脚作为参考电压,在设计原理图的时候一般把VSSA接地,把 VREF+和 VDDA 接 3.3V,可得到 DAC 的输出电压范围为:0~3.3V。

        如果想让输出的电压范围变宽,可以在外部加一个电压调理电路,把 0~3.3V 的 DAC输出抬升到特定的范围即可。

2.数模转换及输出通道

        框图中的“数模转换器”是核心部件,整个 DAC 外设都围绕它而展开。它以左边的 VREF+作为参考电源,以 DAC 的数据寄存器“DORx”的数字编码作为输入,经过它转换得的模拟信号由右侧的“DAC_OUTx”通道输出。其中各个部件中的“x”是指设备的标号,在STM32 中具有 2 个这样的 DAC 部件,每个 DAC 有 1 个对应的输出通道连接到特定的引脚,即:PA4-通道1,PA5-通道2,为避免干扰,使用 DAC 功能时,DAC 通道引脚需要被配置成模拟输入功能(AIN)。

3. 触发源及 DHRx 寄存器

        在使用 DAC 时,不能直接对上述 DORx 寄存器写入数据,任何输出到 DAC 通道 x 的数据都必须写入到 DHRx 寄存器中(其中包含 DHR8Rx、DHR12Lx 等,根据数据对齐方向和分辨率的情况写入到对应的寄存器中)。

        数据被写入到 DHRx 寄存器后,DAC 会根据触发配置进行处理,若使用硬件触发,则 DHRx 中的数据会在 3 个 APB1 时钟周期后传输至 DORx,DORx 随之输出相应的模拟电压到输出通道;若 DAC 设置为外部事件触发,可以使用定时器(TIMx_TRGO)、EXTI_9信号或软件触发(SWTRIGx)这几种方式控制数据 DAC 转换的时机,例如使用定时器触发,配合不同时刻的 DHRx 数据,可实现 DAC 输出正弦波的功能。

        STM32F4 的 DAC 支持 8/12 位模式,8 位模式的时候是固定的右对齐的,而 12 位模式又可以设置左对齐/右对齐。单 DAC 通道 x,总共有 3 种情况:

        ① 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 通道 1,采用 12 位右对齐格式,所以采用第③种情况。

        如果没有选中硬件触发(寄存器 DAC_CR1 的 TENx 位置’0’),存入寄存器 DAC_DHRx 的数据会在一个 APB1 时钟周期后自动传至寄存器 DAC_DORx。

        如果选中硬件触发(寄存器 DAC_CR1 的 TENx 位置’1’),数据传输在触发发生以后 3 个 APB1 时钟周期后完成。一旦数据从 DAC_DHRx 寄存器装入 DAC_DORx 寄存器,在经过时间TSETTLING之后,输出即有效,TSETTLING这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从STM32F407ZGT6 的数据手册查到的典型值为 3us,最大是 6us。所以 DAC 的转换速度最快是 333K 左右。

        不使用硬件触发(TEN=0),其转换的时间框图如图: 

        当 DAC 的参考电压为 Vref+的时候,DAC 的输出电压是线性的从 0~Vref+,12 位模式 下 DAC 输出电压与 Vref+以及 DORx 的计算公式如下:

        DACx 输出电压=Vref*(DORx/4095)

        DAC 控制寄存器(DAC_CR):

        DAC_CR 的低 16 位用于控制通道 1,而高 16 位用于控制通道 2。在 DAC_CR 设置好之后,DAC就可以正常工作了,我们仅需要再设置 DAC 的数据保持寄存器的值,就可以在 DAC 输出通道得到你想要的电压了(对应 IO 口设置为模拟输入)。

        数据保持寄存器(DAC_DHR12R1):

        该寄存器用来设置 DAC 输出,通过写入 12 位数据到该寄存器,就可以在 DAC 输出通 道 1(PA4)得到我们所要的结果。

        通道2和通道1寄存器情况一样,通过PA5输出结果。

        双通道寄存器(DHR12RD):

        该寄存器是 12 位右对齐的双通道寄存器,0-11bit 用于通道1,16-27bit 用于通道2。

        往该寄存器赋值后的数据会在 DAC 被触发的时候搬运到 2 个 DAC 转换器,然后在这 2 个通道中输出以 12 位右对齐表示的这两个通道的电压。

准备

        MDK5 开发环境。

        STM32F4xx HAL库。

        STM32F407 开发板。

        STM32F4xx 参考手册。

        STM32F4xx 数据手册。

        STM32F407 开发板电路原理图。

步骤

  • 新建bsp_dac.h和bsp_dac.c。
  • 在bsp_dac.h定义函数。
#ifndef __BSP_DAC_H
#define __BSP_DAC_H

#include "stm32f4xx.h"

void DAC_Init(void);

void DAC_Ch1_Set_Value(uint16_t value);
uint16_t DAC_Ch1_Get_Value(void);

void DAC_Ch2_Set_Value(uint16_t value);
uint16_t DAC_Ch2_Get_Value(void);

void DAC_TwoCh_Set_value(uint16_t ch1val, uint16_t ch2val);
uint32_t DAC_TwoCh_Get_value(void);
#endif
  • 在bsp_dac.c中实现操作函数。

        第一步:初始化DAC和通道引脚PA4、PA5。

        第二步:单通道CH1、CH2的值设置和获取。

        第三步:双通道寄存器的值设置和获取。

#include "bsp_dac.h"

DAC_HandleTypeDef dac_Handle;

void DAC_Init(void)
{
	
	DAC_ChannelConfTypeDef dac_Config;

	dac_Handle.Instance = DAC;
	HAL_DAC_Init(&dac_Handle); // 初始化 DAC
	
	dac_Config.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE; // DAC1 输出缓冲关闭
	dac_Config.DAC_Trigger = DAC_TRIGGER_NONE; // 无触发功能
	
	HAL_DAC_ConfigChannel(&dac_Handle, &dac_Config, DAC_CHANNEL_1); // DAC 通道 1 配置
	HAL_DAC_ConfigChannel(&dac_Handle, &dac_Config, DAC_CHANNEL_2); // DAC 通道 2 配置
	
	HAL_DAC_Start(&dac_Handle, DAC_CHANNEL_1); // 开启 DAC 通道 1
	HAL_DAC_Start(&dac_Handle, DAC_CHANNEL_2); // 开启 DAC 通道 2
}

void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac)
{
	GPIO_InitTypeDef GPIO_Initure;
	__HAL_RCC_DAC_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	GPIO_Initure.Pin = GPIO_PIN_4 | GPIO_PIN_5;
	GPIO_Initure.Mode = GPIO_MODE_ANALOG;
	GPIO_Initure.Pull = GPIO_NOPULL;
	GPIO_Initure.Speed = GPIO_SPEED_FAST;
	HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}

/**
*  函数名:DAC_Ch1_Set_Value
*  描述:设置通道 1 输出电压
*  输入:value:0~3300,代表 0~3.3V
*  输出:
*/
void DAC_Ch1_Set_Value(uint16_t value)
{
	double temp = value;
	temp /= 1000;
	temp = temp * 4095/3.3;
	HAL_DAC_SetValue(&dac_Handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp);
	
}

uint16_t DAC_Ch1_Get_Value(void)
{
	return HAL_DAC_GetValue(&dac_Handle, DAC_CHANNEL_1);
}

/**
*  函数名:DAC_Ch2_Set_Value
*  描述:设置通道 2 输出电压
*  输入:value:0~3300,代表 0~3.3V
*  输出:
*/
void DAC_Ch2_Set_Value(uint16_t value)
{
	double temp = value;
	temp /= 1000;
	temp = temp * 4095/3.3;
	HAL_DAC_SetValue(&dac_Handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, temp);
	
}

uint16_t DAC_Ch2_Get_Value(void)
{
	return HAL_DAC_GetValue(&dac_Handle, DAC_CHANNEL_2);
}

/**
*  函数名:DAC_TwoCh_Set_value
*  描述:设置双通道的值
*  输入:ch1val 通道1的值 ch2val 通道2的值
*  输出:
*/
void DAC_TwoCh_Set_value(uint16_t ch1val, uint16_t ch2val)
{
	double temp1 = ch1val;
	temp1 /= 1000;
	temp1 = temp1 * 4095/3.3;
	
	double temp2 = ch2val;
	temp2 /= 1000;
	temp2 = temp2 * 4095/3.3;
	HAL_DACEx_DualSetValue(&dac_Handle, DAC_ALIGN_12B_R, temp1, temp2);
}

/**
*  函数名:DAC_TwoCh_Get_value
*  描述:获取两个通道DOR的值
*  输入:
*  输出:返回的是两个通道值  0-15bit 通道1的值  16-32bit 通道2的值
*/
uint32_t DAC_TwoCh_Get_value()
{
	return HAL_DACEx_DualGetValue(&dac_Handle);
}

在main.c中:

        第一步:KEY0设置单通道CH1、CH2电压值。

        第二步:KEY1通过ADC读取CH1电压值。

        第三步:KEY2通过ADC读取CH2电压值。

        第四步:KEY_WKUP 设置双通道的值。在通过KEY1、KEY2读取两个通道值。

#include "bsp_clock.h"
#include "bsp_uart.h"
#include "bsp_key.h"
#include "bsp_adc.h" 
#include "bsp_dac.h"
#include "bsp_led.h"

int main(void)
{
	uint32_t value = 0;
	
	CLOCLK_Init(); // 初始化系统时钟
	UART_Init(); // 串口初始化
	KEY_Init(); // 按键初始化
	LED_Init(); // LED初始化
	ADC1_Init();
	DAC_Init();

	while(1)
	{
		
		uint8_t key = KEY_Scan(0); 
		
		if(key == 1) // KEY0按下 设置单通道值
		 { 
			 LED1_Toggle; // 点亮LED1
			 
			 if(value > 3300)
			 {
					value = 0;
			 }
			 DAC_Ch1_Set_Value(value); // 设置通道1
			 DAC_Ch2_Set_Value(value); // 设置通道2
			 printf("DAC1 set value : %d \n",value);
			 
			 value += 500;	 
		 }
		 
		 if(key == 2) // KEY1按下 读取通道1电压值
		 { 
			 LED2_Toggle; // 点亮LED2
			 float dacx = DAC_Ch1_Get_Value() *(3.3/4096); // 读取通道1值
			 printf("DAC1 CH1 value : %0.2f V \n",dacx);
			 	 
			 float adcx = ADC1_GetAverageValue(ADC_CHANNEL_6, 10) *(3.3/4096); // ADC读取通道1电压值
			 printf("ADC1 CH1 value : %0.2f V \n",adcx);
			 
		 }
		 
		 if(key == 3) // KEY2按下 读取通道2电压值
		 { 
			 LED2_Toggle; // 点亮LED2
			 float dacx = DAC_Ch2_Get_Value() *(3.3/4096); // 读取通道2值
			 printf("DAC1 CH2 value : %0.2f V \n",dacx);
			 	 
			 float adcx = ADC1_GetAverageValue(ADC_CHANNEL_6, 10) *(3.3/4096); // ADC读取通道2电压值
			 printf("ADC1 CH2 value : %0.2f V \n",adcx);
			 
		 }
		 
		 if(key == 4) // WK_UP按下 同时设置双通道的值
		 { 
			 LED2_Toggle; // 点亮LED2
			 DAC_TwoCh_Set_value(2000, 2500); // 设置双通道寄存器DHR12RD
			 printf("CH1 value : %.2f V\n", 2.0);
			 printf("CH2 value : %.2f V\n", 2.5);
			 
			 uint32_t data = DAC_TwoCh_Get_value(); // 读取双通道寄存器DHR12RD
			 uint16_t ch1val = data;
			 uint16_t ch2val = data >> 16;
			 printf("CH1 DOR1 value : %d \n", ch1val);
			 printf("CH2 DOR2 value : %d \n", ch2val);
		 }
		 
		 HAL_Delay(50);
	}
}

现象

        把编译好的程序下载到开发板。检查开发板参考电压Vref+引脚与VDDA短接使能DAC输出。

        按下key0单独设置两个通道值。比如设置到1000,通过ADC读取两个通道电压值。ADC引脚PA6,DAC通道1是PA4,通道2是PA5。连接PA6与PA4按下KEY1读取通道1的值、连接PA6与PA5按下KEY2读取通道2的值。

         按下WKUP设置双通道寄存器的值CH1 2000 、CH2 2500(会经过换算,实际设置到寄存器的值为2481,3102)。连接PA6与PA4按下KEY1读取通道1的值、连接PA6与PA5按下KEY2读取通道2的值。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值