51单片机+DAC0832波形发生器设计:查表法与定时器中断

发布时间:2026/11/5 4:37:16
51单片机+DAC0832波形发生器设计:查表法与定时器中断

简介:“波形发生器.zip”是一份面向单片机学习者与电子设计入门者的实践资料,围绕以MCU为核心、DAC0832数模转换为输出的信号发生方案,覆盖正弦波、方波、三角波与锯齿波的生成思路,并涉及频率、幅度和占空比调节以及按键交互、显示等环节,适合课程设计、实训与实验室测试场景。压缩包共29个文件,约15.05MB,包含C源码、Keil工程与HEX文件、Altium原理图和PCB文件、Proteus仿真图,以及实训报告、预设计文档和演示PPT等,既有可编译烧录的程序,也保留了从电路设计到仿真验证的完整链路。已有403人学习,便于读者对照源码理解查表与波形算法、借助仿真观察DAC输出,并通过报告与PPT梳理设计过程和调试要点。

1. 从一台示波器上要看到四种波形:这套波形发生器方案解决的是什么问题

需求很具体:实验台上要一台能自己输出正弦波、方波、三角波和锯齿波的小信号源,频率在 1Hz 到 1kHz 之间连续可调,幅值分档可调,探头接上去波形要稳、要干净。市售信号发生器价格不低,体积也大,而用一片单片机加一颗 DAC0832 搭出来的波形发生器,成本压到几十块,还能把每个采样点、每一次中断都展开来看。

这套方案适合正在做单片机课程设计、电子类毕业设计的人,也适合想把「数字量怎么变成模拟波形」这条链路完整走一遍的从业者。它不追求射频级指标,追求的是从波形数据表、定时器中断、DAC 电流输出到运放电压输出这一段,每一环都能讲清楚为什么这么定。

2. 选型与总体架构:三条路线比完,为什么落到 51 单片机加 DAC0832

动手画原理图之前,先把「用什么产生波形」这件事定下来。这一步定错,后面所有电路和代码都得推倒。常见的三条路线摆在桌上:专用 DDS 芯片、单片机 PWM 加 RC 滤波、单片机并行接口挂 DAC。三者不是谁先进谁落后,而是各自服务的场景不同,课程设计和毕业设计要的是链路可见、每一级可测、成本可控。

2.1 三条技术路线的对比与取舍

专用 DDS 芯片(比如 AD9833 这一类)内部集成了相位累加器和 10 位 DAC,频率分辨率能做到 0.1Hz 级别,输出正弦波非常干净。代价是它靠串行寄存器配置,上电后要写一串控制字,出了问题只能靠示波器反推,对刚接触时序的人不太友好;而且它输出的正弦波是芯片内部算好的,你没法在中途改某个采样点,教学上「看得见过程」这一条就丢了。

PWM 加 RC 滤波的路线最省钱,一个定时器加两个阻容元件就能出货。但 51 单片机的定时器是 8 位自动重装或者 16 位手动重装,要做 8 位分辨率的 PWM,载波频率会被压得很低。载波低了,RC 滤波之后残余纹波就大,方波的上升沿也被电容拖成斜坡,示波器上一眼就能看出「这不是方波」。

并行 DAC 这条路的优势在于直白。DAC0832 是 8 位电流输出型数模转换器,建立时间 1μs,数据口直接挂到单片机的 P0,写一条指令就出一个采样点。数字量到模拟量的转换发生在一条指令之内,中间没有协议、没有寄存器、没有隐藏状态。哪一级出问题,万用表或示波器探一下就知道。

对比项专用 DDS 芯片PWM + RC 滤波单片机 + DAC0832
成本较高最低低
频率分辨率很高受定时器限制取决于定时器与点数
波形质量好纹波明显取决于滤波与点数
过程可见性低中高,逐点可测
适合场景产品级信号源对波形要求不高的场合课程设计、毕业设计

选 DAC0832 不是因为它性能最好,而是因为它把「一个采样点怎么变成一段电压」这件事完整暴露在你面前。这对要写设计说明书、要在答辩现场讲清楚原理的人,价值比多一位分辨率大得多。

2.2 系统总体架构:三层结构与四条数据通路

整套系统按职责切成三层,层与层之间只有明确的数据流,不做交叉调用。

