Proteus仿真DAC0832波形发生器:从电路到C51实现
2026/9/12 17:40:11 网站建设 项目流程

简介:DAC0832波形发生器Proteus仿真项目,是一份面向电子工程学生与爱好者的DAC应用学习资源,演示如何利用DAC0832产生正弦波、方波与三角波。压缩包共21个文件,约50KB,涵盖Keil C源码(.c)、工程配置(.uv2/.opt)、生成目标文件(.hex/.obj)以及Proteus仿真电路(.dsn/.dbk),可完整还原开发与仿真环境。已有906人学习,资源小巧但内容完整。通过该仿真,读者既能观察实时波形输出,也能结合源码理解8位DAC的数字输入序列与模拟输出电压对应关系,适合在虚拟环境中反复调整频率、幅度,体会积分生成三角波等典型原理。

1. 为什么把DAC0832波形发生器搬进Protues仿真

DAC0832是一块8位并行DAC,在电子技术课程设计和电赛备赛中出镜率极高。很多人一开始选择搭面包板,但接错一根线就要排查半天,更别提手里没有示波器时只能靠万用表量电压,完全看不到波形形态。Protues里的DAC0832波形发生器仿真把单片机、DAC、运放和虚拟示波器放在同一块画布上,改程序、调电路和看波形在几分钟内就能完成一个闭环。压缩包里的DACWAVE1.DSN就是一个可以直接打开的工程,配套C51源码和HEX文件都已经生成好。无论是想搞懂查表法生成正弦波的细节,还是需要快速验证方波/三角波参数,这个仿真工程都是很好的起点。

2. DAC0832并行接口与波形生成的计算模型

2.1 DAC0832引脚与两种锁存方式

DAC0832内部是两个级联的8位寄存器:输入寄存器和DAC寄存器。外部通过ILE、CS、WR1控制输入寄存器,通过WR2、XFER控制DAC寄存器。这给了设计者三种工作模式。直通方式下输入数据直接进入DAC,构造最简单但容易受总线干扰;单缓冲方式用一条写信号锁存数据,适合单路波形输出;双缓冲方式适合多路需要同步输出的场合,例如三相波形发生器。项目中波形是单路输出,采用单缓冲方式即可。

下面这张表是仿真工程中实际采用的引脚连接方法,按照这个接法,MCU只要一个写信号就能刷新DAC。

引脚连接对象引脚连接对象
D0~D7P0.0~P0.7ILEVCC
CSGNDWR1WR
WR2GNDXFERGND
IOUT1运放反相输入IOUT2GND
RFB运放输出VREF+5V
VCC+5VAGND/DGNDGND

说一下为什么CS和XFER都要接地。CS接地后输入寄存器常被选通,每次WR上升沿数据可以进入;XFER接地使DAC寄存器直通,省去第二条写信号。这样做牺牲了双缓冲带来的同步更新能力,但换来了最简单的驱动时序,波形发生器足够用。

2.2 输出电压与数字量之间的数学关系

DAC0832是电流输出型DAC,单独接在引脚上量不到稳定的电压。输出电流Iout1与数字量D成正比,比例系数由Vref和内部梯形网络电阻决定。当RFB直接连运放输出端时,运放输出电压近似为:

Vout = - Vref * D / 256

所以Vref=+5V时,单极性输出范围是0~-5V。要得到正输出,后面再加一级反相放大,或者把IOUT2和运放同相端重新配置。Protues工程里默认用了双运放,第一级做I/V转换,第二级做极性翻转,把输出拉到0~5V。

理解这个公式对调整波形幅度很有用。D从0变化到255,输出近似线性地从0扫到参考电压,因此输出点位的计算可以用整数递增,不需要做浮点乘除,8位MCU也能跑得很轻松。

2.3 三种波形在时域上的映射

波形发生器的实质是把一个序列按时序送给DAC。方波是在两个端点之间跳变,比如先输出255持续N个中断,再输出0持续N个中断;三角波是从0递增到255再递减到0;正弦波则按照预生成表顺序输出。下表展示了三种波形的周期长度与频率公式。

