DAC 数模转换实验

         在上一章《ADC 模数转换实验》中,介绍了如何使用 51 单片机采集外部的模拟信号,并将其转换为数字信号进行处理。本章则重点介绍如何让 51 单片机输出模拟信号。

         在数字世界中,微控制器(MCU)内部运算和存储的数据都是数字量,如果我们需要驱动外部的模拟设备(例如扬声器、模拟仪表、灯光调节电路等),就必须把这些数字信号转换回模拟信号,这就需要用到 DAC(数模转换器)

         不过,专用的 DAC 芯片在部分场景下价格较高且占用额外硬件资源,因此在很多嵌入式应用中,会用 PWM(脉宽调制)技术 结合滤波电路来模拟 DAC 输出,从而降低成本。PWM 方案虽然在精度和线性度上略逊于高性能 DAC 芯片,但对于很多非高精度应用(例如灯光渐变、音量调节等)已经足够实用。

         我使用的普中 A7 开发板 上集成了一个 DAC(PWM) 模块电路。通过单片机 IO 口产生 PWM 波形,并经过板载滤波电路处理,就可以输出平滑的模拟电压信号,实现与 DAC 类似的功能。本章实验的目标是:系统运行时,让 DAC(PWM) 模块上的指示灯 DA1 呈现“呼吸灯”效果——亮度从暗到亮,再从亮到暗,循环往复。这个效果在 LED 灯具、电子钟表、智能硬件的视觉提示中非常常见。学习本章内容时,可参考前面相关实验章节的知识点。


DAC

DAC 简介

         DAC(Digital-to-Analog Converter,数字-模拟转换器)是一种将数字信号转换成连续模拟信号的电子器件。它的功能正好与 ADC(模数转换器) 相反。

         在一个典型的数字信号系统中,数据的流动过程通常如下:

  1. 各种传感器将外界的物理量(温度、声音、光强等)转换成模拟电压或电流信号;
  2. ADC 将这些模拟信号转换成数字量,供计算机或单片机存储和处理;
  3. 经过运算处理后,如果需要控制外部的模拟设备(如扬声器播放声音、驱动马达、调节灯光亮度等),就由 DAC 将处理后的数字量还原成对应的模拟信号输出。

         例如,音频播放器播放音乐时,内部存储的音频文件是数字编码数据,这些数据需要经过 DAC 转换成连续的电压波形,才能驱动扬声器发出声音。


DAC 的主要技术指标

1. 分辨率(Resolution)

         DAC 的分辨率表示输入数字量的最低有效位(LSB)发生变化时,对应的输出模拟量(电压或电流)的的变化量,它反映了输出模拟量的最小变化值。

  • 它直接反映了输出的精细程度,位数越高,输出变化越细腻。
  • 分辨率与输入数字量的位数的关系公式:

         其中 FS 为满量程输入值(Full Scale),n 为输入位数。

         例子:

  • 对于 5V 满量程,8 位 DAC 的分辨率为: 5V / 256 ≈ 19.5mV
  • 12 位 DAC 的分辨率为: 5V / 4096 ≈ 1.22mV

         显然,位数越多,分辨率越高,输出越平滑。

2. 线性度(Linearity)

         线性度描述实际 DAC 转换特性曲线与理想直线特性之间的最大偏差,也称非线性误差

  • 一般以满量程的百分比表示,例如 ±1% 表示在满刻度范围内,实际输出与理论值的最大误差不超过 ±1%。
  • 线性度越好,输出曲线就越接近理想的直线关系,这对于高精度控制尤为重要。

3. 绝对精度与相对精度(Accuracy)

         绝对精度:在整个输入范围内,任一输入数码所对应的实际模拟量输出与理论值之间的最大误差。误差来源包括:增益误差(当输入数码为全 1 时,实际输出值与理想输出值之差)、零点误差(数码输入为全0时,DAC 的非零输出值)、非线性误差、噪声等。工程上要求绝对精度应小于 1 个 LSB。

         相对精度:与绝对精度含义相同,只是用误差占满量程的百分比来表示。

4. 建立时间(Settling Time)

         建立时间是指输入数字量发生满刻度变化时,输出模拟信号达到满刻度值的 ±1/2LSB 所需的时间。它是衡量 DAC 转换速率的一个动态指标。

         根据建立时间长短,DAC 大致分为:

  • 超高速型:< 1 μs
  • 高速型:1–10 μs
  • 中速型:10–100 μs
  • 低速型:≥ 100 μs

         在需要快速响应的场景(如高速数据采集、视频信号处理)中,建立时间的要求会更高。


