尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

AT89C51驱动DAC0832输出正弦波:Proteus仿真与源码详解 简介基于AT89C51与DAC0832的正弦波输出Proteus仿真工程面向单片机初学者、课程设计或电子竞赛备赛者旨在演示通过D/A转换芯片生成模拟正弦波的方法涵盖查表输出、定时更新、运放调理等关键环节。压缩包共14个文件仅45KB包含Proteus硬件仿真工程DSN/DBK/PWI、Keil工程配置UV2/LNP/OPT、C语言源代码以及编译产物HEX/LST/OBJ等可分别在Proteus 8.6与Keil中打开实现仿真运行与程序烧录验证。资源已获得2395人学习具有较高参考价值。读者可依据源码与电路图理解单片机控制DAC0832输出波形的寄存器设置、数据更新时序并延伸设计三角波、方波等任意波形仿真工程自带完整连接关系便于快速复现实验结果适合作为波形发生器模块的参考设计。1. AT89C51通过DAC0832输出正弦波为什么先在Proteus仿真里跑通裸板的 AT89C51 和 DAC0832 焊在一起示波器探针夹上去看到的往往是毛刺而不是正弦波——问题可能出在 I/V 变换电路没接运放也可能是 P0 口没加上拉电阻。Proteus 里把这套电路仿真跑通能把波形调试从「焊一次等一次」变成「改参数秒级验证」这是做单片机课程设计最省时间的一条路。这套方案要解决三件事DAC0832 的双缓冲/直通模式怎么选C 程序源码里的 256 点正弦表和定时器初值怎么算Proteus 示波器上看到的波形怎么核对频率、怎么排查异常。适合想从数字输出跨到模拟量输出的从业者照着一遍搭下来就能得到稳定的正弦波。2. DAC0832的电流输出与AT89C51的并口时序先把工作模式选对DAC0832 是 8 位乘法型 DAC输出是电流而不是电压。直接拿万用表量 Iout1 只能量到微安级电流必须接运放转成电压。这一章把输出公式、三种工作模式和 AT89C51 的写时序一次讲清后面仿真和写代码都依赖这里的接线决策。2.1 DAC0832 的 I/V 变换为什么必须接运放DAC0832 内部是 R-2R 梯形电阻网络数据 D 从 0x00 变到 0xFF 时Iout1 从 0 线性增大到 Vref/Rfb×255/256Rfb 是芯片内置反馈电阻典型值 15kΩ。把 Iout1 接运放反相输入端、Rfb 接回运放输出运放的虚地特性把电流全部引到反馈电阻上输出电压为 Vout -Vref×D/256。这里有两个坑要提前说。第一Vref 接 5V 时运放输出是 0 到 -5V 的反相摆幅运放负电源必须低于 0VProteus 里给 LM358 接 ±5V 或 ±12V 都行。第二如果只想用单运放做出 0~5V可以把 Vref 接 -5V但课程设计里更常见的是两级运放第一级反相第二级 1:1 再反相一次最终得到 0~4.98V 的正向正弦波。两级结构的好处是 Vref 直接用 5V和 AT89C51 共用一个电源省一路负基准。2.2 直通、单缓冲、双缓冲三种模式的接线表DAC0832 内部有两级锁存器输入锁存器由 ILE、CS、WR1 控制DAC 锁存器由 WR2、XFER 控制。按这五个引脚的接法分成三种模式对应关系如下表1 表示高电平0 表示低电平可控表示由软件或地址译码控制工作模式ILECSWR1WR2XFER适用场景直通10000快速验证信号发生器极简电路单缓冲10可控00单路正弦波输出本方案采用双缓冲1可控可控可控可控多路 DAC 分时写数、同步输出单缓冲模式是单片机输出正弦波的默认选择第二级 DAC 锁存器始终透明软件往数据线上写一个字节、给 WR1 一个低脉冲数据立刻出现在模拟输出端。双缓冲是为多路 DAC 同步更新准备的比如四路波形要同时换频先各自写进输入锁存器最后统一给 XFER 脉冲。单路正弦波用双缓冲只会多两条控制线没有收益。直通模式连线最少但数据线一变输出就跟着抖噪声大仿真里看看原理可以不推荐作为交付版。2.3 AT89C51 的 P0 口写出与 WR 脉冲含 XBYTE 写法AT89C51 的数据总线是 P0 口但 P0 是开漏输出驱动 DAC 数据引脚时必须外接 8 个 10kΩ 上拉电阻到 VCC否则高电平浮空。Proteus 里可以用一个排阻 RESPACK-8 代替 8 个独立电阻画图省事。控制线方面本方案把 CS、WR2、XFER 直接接地ILE 接 VCC只留 WR1 接 P3.6 由软件控制。需要注意 AT89C51 的 P3.6 本身就是 WR 信号脚如果不想手动翻转可以用 MOVX 指令自动产生时序#include absacc.h #define DAC0832 XBYTE[0x7FFF] /* 地址值无所谓CS 已接地 */ void dac_out(unsigned char dat) { DAC0832 dat; /* 编译成 MOVX 指令WR 自动拉低再拉高 */ }XBYTE[0x7FFF]把外部数据存储器地址映射到 P0/P2 口赋值语句编译成 MOVX硬件上自动产生写脉冲CPU 时序和 DAC0832 的写脉宽要求是匹配的。手动写法更直观先写 P0再翻转 WR1。但要注意低脉冲宽度两条相邻指令之间至少隔一个_nop_()避免高优化级别下脉冲被编译器吃掉。提示EA 引脚必须接 VCC程序才能从 AT89C51 内部 Flash 启动。Proteus 里忘记接 EA会出现单片机在跑但 DAC 永远没有数据的怪问题。3. C程序源码的查表法256点正弦表生成与定时器初值计算波形合成的核心不是数学是时序。这一章给出可以直接复制的 C 程序源码生成路径以及定时器初值的完整推导。有人用方波转正弦波的做法靠 RC 积分滤波把方波变成正弦缺点是一改频率幅值就跟着变DAC 查表法把频率和幅值解耦改表、改步长即可这正是本方案值得用的原因。3.1 为什么用 256 点查表而不是实时算 sin8051 没有硬件浮点Keil C51 里实时调用数学库算 sin一次要几十上百个机器周期放进中断里会把定时节奏拖乱。查表法把 256 个预先算好的点存在程序 ROM 里中断里只做一次读表和一次端口写。选 256 点有两个原因DAC0832 是 8 位一个点的数字量正好占一个字节查表下标也能用字节直接对上去另一个原因是 256 点每周期与 16 位定时器分频配合频率分辨率是整数关系方便后面做相位累加器变体。也有人用 512 点或 128 点。512 点波形更平滑但 8 位 DAC 的量化台阶只有 255 级多出来的点只是细分同一个台阶波形改善有限128 点则会在示波器上看得出明显折线。256 是 8 位系统的平衡点。3.2 用Python生成正弦表并粘贴进Keil C源码手敲 256 个数不现实也容易错。常见做法是在电脑上用脚本生成再整段粘贴进code数组。生成规则是幅度取 128±127中点为 128峰值 255谷值 1直流分量刚好落在 2.5V 附近又不满出量程。import math N 256 table [int(round(128 127 * math.sin(2 * math.pi * i / N))) for i in range(N)] for row in range(0, N, 16): print( , .join(f{v:3d} for v in table[row:row 16]) ,)脚本输出形如128, 131, 134, ...的十六个一组直接复制到 Keil 的数组初始化里。round之后建议顺手做一次 min/max 检查确认值落在 1~255 区间。放在code数组里越界不会报错但查表下标一旦越界输出就会出现突发毛刺这类问题在示波器上极难定位。3.3 定时器T0初值怎么算12MHz与11.0592MHz对照AT89C51 的机器周期是 12 个晶振周期。12MHz 晶振下 1 个机器周期是 1μs每周期 256 个点时目标频率 f 对应的每点间隔 T 1/(f×256)换算成机器周期数 N T/1μs定时器初值 65536 - N。以 100Hz 正弦波为例T 39.0625μs取 N39实际输出 100.16Hz误差 0.16%示波器上肉眼分辨不出来。晶振目标频率每点间隔N重装值实际频率12MHz100Hz39.06us390xFF, 0xD9100.16Hz12MHz50Hz78.13us780xFF, 0xB250.08Hz11.0592MHz100Hz39.06us360xFF, 0xDC100.00Hz要精确 100Hz 就换 11.0592MHz 晶振机器周期约 1.085μs每点 36 个机器周期256 点共消耗 256×36 个周期正好 10ms。选晶振这件事建议在画原理图前定下来Proteus 里改晶振很快但机器周期一变和时序相关的其他外设比如串口波特率都会跟着变。3.4 完整C程序源码初始化、中断服务函数与频率调整#include reg51.h #include intrins.h sbit WR1 P3^6; /* DAC0832 的 WR1低电平有效 */ code unsigned char sin_tab[256] { 128, 131, 134, 137, 140, 143, 146, 150, 153, 156, 159, 162, 165, 168, 171, 174, /* 3.2 节脚本生成的其余 240 个点整段贴进来 */ }; void dac_out(unsigned char dat) { P0 dat; WR1 0; _nop_(); /* 保证低脉冲宽度至少 1us */ WR1 1; } void timer0_isr(void) interrupt 1 { static unsigned char idx 0; TH0 0xFF; /* 12MHz 晶振100Hz重装 65536-39 */ TL0 0xD9; dac_out(sin_tab[idx]); if (idx 256) idx 0; } void main(void) { TMOD 0x01; /* T0 方式 116 位定时器 */ TH0 0xFF; TL0 0xD9; ET0 1; /* 开 T0 中断 */ EA 1; /* 开总中断 */ TR0 1; /* 启动 T0 */ while (1) { /* 主循环保持空转波形完全由中断驱动 */ } }几个参数说明interrupt 1是 T0 的中断号Keil 会处理现场保护和 reti 返回static unsigned char idx是静态局部变量退出中断后下标不丢。每次进入中断都要重写 TH0/TL0否则定时器溢出后从 0 重新计数中断周期会缩短输出频率会偏离计算值。改频率只动两处重装值和步长。这里的 idx 每中断加 1输出频率完全由中断频率决定改重装值即是改频率。4. Proteus仿真搭建元件连线、HEX加载与示波器验证波形仿真环境的价值在于能直接看波形。这一章从元件清单、连线到示波器读数把整套搭建步骤走通。Proteus 元件库里直接搜 AT89C51、DAC0832、LM358 都有不需要额外装第三方库。4.1 元件清单与原理图关键连线元件型号/值数量用途单片机AT89C511主控产生定时中断DACDAC08321数字量转模拟电流运放LM3581两级 I/V 变换排阻RESPACK-810kΩ1P0 口上拉晶振CRYSTAL 12MHz1时钟源电容30pF2晶振负载电容10uF1复位电路电阻10kΩ2第二级运放增益电阻电源VCC/VEE25V 与 -5V连线要点P0.0~P0.7 接 D0~D7P0 再接 RESPACK-8 到 VCCP3.6 接 WR1WR2、XFER、CS 接地ILE 接 VCCIout1 接 LM358 反相输入2 脚Iout2 接地Rfb 接运放输出1 脚第一级输出经 10kΩ 进第二级反相输入6 脚6 脚再经 10kΩ 反馈到输出7 脚3 脚和 5 脚接地。Vref 接 5VDAC0832 的 VCC 接 5VAGND 和 DGND 统一接地。注意LM358 在 5V 单电源下输出最多摆到约 3.5V正弦波顶部会削平。想看完整的 0~5V 摆幅给运放接 ±12V或者换 rail-to-rail 运放。Proteus 里运输放负电源脚不要直接接 GROUND要用 Power Rail 配置一路 -5V。4.2 Keil生成HEX与Proteus晶振频率设置Keil 里新建工程器件选 AT89C51把 3.4 节的 C 文件加入工程Options for Target 的 Output 页勾选 Create HEX File编译生成 HEX。然后回到 Proteus双击原理图上的 AT89C51在 Program File 里选择这个 HEX 文件。关键一步是 Clock Frequency 必须改成 12MHz——Proteus 默认是 11.0592MHz不改的话机器周期和 Keil 编译时假设的不一致输出频率会对不上计算值这是仿真波形频偏最常见的原因。启动仿真前检查电源Design → Configure Power Rails确认 VCC 默认 5V再给 LM358 的负电源加一路 -5V。如果直接用 GROUND 代替负电源第一级运放输出会饱和在 0V后面什么都看不到。4.3 示波器看正弦波异常排查与频率核对虚拟仪器面板里选 OSCILLOSCOPEA 通道接第二级运放输出GND 接系统地。100Hz 信号周期 10msTimebase 设 2~5ms/divVolts/Div 设 1V正常看到的是 0~5V 摆幅、中线约 2.5V 的正弦波带明显阶梯。把光标放在两个相邻波峰之间读周期与计算值比对。如果后面打算用 STM32 的 ADC 采集这个正弦波做闭环验证建议在第二级输出和 ADC 输入之间加一级 RC 低通截止频率取信号频率的 5 倍削掉阶梯高频。常见异常按症状排查只有负半周或上下翻转第一级运放输出 0~-5V 是正常的看第二级输出第二级仍为负多半是反馈电阻接成了同相放大检查 10kΩ 是否接在 6 脚和 7 脚之间。输出是水平线或 0V先量第一级运放输出有负向摆幅说明 DAC 和运放正常问题在第二级两级都没输出查 WR1 是否在翻转以及 P0 上拉排阻是否漏接。波形对但频率偏查 Proteus 晶振频率设置与 Keil 器件时钟是否一致。波形顶部削平LM358 摆幅到不了正电源轨运放供电改 ±12V。毛刺明显WR1 低脉冲太窄_nop_()后重试也可以把 TH0/TL0 重装操作挪到写 DAC 之前先重装再输出减少中断内的延时累积。频率核对用一段小脚本把设计值和仿真读数对起来f_crystal 12_000_000 # Proteus 里设置的晶振值 machine_cycle 12 / f_crystal # 秒 N 65536 - 0xFFD9 # 从重装值还原计数值 39 t_point N * machine_cycle f_wave 1 / (t_point * 256) print(f{f_wave:.2f} Hz) # 输出 100.16如果示波器光标读数接近这个值说明时序链路没有问题剩余误差就是查表离散化本身。5. 频率可调的相位累加器写法改一个变量就换输出频率5.1 为什么直接改步长会让波形变差3.4 节里 idx 每中断加 1要改频率只能改定时器重装值而重装值是整数100Hz 到 200Hz 之间没有中间档。有人直接把查表步长改成 2 或 3 来提频频率确实翻倍但每周期实际输出的点数从 256 掉到 128 或 85波形阶梯变大谐波明显变多。步长是整数频率分辨率被钉死这不是正弦波输出的理想手段。5.2 16位相位累加器的C程序改动常见做法是引入 16 位相位累加器高 8 位查表低 8 位做分数步进。中断频率先按 11.0592MHz 晶振、每点 36 个机器周期定为 25600 点/秒一个周期 256 点时基准频率正好 100Hz。输出频率等于 phase_step × 25600 / 65536unsigned int phase 0; /* 相位累加器 */ unsigned int phase_step 256; /* 100Hz: 256*25600/65536 */ void timer0_isr(void) interrupt 1 { TH0 0xFF; /* 11.0592MHz: 65536-36 */ TL0 0xDC; phase phase_step; P0 sin_tab[(unsigned char)(phase 8)]; }phase 8取高 8 位当查表下标累加器溢出时下标自动从 255 回到 0不需要手动回绕这比 3.4 节的if (idx 256)省了好几条指令。想输出 150Hz 就把 phase_step 改成 384想输出 123.4Hz 改成 316频率分辨率约 0.39Hz且每周期输出的点数保持 256 个波形质量不随频率劣化。中断里只用 16 位加法Keil 编译出来不过四五条指令。验证方法Proteus 示波器打开 Measurement 栏的 Frequency 显示phase_step 设 256 时读数 100Hz改成 384 立即跳到 150Hz——相邻档位切换时波形连续变化这就是相位累加器相对整数步长的优势也是把 AT89C51 通过 DAC0832 输出正弦波这套方案从固定频率推向可调信号源的最短路径。本文还有配套的精品资源点击获取
返回列表