波形一个周期的输出点数频率公式(中断周期为T)
方波2 * Nf = 1 / (2 * N * T)
三角波510f = 1 / (510 * T)
正弦波256f = 1 / (256 * T)

正弦波为什么固定256点?因为8位DAC的数据范围是0~255,用256个点刚好覆盖一个周期,索引值还能直接当成数据表的地址。三角波一个周期需要510个点,是因为从0到255共256次输出,从255回到0还要再递减254次,顶点只能出现一次,总计510点。方波点数N取决于高电平要持续多久,N越大频率越低。

提示:上表中的T是中断服务程序里两次更新DAC之间的时间间隔,由定时器初值决定。修改T可以连续调整三种波的频率。

2.4 定时器中断负责更新DAC

实时性要求较高的波形输出不能用主循环delay,否则按键消抖、状态切换都会让波形产生毛刺。常见做法是让定时器以固定周期产生中断,在中断里把下一个数据写入P0。

#include <reg51.h> #define DAC_PORT P0 void Timer0_Init(void) { TMOD |= 0x01; // 方式1:16位定时器 TH0 = 0xFF; TL0 = 0xD9; // 初始值,配合12MHz产生约39us中断 ET0 = 1; TR0 = 1; EA = 1; } void Timer0_ISR(void) interrupt 1 { TH0 = 0xFF; TL0 = 0xD9; // DAC_PORT = 某个波形值,稍后详述 }

这段代码的关键在于重装初值。如果不重装,定时器溢出后会自动从0开始,中断周期会变成超过65536us,远远偏离预期。TH0/TL0=0xFFD9时,定时长度是65536-65497=39us;对于12MHz晶振、12T模式,机器周期1us,所以中断周期正好约39us。后面会介绍怎样根据目标频率倒推初值,这里只需要理解中断周期T的来源。

3. Protues电路搭建:从元件库到虚拟示波器

3.1 元件清单与Protues里的搜索名称

刚开始用Protues的人经常找不到DAC0832,因为元件库搜索框里输入“DAC”会出来一大串,而且很多元件名和数据手册缩写不一样。以Protues 8.x为例,下面这个元件清单可以直接照抄。

元件搜索关键字Protues器件名数量
单片机AT89C51AT89C511
DACDAC0832DAC08321
运放LM358LM3581
晶振12MCRYSTAL1
按键BUTTONBUTTON1
电阻10KRES若干
电容33pFCAP2
电位器POTPOT-HG1

关于protues元件对照表,很多新手会卡在运放找不着,其实直接搜“LM358”就能看到双运放,第二个运放用来把负极性输出翻转回正极性。电位器用来调节Vref,旋转它可以改变输出波形幅度,比改程序方便得多。

3.2 最小系统接线与网络标号

在Protues画电路时建议大量使用网络标号而不是走线。把单片机的P0口标成DB0~DB7,DAC的D0~D7也标成DB0~DB7,系统会自动识别连接关系,电路图会干净很多。如果找不到运放的电源引脚,双击LM358,在属性里把Power Pins的Visible勾上,或者用隐藏引脚连接VCC/GND。

连接规则如下:

  • AT89C51的EA必须接VCC,否则运行后单片机会去外部ROM取指,程序无法执行,这是常被忽略的坑。
  • P0口接数据总线,P0内部无上拉,仿真中不接上拉也能运行,但移植到硬件时建议加10k排阻。
  • DAC0832的CS、WR2、XFER全部接地,WR1接单片机WR,ILE接VCC。
  • VREF接+5V或电位器中间抽头,电位器两端分别接+5V和GND。
  • IOUT1连接LM358第一个运放的反相输入端,IOUT2连接同相输入端并将同相端接地,RFB连接运放输出端。

第二级运放的接法:第一级输出是负电压,把该输出接到第二级反相输入端,第二级同相端接地,反馈电阻10kΩ跨接在输出和反相端之间,输入电阻也用10kΩ,这样第二级增益为-1,输出变为正压。下面给出网络标号对照表。

网络标号连接到
DB0~DB7P0口、DAC D0~D7
DAC_OUT运放第一级输出
WAVE_OUT第二级运放输出,接示波器
VREF_W电位器抽头、DAC VREF