人机交互层在最上面,由四个独立按键和一块 LCD1602 组成。按键负责波形切换、频率加减、幅值档位切换;LCD1602 负责把当前波形名、频率值、幅值档位显示出来。这一层只产生「参数变更事件」,不碰任何波形数据。

控制运算层是 51 单片机,也是整套方案的大脑。它内部维护三样东西:四张波形数据表、当前频率对应的定时器重装值、当前幅值对应的缩放系数。定时器中断按固定间隔触发,每次触发从表里取一个点送到 P0 口。

模拟输出层从 DAC0832 开始。P0 口上的 8 位数字量进入 DAC,转换成一对互补电流,运放把电流转成电压,再经过一级 RC 低通滤波和电压跟随器,最后从 BNC 或者排针送出去。

数据通路可以顺着走一遍:按键按下 → 参数变量更新 → 重装值重新计算 → 定时器按新节奏中断 → 中断里按相位指针读表 → 表值经幅值缩放后写 P0 → DAC 输出对应电流 → 运放转电压 → 滤波平滑 → 示波器看到波形。这四条通路里任何一环断了,现象都不一样,后面排查章节会逐条对应。

2.3 参数预算:先把定时器的余量算清楚再写代码

这一步最容易跳过,也最容易在后面返工。输出频率、每周期采样点数、采样率三者是绑死的:

采样率 = 输出频率 × 每周期采样点数 采样间隔 = 1 / 采样率

假设目标最高输出 1kHz,每周期取 64 个点,采样率就是 64kHz,采样间隔 15.6μs。12MHz 晶振、12T 模式下,一个机器周期正好 1μs,也就是中断服务程序必须在 15 个机器周期内跑完。而 51 的一次中断响应本身就要压栈、跳转,进出加起来接近 10 个机器周期,再加上重装定时器、读表、写端口、指针自增和边界判断,实际开销在 25 到 35 个机器周期之间。15 个周期的预算根本装不下。

所以参数必须往回收。可行的组合有三种:把最高频率压到 200Hz,64 点采样对应 12.8kHz 采样率,采样间隔 78μs,余量充足;或者保持 1kHz 但把每周期点数减到 16 点,波形会明显阶梯化;或者换成 1T 模式的单片机,一个计数周期变成 83ns,余量一下子拉开。

这套方案选第一种:频率范围 10Hz 到 200Hz,每周期 64 点。示波器上看正弦波已经足够平滑,中断余量也留得下。后面如果换 1T 芯片,只需要改重装值计算里的分频系数,上层代码不动。

提示:参数预算这一步不要拍脑袋,把「中断服务程序的机器周期数」当成硬约束写进设计文档,答辩时被问到「为什么最高只做到 200Hz」,这就是标准答案。

3. 硬件电路设计:从 DAC0832 的电流输出到一段能用的电压波形

数字部分只负责产生 0 到 255 的数字量,真正决定波形好不好看的是模拟这一小段。DAC0832 出来的是电流,不是电压,中间少了运放这一级,示波器上什么也看不到。这一章按信号流向把电路拆成四段:DAC 工作方式、I/V 转换、低通滤波、人机接口与供电。

3.1 DAC0832 的三种工作方式与接线选择

DAC0832 内部有两级锁存器:第一级是输入寄存器,由 CS 和 WR1 控制;第二级是 DAC 寄存器,由 WR2 和 XFER 控制。两级怎么组合,决定了三种工作方式。

直通方式把所有控制脚全部接地,输入寄存器和 DAC 寄存器都处于常通状态,P0 口上的数据一变,输出电流立刻跟着变。接线最简单,但总线上的任何波动都会直接串到模拟输出上。

单缓冲方式把 WR2 和 XFER 接地,只让第一级锁存器受控,CS 接地址译码、WR1 接单片机写信号。每写一次 P0,数据被锁存一次再送进 DAC 寄存器,输出相对稳定。

双缓冲方式两级都受控,主要用在多路 DAC 需要同步更新的场合,单路波形发生器用不上。

这套方案选单缓冲。接线表如下:

DAC0832 引脚连接对象说明
DI0~DI7P0.0~P0.78 位数据口,P0 需外接 10k 上拉排阻
CSP2.7片选,低有效
WR1单片机 WR写信号
WR2GND第二级常通
XFERGND传送信号常通
ILEVCC输入锁存允许,高有效
VREF-5V参考电压,决定输出范围
IOUT1运放反相输入端电流输出主路
IOUT2GND电流输出副路
Rfb运放输出端内部反馈电阻,15kΩ

