
电子实验室里最离不开的几台仪器示波器、万用表再往下排就是信号发生器了。我最早接触信号发生器是修音频功放时用手机里的频率发声App临时顶替后来要调准频率、还要带特定波形才发现手机那套根本不顶用。市售的台式信号发生器功能齐全但价格劝退了不少爱好者于是就有了自己做一台多功能信号发生器的念头——用DDS芯片做核心、单片机做控制、再加上一级模拟输出电路成本压到百元以内却能把正弦波、方波、三角波、扫频这些常用功能全包圆。本文就把这个项目从方案选型、硬件搭建、固件编写到校准排障的完整过程拆开讲透适合有一定单片机基础、想动手做仪器的电子爱好者参考。这个项目解决的核心问题很直接让你在调电路、测音频、模拟传感器信号时手边有一台波形可换、频率可调、幅度可控的稳定信号源而不是每次都用示波器自带校准方波或者手机App凑合。我下面会解释为什么选DDS方案而不是纯单片机生成波形也会把输出级阻抗匹配、频率字计算、扫频实现这些容易被忽略的细节全部交代清楚。1. 项目整体设计与思路拆解1.1 为什么需要一台多功能信号发生器先说说我在什么场景下被逼着做了这台机器。有一阵子我在调一款自己设计的前级放大电路需要给输入端灌入不同频率的正弦波来看频响曲线。用手机App生成的正弦波频率误差大就不说了输出幅度还跟着系统音量走音量条稍微动一下测试结果就全乱了。后来又遇到测无源滤波器的截止频率、模拟热电偶的微弱信号、给舵机控制板送PWM信号这类需求每一样都要专用的信号源。这时候多功能三个字就特别重要。所谓多功能至少包含这几层含义波形种类要多正弦波、方波、三角波、斜波是底线最好还能出PWM频率范围要宽从零点几赫兹的低频到几百千赫兹的高频都要覆盖幅度要可调既能输出几毫伏的微弱信号也能输出几伏的满幅信号再往上加扫频、频率微调、占空比调节这些功能就是加分项了。我身边不少朋友一开始觉得直接买一台几十块钱的DDS模块不就完了吗但模块只是核心板真正要用起来还得配单片机控制、配输出驱动电路、配外壳和人机界面。把这些做完整本质上就是一个完整的仪器设计项目这也是我把它当作一个独立项目来做而不是只买个模块的原因。1.2 方案选型DDS芯片还是单片机纯软生成在定方案之前我把几条常见路线都列出来对比了一轮。第一条路线是纯单片机DAC输出波形用STM32自带DAC查表输出正弦波或者三角波。这条路线的问题是DAC位数和更新率都有限想要输出1MHz以上的正弦波每一周期至少得采样几十个点对主频和DMA时序要求很高波形毛刺还多。第二条路线是PWM加RC滤波用定时器输出PWM再通过低通滤波器把载波滤掉。优点是便宜简单缺点是滤波器截止频率和输出频率互相牵扯想要宽频覆盖很难幅度和频率还互相影响。第三条路线就是DDS专用芯片也是我最终选择的方案。DDS的工作原理可以这么理解芯片内部有一个相位累加器每来一个时钟脉冲相位累加器就加上一个频率控制字然后以这个相位值查正弦查找表输出对应幅度。换一句话说频率控制字决定了每次相位跳变的步长步长越大输出频率越高。最终频率的公式是 f_out (频率控制字 × 主时钟频率) / 2^N其中N是累加器位数AD9833是28位AD9850是32位。这是纯数字频率合成所以频率分辨率极高AD9833在25MHz主时钟下理论分辨率可以达到0.1Hz以下这是模拟振荡器完全做不到的。我最后选了AD9833模块作为核心原因有三个一是便宜模块十几块钱就能搞定二是SPI接口控制单片机端代码量不大三是它自带正弦波、三角波和方波输出常见的三种波形全有了。如果你需要更高的输出频率可以考虑AD9850它能到40MHz以上但控制字位宽、接线方式略有区别整体思路是一样的。对于绝大多数音频、传感器模拟和低频数字电路调试场景AD9833已经绰绰有余。2. 核心细节解析与实操要点2.1 波形类型、频率与幅度指标怎么定做仪器和做普通电路最大的区别在于指标要提前想清楚不能搭完了再回头改。我把这台多功能信号发生器的目标指标定成这样输出频率范围0.1Hz到1MHz之所以卡在1MHz是因为AD9833在25MHz主时钟下理论上最高能出12.5MHz但后级运放和PCB走线会限制高频性能做到1MHz以内的波形质量稳定可控是这个价位比较务实的目标幅度范围0到5Vpp可调输出阻抗50欧姆波形类型包括正弦波、方波、三角波、斜波以及可调占空比的PWM。这里补充一个容易被新手忽略的概念输出阻抗。标准信号发生器基本都是50欧姆输出阻抗这既是为了和同轴电缆、示波器探头匹配也是为了保证和被测电路之间的连接是可控的。做信号源的时候如果在输出端直接串一个50欧姆电阻再接BNC座那么当对端接的是示波器1M输入阻抗时显示出来的电压基本等于开路电压但当对端接的是50欧姆负载时电压会衰减一半。这个特性不是bug是仪器界通用的约定我在后面第三节会讲怎么在固件里做幅度补偿。幅度控制这一块我建议做两级第一级在DDS输出端用运放做固定增益放大把波形幅度抬高到接近满摆幅第二级用电阻网络或者数字电位器做衰减控制。不要试图用DDS模块自带的可调电位器去调幅度那个电位器同时会影响波形质量而且调节精度很差做不到可重复的设置。数字电位器加运放衰减网络是更可靠的做法。2.2 硬件选型与电路设计要点主控我选了STM32F103C8T6也就是大家常说的蓝板小板成本十来块钱主频72MHz带SPI、I2C、定时器资源对这个小项目来说相当充裕。如果你更熟悉Arduino环境用Nano也行代码逻辑几乎可以平移只是SPI库和寄存器写法略有区别。DDS模块选用最常见的AD9833模块板上自带25MHz有源晶振卖家一般会把FREQ0和FREQ1跳线引出这部分我们只用到FREQ0保持默认即可。后级输出运放的选型是这个项目里最需要上心的地方。很多人习惯性地抓一个LM358就上结果发现1MHz正弦波直接变成三角波了。原因是LM358的增益带宽积只有1MHz左右用在1MHz信号上根本没有闭环增益余量。正弦波输出级我用了AD8051单电源供电、带宽110MHz、压摆率140V/us驱动1MHz、5Vpp的波形完全没压力。如果你手头有OPA690、THS3001这类高速运放也可以替代。方波输出如果要求边沿够陡可以直接从AD9833的方波输出脚接在FSYNC脚附近经过一个74LVC1G17施密特整形后送出边沿会干净很多。电源设计是另一个容易翻车的地方。AD9833模块和MCU用5V供电就能跑但运放要输出接近满幅的波形建议使用正负5V双电源或者用单电源加虚拟地的方式。我实际用的是两节18650电池供电经过稳压模块输出5V给数字部分再用一颗电荷泵反压芯片从5V得到-5V给运放供电。每个电源引脚旁边都贴了0.1uF和10uF的去耦电容电源输入端再加一颗大容量电解电容这是我后来排查高频噪声时总结出来的教训后面第四部分会详细讲。2.3 输出级的阻抗匹配与幅度控制具体做法信号发生器输出级的设计目标可以总结成一句话源阻抗恒定、幅度可设置、波形不畸变。最简单的实现方法是在运放输出端串联一颗50欧姆电阻再接到BNC座上这颗电阻就是输出阻抗。当外部接1M负载时运放输出的开路电压V_open基本就是BNC处的电压当外部接50欧姆负载时负载分到的电压是V_open的一半。这个分压关系必须记牢否则校准幅度时会被绕晕。幅度衰减网络我做了三档固定衰减加一档微调的组合固定衰减档用电阻分压实现分别是0dB、-20dB、-40dB用旋转编码器加继电器切换既能保持精度又不会引入数字电位器的额外噪声。微调则用一颗数字电位器通过固件把幅度编码成0到100%显示在屏幕上。如果你的项目想更省事也可以只用一颗数字电位器接在运放反馈回路里做闭环增益调节但要注意数字电位器的带宽和寄生电容会影响高频响应实测下来1MHz以上会有明显衰减这一点在选型时要心里有数。另一个细节是DDS输出的正弦波里有明显的时钟馈通噪声25MHz的时钟频率会在输出波形上叠加一些高频毛刺。解决办法是在AD9833输出脚与运放输入之间加一个简单的RC低通滤波器截止频率设置在所需最高输出频率的3到5倍左右。比如目标最高频率1MHz就把低通截止频率设在3MHz上下。用了这个滤波器之后示波器上看到的波形干净程度会有质的提升。3. 实操过程与核心环节实现3.1 硬件搭建与接线顺序我在做板子之前先在洞洞板上搭了一版验证电路确认方案没问题之后才画了PCB。这里建议你也按照先模块验证、再整合的顺序走能省掉很多反复拆焊的时间。先讲洞洞板验证阶段的接线核心就三组线STM32和AD9833之间是SPI三根线加片选OLED屏幕走I2C两根线旋转编码器占三个GPIO口。AD9833模块的引脚定义比较统一VCC接5VGND接地SCLK、FSYNC、DATA分别接单片机的SPI时钟、片选和数据输出脚。我用的是STM32的SPI1PA5接SCLKPA6接MISO但实际不用PA7接DATAPA4接FSYNC。OLED用的是I2C1PB6接SCLPB7接SDA。旋转编码器接在PC13到PC15这组引脚上三个引脚分别是CLK、DT、SW其中SW是编码器按下时的开关用来切换选中的参数项。焊接顺序上我的经验是先把电源部分弄好并测试无误再接MCU最小系统然后接OLED等屏幕能点亮了再接AD9833最后才接运放输出级。每一级都独立验证不要一口气全部焊完再上电否则出了问题很难定位。运放输出级单独用一节面包板区域拼装方便更换元件做调试确认波形质量达标后再把电路固定到正式板子上。3.2 固件设计与核心代码实现固件的核心是操作AD9833这部分必须把芯片手册看清楚。AD9833的寄存器是16位的最高两位DB15和DB14用来选择目标寄存器00表示写FREQ0寄存器01表示写FREQ1寄存器10表示写PHASE0寄存器11表示写PHASE1寄存器。写频率寄存器时因为28位数据要分两次写入还需要先设置控制寄存器中的B28位为1让它接收两个16位的数据拼接成一个28位频率字。频率字的计算公式是 freq_word (目标频率 × 2^28) / 25MHz。比如想要输出1kHz正弦波计算得到 freq_word 1000 × 268435456 / 25000000 ≈ 10737。换算成十六进制是0x29F1要拆成高14位和低14位两次写入。下面是一段我实际用的初始化加设频函数可以直接参考void AD9833_Init(void) { SPI_Init(); // 配置SPI1时钟极性CPOL1相位CPHA1 AD9833_Write(0x2100); // B281输出正弦波使用FREQ0 } void AD9833_SetFrequency(uint32_t freq_hz) { uint32_t freq_word; uint16_t high_word, low_word; freq_word (uint32_t)((uint64_t)freq_hz * (1 28) / 25000000ULL); low_word freq_word 0x3FFF; high_word (freq_word 14) 0x3FFF; AD9833_Write(0x2000 | low_word); // 写FREQ0低14位 AD9833_Write(0x4000 | high_word); // 写FREQ0高14位 }注意这里有一个关键点写入频率字时最高两位是寄存器地址剩下的14位才是数据所以低14位和高14位都要保持在第15、14位为正确的寄存器地址。很多第一次写AD9833驱动的人在这里栽跟头比如直接用0x29F1这样的完整数值去写发现频率完全不对。正确做法就是上面代码里那样把地址位和高低位数据位通过逻辑或拼接到一起。人机交互部分我用的是旋转编码器加OLED的方案。旋转编码器旋转时产生两路正交脉冲在中断里判断正反转用来改变当前选中参数的值按下编码器切换选中参数参数在屏幕上高亮显示。参数包括频率、波形类型、幅度、衰减档位、扫频起点和终点。显示频率的时候我做了自动单位切换小于1000Hz显示Hz大于等于1000Hz显示kHz并保留三位小数。这个细节看似不起眼实际使用时非常提升体验。扫频功能是这台机器的一个加分项。实现方法很简单在中断里定时从起点频率逐步增加到终点频率每一步设一次AD9833频率字步进量根据扫频时间和频率范围自动计算。比如从100Hz扫到10kHz扫频时间2秒步进间隔1毫秒那么每步增加约5Hz。为了让扫频更平滑我用了一个小技巧对频率值做指数插值而不是线性插值。因为人耳和很多电路对倍频程更敏感线性扫频在低频段变化太慢、高频段变化太快指数扫频听起来和看起来都更自然。3.3 校准与测试硬件和固件都准备好之后校准是让这台发生器从能出波形变成指标可信的关键一步。我用一台台式示波器作为参考先把输出直接接到示波器的1M输入设置正弦波1kHz、理论幅度1Vpp看示波器实测值。如果和理论值有偏差就在固件里加一个幅度校准系数把数字电位器的目标值和实际输出电压对应起来。然后再在输出端接一个50欧姆终端负载重复同样的测量这时候示波器测到的幅度应该是开路时的一半如果偏差明显就要检查输出电阻是否真的是50欧姆。频率校准相对简单。最好是有一台频率计如果没有用示波器的频率测量功能也行。先设置输出1kHz看示波器实测是不是1000Hz再设置输出100kHz看实测是不是100kHz。如果每个频率点都偏同样的比例那就是AD9833模块上的晶振频率和标称25MHz有偏差。解决办法是在固件里把公式中的25MHz改成实测晶振频率比如实测是24.9975MHz就把它写进一个常量里统一使用。这个校准值每个模块不一样建议在代码里留一个可配置项方便每台机器单独标定。最后还要测一下方波的边沿和正弦波的高频失真。方波用示波器看上升沿10%到90%上升时间应该远小于周期的10%如果上升沿圆了多半是输出级压摆率不够或者走线电容太大。正弦波在500kHz以上会逐渐出现幅度跌落和轻微削顶记录一下各频率点的实际幅度画一条频率-幅度曲线这就是这台机器的有效带宽以后用的时候心里有数。4. 常见问题与排查技巧实录4.1 高频波形失真和毛刺严重这个问题的出现频率极高而且很多情况下不是DDS芯片的问题而是输出级和布线的问题。我调试过程中遇到的第一个典型症状是1kHz正弦波一切正常调到200kHz以上波形就开始变形顶端变圆甚至变成明显的三角波。排查一圈后发现是运放选型不对我最初图省事用了LM358压摆率只有0.1V/us量级根本追不上高频信号的变化速度。换用AD8051之后问题立即缓解。这里记住一个经验运放压摆率至少要满足 SR ≥ 2π × f_max × V_peak也就是对1MHz满幅5V输出来说压摆率至少得30V/us以上留足余量最好上百。毛刺问题的根源往往是电源去耦不足。我曾在AD9833的供电脚旁只贴了一个0.1uF电容结果在输出波形上看到明显的25MHz时钟馈通。后来把数字部分和模拟部分的电源分开走线在DDS输出到运放之间加了3MHz低通滤波器并给每颗芯片的电源脚都补上0.1uF和10uF的组合电容示波器上的毛刺才基本消失。如果你用的是洞洞板或面包板走线尽量短粗地线不要绕圈这一点在几百千赫兹以上频段尤其重要。4.2 频率显示与实际输出对不上频率不准分两种情形一种是所有频率点都按同一比例偏移另一种是低频准确但高频逐渐偏低。第一种基本就是晶振频偏按前面说的校准方法处理即可。第二种往往是SPI时序问题AD9833写入频率字时如果时序不满足芯片要求高频部分的数据字可能写入失败导致实际输出频率和设置值不一致而且这种错误通常不是线性的。排查步骤我建议这样先用频率计测几个低频点比如100Hz、1kHz、10kHz确认低频段是否准确再测100kHz和1MHz对比偏差比例。如果低频精确、高频偏低而且比例逐渐增大就先检查SPI时钟频率是否过高AD9833的SCLK最高支持25MHz但STM32的SPI分频最少也要给到4分频也就是18MHz建议保守一点用8分频也就是9MHz。另外在每次写频率字之前拉低FSYNC、写完再拉高这个时序动作不能省很多奇怪的写入失败都是片选时序不规范导致的。4.3 常见问题速查表我把项目排障过程中遇到的典型问题整理成了表格方便你对照处理问题现象可能原因解决办法输出无波形AD9833未退出复位状态初始化时写控制寄存器并清RESET位频率显示正确但输出频率差一个固定比例晶振频偏用实测晶振频率替换公式中的25MHz波形顶部削平输出幅度超出运放供电范围降低输出幅度或提高运放供电电压高频波形变三角波运放压摆率不足换高GBW、高压摆率运放波形上叠加高频毛刺时钟馈通、电源去耦不足加低通滤波器、完善电源去耦接50欧姆负载后幅度减半且波形变形输出级驱动能力不足运放输出加缓冲级或使用更强驱动能力的运放旋转编码器调节跳数编码器未做消抖在中断里加防抖延时或改用定时器扫描OLED显示花屏I2C时序过快或供电不稳降低I2C频率屏幕电源加滤波电容这里再补充一个独门经验如果你发现正弦波输出幅度会随着频率上升而缓慢下降不要急着怀疑DDS模块先检查运放反馈电阻的取值。反馈电阻太大时运放输入端的寄生电容会和反馈电阻形成一个高频极点导致增益随频率滚降。我实测过用100k反馈电阻时500kHz处的增益就掉了将近3dB换成1k到2k的反馈电阻后问题明显改善。5. 实际应用场景与扩展升级建议5.1 几个我实测过的应用场景这台多功能信号发生器做出来之后我用了将近三个月覆盖了不少实测场景每一个都能反过来说明多功能这三个字的含金量。第一个场景是测音频功放的频响用正弦波扫频从20Hz到20kHz配合功放输出端的毫伏表就能快速描出一条频率响应曲线。这个应用对波形纯度要求比较高AD9833加上输出端低通滤波器之后的THD表现在这个频段完全够用。第二个场景是给无源滤波器做扫频测试。我用这台机器的扫频功能把起点设在目标滤波器截止频率的十分之一终点设在截止频率的十倍观察示波器上信号从通带到阻带的变化过程。相比手动一点一点调频率扫频模式直观得多滤波器是低通、高通还是带通特性一眼就能看出来。第三个场景比较有意思是模拟传感器信号。我调试一个温度采集电路时需要输入一个缓慢变化的电压来模拟温度传感器的输出于是把信号发生器设置成0.1Hz三角波幅度调到传感器量程对应的电压范围直接接入采集电路输入端。这样整个采集链路的线性度和响应速度都得到了验证比拿电烙铁去加热传感器做实验高效太多。这种低频小幅度的信号正好是通用信号发生器覆盖得最好的区间。5.2 值得做的扩展方向这台机器做到能用之后我给它加了不少扩展功能这里列几个性价比高的方向供你参考。第一个是加一个频率计输入口用STM32的定时器输入捕获功能测量外部信号频率这样一台设备就同时具备信号源和频率计两个功能调试时不用在桌面来回换仪器。实现上只需要一个BNC输入座、一个分压保护电路和一个定时器中断服务函数改动量不大收益却很明显。第二个扩展是加上位机控制。STM32有串口接一个USB转TTL模块再用Python写一个小界面就能在电脑上设置波形参数、启动扫频、导出扫频数据。这样做的意义在于当你需要做一套完整的频率响应测试时数据和曲线可以直接在电脑端保存和绘图不用每次拿手机拍照记录。我后续会把Python上位机的几个关键函数单独写一篇分享出来。第三个扩展是外部触发和突发模式。给固件增加一个外部脉冲触发输入当检测到上升沿时输出一段设定频率和时长的波形这在测试继电器响应、电机启动电流这类瞬态过程的时候非常有用。这一块我的建议是先把基础功能做扎实再根据自己实际遇到的测试需求去开发不要追求功能堆砌。毕竟一台仪器好不好用不在于功能列表多长而在于你每次需要它的时候它能不能在几秒钟之内给你一个可信的波形。我个人在实际操作中最大的体会是做这种仪器类项目百分之六十的功夫都花在细节上一块地线的走法、一颗运放的选型、一段SPI时序的收尾都会直接影响最终效果。整套做下来你对DDS原理、模拟信号链设计、单片机外设配合的理解都会比单纯抄板子或者看教程深刻得多。如果你也在计划做一台自己的信号发生器我建议第一步不要急着买齐所有零件先把这篇文章里的指标确定下来画一张需求清单再动手你会发现后面的路顺畅很多。