拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AT89C51驱动DAC0832输出正弦波:Proteus仿真与C代码详解

简介面向单片机初学入门者与电子设计爱好者实例基于AT89C51与DAC0832构建正弦波输出电路配套Proteus仿真源文件与C语言程序可在Proteus 8.6中直接打开和仿真运行直观展示D/A转换、查表法生成正弦波以及运放OP07信号调理的整体流程既适合课堂教学演示也适合课程设计或自学参考。压缩包共14个文件包含Proteus原理图.dsn、Keil工程源文件.c、可烧录的HEX文件以及调试辅助文件目录结构清晰方便对照程序与硬件图逐行理解。整个资源包仅45KB轻量易得不占用存储空间。目前已有2395人学习下载经众多使用者验证可正常仿真。借助这套资料读者能快速掌握单片机控制DAC输出波形的要点学习正弦波数据表的编写方法并借鉴工程中的仿真配置思路为后续完成函数信号发生器、波形发生类设计打下基础。1. 先看结果AT89C51 通过 DAC0832 输出正弦波的最小闭环单看原理AT89C51 给 DAC0832 循环送 256 个数据就能得到正弦波但很多人在 Proteus 里搭完电路示波器上要么是一条直线要么只有正向半周。问题往往不在查表程序而在 VREF 接法、运放供电和 P0 口上拉这三处。这篇内容围绕 AT89C51、DAC0832、Proteus 仿真和 C 程序源码展开适合正在做 51 单片机课程设计的学生也适合需要把数字波形转成模拟量输出的嵌入式开发。接下来先讲清楚 DAC0832 的接口怎么选再给出可直接编译的 C 程序最后落实到 Proteus 搭建和示波器验证让你照着做能跑出一个可测的正弦波。2. DAC0832 引脚与接口选择AT89C51 数据口和直通方式的连线细节2.1 DAC0832 的电流输出原理与 VREF 方向DAC0832 是 8 位并行乘法型 DAC内部是 R-2R 电阻网络核心输出是电流而不是电压。IOUT1 和 IOUT2 是一对互补电流输出满足 IOUT1 IOUT2 满幅电流的关系。外部接运算放大器把电流转换成电压这是整个电路里最容易出错的一环。需要特别关注的是 VREF 基准电压方向。VREF 接 5V 时IOUT1 的电流方向会让运放输出负电压VREF 接 -5V 时运放输出正向电压。对于 Proteus 仿真最常见的做法是 VREF 接 -5V运放输出得到 0 到 5V 左右的正向正弦波这样示波器看起来直观模数转换后续处理也方便。直通、单缓冲、双缓冲三种工作方式对应不同的控制时序。作为单个正弦波发生器数据更新没有跨芯片同步需求用直通方式最省事CS、WR1、WR2、XFER 全部接地ILE 接高电平。此时 DAC0832 内部两个锁存器都处于透明状态AT89C51 的 P0 口写入什么数字量DAC 立即转换对应电流不需要额外写控制时序。2.1.1 为什么输出侧必须接运放如果不接运放IOUT1 直接悬空或接电阻测量到的电压不稳定且带载能力极差因为 DAC0832 输出的是电流型信号。标准接法是运放构成 I-V 转换电路IOUT1 接运放反相输入端IOUT2 接地DAC0832 的 RFB 反馈电阻引脚接运放输出端。RFB 引脚在芯片内部已经连到 R-2R 网络的反馈电阻接反或漏接都会导致输出饱和波形削顶甚至变成直线。2.2 Proteus 元件选型从 AT89C51 到 LM358 的完整物料清单在 Proteus 里做这个仿真元件选择并不复杂。AT89C51 直接搜元件名DAC0832 也在常用元件库里运放我一般用 LM358Proteus 元件库里数量充足而且支持单电源或双电源供电。再用一个 RESPACK-8 排阻给 P0 口做上拉外加 5V、-5V 和 GND 电源端子。元件Proteus 搜索关键字数量用途单片机AT89C511主控通过 P0 口输出 8 位数据D/A 转换器DAC08321数字量转模拟电流运算放大器LM3581电流转电压只用一个运放排阻RESPACK-818 个 10k 上拉电阻电源端子5V、-5V、GND若干单片机、运放、VREF 供电连线时注意 AT89C51 的 EA/VPP 引脚要接 VCC确保执行片内程序存储器。单片机的 XTAL1 和 XTAL2 在 Proteus 里通常由元件属性的晶振频率设置管理不需要手动搭晶振电路。这一点经常被忽略导致仿真运行时程序不执行。2.3 P0 口上拉电阻与数据总线连接的常见错误AT89C51 的 P0 口是开漏结构高电平输出能力几乎为零。直接把 P0 接 DAC0832 的 DI0 到 DI7在仿真里可能碰巧能跑但遇到负载较重或布线较长时数据线上的高电平会被拉低输出波形出现随机毛刺。标准做法是在 P0 和 VCC 之间接 8 个 10kΩ 上拉电阻Proteus 里用排阻 RESPACK-8 一次解决。/* 数据总线与 DAC0832 控制引脚定义直通方式 */ #include reg51.h #define DAC_DATA P0 // 8 位数据输入接到 P0 // 直通方式CS、WR1、WR2、XFER 直接接地ILE 接 5V // 程序不需要操作控制引脚往 P0 写数据即完成一次 DAC 转换 void DAC_Init(void) { DAC_DATA 0x00; // 上电先把输出拉到最低电平 }这段初始化代码只做了两件事把 P0 口定义成 DAC 数据总线然后上电写一个 0 保证输出不处在随机状态。直通方式下 DAC0832 的控制引脚全部固定电平所以程序里看不到 CS、WR 相关的位操作这是直通方式相比双缓冲方式在代码上最大的简化。如果你的 proteus 仿真连线里出现了 WR1 接单片机 WR 引脚的情况那就不是直通方式而是单缓冲方式需要额外考虑写时序初学阶段不推荐。提示P0 口上拉电阻对波形质量影响很大。去掉上拉后高电平可能只有 2V 左右转换出的正弦波峰峰值明显偏低。3. C 程序源码正弦波查表、延时控制和频率换算3.1 用 Python 生成 256 点正弦波数据表正弦波的核心思路是查表把一个周期的正弦函数离散成 N 个采样点每个点对应一个 0 到 255 的数字量单片机循环把这些数字量送到 DAC0832。点数越多波形越平滑但数据表占用程序存储器空间也越大。我用 256 点兼顾波形质量和 8 位 DAC 的分辨率。手工把 256 个数敲进代码不现实常见做法是用脚本生成。下面这段 Python 脚本输出的是可以直接粘贴进 Keil C51 的十六进制数组。# 生成 256 点正弦波数据表供 Keil C51 的 unsigned char code 数组使用 import math points 256 for i in range(points): # 128 为直流偏置127 为峰值保证结果落在 0~255 value int(128 127 * math.sin(2 * math.pi * i / points)) print(f0x{value:02X},, end) if (i 1) % 16 0: print()脚本的逻辑很直接对每个索引 i计算 sin(2πi/256)映射到整数范围。128 是直流偏置127 是幅度这样输出数字量范围是 1 到 255 而不是 0 到 255避免最低点输出 0 导致运放摆幅提前饱和。生成结果每 16 个换行方便直接粘贴到 C 语言数组里。3.2 Keil C51 主循环查表输出与可调延时C 程序源码的主结构比想象中短。数据表用code关键字存放在程序存储器主循环里不断取数、送 P0、延时构成一个周期波形输出。#include reg51.h // 256 点正弦表每个点对应一个数字量P0 直接送出 unsigned char code sin_table[256] { 0x80, 0x83, 0x86, 0x89, 0x8C, 0x8F, 0x92, 0x95, // 完整表由上面 Python 脚本生成后粘贴到这里 }; // 简单软件延时延时常数 n 决定采样间隔 void delay(unsigned int n) { while (n--); } void main(void) { unsigned int idx 0; while (1) { P0 sin_table[idx]; // 输出当前采样点的数字量 idx (idx 1) 0xFF; // 查表索引循环 0~255 delay(10); // 改变延时常数可改变正弦波频率 } }idx 0xFF的作用是让索引在 0 到 255 之间循环等效于idx % 256但按位与的运算速度更快。delay(10)是采样间隔调整入口这个值越大两个采样点之间的时间越长正弦波频率越低。如果示波器上波形频率不符合预期优先修改这一行。3.3 频率怎么算延时、晶振与查表步进的关系输出频率由采样间隔和查表点数共同决定。一个完整周期要输出 256 个点所以输出频率 f 1 / (T_sample × 256)其中 T_sample 是每输出一个点的间隔时间。软件延时方式下T_sample 由延时函数和循环中的指令周期决定很难精确计算但可以估算。延时常数 delay()近似采样间隔估算输出频率1约 5 μs约 780 Hz10约 20 μs约 195 Hz50约 100 μs约 39 Hz100约 200 μs约 19.5 Hz上表基于 12MHz 晶振估算实际值以 Proteus 示波器光标测量为准。如果你需要精确的 50Hz 或 1kHz 输出软件延时很难调准这是后面用定时器中断的原因。另一个常见误用是把 P0 直接当作电压值理解DAC0832 输出的数字量是线性映射但实际电压还受 VREF 幅度和运放增益影响别指望数字量 255 一定等于 5.000V。4. Proteus 仿真搭建与示波器验证4.1 Proteus 8 Professional 中放置元件与连线的顺序打开 Proteus 8 Professional新建原理图后先放元件再连线。连线的顺序有讲究先搭单片机最小系统再接数据总线最后接 DAC 模拟输出部分这样排查问题时可以分段确认信号通路。元件放置完成后连线清单如下表。按这个顺序接完基本不会漏线。连接对象起点终点说明数据总线P0.0~P0.7DAC0832 DI0~DI78 位并行数据排阻P0.0~P0.75VP0 上拉直通控制CS、WR1、WR2、XFERGND直通方式接地直通控制ILE5V高电平有效基准电压VREF-5V决定输出极性I-V 转换IOUT1LM358 反相输入接运放负输入端I-V 转换IOUT2GND互补电流接地反馈RFBLM358 输出内部反馈电阻电源LM358 VCC5V正电源电源LM358 VEE-5V负电源示波器运放输出Oscilloscope A 通道观察波形电源部分要特别注意LM358 如果只接 5V 和 GND输出无法靠近 0V正弦波底部会被切掉。给 VEE 接 -5V 后运放才能完整输出 0 到 5V 范围波形。DAC0832 的 VREF 也要单独用 -5V 电源端子不能和 GND 混用。双击 AT89C51 元件在属性对话框里确认 Crystl Frequency 是 12MHz。Proteus 仿真 51 单片机时晶振电路由仿真内核自动处理不需要再连外部晶振。如果单片机不工作第一件事检查这里。4.2 示波器接法与显示参数调整示波器从 Proteus 左侧工具条的 Virtual Instruments 菜单调出选择 Oscilloscope。A 通道接 LM358 输出端运放输出端引线靠近示波器探针位置即可不需要额外端接电阻。运行仿真后如果波形只在示波器上一条细线可能是时基设置不对。点击示波器面板上的按钮调整时间轴先从 5ms/div 开始看再根据实际波形缩放到合适档位。电压档选 1V/div 或 2V/divVREF 接 -5V 时正弦波应该在 0 到 5V 之间摆动。// DA 通道自检让 P0 交替输出全 0 和全 1 // 正常时运放输出应约为 0V 和 5VVREF -5V void DA_SelfTest(void) { while (1) { P0 0xFF; delay(100); P0 0x00; delay(100); } }这段程序是排错时的第一步。如果 P0 输出 0xFF 和 0x00 时示波器上电压没有变化说明硬件链路有问题与查表程序无关。如果电压变化范围只有 1V 不到几乎可以断定 P0 上拉电阻缺失或 VREF 电压没加上。4.3 常见仿真现象只亮不输出、负半周缺失、波形台阶仿真中常见的现象就那么几类逐一排除比盲目改代码有效。现象常见原因处理位置示波器无波形输出接近 0V 直线VREF 未接 -5V或 LM358 缺负电源检查 VREF 和运放 VEE输出恒为接近 5V 直线P0 一直输出 0xFF或查表索引没更新单步执行确认 idx 是否变化只有正半周或底部明显削平LM358 单电源供电无法输出接近 0V 电压VEE 接 -5V波形是明显台阶而非平滑曲线采样点数太少或延时过大确认用 256 点表调小时基波形毛刺多仿真步长太小或数据总线干扰增大仿真步长检查上拉P0 口上拉缺失的表现比较隐蔽波形频率还在但整个波形下移且幅度偏小因为高电平达不到 5V。把 RESPACK-8 换成 4.7kΩ 或 10kΩ 都可以Proteus 仿真里差异不大实际硬件选 4.7kΩ 到 10kΩ 之间都不用改代码。负半周缺失的另一个原因是 VREF 接反成 5V此时输出是负向波形Proteus 里电压源极性接反很常见注意看电源端子标注。注意Proteus 中 DAC0832 的 IOUT1 和 IOUT2 不能交换。交换后反馈回路仍能出波形但幅度会明显异常且无法通过调整 VREF 修正。5. 进阶技巧用定时器中断把正弦波频率做准5.1 定时器初值计算与中断查表输出软件延时的频率受编译器优化和循环开销影响做演示足够但要输出 50Hz 工频或 1kHz 标准信号就不够看。改用定时器中断后采样间隔由硬件定时器控制频率稳定性好得多。思路是在中断服务函数里输出一个查表点中断频率等于采样频率输出正弦波频率 中断频率 / 256。以 12MHz 晶振为例机器周期 1μs。要输出 50Hz 正弦波采样周期 T_sample 1 / (50 × 256) 78.125μs取整 78μs。定时器方式 1 是 16 位计数初值 65536 - 78 65458换算成十六进制就是 0xFFB2。#include reg51.h unsigned char code sin_table[256] { /* 数据表同前 */ }; unsigned int idx 0; // 定时器 0 中断每次中断输出一个采样点 void timer0_isr(void) interrupt 1 { TH0 0xFF; // 重装初值高 8 位 TL0 0xB2; // 重装初值低 8 位对应 78μs 采样周期 P0 sin_table[idx]; // 输出当前点 idx (idx 1) 0xFF; // 索引循环 } void main(void) { TMOD 0x01; // 定时器 016 位定时模式 TH0 0xFF; // 初始初值 TL0 0xB2; ET0 1; // 使能定时器 0 中断 EA 1; // 使能总中断 TR0 1; // 启动定时器 while (1); // 输出交给中断处理 }中断服务函数里的 TH0、TL0 重装不能省略。定时器溢出后不重装下一次周期会变成 65536μs输出频率和预期完全不同。主循环留空所有波形输出都在中断上下文完成。常用频率对应的初值如下直接换数就能用。目标频率采样周期TH0TL020 Hz195 μs0xFF0x3D50 Hz78 μs0xFF0xB2100 Hz39 μs0xFF0xD9200 Hz20 μs0xFF0xEC5.2 加一级 RC 低通滤波改善波形毛刺DAC0832 输出的本来就是台阶波256 点 8 位分辨率下台阶肉眼可见。想看到更平滑的正弦波在运放输出后面加一级 RC 低通滤波。我一般取 R 1kΩ、C 0.1μF截止频率 f 1 / (2πRC) ≈ 1.6kHz可以滤掉高频台阶分量又不影响几百赫兹以内的正弦波。如果输出频率超过 1kHz把 C 改小到 0.01μF避免把信号本身也滤掉。加了 RC 滤波后输出幅值会有衰减带负载能力也变差。要求高的时候在 RC 后面再接一个运放做电压跟随器保持波形幅度稳定。用示波器验证时把通道耦合切成 AC滤掉直流偏置后能看到以 0V 为中心的正弦波形此时更容易对比不同 RC 参数对波形平滑度的影响。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门