简介:基于单片机的DAC0832三角波产生与输出设计资源包,面向电子、自动化及嵌入式系统初学者,提供完整程序源码与Proteus仿真电路,帮助理解数模转换原理、单片机定时/计数控制以及三角波信号生成方法。资源共14个文件,大小仅131KB,包含Proteus工程文件(pdsprj、pdsbak、workspace)、Keil工程文件(uvproj、uvopt、c、hex)和波形截图(png)等,既可在软件中直接打开仿真,也可参考源码进行二次修改。已有698人学习下载。读者不仅能运行仿真观察三角波输出,还能结合源码与原理图梳理DAC0832的控制时序和累加器算法,掌握从数字量到模拟电压的完整设计流程。对于正在学习单片机接口技术或准备课程设计、电子竞赛的读者,是一份轻量而实用的参考资料。
1. DAC0832三角波输出:先看清电流型DAC再谈波形
DAC0832做三角波发生器,看着是课设题里的标准件。但真正在Proteus里把电路搭完、程序烧进去,第一轮测量就会愣一下:数据口从0xFF一路递减,电压表上却什么都读不到。原因不复杂,DAC0832是电流输出型DAC,Iout1与Iout2给出的是电流差,必须先经过运放转成电压,才能在示波器上看到三角波。这个题目真正要解决的其实是三件事:DAC0832的接口工作在哪种缓冲方式、三角波数字序列怎么写、以及Proteus里的仿真怎么调才不失真。这套流程适合正在做51单片机课程设计、或者第一次接触并行DAC的工程师直接参考,下面按接口、程序、仿真和排错的顺序一次说清。
2. DAC0832接口设计:三种工作方式与运放输出级参数
2.1 DAC0832的三种工作方式:直通、单缓冲与双缓冲选型
DAC0832内部是8位输入寄存器、8位DAC寄存器加一片R-2R梯形电阻网络。两个寄存器各有一个锁存使能端,外部对应两组控制信号:ILE、CS、WR管输入寄存器写入,XFER管DAC寄存器从输入寄存器锁数据。两组寄存器前后串联,才衍生出三种工作方式。
| 工作方式 | 接线要点 | 控制时序 | 适用场景 |
|---|---|---|---|
| 直通 | ILE接高、CS接低、WR接低、XFER接低 | 数据线上有值就转换 | 无锁存需求,IO紧张时常用 |
| 单缓冲 | ILE接高、CS接IO口、WR接单片机WR、XFER接低 | 一次片选加WR脉冲完成写入 | 本设计采用,引脚占用少 |
| 双缓冲 | 两组寄存器分别由CS和XFER控制 | 两次写操作分批锁存 | 多片DAC同步输出的场景 |
选型上我一般建议三角波这种单路输出直接用单缓冲。直通虽然接线最少,但P0上数据跳变过程中会夹杂中间电平,输出波形容易毛刺;双缓冲是为多片DAC同步输出准备的,单路波形用不上,反而要多占一根控制线。单缓冲的锁存动作只在WR上升沿发生一次,数据线上的毛刺不会被DAC寄存器接收,输出更干净。
2.2 引脚映射与最小接口:从数据口到片选信号
常见的51接线方案是P0接DAC数据线,CS用P2.5,WR直接复用单片机的P3.6写信号。注意P0是开漏结构,必须接一个10k排阻到VCC,否则高电平抬不上去,波形顶部会削平。控制引脚的宏定义如下:
/* dac0832_hw.h —— 硬件引脚映射,保持与Proteus原理图一致 */ #include <reg52.h> #define DAC_DATA P0 /* 8位数据总线接P0.0~P0.7 */ sbit DAC_CS = P2^5; /* 片选信号,低有效 */ sbit DAC_WR = P3^6; /* 写信号,低有效 */引脚映射决定了单片机和DAC0832的同步方式。宏定义里把CS放在P2^5,是为了在程序里通过一个宏或函数就能完成一次完整写入,不需要在中断里反复修改P0方向。DAC0832的数据建立时间和WR最小脉宽都在几百纳秒量级,12MHz晶振下,一条C语句编译后约几个微秒,时序余量足够,不需要额外插入延时。
2.3 电流到电压的输出级:运放选型与增益参数
DAC0832的输出电流与数字量成正比,参考电压Vref决定满量程电流。典型接法是Iout1接运放反相输入端,Iout2接地,Rfb接运放输出形成反馈。单片DAC0832工作在Vref为5V时,满量程输出电流约2mA量级,经过内部反馈电阻后,第一级输出电压为:
Vout = -(N / 256) × Vref
其中N是写入的8位数字量。N从0变到255,Vout从0V变到约-4.98V,是负向的。要得到正向三角波,常见做法是加第二级反相放大器,用两个10k电阻构成1:1反相,把负电压翻成正电压。运放选LM324就够了,轨到轨输出特性一般,5V供电时输出上限约3.5V,如果想要满摆幅,换LMV358这类轨到轨运放更合适。
3. 三角波生成程序:用定时器中断控制DAC0832的每一步
3.1 三角波数字序列的生成逻辑:正程、回程与顶点处理
三角波在数字域里就是一个等间隔步进的序列:从0开始每步加1,到255后转为每步减1,回到0再反转。每一步对应DAC0832的一个输出电平,步进间隔由定时器决定,间隔相同则斜率恒定。输出序列写成电压就是0V、0.02V、0.04V……一路到4.98V再折返,周期由步数乘以步进时间决定。
一个完整三角波包含256个上升点和256个下降点,共512拍。这里有一个容易被忽略的细节:顶点255和谷底0到底驻留几拍。如果正程用先写后加、回程用先写后减,255会连续写两次,波峰处多驻留一个中断周期,宏观表现为峰顶一个小平顶。对波形对称性要求高的场合,需要在顶点处跳过一拍,让序列严格走0到255再到0。
3.2 完整C程序:单缓冲方式下用定时器0驱动DAC0832
下面这段程序按单缓冲方式驱动DAC0832,用定时器0模式1产生步进中断,在12MHz晶振下输出约20Hz的三角波。Proteus仿真可以直接跑。
/* main.c —— AT89C52 + DAC0832 三角波产生,Proteus 仿真可用 */ #include <reg52.h> #define DAC_DATA P0 /* 数据口 */ sbit DAC_CS = P2^5; /* 片选 */ sbit DAC_WR = P3^6; /* 写信号 */ /* 向DAC0832写入一个字节,单缓冲方式 */ void dac_write(unsigned char value) { DAC_CS = 0; /* 选中DAC */ DAC_DATA = value; /* 数据先稳定 */ DAC_WR = 0; /* 拉低WR */ DAC_WR = 1; /* 上升沿锁存 */ DAC_CS = 1; /* 释放片选 */ } /* 定时器0中断服务函数:每中断一次输出一个步进点 */ void timer0_isr(void) interrupt 1 { static unsigned char level = 0; /* 当前电平 */ static unsigned char rising = 1; /* 1为正程,0为回程 */ if (rising) { dac_write(level++); /* 写入当前值,再递增 */ if (level == 255) rising = 0; /* 到顶,转回程 */ } else { dac_write(level--); /* 写入当前值,再递减 */ if (level == 0) rising = 1; /* 回零,转正程 */ } TH0 = 0xFF; /* 重装初值高8位 */ TL0 = 0xA0; /* 重装初值低8位,96微秒中断一次 */ } void main(void) { TMOD = 0x01; /* 定时器0,模式1,16位 */ TH0 = 0xFF; TL0 = 0xA0; ET0 = 1; /* 开定时器0中断 */ EA = 1; /* 开总中断 */ TR0 = 1; /* 启动定时器 */ while (1) { /* 主循环不参与波形生成,留空 */ } }代码逻辑并不复杂:中断服务函数是唯一的输出驱动点,每96微秒触发一次,向DAC写入一个level值,再根据方向递增或递减。static变量保证level和rising在中断前后保持连续状态,这是三角波不跳变的必要条件。dac_write函数里的信号顺序是数据先稳定、再给WR上升沿锁存,对应DAC0832手册的时序要求,CS在整个过程中保持低电平。
定时初值0xFFA0对应计数256减160等于96个机器周期,12MHz晶振下机器周期为1微秒,所以中断间隔96微秒。改变TH0和TL0就能改变步进速度,步数固定512拍,波形频率随之变化。
3.3 频率计算与定时初值对照
三角波频率的完整计算式是:T = 512 × 定时器溢出周期,f = 1/T。定时器溢出周期 = (65536 - 初值) × 机器周期。以下表格列出不同初值在12MHz晶振下的实际频率:
| TH0初值 | TL0初值 | 定时器溢出周期 | 三角波频率 |
|---|---|---|---|
| 0xFF | 0xF0 | 48 μs | 约40.7 Hz |
| 0xFF | 0xC8 | 72 μs | 约27.1 Hz |
| 0xFF | 0xA0 | 96 μs | 约20.3 Hz |
| 0xFF | 0x80 | 128 μs | 约15.3 Hz |
| 0xFE | 0xA0 | 352 μs | 约5.5 Hz |
注意表格里的频率是理论值。中断服务函数里dac_write本身要执行几条引脚操作指令,占用约几个微秒。定时器溢出后CPU响应中断有延迟,溢出周期越短,这个开销占比越高,实际频率会比理论值略低。如果追求精确频率,用示波器测实际值后再微调初值更稳妥。
前面提到顶点驻留问题,可以用下面这段改进逻辑让波形严格对称:
if (rising) { dac_write(level); if (level == 255) { rising = 0; level = 254; } else level++; } else { dac_write(level); if (level == 0) { rising = 1; level = 1; } else level--; }改进点在于写入255后直接让level变成254,回程时再写254,这样255只驻留一拍,整个三角波的上升时间和下降时间严格相等。谷底的处理同理。这个细节在示波器上放大波峰时才能看出来,但对DAC输出精度敏感的场景值得保留。
4. Proteus仿真DAC0832三角波:电路搭建、示波器读数与排错
4.1 Proteus元件清单与连接步骤
Proteus里做这个仿真需要准备以下元件,元件搜索框直接输入名称即可定位:
| 元件 | 型号 | 数量 | 用途 |
|---|---|---|---|
| 单片机 | AT89C52 | 1 | 波形产生主控 |
| DAC | DAC0832 | 1 | 数模转换 |
| 运放 | LM324 | 1 | 电流转电压及反相 |
| 排阻 | RESPACK-8,10k | 1 | P0上拉 |
| 晶振 | 12MHz | 1 | 主时钟 |
| 电容 | 33pF | 2 | 晶振负载 |
| 电阻 | 10kΩ | 2 | 第二级反相增益 |
| 示波器 | 虚拟示波器 | 1 | 波形观察 |
连接步骤按这个顺序比较顺:先放单片机和DAC0832,把P0.0到P0.7接到DAC的DI0到DI7,P0再接排阻到VCC。然后处理控制信号,ILE接VCC,CS接P2.5,WR接P3.6,XFER接地。接着是输出级,DAC0832的Iout1接LM324第一级反相输入,Iout2接地,Rfb接第一级输出,第一级同相输入端接地。最后加第二级反相放大器,两个10k电阻做成1:1反相,把负电压翻成正电压。
晶振和电容接到单片机的XTAL1和XTAL2,复位电路用10k电阻和10μF电容即可。双击单片机,在属性里确认Clock Frequency为12MHz,设置好后加载Keil编译生成的hex文件。
4.2 仿真运行与示波器参数设置
运行仿真后,把示波器A通道接到第二级运放输出端。电压挡位设为2V/Div,时间挡位根据波形频率调整,20Hz左右对应周期约50ms,用20ms/Div可以看到完整两三个周期。耦合方式切到DC,否则低频三角波会被交流耦合滤掉幅度。
正常现象是示波器上呈现0到5V之间线性变化的三角波,顶点轻微圆滑属于运放压摆率限制,如果看到波形呈台阶状,先查P0上拉排阻是否漏接。示波器读数时可以直接测峰峰值,验证是否接近Vref值。调整程序里的TH0和TL0初值后重新加载hex,频率应该按第3章表格的规律变化。
4.3 三角波仿真中5个常见坑及排查顺序
第一个坑是单片机没加载hex文件,仿真跑起来没有任何输出。双击单片机,在Program File里选择编译好的hex,这是Proteus仿真最常见的基础错误。
第二个坑是P0上拉排阻缺失。P0是开漏结构,不加上拉时高电平最高只能到1.5V左右,三角波顶部被削平,甚至变成锯齿状。排阻一端接P0全部引脚,另一端接VCC。
第三个坑是Iout1和Iout2接反。Iout1要接运放反相输入端,Iout2接地。接反后运放正反馈,输出会直接饱和到电源轨,完全没有三角波形状。
第四个坑是输出为负电压。第一级运放输出天然是负向波形,必须经过第二级反相才能看到正向三角波。如果只搭第一级,示波器上看到负向三角波是正常的,不是程序错误。
第五个坑是ILE或XFER悬空。Proteus的DAC0832模型对这些引脚状态敏感,悬空可能造成输出恒为零。ILE必须接高电平,XFER必须接地或受控拉低。
按供电检查、晶振频率、hex文件、P0上拉、运放输入输出、示波器耦合这个顺序排查,大部分仿真问都可以定位。
5. 高频三角波与幅度微调:基于定时器模式2和运放反馈
5.1 用定时器模式2消除重装误差
16位模式1下每次中断都要在服务函数末尾重装TH0和TL0,如果重装代码执行时间不固定,波形周期会抖动。要提升频率稳定性,切换到定时器模式2:8位自动重装,溢出后硬件自动把TH0的值装入TL0,不需要软件介入。改法很简单,TMOD设为0x02,TH0和TL0都写同一个初值,中断服务函数里删掉重装语句。
void main(void) { TMOD = 0x02; /* 定时器0,模式2,8位自动重装 */ TH0 = 0xC8; /* 自动重装值 */ TL0 = 0xC8; /* 当前计数值 */ ET0 = 1; EA = 1; TR0 = 1; while (1) { /* 主循环空转 */ } }模式2下初值0xC8对应计数56个机器周期,中断间隔56微秒,三角波频率约35Hz。把TH0往小了调,比如0x80对应中断间隔128微秒,但要注意中断服务函数本身要额外占用几十个微秒,初值太小时实际频率反而不线性,波形也会出现疏密不均。
5.2 Vref与Rfb配合的幅度微调
DAC0832的输出幅度由参考电压Vref直接决定。把Vref从5V降到2.5V,满量程输出就从约5V变成约2.5V,代码不用改。这个方法最简单,但会牺牲分辨率,本来255级对应5V,每级约19.6mV,降到2.5V后每级约9.8mV。
另一个方式是改第二级运放的增益。第一级输出固定为负向电压,第二级用R2/R1的比例控制翻转幅度,两个10k是1:1,换成10k输入加20k反馈就是2倍增益,输出变0到10V,但LM324在5V供电下输出会饱和,需要同步提高运放供电电压。调幅度时优先改Vref,运放增益只做精细调整。
本文还有配套的精品资源,点击获取