注意模拟地和数字地在Protues仿真里可以共地,但实际硬件应该单点连接,否则DAC的开关噪声会耦合到模拟输出端,波形上会出现一组一组的小毛刺。

3.3 加载HEX、验证通路与示波器设置

Keil中编译出HEX后,回到Protues。双击AT89C51打开属性对话框,在Program File一栏选择编译好的DAC0832.hex,Clock Frequency填12MHz。点击左下角运行按钮,如果没反应,先暂停再检查程序是否加载正确。

如果想先确认单片机到DAC再到运放的整条链路是否通畅,可以用一个最小的写数程序做测试:

#include <reg51.h> #include <absacc.h> #define DAC_PORT XBYTE[0x0000] void main() { DAC_PORT = 128; // 写入128,期望输出约2.5V while(1); }

使用XBYTE[0x0000]把DAC当作外部RAM单元,CPU执行写操作时自动产生WR脉冲,DAC0832在WR上升沿锁存128。如果运放输出端在Protues里量到约2.5V直流,说明从P0、DAC到运放的连接全部正确。这个测试代码比直接P0=128可靠,因为后者不会真正产生WR信号。

虚拟示波器在左侧仪表工具栏里,图标是一个带正弦波的方形表。选好后放到原理图空白处,把WAVE_OUT连接到示波器A通道。打开示波器后,右键选择Set Scale,电压量程设为2V/div,时间量程根据频率调整:几十赫兹的波形用20ms/div,上千赫兹用0.5ms/div。示波器上如果是一条直线无变化,优先检查EA引脚和晶振配置;如果只在0V附近有小波动,说明运放没工作,检查LM358的VCC和GND是否接好。

4. 用Keil C51实现三种波形:查表生成与参数计算

4.1 用Python离线生成正弦波表

正弦波表如果手算,256个值太枯燥,而且最容易在最后几个点出现抄错。我习惯用一段短小的Python脚本生成,然后复制进Keil工程。

import math n = 256 with open("sine.h", "w") as f: f.write("const unsigned char code sine_table[256] = {\n") for i in range(n): v = int(round(127.5 * math.sin(2 * math.pi * i / n) + 127.5)) f.write(f"0x{v:02X}, ") if i % 16 == 15: f.write("\n") f.write("};\n")

这段脚本把正弦值从-127.5到+127.5移到0到255,正好对应8位DAC输入。数据表用code关键字存放,C51会把它放在ROM而不是RAM,因为片上RAM非常紧张。如果想把表改成余弦波或其它波形,只需替换嵌套里的表达式。注意取整使用round,最后一个点sin(2π)=0,映射到0x80,所以表首和表尾都是128。

4.2 定时器初值反推:以正弦波为例

目标波形频率f,每个中断输出一个点,正弦波每周期256点。12MHz晶振、12T模式下机器周期为1us。中断周期T_us = 1000000 / (f * 256),定时器初值就是65536 - T_us。常用频率对应的初值如下。

目标正弦波频率T_us定时器初值(TH0:TL0)
20Hz195.3us0xFF3D
50Hz78.1us0xFFB2
100Hz39.1us0xFFD9
500Hz7.8us0xFFF8

从表里可以看到,频率超过500Hz后T_us不足8us,中断里要完成重装初值、查表、写P0,8us非常紧张。这时有两种调整方向:一是换22.1184MHz等高频率晶振,二是减少查表点数,比如用128点表,频率可以提高一倍。

对于三角波,公式变成T_us = 1000000 / (f * 510)。如果要在100Hz下运行,T_us约19.6us,初值0xFFEC。方波则需要自己决定半周期点数N,频率f = 1 / (2 * N * T_us),实际调试时先用示波器量频率,再微调初值。

4.3 三种波形的中断服务程序

下面给出可以直接在Keil中编译的中断服务函数。外部按键切换逻辑没有写进去,只展示波形的更新核心。