P0 口作为数据总线用,必须加上拉排阻,否则高电平驱动能力不够,DAC 收到的数据会出错。这一点在很多原理图上被省略,实际焊出来就会出现「某些数字量对应的电压偏低」的怪现象。

3.2 电流输出到电压输出:I/V 转换与量程换算

DAC0832 的输出电流满足:

IOUT1 = (VREF / REXT) × (D / 256)

其中 D 是 0 到 255 的数字量,REXT 是内部参考电阻,典型值 15kΩ。这个电流流进运放的反相输入端,配合反馈电阻 Rfb 转成电压:

VOUT = -IOUT1 × Rfb = -VREF × (Rfb / REXT) × (D / 256)

DAC0832 的 Rfb 引脚内部就连着 15kΩ 反馈电阻,把它直接接到运放输出端,Rfb 等于 REXT,公式简化成:

VOUT = -VREF × (D / 256)

VREF 接 -5V 时,D 从 0 变到 255,输出从 0V 变到接近 +5V,正好是正电压单极性输出。VREF 接 +5V 时输出就是负电压,这属于常见误区,很多人第一次焊完发现万用表读数是负的,问题就出在 VREF 极性上。

运放选型上,普通通用运放(比如 TL082 这类双运放)就够用,但要注意两点:一是供电要留出输出摆幅余量,输出接近 5V 时轨到轨运放更保险;二是在运放输出端和反相输入端之间并一个几十皮法的小电容,抑制高频自激。

如果需要双极性输出(比如 -2.5V 到 +2.5V),在后面再加一级反相求和电路,把 VREF/2 的偏置叠加上去。这一级不是必需的,多数课程设计做到单极性就能演示。

3.3 输出低通滤波与缓冲:让阶梯变成曲线

DAC 每个采样间隔输出一个台阶,采样率越高台阶越密,但台阶本身不会消失。示波器在低时基下能看到明显的阶梯感,这就是常说的量化噪声。

解决办法是在 DAC 之后加一级低通滤波,把采样率附近的高频成分滤掉。滤波器的截止频率取值有讲究,太高了台阶滤不干净,太低了波形被削得变形。通常按下面这条经验取:

截止频率 ≈ 最高输出频率 × 5 ~ 10,且低于采样率的一半

按这套方案的参数,最高输出 200Hz,采样率 12.8kHz,奈奎斯特频率 6.4kHz。截止频率取 2kHz 左右比较合适。一阶 RC 滤波的参数:

# 一阶 RC 低通,R = 1kΩ,C = 82nF # fc = 1 / (2 × π × R × C) ≈ 1.94kHz

一阶滤波的滚降只有 -20dB/十倍频,对台阶的抑制有限。如果示波器上还能看到明显锯齿,可以改成二阶 Sallen-Key 低通,滚降变成 -40dB/十倍频,代价是多一个运放和几个阻容。

滤波之后再加一级电压跟随器,用运放接成同相跟随,输出阻抗降到接近零,接示波器探头或者后级电路都不会把波形拉塌。这一级很多人省掉,结果空载时波形正常,一接负载幅值就掉一半。

需要注意,滤波一定会带来相移,方波经过低通之后边沿会变缓、顶部的过冲会被抹平。这是物理规律,不是电路做错了。答辩时被问到「为什么方波不够方」,把滤波器截止频率和方波谐波成分讲清楚就行。

3.4 人机接口与供电的抗干扰处理

四个独立按键接在 P3 口上,每个按键一端接地,另一端接单片机引脚,引脚上拉 10kΩ 电阻到 VCC,并联 100nF 电容到地做硬件消抖。软件里再做一次 10ms 定时扫描确认,双保险。

LCD1602 用 4 位数据模式接线,省下四个 IO。对比度调节脚接一个 10kΩ 电位器,中间抽头接 VO,两端分别接 VCC 和 GND。背光串一个 100Ω 限流电阻。显示内容只有波形名、频率值、幅值档位三行信息,不用做复杂菜单。

