基于STC89C51与DAC0832的DDS多波形信号发生器设计 简介这份基于STC89C51单片机的信号发生器毕业设计文档面向电子、自动化、通信等专业的本科生及课程设计、毕业设计参考者围绕低频函数信号发生器的完整实现展开。压缩包内仅含1个doc文档约505KB内容为结构完整的毕业设计报告涵盖摘要、设计任务与要求、总体方案论证、系统框图、单元电路设计及软件设计等章节。文档中给出模拟电路、锁相环、集成信号芯片与单片机配合DAC0832四种方案的比较并详细说明数模转换、低通滤波、键盘、电源及主控电路的设计要点还介绍了用MATLAB生成64点波形ROM表、通过外部中断切换波形与频率的方法可输出正弦波、方波、三角波、梯形波等最高频率约798.6Hz。目前已有169人学习下载适合需要参考硬件选型、电路连接、程序框架与论文写作结构的读者也可作为课程设计选题与答辩准备的素材。1. STC89C51 配 DAC0832 做信号发生器先想清楚走 DDS 还是走延时查表很多毕业设计的初稿都卡在同一个地方方波能用定时器翻转 IO 口出来正弦波一到调频就散架。用for循环加_nop_()延时输出一个正弦表点频率改一次就要重算延时次数多波形切换更是要推倒重来。STC89C51 只有 4KB Flash、128B RAM跑不动浮点但它有两个定时器和一个 8 位并口正好够撑起一套定点 DDS 信号发生器。DDS 的思路是把「出波形」和「定频率」拆开定时器只负责以固定采样率把表里的点送进 DAC频率由每周期累加一个频率控制字决定。改频率就是改一个 32 位整数方波、三角波、锯齿波只是换一张表和一段拼接逻辑软件代价几乎为零。这条路对 51 单片机的算力很友好也是多波形信号发生器设计里最常见的落地方案。2. STC89C51 最小系统与波形输出通道选型2.1 先盘清楚 STC89C51 的定时器和 IO 资源STC89C51 属于 8051 内核12 个机器周期/指令周期的经典结构部分型号支持 1T 模式但毕业设计里默认按 12T 算更保险。它有两个 16 位定时器 T0、T1四个 8 位准双向口 P0P3其中 P0 是开漏输出做并口数据总线时要外接 10kΩ 上拉排阻。DDS 方案里资源是这样分配的T0 负责产生固定采样率的中断T1 留给按键消抖或串口波特率P0 接 DAC 的 8 位数据口P2 的高位做地址译码产生 DAC 的片选P1 接 LCD1602 的数据线和按键。这样分配的好处是地址总线和数据总线复用MOVX指令一条就能把数据写进 DAC不用软件模拟时序。提示P0 口在访问外部存储器时会自动切换到地址/数据复用模式如果 DAC 就挂在 P0 上上拉排阻不能省否则高电平拉不起来输出波形顶部会被削平。2.2 DAC0832、R-2R 电阻网络、PWM 加 RC 滤波怎么选波形输出通道有三种常见做法选错了后面全是坑。下面这张表是实际做过对比之后的结论方案分辨率接口占用建立时间适合波形主要问题DAC08328 位P0 八根 CS/WR1μs正弦、三角、锯齿需要运放做 I/V 转换R-2R 电阻网络取决于电阻精度P0 八根与运放有关锯齿、阶梯波1% 电阻下线性度差台阶明显PWM RC 滤波取决于 PWM 位数1 根 IO取决于 RC 时间常数正弦、三角方波边沿被 RC 吃掉高频衰减严重DAC0832 的价值在于它是电流输出型配合一片 LM358 或 TL082 做 I/V 转换输出阻抗低、带负载能力强8 位分辨率在 256 点表下做 2Vpp 的正弦波完全够看。R-2R 网络省一颗芯片但电阻匹配误差会直接变成波形失真示波器上一眼就能看出台阶不均匀。PWM 加 RC 滤波则是「能用但不耐看」截止频率压在 1kHz 以下时方波基本废掉。对毕业设计来说DAC0832 是性价比和答辩效果的双重稳妥选择。2.3 最小系统电路与引脚分配2.3.1 晶振与复位电路的参数取值晶振选 12MHz主要图一个机器周期正好 1μs定时器初值算起来干净。晶振两端各接一只 30pF 瓷片电容到地负载电容偏大或偏小都会让起振变慢甚至不振。复位用经典的 10μF 电解电容加 10kΩ 下拉电阻上电时电容充电产生高电平复位脉冲。如果想加手动复位按键按键并联在电容两端即可。注意 STC89C51 内部有看门狗和复位电路但外部 RC 复位仍建议保留仿真实测都更稳。2.3.2 DAC0832 的工作方式与引脚连接DAC0832 内部有两级寄存器输入寄存器和 DAC 寄存器。三种工作方式里直通方式最省引脚但因为只用一级锁存写数据时输出会有瞬间毛刺单缓冲方式把 WR2 和 XFER 接地写一次就更新输出是毕业设计里最常用的折中。下面是单缓冲接法的引脚分配表单片机引脚连接到说明P0.0P0.7DAC0832 DI0DI78 位数据需 10kΩ 上拉P2.7DAC0832 CS片选低有效P3.6 (WR)DAC0832 WR1写信号低有效GNDWR2、XFER固定接地单缓冲5VILE输入锁存允许接高P1.0P1.7LCD1602 D0D7显示频率和波形类型P3.2、P3.3、P3.4按键调频 、调频 −、波形切换P3.5按键调幅DAC0832 的 IOUT1 接运放反相输入端IOUT2 接模拟地Rfb 引脚接运放输出端形成 I/V 转换Vref 接基准电压源。Vref 的精度直接决定输出幅值精度别用 5V 电源分压凑合分压电阻的温漂会让幅值随温度飘。3. DDS 相位累加器在 STC89C51 上的定点实现3.1 频率控制字 K f_out × 2³² / fs 的推导DDS 的核心是一个 32 位相位累加器。每次采样中断到来累加器加上一个固定的频率控制字 Kphase (phase K) 0xFFFFFFFF累加器的 32 位里高 8 位取出来当波形表的索引低 24 位是小数部分。每 2³²/K 次累加相位正好绕回一圈所以输出频率是f_out K × fs / 2³²反推频率控制字K f_out × 2³² / fs这个公式是整个设计里唯一需要仔细算的地方。取采样率 fs 20kHz则 2³²/20000 214748.36也就是每 1Hz 输出频率对应约 214748 个控制字单位。频率分辨率是 fs/2³² ≈ 4.66μHz对 51 单片机来说这种分辨率纯属过剩好处是频率可以做得非常准。输出频率控制字 K十进制控制字 K十六进制每周期输出点数1 Hz2147480x000346DC2000010 Hz21474840x0020C49C2000100 Hz214748360x0147AE14200500 Hz1073741820x06666666401000 Hz2147483650x0CCCCCCD202000 Hz4294967300x1999999A10每周期输出点数小于 10 以后正弦波的台阶会肉眼可见。用 256 点表加 20kHz 采样率正弦波实用上限大概在 1kHz 到 2kHz 之间方波因为只有两级电平可以做到 5kHz 以上。3.2 32 位相位累加器在 C51 里的加法开销51 是 8 位机32 位加法要拆成 4 字节带进位加。Keil C51 里直接写unsigned long phase; phase K;编译器会生成一串ADDC指令实测大约 30 到 40 个机器周期12MHz 晶振下就是 3040μs。20kHz 采样率意味着中断间隔只有 50μs留给中断服务的时间非常紧。如果中断服务里还要加if判断波形类型、做幅值缩放很容易超时。可行的做法有三个把采样率降到 10kHz把中断服务里所有判断提前算好用函数指针或查表索引或者干脆用 STC 的 1T 模式型号把指令周期压缩到 1/12。中断服务函数越短越好能放在主循环里的显示刷新、按键扫描绝对不要塞进中断。3.3 定时器 0 中断服务函数的完整 C51 代码先看定时器初值的核算。12MHz 晶振、T0 方式 116 位定时要 50μs 中断一次初值 65536 − 50μs / 1μs 65486 0xFFCE对应的初始化和中断服务代码如下#include reg51.h #define DAC_ADDR 0x7FFF /* P2.7 0 时选中 DAC0832 */ #define FS_HZ 20000UL /* 采样率 20kHz */ #define PHASE_MAX 4294967296.0 /* 2^32 */ unsigned char xdata *dac (unsigned char xdata *)DAC_ADDR; unsigned char code wave_tab[256]; /* 256 点波形表由工具生成 */ unsigned long phase 0; /* 32 位相位累加器 */ unsigned long freq_word 214748365UL; /* 默认 1kHz 的控制字 */ unsigned char amp 100; /* 幅值百分比100 表示满幅 */ /* 定时器 0 初始化方式 150us 中断 */ void timer0_init(void) { TMOD 0xF0; TMOD | 0x01; /* T0 方式 116 位定时 */ TH0 0xFF; /* 65486 高字节 */ TL0 0xCE; /* 65486 低字节 */ ET0 1; /* 允许 T0 中断 */ EA 1; /* 开总中断 */ TR0 1; /* 启动 T0 */ } /* T0 中断服务DDS 累加 查表输出 */ void timer0_isr(void) interrupt 1 { unsigned char idx; unsigned char sample; TH0 0xFF; /* 重新装初值必须先装减少中断延迟抖动 */ TL0 0xCE; phase freq_word; /* 32 位累加低 24 位是小数部分 */ idx (unsigned char)(phase 24); /* 取高 8 位做表索引 */ sample wave_tab[idx]; sample (unsigned char)(128 ((int)sample - 128) * amp / 100); *dac sample; /* MOVX 写8 位并口输出 */ }这里有三处细节值得说明。第一重装 TH0/TL0 放在中断服务最前面减少中断响应延迟带来的采样抖动如果放到最后下一次中断的间隔会随代码分支长度变化。第二phase 24取的是高 8 位正好对应 256 点表索引范围 0255不会越界。第三幅值缩放放在中断里做是有代价的一次 8×8 乘法和除法会吃掉几十个周期如果中断时间紧张就把幅值缩放挪到波形表本身用预先生成的多张表切换。写 DAC 的*dac sample;会被编译成MOVX DPTR, A或MOVX R0, A一条指令完成片选、写使能和数据传输这是把 DAC 挂在外部数据总线上的最大好处。4. 波形表生成、多波形切换与 2Vpp 幅值校准4.1 用 Python 生成 256 点正弦表并导出 C 数组手算正弦表不现实用一段脚本生成最省事import math N 256 lines [] for i in range(N): # 中心 128幅度 127覆盖 DAC 的 0~255 全量程 val 128 int(round(127 * math.sin(2 * math.pi * i / N))) val max(0, min(255, val)) # 防止取整越界 lines.append(0x%02X % val) # 每行 16 个方便在 Keil 里对齐查看 for i in range(0, N, 16): print( , .join(lines[i:i16]) ,)这段脚本输出的是unsigned char code数组内容直接粘进 C 文件即可。中心值取 128 而不是 127是为了让双极性输出的零点正好落在中值上避免输出波形带直流偏置。取整后用min/max夹一下因为round在峰值处可能给出 128127255 或 128−1271边界上不出错。4.2 方波、三角波、锯齿波的查表构造多波形信号发生器的关键不是生成四种表而是让四种表共用同一套 DDS 索引逻辑。下面是一段生成另外三种波形的脚本片段N 256 square [255 if i N // 2 else 0 for i in range(N)] sawtooth [i for i in range(N)] triangle [i * 2 if i N // 2 else (N - 1 - i) * 2 for i in range(N)]方波只有两个值严格来说不需要 256 点表但为了共用索引逻辑仍然铺成 256 点。锯齿波用 0255 线性增长一个周期正好走完全量程。三角波先升后降斜率是锯齿波的两倍。波形切换用一个索引变量选表首地址unsigned char code *wave_ptr; unsigned char wave_sel 0; void wave_switch(void) { switch (wave_sel) { case 0: wave_ptr sin_tab; break; case 1: wave_ptr square_tab; break; case 2: wave_ptr triangle_tab; break; default: wave_ptr sawtooth_tab; break; } }中断服务里改成sample wave_ptr[idx];就行。注意wave_ptr要声明成unsigned char code *因为表放在 Flash 里用普通指针访问会走数据空间读出来全是 0。4.3 从 DAC 满量程到 2Vpp 输出的幅值校准DAC0832 接成双极性输出时输出电压满足Vout Vref × (D − 128) / 128其中 D 是写入 DAC 的 8 位码Vref 是基准电压。要得到目标峰峰值 Vpp波形表里的峰值码 A_max 应该取A_max Vpp × 64 / Vref按这个公式算出来的常用配置如下目标 VppVref表内峰值码 A_max实际输出摆幅2 Vpp1 V128满幅±1 V2 Vpp2 V64±1 V2 Vpp5 V26±1 V1 Vpp1 V64±0.5 V3 Vpp2 V96±1.5 V从表里能看出Vref 越小需要的表内幅度越大但 DAC 的量化噪声占比也越小。1V 基准配满幅表的组合信噪比最好代价是基准源要稳。用 TL431 做 2.5V 基准再分压到 1V是成本可控的做法。如果要做软件调幅不要每次都重算整张表按 3.3 节代码里那样在中断里做一次缩放即可但要先把amp的取值范围限制在 0100并且在主循环里做按键增减不要在中断里改。注意调幅时如果直接改Vref运放的工作点会一起变可能出现波形上下不对称。软件缩放不会动工作点是更干净的做法。5. Proteus 仿真与示波器实测DDS 信号发生器的排错清单5.1 Proteus 里的仿真设置要点Proteus 元件库里搜DAC0832和LM358晶振用CRYSTAL复位用RES加CAP-ELEC。仿真前有两个地方必须改一是单片机的 Clock Frequency 设为 12MHz和真实晶振一致否则频率对不上二是把 DAC0832 的Vref接一个直流源别用电源符号直接接方便仿真时改值看幅值变化。虚拟示波器接在运放输出端时间基准先设 1ms/div扫一遍再收紧。如果波形显示为一条直线先查 DAC 的 CS 有没有被拉低再看wave_ptr是不是指向了数据空间。5.2 实测阶段最容易踩的五个坑现象可能原因排查动作波形顶部被削平P0 口没接上拉排阻万用表测 P0 高电平低于 3V 就是这个问题频率是设定值的两倍或一半定时器初值算错或中断重入检查 TH0/TL0 是否为 0xFFCE波形整体上移或下移运放零点偏移Rfb 阻值不匹配断开输入看运放输出是否在 0V高频段波形畸变采样率不足抗镜像滤波缺失在运放后再加一级 RC 低通截止频率取 2×fs/2切波形后无输出表指针指向了 data 空间确认指针声明带code关键字5.3 一个提升输出质量的实用技巧DAC 输出的是阶梯波每一级台阶的宽度是 1/fs。台阶能量集中在 fs 的整数倍附近如果不滤掉示波器上看正弦波会觉得「毛」。在 I/V 转换运放后面再加一级二阶 Sallen-Key 低通截止频率取 10kHz 左右对 1kHz 正弦的幅度影响在 0.5dB 以内但台阶感会明显消失。改滤波器的同时把采样率从 20kHz 提到 30kHz 是个更直接的收益。代价是中断服务时间被压缩到 33μs这时候必须把幅值缩放从中断里拿出去改成四张预缩放的表用按键切换表首地址。这样中断里只剩一次 32 位加法和一次查表写口12MHz 下实测中断占用约 28μs还有 5μs 余量示波器上波形的抖动会从肉眼可见降到几乎看不出来。本文还有配套的精品资源点击获取