#include <reg51.h> #include "sine.h" #define DAC_PORT P0 unsigned char mode = 0; // 0:正弦 1:方波 2:三角波 unsigned char idx = 0; unsigned char tri_val = 0; unsigned char tri_dir = 0; // 0:递增 1:递减 unsigned char sqr_cnt = 0; #define SQR_HALF 10 // 方波半周期点数 void Timer0_ISR(void) interrupt 1 { TH0 = 0xFF; TL0 = 0xD9; // 100Hz正弦波对应的初值 switch(mode) { case 0: DAC_PORT = sine_table[idx++]; if(idx >= 256) idx = 0; break; case 1: DAC_PORT = (sqr_cnt < SQR_HALF) ? 0xFF : 0x00; sqr_cnt++; if(sqr_cnt >= 2 * SQR_HALF) sqr_cnt = 0; break; case 2: if(tri_dir == 0) { if(++tri_val == 0xFF) tri_dir = 1; } else { if(--tri_val == 0x00) tri_dir = 0; } DAC_PORT = tri_val; break; } }

逻辑说明:正弦波通过查表顺序输出,表长度256,idx清零用大于等于判断。方波半周期点数为SQR_HALF,高电平期间输出0xFF,低电平期间输出0x00。三角波用tri_dir标志控制递增和递减,当tri_val到达0xFF后才允许方向反转,即使时序抖动也不会出现锯齿。注意三角波分支里是先改变值再判断边界,配合标志位可以避免溢出跳变。

如果要单独调整某个波形的频率,直接改TH0/TL0会影响所有波形,因为三种波共用同一个中断周期。实用做法是在中断里加入计数器,例如正弦波每两个中断才输出一个新点,等效于频率减半,但这样会引入相位抖动,工程上更推荐为每种波形准备独立的分频参数。

4.4 波形幅度与直流偏移的调节

DAC输出幅度主要由Vref决定。在Protues中把DAC0832的VREF从+5V改接到电位器中心抽头,就能连续调整波形幅度。如果希望输出在±2.5V之间摆动,可以在运放同相输入端叠加一个偏置电压,或者在数字表里先对每个输出值做偏移处理。

数字域偏移最省硬件:把正弦表值先减去128,再经DAC转换,就相当于把单极性波形变成双极性。但要保持0~255范围,生成表时直接把[-128,127]映射成[0,255],这样输出端依然从0扫到满幅,后续不依赖硬件偏置,改起来也最直观。

5. 用虚拟示波器校准频率和扩展任意波形

5.1 利用示波器的测量光标校准实际频率

Protues虚拟示波器虽然没有真实示波器精细,但测量光标的读数足够用来判断频率误差方向。操作方法是:暂停仿真,在示波器波形上点击右键,选择Cursor或Measure,把两条光标分别移到相邻两个波峰上,读取时间差即为周期。例如目标正弦波100Hz,测量T=10.08ms,实际频率为99.2Hz,说明定时器初值偏小,可以把0xFFD9改成0xFFDA,再跑一次看是否收敛。用这个方法校准初值比单纯靠公式估算可靠,因为Protues里晶振仿真和中断插入也会带来微小偏差。

注意虚拟示波器在暂停后波形可能偏移,建议先让仿真运行几秒再暂停,等波形稳定后再测量。如果波形漂移厉害,先用示波器的Single模式抓一段稳定波形再测。

5.2 把查表模式扩展成任意波形信号源

现有正弦波、方波、三角波的实现已经覆盖了DAC0832最常见的用法。要扩展成任意波形源,只需把sine.h替换成目标波形的一个周期数据,例如用500点保存一帧心电信号,把中断更新点数和表长度改成一致即可。甚至可以把表放在外部EEPROM或由串口动态写入RAM,这样运行中还能换波形,变成一个简单的函数信号发生器。

5.3 把仿真工程移植到真实硬件时的三个重点

移植时最容易被忽视的是DAC0832的输出电流能力。仿真里运放输入阻抗再低也能驱动,但真实硬件应选择输入偏置电流小的运算放大器,比如OP07或者TL081。另外DAC0832的供电最好单独用LDO,不要把数字3.3V直接拿给模拟电路使用。最后是Protues里的总线连接太理想化,真实PCB上数据线尽可能等长,并串入22Ω电阻,能明显降低振铃。这些细节不影响仿真结果,却决定硬件能否一次跑出干净波形。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询