供电部分是整套电路的隐含重点。数字部分和模拟部分共用一组 5V 电源时,数字开关噪声会顺着地线串进 DAC 的参考电压。处理办法有两条:一是 DAC 的 VCC 引脚就近放 0.1μF 陶瓷电容和 10μF 电解电容做去耦,二是数字地和模拟地在 DAC 芯片下方单点连接,不要做成大面积铺铜后再随便连。

VREF 是整条链路里最敏感的一根线。如果 VREF 上叠了几毫伏纹波,输出波形上就会看到同频的抖动。条件允许时用一颗 TL431 这类基准源单独给 VREF 供电,比直接从 5V 分压稳得多。

4. 软件实现:查表法、定时器中断与波形切换的组合方式

硬件把数字量变成电压,软件决定这个数字量怎么变。整套程序的结构不复杂,但有几处细节如果写法不对,示波器上立刻能看出问题。这一章按「表怎么来、点怎么送、参数怎么调、按键怎么处理」的顺序展开。

4.1 四张波形数据表的生成方式

正弦波的数据没法用一条简单公式在单片机里实时算,51 没有浮点单元,算一次正弦要几百个机器周期。标准做法是离线算好存成常量表,运行时只查表。表放在code区,不占 RAM。

用一段 Python 脚本生成 64 点正弦表:

import math N = 64 # 每周期采样点数,取 2 的幂,后续做相位运算方便 tab = [] for i in range(N): # 中心点 128,上下摆动 127,保证落进 0~255 的 8 位范围 v = 128 + 127 * math.sin(2 * math.pi * i / N) tab.append(int(round(v))) print("const unsigned char code sine_tab[%d] = {" % N) for i in range(0, N, 8): row = ", ".join("0x%02X" % x for x in tab[i:i + 8]) print(" " + row + ",") print("};")

输出的数组直接粘进 C 文件。尖峰处的值是 255 和 1 附近,不会溢出 8 位范围,这是把幅度定成 127 而不是 128 的原因。

另外三种波形不用存表,运行时按相位指针现算更省空间,但 51 上做除法开销大,实际项目里通常也直接写成表。方波最简单,前 32 个点写 0xFF,后 32 个点写 0x00;三角波从 0 线性升到 255 再线性降回 0,每步 8 左右;锯齿波从 0 线性升到 255 后直接跳回 0。四张表长度统一为 64,这样切换波形时相位指针不用重置。

表生成之后建议加一步校验:把数组里的最大值和最小值打印出来,确认落在 1 到 255 之间。少了这一步,生成的表如果在某个点算成 0 或者 256,输出波形顶部或底部会被削平,排查起来很费时间。

4.2 定时器中断里的采样输出

定时器 0 工作在模式 1,16 位手动重装。初始化时先算好重装值,中断服务程序里第一时间重装,保证采样间隔均匀。

#define FOSC 12000000UL /* 晶振频率,单位 Hz */ #define TIMER_DIV 12UL /* 12T 模式,定时器计数频率为 FOSC/12 */ static unsigned char t0_h, t0_l; /* 定时器重装值的高字节和低字节 */ static unsigned char phase_idx; /* 当前采样点在波形表中的位置 */ static unsigned char point_num; /* 每周期采样点数,固定 64 */ static unsigned char amp_level; /* 幅值档位,0~100 */ /* 根据目标频率计算定时器重装值 * f_out : 目标输出频率,单位 Hz * n_pt : 每周期采样点数 */ void calc_reload(unsigned int f_out, unsigned char n_pt) { unsigned long sample_rate; unsigned long ticks; unsigned int reload; sample_rate = (unsigned long)f_out * n_pt; /* 采样率 = 频率 × 点数 */ ticks = (FOSC / TIMER_DIV) / sample_rate; /* 一个采样间隔的计数值 */ if (ticks > 65535UL) ticks = 65535UL; /* 频率太低时钳位,防止溢出 */ reload = (unsigned int)(65536UL - ticks); t0_h = (unsigned char)(reload >> 8); t0_l = (unsigned char)(reload & 0xFF); } /* 定时器 0 中断服务程序,中断号 1 */ void timer0_isr(void) interrupt 1 { TH0 = t0_h; /* 先重装,减小采样间隔抖动 */ TL0 = t0_l; P0 = wave_buf[phase_idx]; /* 当前采样点送到 DAC 数据口 */ phase_idx++; /* 相位指针后移一位 */ if (phase_idx >= point_num) /* 走完一个周期回到起点 */ { phase_idx = 0; } }

