前阵子要给一个音频滤波电路做扫频测试,翻遍手头设备才发现少了一台函数信号发生器。买成品当然不贵,但在那会儿我更想要一台“能随时改波形、能精确调到任意频率、还能自己定义输出内容”的仪器。于是我把目光放回桌面上那块吃灰的Cortex-M单片机开发板,决定自己动手造一台。
这篇东西不是什么教科书式的理论课,而是一份完整的实战记录:从方案选型、硬件电路、DDS原理、固件代码,到实测阶段踩过的坑和最终成果。目标读者很明确——想拥有一台自制信号源的学生、电子爱好者,以及需要给滤波器、功放、传感器电路做日常测试但不想一直排队借仪器的工程师。读完你至少能搞清楚三件事:单片机为什么能产生任意波形、DDS的核心思想是什么、以及把这套东西落地成实物时最容易在哪些地方翻车。
1. 借不到仪器之后:为什么我决定用单片机自己造一台
1.1 先把需求说清楚,别一上来就画原理图
做东西最忌讳的是“感觉能行”就开始干,做到一半发现需求根本不匹配。我当时的测试需求并不复杂:需要0.5Hz到20kHz范围内的正弦波、三角波、方波和锯齿波,频率能连续调节,幅度能调,最好还能支持扫频输出。这个范围覆盖了绝大部分音频段、传感器激励和小信号调理测试。
有人可能会说,这点需求随便买一台几十块钱的DDS模块不就行了。确实可以,但我想弄清楚底层原理,同时希望这台仪器不只是一个“会出波形的黑盒子”,而是能跟着项目需求随时改固件、加功能。比如以后想输出一段自定义波形数据,或把正弦波改成扫频模式,都能在固件层面直接解决。这个需求就决定了:必须自己掌握从波形生成到输出调理的全链路。
1.2 方案对比:DAC、PWM、专用DDS芯片和成品,究竟怎么选
市面上的函数信号发生器实现路径就那么几条,我把能想到的方案列了一张表,对着自己的需求逐条打分:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 成品仪器 / 贴牌模块 | 频率高、波形干净、不用折腾 | 贵;黑盒子;不好扩展自定义波形 | 只当工具用 |
| 专用DDS芯片 | 轻松输出1MHz以上正弦,相位累加器由硬件实现 | 波形种类受限;幅度控制不够灵活;偏重“量测工具”而非“学习平台” | 对频率上限要求高的场合 |
| 单片机PWM + 低通滤波 | 成本极低,任何单片机都能做 | 输出带宽低,波形质量依赖滤波器,低频段都难保证干净 | 简单演示、语音提示音 |
| 单片机内置DAC / R-2R网络 + DDS | 波形种类灵活,频率可精确控制,适合理解原理和二次开发 | 输出频率上限受采样率限制,通常做到几十kHz至几百kHz量级 | 教学实验、中低频测试、自定义波形 |
最终我选了最后一种,而且要玩就玩DDS,不搞那种“把正弦表按顺序循环播放”的低级做法。DDS能实现从极低频率到采样率上限的连续精确调频,这才是函数信号发生器的灵魂。
1.3 我这次的目标参数
结合手上的资源和实际测试需求,我给这次DIY定下的目标是:
- 输出波形:正弦波、三角波、锯齿波、方波,外加一路用户自定义波形
- 频率范围:0.1Hz到20kHz,可以连续可调
- 频率设置精度:至少到0.1Hz
- 输出幅度:0到±3.3V可调,经输出级后带50Ω负载能力
- 人机接口:一个旋转编码器调频率,两个按键切波形,一块小屏显示实时频率
这个目标不高,但足够实用。20kHz上限对应的是音频和多数传感器测试场景,往上走需要更高速的DAC、更大带宽的输出级,那种“军备竞赛”已经不是纯粹的单片机DIY能轻松覆盖的了。
2. 硬件链路设计:从DAC输出到BNC接口的每一步
2.1 波形从哪出来:内置DAC还是R-2R电阻网络
第一步是选定“数模转换”的方式。我手头这块72MHz的Cortex-M单片机自带12位DAC,两条输出通道,速度对20kHz级别应用绰绰有余。使用内置DAC的另一个好处是省掉了大量分立电阻和并联电容,布线难度显著下降,输出毛刺也更容易控制。
如果你手上的单片机没有DAC,另一个经典做法是R-2R电阻网络,用一组普通IO口并行输出8位数字值到电阻网络,产生模拟电压。R-2R网络看起来省钱,实际坑一点都不少:电阻阻值必须严格配对,普通5%精度电阻做出来的非线性肉眼可见;而且并行IO同时翻转时会产生大量尖峰毛刺,需要精心匹配走线长度。更致命的是,IO翻转速率有限,想跑到500kHz以上的采样率很吃力。
所以我的建议非常直接:能用内置DAC就优先用内置DAC,先把信号链路跑通,再回头研究R-2R网络当作理论学习。12位分辨率对绝大多数中低频测试已经足够,量化噪声低到不是主要矛盾。
2.2 双极性输出:单电源芯片的“虚拟地”困境与负压方案
DAC直接输出的信号是单极性的,比如0到3.3V,而函数信号发生器通常需要输出正负对称的波形,也就是双极性信号。这一步不做,后面接功放或某些传感器电路时就会出现偏置失调,测试结果一团糟。
最简单的双极性转换电路是用运放做减法/加法:把DAC输出电压与参考电压的一半做差,再按一定增益放大。原理不复杂,但有个关键选择:运放用什么电源。
如果整个系统只有单5V或3.3V电源,你可能会想到用“虚拟地”方案——把运放同相端偏置在1.65V,输出以这个电压为“0V”参考。这个做法在极低频下能用,但问题很多:虚拟地的纹波会直接叠加到输出上,高频时地回路又容易振荡,输出电流也很容易被偏置网络“吃掉”。我在实验板上试过几次,低频小信号还能忍,一旦输出几伏峰峰值的正弦波,波形在跨零点附近肉眼可见地抖动。
后来我彻底放弃虚拟地,改用双电源方案:用一个很常见的负压电荷泵芯片把+3.3V翻转成-3.3V,让运放跑在±3.3V双电源下。这样减法器电路简洁,输出线性度好,地回路也干净。很多入门者会忽略运放电源的质量,实际上输出波形的“毛糙感”有相当一部分是电源纹波和地噪声贡献的。
2.3 输出级与阻抗匹配:空载正常不等于接上负载正常
DAC输出经过双极性变换后,还有一个重要环节:输出阻抗匹配。我做第一版的时候偷懒,把运放输出直接接了一根杜邦线到示波器,空载测波形很漂亮。后来接了一个几百欧的负载,波形幅度立刻塌掉一截,仔细量才发现输出阻抗太高,带负载能力不足。
函数信号发生器的输出端一般会按50Ω标准设计。实际电路就是运放输出后串一个50Ω电阻到BNC接口,对外呈现“50Ω源阻抗”的特征。当你用高阻探头测量时,看到的是信号源内阻和探头阻抗分压后的结果;当你接50Ω负载时,输出幅度会减半,这是正常的,因为内部那50Ω和外部50Ω构成了分压。关键是要让这个阻抗关系是确定、可预期的,而不是取决于你接了什么。
另外运放输出还要考虑容性负载稳定性。BNC线本身的电容不算大,但如果你在输出级加了一级RC低通滤波,一定要算清楚运放能否稳定驱动这个电容。我在第一版里直接在运放输出端加了一个0.1uF电容滤波,结果高频段开始自激振荡,示波器上看到一串莫名其妙的正弦叠加,排查了很久才意识到是容性负载把运放推到了不稳定区。
2.4 人机接口:编码器、按键和显示屏的设计取舍
硬件设计里最容易被低估的是人机接口。函数信号发生器要频繁调节频率,如果只用两个按键一格格地加减,调到习惯值会非常痛苦。我选择用一个旋转编码器加上两个独立按键:旋转编码器负责快速调频率,短按可以切换步进档位,两个按键分别切换波形和启动扫频。
编码器也有讲究:推荐选带按键开关的增量式编码器,机械手感好,固件处理也不难。要注意编码器的A、B相要接上拉电阻,旋转时的抖动脉冲必须用消抖状态机处理,否则频率数字会自己乱跳。显示部分我用了一块很小的OLED屏,I2C接口,占用引脚少,刷新速度也够用。你看到“频率跳变”“波形乱切”这类问题,往往不是电路问题而是固件消抖没做好,这一点后面第5章还会详细展开。
3. DDS原理拆解:频率和波形是怎么被“算”出来的
3.1 相位累加器:一个一直在转的“数字表盘”
DDS全称是直接数字频率合成,核心只有一句话:用一个足够宽的累加器模拟相位随时间线性增长,再从查找表里取出当前相位对应的波形幅值。你可以把相位累加器想象成一块数字表盘,它每一圈代表波形的一个完整周期,但“表针”不是连续转动,而是在每个采样时钟到来时跳过一个固定角度。
这个“固定角度”就是频率控制字。跳的角度越大,走完一圈所需时间越短,输出频率就越高。累加器的宽度决定了它可以累计的范围,我用的是32位累加器,也就是从0到2^32-1。相位溢出自然归零,相当于表盘转完一圈后重新开始,不需要任何额外判断。
用生活类比理解就是:操场跑圈,每一步跨多大角度决定你多少步跑完一圈,而DDS的频率控制字就是“每步跨多大角度”。这个角度越大,波形频率越高。
3.2 频率控制字怎么算:32位累加器带来极细分辨率
频率控制字和输出频率的关系非常简单直接:
F = f_out × 2^N / f_s
其中N是相位累加器位宽(这里N=32),f_s是DAC采样率,f_out是目标输出频率。举个例子,采样率100kHz,想输出1kHz正弦,那么:
F = 1000 × 2^32 / 100000 = 42949672.96
取整数42949673,频率精度已经远远超出实际需求。这个方案的惊人之处在于频率分辨率只取决于累加器位宽,最低可以做到0.00002Hz量级,实际限制只来自晶振精度,而不是算法本身。
写程序时要注意计算溢出问题。我一开始直接写(uint32_t)(f_out * 4294967296.0 / fs),在编译器隐式转换时没问题,但换成整数运算就容易踩坑。稳妥写法是用uint64_t做中间计算:
uint32_t calc_freq_word(uint32_t f_out, uint32_t fs) { return (uint32_t)(((uint64_t)f_out << 32) / fs); }3.3 一张波形表,四种波形:查表映射的设计
相位累加器只是个“计数器”,真正把相位变成电压的是波形查找表。我建了一张256点的波形表,存放四种常用波形:正弦、三角、锯齿、方波。每次采样周期到来时,从累加器的高8位取出当前相位索引,去表里查到对应的DAC值,写入DAC数据寄存器。
有人会疑惑为什么要“跳表”存取,而不是像播放音频那样顺序读表。因为DDS的相位步进不是固定的1,而是频率控制字决定的任意整数,高频率时相位索引会跳好几个点,这正是它能精确调频的关键。用一张表存多种波形的做法也非常高效:只要在中断里根据当前选择的波形切换“第几行表”即可,代码几乎一样。
关于表的大小,我选择了256点而不是1024点,主要是考虑到中断执行时间。采样率已经到100kHz,每次中断只能占用几十个机器周期,查表索引计算和DAC写入必须非常精简。256点在20kHz以下的输出频率下完全够用,谐波失真主要由采样点数和后面的低通滤波器决定。
3.4 什么时候不该走DAC:方波的另一种做法
方波是一个特殊存在。用DAC输出方波当然可以,但受DAC建立时间和运放压摆率限制,边沿会被拉成斜坡,高频时看起来就像梯形波。如果对方波边沿要求不高,用DAC直接输出方波表完全能接受。但我实验时发现,1kHz以下没问题,一到10kHz以上边沿退化明显,于是换了个思路:把方波从DAC链路上“摘”出来,直接用定时器的PWM通道输出。
这样方波的频率由定时器重装值控制,边沿由硬件产生,非常干净。代价是频率设置逻辑和DDS路径不统一,需要单独算定时器的ARR值和预分频值。我把方波和平时的DDS路径分开后,整台仪器反而更好用:DAC专注于正弦波、三角波、锯齿波和任意波形,方波则走一条“快车道”。
4. 固件实现关键代码:定时器中断里的查表输出与频率更新
4.1 为什么核心输出没有用DMA循环重放波形表
很多初学者看到“波形输出”就想到用DMA把正弦表循环搬到DAC,觉得这样不占CPU。这个做法如果只输出固定频率确实可行,但和DDS是两种思路。DMA循环重放是“顺序播放”,波形表从头到尾扫一遍就代表一个周期,改变频率的唯一办法是改变采样率或表大小,灵活性远不如DDS。
DDS的核心恰恰是“每次输出一个由相位累加器决定的跳跃位置的样本”,这个位置是乱的(频率控制字可能大于1),没法靠DMA顺序搬运实现。除非你额外维护一个“索引序列缓冲”,先算好一整串索引,再让DMA按这个缓冲里的地址去查表,这既浪费内存又绕远路。
所以我最终选择了最直接、也最能体现DDS本质的实现方式:定时器更新中断里做一件事——累加相位、查表、写DAC。100kHz的中断触发率对72MHz主频的单片机来说只占用不到一半CPU资源,主循环依然有充足时间处理按键、显示和扫频逻辑。
4.2 定时器触发中断的配置要点
配置要点是把定时器溢出周期设为DAC采样周期的整数倍。比如系统时钟72MHz,想得到100kHz采样率,定时器分频系数设72(得到1MHz计数频率),重装值设10,溢出就是100kHz。这个频率也就是每个波形点的输出速率,它决定了DDS的采样率f_s,公式里的fs就是它。
不要在定时器中断里调用任何HAL库的“阻塞式”函数,比如延时、打印、等待DAC转换完毕。DAC写数据寄存器本身很快,但如果你用库函数先查状态再写值,中断里可能多耗几百个周期,波形输出的时间抖动会明显加大,表现就是频谱变脏。我把DAC数据寄存器的写入精简成一条直接赋值语句:
void TIMx_IRQHandler(void) { if (TIMx->SR & TIM_SR_UIF) { TIMx->SR &= ~TIM_SR_UIF; phase_acc += freq_word; uint8_t idx = (uint8_t)(phase_acc >> 24); DAC->DHR12R1 = wave_table[wave_sel][idx]; } }其中phase_acc是32位相位累加量,freq_word是频率控制字,idx取累加器最高8位对应256点表索引。这个写法在绝大多数Cortex-M单片机上都是可用的,具体寄存器名请以自己所用型号的数据手册为准,但逻辑完全一致。
4.3 波形表生成代码:一次算好,剩下全是查表
波形表我选择在初始化阶段用浮点函数预生成一次,生成完以后中断里只有整数查表,没有浮点运算,保证实时性。正弦表和三角波、锯齿波、方波一起合成一张二维数组:
#define TABLE_SIZE 256 static uint16_t wave_table[4][TABLE_SIZE]; void build_wave_tables(void) { int i; for (i = 0; i < TABLE_SIZE; i++) { float phase = 2.0f * 3.1415926535f * i / TABLE_SIZE; // 正弦波,偏置到0~4095范围 wave_table[0][i] = (uint16_t)((sinf(phase) + 1.0f) * 2047.5f); // 三角波 if (i < TABLE_SIZE / 2) wave_table[1][i] = (uint16_t)(i * 4095.0f / (TABLE_SIZE / 2)); else wave_table[1][i] = (uint16_t)((TABLE_SIZE - i) * 4095.0f / (TABLE_SIZE / 2)); // 锯齿波(上升沿) wave_table[2][i] = (uint16_t)(i * 4095.0f / TABLE_SIZE); // 方波 wave_table[3][i] = (i < TABLE_SIZE / 2) ? 4095 : 0; } }我把12位DAC的满量程当成4095来用,正弦波幅度范围是0到4095,对应DAC参考电压0到3.3V。后续经过运放减法器变成正负对称输出时,偏置自然被去掉。这里有个细节需要注意:三角波在i等于TABLE_SIZE/2时,按上述代码算出来会略超过4095,但由于整数截断原因实际不会越界,如果你想严谨一点可以加个if限制。
4.4 人机交互与波形输出的“握手”:并发修改频率的安全姿势
频率控制字freq_word是主循环和中断服务函数共享的变量。主循环负责根据按键、编码器修改它的值,中断服务函数每个采样周期读取它。这个共享本质上是个多线程并发问题,处理不好就会出现“波形频率乱跳”的偶发现象。
我的做法是这样:频率控制字在主循环中计算完毕,通过一个volatile uint32_t变量传给中断。Cortex-M系列的32位整数读写本身就是原子的,只要保证主循环修改变量时不被中断打断到“读到一半改到一半”即可。更稳妥的办法是关中断更新:
uint32_t old, new_word; __disable_irq(); old = freq_word; new_word = calc_freq_word(new_freq, fs); freq_word = new_word; __enable_irq();如果两个变量都写入同一个整数,这么做基本不会出错。但如果像某些设计那样把频率拆成高位低位两个变量分别更新,就一定要关中断或做影子变量,否则极低概率下示波器会看到输出频率跳到某个莫名其妙的值,且只在改频率的瞬间出现。
按键消抖部分也要重视。我在主循环里实现了一个简单的状态机消抖,检测到稳定按下才更新频率,不然机械开关的抖动会让中断看到一连串伪操作,明明只按一下,频率却跳了十几格。这时候先别怀疑DDS算法,先去查消抖逻辑——这是经验之谈。
5. 实测调校:四个高频问题的完整排查链路
5.1 现象一:正弦波莫名出现台阶
第一版固件烧进去后,示波器上看到的“正弦波”像被劈了几刀——有明显的台阶和畸变。我第一反应是波形表生成代码有问题,但反复检查正弦表数值都正常。后来把输出频率设到极低,比如1Hz,然后逐个点查看DAC输出,发现有一个位置出现了跳变。
排查链路是这样的:先用逻辑分析仪同时抓定时器中断标志和DAC写入时序,确认每次中断都有且只有一次DAC写操作。然后怀疑是不是相位索引算错了——DDS的索引是累加器的高8位,256点表必须有且只有256个不同索引。我猛然想起,表格大小常量写的是256,但索引掩码用的却是& 0x80,只保留了最高一位,相当于表长度被截断成128甚至更少。台阶就是这么来的:索引被截断导致波形周期内出现重复片段。
修复方式很简单:索引掩码要和表大小严格对应。256点表正好对应取高8位,掩码应该是0xFF而不是0x80。这类“小细节浪费一下午”的情况在嵌入式项目里太常见了,调这类问题最有效的办法就是把数据逐点打印出来,而不是盯着示波器看波形猜原因。
5.2 现象二:频率调高后波形明显毛糙
输出1kHz正弦时相当漂亮,拧到15kHz后波形开始出现明显的“颗粒感”,不再是平滑曲线。排查这个问题时我先在纸上算了一个数:采样率100kHz、输出15kHz,每周期只有6.7个点。这就意味着DAC本身输出的就是阶梯状波形,无法还原平滑正弦,后面的低通滤波器如果截止频率不是匹配设计的,阶梯效应就会非常刺眼。
完整排查过程是这样的:第一步,把频率降到2kHz(每周期50个点),确认毛糙消失。这一步说明问题根源不是高频干扰,而是采样点数不足。第二步,给DAC输出加一阶RC低通,截止频率设在30kHz左右,让10kHz以上的台阶谐波被衰减。第三步,在电源端加退耦电容,并确认波形毛糙没有叠加在低频纹波上。最终发现,制约高频段波形质量的是“每周期采样点数”和滤波器设计,而不是单片机算不过来。
要输出平滑的20kHz正弦,采样率就得提到500kHz以上,且后级低通滤波要有足够带宽。如果只是做实验,20kHz输出直接用10kHz以下的波形质量是不够的。商业仪器内部其实也干同样的事,只是它的采样率和滤波器指标高得多。
5.3 现象三:空载正常、接上负载就“塌”了一半
这是所有自制信号发生器都会撞上的经典问题。现象:示波器探头直接测DAC输出端,波形幅度正常;把输出接到运放输入或功放输入后,幅度跌落超过预期,甚至波形变方。
我把万用表量了一遍输出节点,发现输出阻抗远高于标称的50Ω。原因是我在第一版里为了“加强驱动能力”,在运放后面又串了一个10kΩ电阻,结果变成和负载分压,负载轻时影响不明显,一旦接几百欧以下负载就露馅。修改方法是把串阻改成50Ω标准值,并确认运放输出级“源阻抗”接近于零——换句话说,让运放主导驱动能力,而不是用外围电阻做无谓的限流。
排查这个问题时有个容易误判的点:示波器探头本身就带约20pF输入电容,如果信号源输出阻抗极高,探头电容和源阻抗会形成低通滤波,让波形高频段衰减变快。你在示波器上看到“幅度塌了一半”不一定是负载问题,可能是探头容性负载和输出阻抗的交互。判断办法很简单:换用1x无源探头或直接短接线测,看幅度差异。
5.4 现象四:旋转编码器调整频率时,输出偶尔乱跳
这个问题极其隐蔽,而且只在“旋转编码器+显示刷新”同时工作时才出现。表现为:旋钮转了几下,输出频率突然跳到几千Hz,再转又正常,接着又跳。我一开始怀疑编码器信号没加滤波,把状态机消抖重写了一遍,没用。
后来用示波器抓显示刷新引脚的波形,发现每当OLED更新一帧画面时,主循环被大量占用,定时器中断服务函数虽然能执行,但读取freq_word的时刻恰好卡在编码器状态机更新了一半的地方。也就是说,编码器计数和DDS频率控制字之间缺乏同步保护。
修复办法是在编码器状态机的更新函数里也做关中断的原子更新,保证编码器计数变化对主循环可见时是完整的一次变化。另外显示刷新不要每帧都阻塞式等I2C传输完成,改用异步或者降低刷新率,可以留出更多时间给主循环处理按键逻辑。这类问题不细抓表面代码根本看不出问题,但一旦思路转到这里,排查就很快了。
6. 进阶玩法:把信号源变成真正的调试工具
6.1 扫频模式:徒手给滤波器画幅频曲线
基础版信号发生器已经能稳定输出各类波形了,但我做这台设备真正的动力来自扫频测试。所谓扫频,就是让输出频率按照设定范围从低到高连续变化,同时记录被测电路的响应幅度,从而画出幅频特性曲线。
固件实现非常符合DDS的“气质”:在主循环里每隔固定时间更新一次频率控制字,比如每10ms增加0.5Hz,从100Hz扫到10kHz大约需要200秒,中间随时可以暂停。需要注意扫频步进和间隔时间的配合,步进太大会让曲线不够平滑,间隔太短又会让被测电路来不及响应。对于大多数音频滤波器,我试过步进0.5Hz、间隔20ms的效果已经足够精细。
实际操作中我的做法是:示波器CH1接信号源输出,CH2接被测电路输出,用X-Y模式或者直接用两个通道的包络对比,就能看到滤波器的谐振峰和衰减斜率。这个玩法比单纯用万用表量电压直观得多。
6.2 调幅和包络:第二路DAC的用武之地
很多中低频测试还需要调幅信号,比如模拟传感器调制输出、测试音频解调电路。如果单片机的DAC有第二路通道,可以把它当作低频包络发生器,外部用一个模拟乘法器将载波和包络相乘,就能得到幅度受控的AM信号。
我没有额外接乘法器,而是利用DDS的特点做了一种更投机但也有效的简化做法:直接让波形的幅值乘以包络采样值。具体是每隔一定采样点更新一次幅度缩放系数,在中断里把查表结果乘以系数再右移。这个做法对低频包络效果很好,但如果包络频率高到和载波相近,就会产生混叠,需要慎用。做AM信号时我会把载波设在10kHz以上,包络设在100Hz以下,效果跟专门芯片做出来的没有本质区别。
6.3 用户自定义波形:最简单的任意波形发生器
既然波形表已经是一段可替换的数组,自定义波形就是水到渠成的事。我用串口接收上位机发来的256点或1024点数据,存入一段用户波形缓冲区,再把波形选择切到“用户”档位,中断查表时自动使用新数组。
一个小技巧:如果自定义波形的点数不是256而是1024,索引就不是取高8位那么简单了,得用(phase_acc >> 22) & 0x3FF。位数对应关系容易写错,建议在代码里用宏定义封装表大小和索引掩码:
#define WAVE_TABLE_BITS 8 #define WAVE_TABLE_SIZE (1 << WAVE_TABLE_BITS) #define WAVE_INDEX_MASK (WAVE_TABLE_SIZE - 1) uint8_t idx = (uint8_t)((phase_acc >> (32 - WAVE_TABLE_BITS)) & WAVE_INDEX_MASK);这样以后想换表大小,只需要改一个宏。我用这个功能生成了扫频的线性调频信号、带毛刺的“故障波形”来模拟传感器异常,效果非常理想。一个你亲手做得能跑、能调、能改的功能,远比一个什么都行的商用黑盒子有价值。
做完整台机器后,我发现自己最常用的功能反而不是高深的自定义波形,而是1kHz正弦和扫频模式。前者是调各种电路的基准信号源,后者是一把“专业仪器才有的尺子”。回头再看这套项目,硬件成本不高,难点始终在DDS的理解和输出链路的细节上。如果你也打算动手做一次,我的建议是先跑通最基础的DDS正弦输出,再加其它波形和功能——每一步都是看得见摸得着的进展,改代码也更有章法。