DAC 工作原理

         在前面了解了 DAC 的基本概念与主要特性后,再来看一下它的基本工作原理。这里以 T 型电阻网络 DAC 为例进行介绍。

         T 型电阻网络 DAC 是一种常见的数模转换电路结构,常用于中低速、低成本的 DAC 设计中。它通过精确匹配的电阻阵列实现位权分配,将数字信号逐步“加权”并转换成模拟电压。其内部结构图如下所示:

输出电压计算公式

  • z:单片机输出给 DAC 的数字量(0~255,因精度为 8 位)
  • Vref:参考电压,通常接在系统电源(如 5V)上
  • 256:表示 8 位 DAC 的分辨率(28=2562^8 = 25628=256)

         举例:若参考电压 Vref = 5V ,当数字量 z = 128 时,输出电压 V0 = 5 × 128 / 256=2.5V。

典型结构组成

         一个完整的 DAC 通常由以下部分构成:

  • 数字寄存器:存储来自单片机的数字输入值(z)。
  • 模拟电子开关:根据数字寄存器中每一位的值(0 或 1)控制对应的电阻分支是否接入电路。
  • 位权网络:由一组精确比例的电阻或电流源组成,用于将参考电压按位权分配。高位对应的权值(电压/电流)更大,低位更小。
  • 求和运算放大器:将各分支输出的电流加总,并转换成对应的模拟电压。
  • 基准电压源 / 恒流源:提供稳定、精确的参考电压,确保 DAC 输出的精度和稳定性。

工作原理说明

数字量控制开关

  • 数字寄存器的每一位(bit)都对应一个模拟电子开关。
  • 当该位为 1 时,开关闭合,将该位对应的电阻支路接入电路;
  • 当该位为 0 时,开关断开,支路不参与输出。

位权分配

  • 位权网络中,各支路的电阻值是经过精确设计的,并非平均分压。
  • 高位(MSB)对应的电阻值较小,能分得的电压或电流份额较大;低位(LSB)则相反。

电流求和与电压转换

  • 所有开启的支路电流在运算放大器的求和节点相加,形成一个与数字输入值成比例的总电流;
  • 运算放大器将该总电流转换成电压,输出到 DAC 的模拟端口。

         这样,每一个输入数字值 z 都能唯一对应到一个输出电压 V0,实现数字量到模拟量的转换。


PWM 

         出于成本和硬件复杂度的考虑,很多实际嵌入式开发中并不直接使用专用 DAC 芯片,而是通过 PWM(Pulse Width Modulation,脉冲宽度调制)来模拟 DAC 输出。本节对 PWM 的概念、工作原理、与 DAC 的对应关系以及工程上应注意的要点做一并说明。

PWM 简介

         PWM(Pulse Width Modulation)脉冲宽度调制,简称脉宽调制,是用一串占空比可变的方波来表示模拟电平的一种方法。PWM 信号在任意时刻仍是数字信号(要么为高电平 ON,要么为低电平 OFF),但通过调节 占空比(高电平持续时间 / 周期的比例),可以使该方波在经过适当低通滤波后表现为一个对应的平均电压,从而等效产生连续的模拟电平。

         那么,PWM控制是遵循什么样的原理来进行脉宽调制从而等效的获取所需的波形呢?下面就来详细介绍一下PWM控制的基本原理——面积等效原理

面积等效原理

  面积等效原理:当两个脉冲的面积( 即幅值与宽度的乘积,也称冲量)相等时,即使它们的波形形状不同,将这些脉冲作用在具有惯性特性的系统上时,其产生的平均效果基本相同。

  :这里的“具有惯性特性的系统”并不是指机械上的转动惯量,而是泛指对输入变化不能瞬时完全响应、会对输入信号进行时间平滑或延迟响应的系统。这类系统通常由储能元件(如电感、电容、质量块、热容等)构成,具有“平均化”输入能量的特性,因此对相同冲量的不同波形输入,其输出变化趋势基本一致。两个重要的点:

  • 冲量:指窄脉冲的面积
  • 效果基本相同:指具有惯性特性的系统的输出响应波形基本相同

  形状不同而冲量相同的各种窄脉冲,如下所示:

图1:形状不同但冲量相同的各种窄脉冲

  分别将图1所示的电压窄脉冲加在一阶惯性环节(R-L电路)上,如图2 a 所示。其输出电流 i(t) 对不同窄脉冲时的响应波形如图2 b所示。从波形可以看出,在i(t)的上升段,i(t)的形状也略有不同,但其下降段则几乎完全相同。脉冲越窄,各i(t)响应波形的差异也越小。如果周期性地施加上述脉冲,则响应i(t)也是周期性的。用傅里叶级数分解后将可看出,各 i(t) 在低频段的特性将非常接近,仅在高频段有所不同。

  • e(t):电压窄脉冲,是电路输入,输入信号为图1所示的各种窄脉冲;
  • i(t):输出电流,是电路的响应,输出信号为图2 b所示;