calc_reload里的除法是 32 位除 32 位,执行时间不短,所以只在参数变化时调用一次,不能放进中断。中断里只做四件事:重装、写口、指针自增、边界判断,全部用 8 位操作,实测开销在 20 个机器周期左右。

phase_idx声明成unsigned char而不是int,是因为点数固定 64,8 位足够,访问速度也快。如果以后点数扩到 256,这个变量仍然够用;超过 256 才需要换unsigned int。

中断里直接写P0有个前提:P0 口没有其他外设复用。如果板上 P0 还挂着别的东西,就必须先把数据写进一个中间变量,再由主循环搬到端口上,代价是多一次判断和一次赋值。

4.3 频率与幅值的参数化调节

频率调节走的是「改重装值」这条路。用户按一次加键,目标频率加 10Hz,调用一次calc_reload,中断节奏立刻变化。因为重装值只在中断开头生效,切换过程中不会出现半个周期被拉长的现象。

频率步进不能太小。10Hz 步进在 200Hz 上限下是 20 档,操作起来合适。如果做成 1Hz 步进,一是按键要按很多次,二是低频段的重装值变化很大,实际输出的频率误差反而不小。

幅值调节有两条路。改 VREF 需要外加数字电位器或者专用基准源,电路复杂度上去了;软件缩放只需要改一个系数,代价是牺牲一点分辨率。这套方案走软件缩放:

/* 把原始表值按幅值档位缩放 * raw : 表里的原始值,范围 0~255,中心点 128 * amp : 幅值档位,0~100,100 表示满幅 */ unsigned char scale_point(unsigned char raw, unsigned char amp) { int offset; offset = (int)raw - 128; /* 相对直流中心点的偏移 */ offset = offset * amp / 100; /* 按百分比缩放 */ offset += 128; /* 加回直流偏置 */ if (offset > 255) offset = 255; /* 上限钳位,防止溢出回绕 */ if (offset < 0) offset = 0; /* 下限钳位 */ return (unsigned char)offset; }

这段代码有两个容易翻车的点。一是offset必须声明成有符号类型,如果写成unsigned char,减去 128 之后负数会回绕成大正数,输出波形整体翻上去。二是钳位不能省,offset * amp / 100在极端档位下可能超出 0 到 255,不钳位就会看到波形被「折」回去。

软件缩放的精度损失客观存在。8 位表值缩到 50% 幅值时,有效位数只剩 7 位,缩到 25% 只剩 6 位,低幅值档位下波形会显得粗糙。如果 MCU 资源有余量,可以把表做成 16 位(unsigned int code),缩放后再右移 8 位送给 DAC,精度损失就小得多,代价是表占用的空间翻倍。

4.4 按键扫描与界面状态机

按键扫描不能放在主循环里裸跑,用定时器 1 每 10ms 触发一次,在中断里做采样和消抖。

#define KEY_NONE 0 #define KEY_WAVE 1 #define KEY_FREQ_UP 2 #define KEY_FREQ_DN 3 #define KEY_AMP 4 /* 10ms 调用一次,返回键值,0 表示无按键 * 只在按下沿返回一次,避免长按连续触发 */ unsigned char key_scan(void) { static unsigned char last_key = KEY_NONE; unsigned char cur_key = KEY_NONE; P3 |= 0x0F; /* 读引脚前先写 1,准双向口才能读准 */ if ((P3 & 0x01) == 0) cur_key = KEY_WAVE; if ((P3 & 0x02) == 0) cur_key = KEY_FREQ_UP; if ((P3 & 0x04) == 0) cur_key = KEY_FREQ_DN; if ((P3 & 0x08) == 0) cur_key = KEY_AMP; if (cur_key != KEY_NONE && last_key == KEY_NONE) /* 检测按下沿 */ { last_key = cur_key; return cur_key; } if (cur_key == KEY_NONE) /* 松开后清状态,允许下次触发 */ { last_key = KEY_NONE; } return KEY_NONE; }

P3 |= 0x0F这一句很多人会写成P3 = 0xFF,在只有按键占用低四位的板子上没区别,但如果 P3 高四位还接着别的外设,直接赋值会把它们全拉高。写端口用「或」而不是「赋值」,是 51 编程里一条很实在的习惯。

