
简介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.7ILEVCCCSGNDWR1WRWR2GNDXFERGNDIOUT1运放反相输入IOUT2GNDRFB运放输出VREF5VVCC5VAGND/DGNDGND说一下为什么CS和XFER都要接地。CS接地后输入寄存器常被选通每次WR上升沿数据可以进入XFER接地使DAC寄存器直通省去第二条写信号。这样做牺牲了双缓冲带来的同步更新能力但换来了最简单的驱动时序波形发生器足够用。2.2 输出电压与数字量之间的数学关系DAC0832是电流输出型DAC单独接在引脚上量不到稳定的电压。输出电流Iout1与数字量D成正比比例系数由Vref和内部梯形网络电阻决定。当RFB直接连运放输出端时运放输出电压近似为Vout - Vref * D / 256所以Vref5V时单极性输出范围是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; // 方式116位定时器 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/TL00xFFD9时定时长度是65536-6549739us对于12MHz晶振、12T模式机器周期1us所以中断周期正好约39us。后面会介绍怎样根据目标频率倒推初值这里只需要理解中断周期T的来源。3. Protues电路搭建从元件库到虚拟示波器3.1 元件清单与Protues里的搜索名称刚开始用Protues的人经常找不到DAC0832因为元件库搜索框里输入“DAC”会出来一大串而且很多元件名和数据手册缩写不一样。以Protues 8.x为例下面这个元件清单可以直接照抄。元件搜索关键字Protues器件名数量单片机AT89C51AT89C511DACDAC0832DAC08321运放LM358LM3581晶振12MCRYSTAL1按键BUTTONBUTTON1电阻10KRES若干电容33pFCAP2电位器POTPOT-HG1关于protues元件对照表很多新手会卡在运放找不着其实直接搜“LM358”就能看到双运放第二个运放用来把负极性输出翻转回正极性。电位器用来调节Vref旋转它可以改变输出波形幅度比改程序方便得多。3.2 最小系统接线与网络标号在Protues画电路时建议大量使用网络标号而不是走线。把单片机的P0口标成DB0~DB7DAC的D0~D7也标成DB0~DB7系统会自动识别连接关系电路图会干净很多。如果找不到运放的电源引脚双击LM358在属性里把Power Pins的Visible勾上或者用隐藏引脚连接VCC/GND。连接规则如下AT89C51的EA必须接VCC否则运行后单片机会去外部ROM取指程序无法执行这是常被忽略的坑。P0口接数据总线P0内部无上拉仿真中不接上拉也能运行但移植到硬件时建议加10k排阻。DAC0832的CS、WR2、XFER全部接地WR1接单片机WRILE接VCC。VREF接5V或电位器中间抽头电位器两端分别接5V和GND。IOUT1连接LM358第一个运放的反相输入端IOUT2连接同相输入端并将同相端接地RFB连接运放输出端。第二级运放的接法第一级输出是负电压把该输出接到第二级反相输入端第二级同相端接地反馈电阻10kΩ跨接在输出和反相端之间输入电阻也用10kΩ这样第二级增益为-1输出变为正压。下面给出网络标号对照表。网络标号连接到DB0~DB7P0口、DAC D0~D7DAC_OUT运放第一级输出WAVE_OUT第二级运放输出接示波器VREF_W电位器抽头、DAC VREF注意模拟地和数字地在Protues仿真里可以共地但实际硬件应该单点连接否则DAC的开关噪声会耦合到模拟输出端波形上会出现一组一组的小毛刺。3.3 加载HEX、验证通路与示波器设置Keil中编译出HEX后回到Protues。双击AT89C51打开属性对话框在Program File一栏选择编译好的DAC0832.hexClock 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到运放的连接全部正确。这个测试代码比直接P0128可靠因为后者不会真正产生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(f0x{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.3us0xFF3D50Hz78.1us0xFFB2100Hz39.1us0xFFD9500Hz7.8us0xFFF8从表里可以看到频率超过500Hz后T_us不足8us中断里要完成重装初值、查表、写P08us非常紧张。这时有两种调整方向一是换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; } }逻辑说明正弦波通过查表顺序输出表长度256idx清零用大于等于判断。方波半周期点数为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测量T10.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Ω电阻能明显降低振铃。这些细节不影响仿真结果却决定硬件能否一次跑出干净波形。本文还有配套的精品资源点击获取