图2:冲量相等的各种窄脉冲的响应波形
面积等效原理示例——用PWM波替代正弦半波

​  用 PWM 波形等效替代正弦半波的过程如下图 3 所示:

图3:用PWM波代替正弦半波

         首先,将正弦半波(图 3(a))沿时间轴等分为 N 段。此时,每段的脉冲宽度相同(等宽),但幅值不同,幅值取自正弦曲线在该区间的采样值。

         接下来,利用一系列 PWM 波(图 3(b))来替代这些等宽不等高的矩形脉冲。PWM 波的特点是幅值相同(等幅)但脉宽不同(不等宽),其脉宽按照正弦规律变化。图 3(b) 中的每个等幅不等宽的脉冲,其面积(即冲量)与图 3(a) 中对应的脉冲相等,且脉冲的中心位置(中点)与原脉冲对齐。

         如果需要改变等效输出正弦波的幅值,只需按相同的比例同时调整所有 PWM 脉冲的宽度即可。最终,这组 PWM 波在经过系统滤波或惯性作用后,将等效为一条正弦交流波形。

:正弦半波等分为 N 段后,可视为 N 个相连的等宽不等高矩形脉冲序列。利用等幅不等宽的矩形脉冲替换它们,使替换后的脉冲在面积(冲量)上与原脉冲相等,并保持中点对齐,同时脉宽按照正弦规律变化。

         PWM 输出是一种脉宽可调(即可调节占空比)的方波信号。其中,信号频率由周期 T 决定,占空比则由比较值 C 控制。具体信号波形如下图所示:

         从图中可见,PWM输出频率保持恒定,通过调节C值来改变PWM信号的占空比。占空比指的是一个周期内高电平时间与总周期的比值。至于频率设定,则可通过51单片机的定时器来实现。


PWM 的基本量与数学关系

  • 周期 T:PWM 的一个完整周期
  • 高电平时间(脉宽): Ton(秒)
  • 频率 f = 1 / T(Hz)
  • 占空比:

         因此要输出 1.5V(假设系统供电 VH=5V),所需占空比约为:

  • 理想平均电压:若 PWM 方波的高电平电压为 VH(例如单片机的输出电压 5V),理想情况下(在足够低通滤波的前提下),输出的平均电压为:

         例:若 VH = 5V,D = 0.3,则 Vavg = 1.5V。

硬件设计

         本实验使用到硬件资源如下:

(1)DAC(PWM)模块

         下面来看下开发板上 DAC(PWM)模块电路,如下图所示:

         该独立模块由 J50 端子输入PWM 信号,可使用单片机P2.1 管脚输出 PWM 信号至 J50 端子,DAC1 为 PWM 输出信号,将其连接一个LED,这样可以通过指示灯的状态直观的反映出 PWM 输出电压值变化。LM358 芯片与这些电容电阻构成了一个跟随电路,即输入是多少,输出即为多大电压,输出电压范围是 0-5V。输出信号由 J52 端子的 DAC1 引出,在其端子上还有一个AIN3脚, 它是上一章介绍 ADC 时的外部模拟信号输入通道。如果使用短接片将DAC1 和AIN3 短接,这样就可以使用 XPT2046 芯片采集检测 PWM 输出信号。

软件设计

         本章所要实现的功能是:DAC(PWM)模块上的指示灯DA1 呈呼吸灯效果,由暗变亮再由亮变暗。

         程序框架如下:

(1)编写 PWM 函数

(2)编写主函数         

         本章软件的重点是如何让单片机 IO 口输出 PWM。下面着重分析一下 pwm.c 文件(里面包含了 PWM 驱动程序)里的几个重要函数,其他部分程序大家可以打开工程查看。

         源代码:通过网盘分享的文件:24-DAC数模转换实验.zip
链接: https://pan.baidu.com/s/13rk-wfhYZp5o1yc6_LpgCA 提取码: d5zk 复制这段内容后打开百度网盘手机App,操作更方便哦