界面状态机只有三个状态:正常运行态、波形选择态、参数调节态。正常运行态下 LCD 显示当前波形和频率;按下波形键进入波形选择态,用加减键切换;再按一次波形键确认并返回。状态机用switch加一个枚举变量实现,不要用一堆嵌套if,否则加一个功能就要改一遍逻辑。

状态切换时要顺手做一件事:把 LCD 的刷新函数置一个标志位,主循环里检测到标志才刷新,不要每次按键都直接写 LCD。LCD1602 的一次写操作要几十微秒,放在主循环里没问题,但如果哪天真被塞进中断,整个时序就乱了。

5. 避坑与排查:调试台上最容易翻车的五个点

电路焊完、程序烧进去,第一次上电大概率不会一次成功。这一节列的五种现象,是这类波形发生器项目里出现频率最高的,每条按「看到什么、为什么、怎么改」的顺序写,遇到问题时可以当检查表用。

5.1 输出波形带明显台阶和毛刺

现象:示波器把时基调到每格 1ms 以下,正弦波看起来像一级一级的楼梯,台阶边缘还带细小的尖刺。

原因有两层。楼梯感来自采样率不够或者滤波器截止频率设得太高,DAC 每个采样点之间的跳变没有被平滑掉。尖刺来自数字信号串扰,P0 口在写数据的瞬间会有较大的 di/dt,如果 DAC 的 VCC 去耦不到位,这个跳变会顺着电源串进输出。

解决:先把低通滤波器的截止频率降下来,一阶 RC 不够就换二阶;再检查 DAC 的 VCC 引脚旁边有没有 0.1μF 陶瓷电容,没有就补上,位置要尽量贴近芯片引脚,引线长了等于没加。还不行就在 P0 口每一位上串一个 33Ω 的小电阻,抑制边沿陡度。

5.2 设定 100Hz 实测只有 97Hz

现象:LCD 上显示 100Hz,示波器测出来是 97Hz 左右,频率越高偏差越大。

原因:中断服务程序本身的执行时间被忽略了。定时器重装值是按理想采样间隔算的,但中断从响应到第一条重装指令执行完,中间已经过去十几个机器周期,这段开销没有算进周期里,实际采样间隔比设计值长,输出频率就偏低。频率越高,这个固定开销占的比例越大,偏差越明显。

解决:在calc_reload里把中断开销补进去,ticks减去实测的中断开销计数值。实测方法是用一个空闲 IO 口在中断开头置高、结尾置低,用示波器量高电平宽度,换算成机器周期数。补偿之后误差能压到 1% 以内。如果对频率精度要求高,就别用 12T 模式,换 1T 芯片,固定开销的绝对值会小很多。

5.3 调幅时波形顶部被削平

现象:幅值调到 80% 以上,正弦波的波峰和波谷变成一条平线,示波器上看像被切掉了一块。

原因:软件缩放之后没有做限幅。原始表值是 1 到 255,偏移量是 -127 到 +127,乘以 100% 还是原值,但中间如果有取整或者累加,某些点会算出 256 甚至更大,赋给 8 位变量时回绕成 0 附近的值,波形上就出现一个跳变。另一种情况是运放输出摆幅不够,DAC 输出已经到 4.9V,运放供电只有 5V,顶部自然被削。

解决:先看scale_point里的钳位有没有写,两个if一个都不能少。钳位没问题就量运放供电和输出,输出接近电源轨时换轨到轨运放,或者把 VREF 从 -5V 降到 -4V,给摆幅留余量。

5.4 按一次键频率连跳好几格

现象:手指按下去明明只按了一下,频率从 100Hz 直接跳到 130Hz,甚至跳到上限。

原因:按键没有做按下沿检测。主循环每转一圈都在读按键,只要按键还处于按下状态,每一轮都会返回一次键值,看起来就是连跳。硬件消抖电容只能滤掉毫秒级的抖动,滤不掉人手指按住的那几百毫秒。

解决:按键扫描里必须有last_key这个静态变量,只在「上次没按、这次按下」的瞬间返回键值,参考 4.4 节的写法。另外确认按键扫描的调用周期是 10ms 而不是主循环里裸调用,调用周期不固定,消抖逻辑就不成立。

5.5 换一块板子就不出波形

现象:在自己焊的板子上跑得好好的,换到另一块同款板子或者洞洞板上,LCD 亮着但示波器上是一条直线。

原因:最常见的是 P0 口上拉排阻漏焊。P0 口内部是开漏结构,不加上拉电阻时输出高电平全靠外部,DAC 收到的数据全是 0 或者全是错值。第二种可能是 DAC 的 ILE 引脚没有接高,输入锁存器一直处于关闭状态,数据根本没进去。第三种是 VREF 没接或者接成正电压,输出是负压,示波器耦合方式还是 DC,看起来就像没信号。

解决:按顺序量三个点。先量 P0 口在中断运行时有没有高低变化,没有就查上拉和程序;再量 ILE 和 CS 的电平,确认锁存器是开的;最后量 VREF 和运放输出,确认极性。这三步能覆盖九成以上的「无输出」问题。

注意:排查时不要一上来就怀疑程序。这类项目里硬件虚焊和漏接元件的比例,比软件逻辑错误高得多。先量电压,再看波形,最后才动代码。

6. 验收与答辩:用一台示波器把设计讲成一个闭环

东西做出来只是第一步,能在答辩现场用几分钟把它讲明白,是另一回事。评审老师看的不是你焊了多少个元件,而是你能不能把「指标怎么定、电路怎么算、误差从哪来」这三件事串起来。我一般会让学生准备一张测试用例表,现场按表逐条演示,比临场发挥稳得多。

6.1 一张能直接用的测试用例表

表里每一行对应一个可以当场复现的动作,左侧是设定值,右侧是示波器上应该看到的读数,中间留一列写实测值。答辩时照着表走,节奏不会乱。

用例编号设定条件预期现象判定标准
T01正弦波,50Hz,幅值 100%峰峰值接近 4.8V,波形无削顶频率误差小于 2%
T02方波,50Hz,幅值 100%上升沿小于 200μs,顶部无过冲高低电平差大于 4V
T03三角波,100Hz,幅值 50%峰峰值约为满幅一半,线性无明显台阶幅值误差小于 5%
T04锯齿波,10Hz,幅值 100%回扫沿陡直,周期稳定周期抖动小于 1%
T05正弦波,频率从 10Hz 连续加到 200Hz频率单调上升,无跳变无中断每个档位实测偏差小于 3%
T06运行中反复切换四种波形波形切换无残留、无闪断切换响应小于 100ms

T02 这一条的判定标准值得单独说。方波经过低通滤波器之后边沿一定会变缓,如果老师问「为什么不是理想方波」,回答要落到滤波器截止频率和方波谐波成分上:50Hz 方波的三次谐波是 150Hz,五次是 250Hz,滤波器截止在 2kHz,前几次谐波都通过了,所以边沿还比较陡;如果把截止频率降到 500Hz,边沿就会明显变缓。这个解释能把电路参数和波形现象对应上,比说「设计就是这样」有说服力。

6.2 现场演示时先讲结论再讲过程

答辩时间通常只有几分钟,讲述顺序建议倒过来:先给示波器上稳定运行的波形,再说这套系统分三层、每层负责什么,最后才展开具体电路和代码。先看到结果,听众才有耐心听原理。

演示过程中留一个「主动暴露问题」的环节。比如主动说明频率误差的来源是中断开销,并且已经做了补偿;或者说明低幅值档位下波形会粗糙,原因是 8 位表值的有效位数下降。主动讲出局限,比被追问时才发现要体面得多。

代码部分不用逐行念,挑calc_reload和中断服务程序这两段讲,因为它们直接对应「频率怎么来的」和「波形怎么送出去的」两个核心问题。硬件部分挑 I/V 转换那一级讲,把VOUT = -VREF × D / 256这个式子写出来,说明 VREF 为什么接负压、Rfb 为什么用内部电阻,两个问题就把模拟链路讲透了。

从那以后我每次带这类项目,都会在焊接之前先逼着把参数预算那张表算完,把「中断开销到底占多少」用示波器实测一遍再定最高频率。很多返工不是因为电路画错,而是因为一开始就把指标定在了定时器装不下的地方。希望这套结构能帮到你,把手上那台波形发生器做成一份能讲清楚的完整方案。

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