PWM 实现函数

         代码的实现思想:

  • 先用定时器 0 产生“中断时间片”(每次中断代表一个最小时间单元);
  • 在中断里做计数(time++),当计数小于占空比(gduty)时输出高电平,否则输出低电平;当计数等于或超过周期倍数(gtim_scale)时清零,开始新周期。

         因此:

  • 中断间隔(interrupt_period) = 定时器从写入初值到溢出的时间(与 TH0/TL0 初值有关)
  • PWM 周期 = 中断间隔 × 周期倍数gtim_scale
  • 占空比百分比 = gduty / gtim_scale(注意:gduty 的有效范围是 0..gtim_scale)

         PWM 的实现均在 pwm.c 文件中,代码主要是基于定时器实现 PWM 输出,PWM 初始化实际上为定时器0初始化,pwm_init 函数有 4 个入口参数,tim_h 和 tim_l 为定时器定时初值,即进入中断时间;tim_scale 参数为 PWM 的周期倍数,使用该值乘以定时器初值可得出 PWM 的周期;duty 参数为 PWM 占空比,即一个周期内高电平所占的时间比例。在 pwm 初始化函数内,将函数入口参数通过全局变量保存,方便在后续中断函数内使用。

         pwm_set_duty_cycle 函数是占空比设置函数,该函数有一个入口参数,用于设置 PWM 占空比,注意,该值不能超过初始化中的 PWM 的周期倍数值。

         最后就是定时器 0 的中断服务函数,在中断内定义了一个静态变量用于统计进入中断的次数时间,当进入中断次数时间大于等于 gtim_scale 周期倍数,则重新开始计数,表示 PWM 周期为定时器初值*gtim_scale;然后当计数次数时间小于等于设置的占空比次数时间,则使对应 IO 输出高电平,否则输出低电平。

         代码如下:

#include "pwm.h"

//全局变量定义
u8 gtim_h=0;//保存定时器初值高8位
u8 gtim_l=0;//保存定时器初值低8位
u8 gduty=0;//保存PWM占空比
u8 gtim_scale=0;//保存PWM周期=定时器初值*tim_scale


/*******************************************************************************
* 函 数 名       : pwm_init
* 函数功能		 : PWM初始化函数
* 输    入       : tim_h:定时器高8位
				   tim_l:定时器低8位
				   tim_scale:PWM周期倍数:定时器初值*tim_scale
				   duty:PWM占空比(要小于等于tim_scale)
* 输    出    	 : 无
*******************************************************************************/
void pwm_init(u8 tim_h,u8 tim_l,u16 tim_scale,u8 duty)
{
	gtim_h=tim_h;//将传入的初值保存在全局变量中,方便中断函数继续调用
	gtim_l=tim_l;
	gduty=duty;
	gtim_scale=tim_scale;

	TMOD|=0X01;	//选择为定时器0模式,工作方式1
	TH0 = gtim_h;	//定时初值设置 
	TL0 = gtim_l;		
	ET0=1;//打开定时器0中断允许
	EA=1;//打开总中断
	TR0=1;//打开定时器
}

/*******************************************************************************
* 函 数 名       : pwm_set_duty_cycle
* 函数功能		 : PWM设置占空比
* 输    入       : duty:PWM占空比(要小于等于tim_scale)
* 输    出    	 : 无
*******************************************************************************/
void pwm_set_duty_cycle(u8 duty)
{
	gduty=duty;	
}

void pwm(void) interrupt 1	//定时器0中断函数
{
	static u16 time=0;

	TH0 = gtim_h;	//定时初值设置 
	TL0 = gtim_l;
	
	time++;
	if(time>=gtim_scale)//PWM周期=定时器初值*gtim_scale,重新开始计数
		time=0;
	if(time<=gduty)//占空比	
		PWM=1;
	else
		PWM=0;		
}

主函数

         主函数比较简单,首先调用外设驱动头文件,然后进入主函数初始化PWM,将定时器设置为 0.01ms,初值为 0XFFF6,即每隔 0.01ms 进入一次中断。PWM周期倍数设置为 100,即 PWM 周期为 1ms,占空比设置为0。最后进入while 循环,通过 dir 切换方向实现 duty 值的自增和自减来调节占空比,将该值传入到占空比调节函数 pwm_set_duty_cycl。为了使呼吸灯流畅,每调节占空比短暂延时一下。

         main.c 文件,代码如下:

/**************************************************************************************
实验名称:DAC模数转换实验
接线说明:	
实验现象:下载程序后,DAC(PWM)模块上的指示灯DA1呈呼吸灯效果,由暗变亮再由亮变暗
注意事项:																				  
***************************************************************************************/
#include "public.h"
#include "pwm.h"


/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	u8 dir=0;//默认为0
	u8 duty=0;

	pwm_init(0XFF,0XF6,100,0);//定时时间为0.01ms,PWM周期是100*0.01ms=1ms,占空比为0%

	while(1)
	{
		if(dir==0)//当dir为递增方向
		{
			duty++;//占空比递增
			if(duty==70)dir=1;//当到达一定值切换方向,占空比最大能到100,但到达70左右再递增,
								//肉眼也分辨不出亮度变化	
		}
		else
		{
			duty--;
			if(duty==0)dir=0;//当到达一定值切换方向	
		}
		pwm_set_duty_cycle(duty);//设置占空比
		delay_ms(1);//短暂延时,让呼吸灯有一个流畅的效果			
	}
}

实验现象

         使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上CH340 串口),把编译后产生的.hex 文件烧入到芯片内,实现现象如下:DAC(PWM)模块上的指示灯 DA1 呈呼吸灯效果,由暗变亮再由亮变暗。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