简介:基于AD9910的波形发生器完整工程源码,面向STM32开发者、电子竞赛参赛者及需要高频信号源的工程师,可解决DDS波形生成与参数调制的开发难题。工程实现1Hz-400MHz正弦波输出,幅度在1mV-650mV间可调,支持上下限频率、频率步进及1-262us步进时间间隔可调的扫频波,并利用RAM调制模式产生方波,采样间隔可在4ns-262us范围内设置。资源共72个文件,包括31个C源码、31个头文件、8个启动文件、1个Keil工程文件和1个开发文档,压缩包仅303KB,目录结构按DDS库、CMSIS、启动代码、APP及驱动分层,便于阅读和移植。已有2874人学习下载,适合需要快速搭建波形发生器或深入学习AD9910驱动及STM32嵌入式开发的读者。内含DDS v2.0库、标准外设库及方波、扫频等模块,结合开发文档可轻松二次开发,是电路设计、实验和竞赛调试的实用资料。 2015年电赛题目里“波形发生器”一出,实验室里至少有一半的队伍开始翻AD9910的数据手册。这东西确实是做信号源的经典方案,1 GSPS的采样率、14位DAC、支持多种调制模式,配合FPGA或者单片机都能出活。我当年就是用这颗芯片做的参赛方案,从原理图到代码到调试踩坑一路走下来,积累了不少经验,这里系统整理一下,给准备拿DDS做信号源的同学们做个参考。
1. 为什么选AD9910而不是其他DDS方案
搞波形发生器,方案其实挺多的。直接数字频率合成(DDS)、锁相环(PLL)加分频、甚至用FPGA内部DDS IP核,都能产生波形。但一到具体比赛和工程场景,差异就出来了。
AD9910属于ADI的高性能DDS系列,核心指标是内部采样率1 GSPS,也就是说DAC每秒能更新10亿个点。这个速度决定了它能直接合成最高约400 MHz的正弦波(理论上是采样率的一半,实际受限于重建滤波器和DAC输出特性,工程上一般用到400 MHz以内没问题)。14位DAC的分辨率也让输出信号的幅度精度和杂散表现比常见的8位或者10位方案好不少,这一点在示波器上看信号质量时特别明显。
还有个关键点是它内部集成了32位频率控制字(FTW),频率分辨率可以做到大约0.23 Hz(1 GHz / 2^32)。这意味着在比赛要求“输出频率可精确到1 Hz”甚至更细的时候,AD9910可以轻松满足,完全不需要外部做额外的频率微调电路。相比之下,如果用PLL方案,要兼顾频率分辨率和锁定时间,在高频段做扫频就会非常痛苦;而FPGA DDS IP核虽然灵活,但DAC还得外挂,高频段对PCB布局要求极高,不如AD9910这种单芯片方案省心。
我见过有些队伍用AD9850或者AD9854,这些芯片体积小、驱动简单,但采样率最高只有几百MSPS甚至更低,输出频率上限和杂散指标都不如AD9910。电赛题目一般不会只要求出个正弦波,往往还要求输出的频率范围覆盖几十kHz到几十MHz,甚至到100MHz以上,这时候AD9910的优势就很明显了。所以我们的队伍当时毫不犹豫选了AD9910作为核心。
用生活化一点的话说,AD9910就像一个精度极高的数字“调音台”,你把频率、相位、幅度这些参数通过数字接口告诉它,它就能以极高的节奏把波形“演奏”出来。其他方案要么节奏不够快,要么精度不够细,在综合性能和上手难度上,AD9910是平衡得最好的一个选择。
2. DDS原理与核心参数计算详解
2.1 相位累加器到底在累加什么
DDS的基础概念不复杂,但很多初学者卡在相位累加器这个环节。我用一个简单的类比来解释:想象一个圆的周长是2^32(也就是4294967296),你每一步走FTW这么长。如果FTW很大,你绕圈的速度就快;如果FTW很小,绕圈就慢。每绕完一圈,就对应输出一个完整周期的波形。这个“走步子”的动作就是相位累加器在做的事,它每个时钟周期累加一次FTW,累加结果的高位作为查表地址,去ROM里取出对应的幅度值。
关键公式是:
[ f_{out} = \frac{FTW \times f_{sys}}{2^{32}} ]
反过来,已知目标频率求FTW:
[ FTW = \frac{f_{out} \times 2^{32}}{f_{sys}} ]
用AD9910在1 GHz系统时钟下输出10 MHz正弦波来算:FTW = 10e6 × 4294967296 / 1e9 ≈ 42949673。写成十六进制是0x028F5C29。这个值就是需要写入频率控制寄存器的核心数据。
2.2 频率控制字的取整误差
实际计算FTW时几乎总会遇到小数。比如算出来是4294967.6,寄存器只能写整数,就存在截断误差。带来的频率误差大约是多少?可以用极端情况估算:FTW差1,输出频率差 f_sys / 2^32 ≈ 0.23 Hz。这就是之前说的0.23 Hz分辨率的来源。对绝大多数应用来说这个误差完全可以接受,但如果比赛要求输出频率精确到0.1 Hz级别,就需要考虑用两个DDS芯片做细调和粗调,或者对FTW做余数补偿。不过从我实际接触的赛题来看,做到1 Hz精度已经能覆盖绝大多数测试点了。
2.3 幅度控制与相位控制
AD9910的幅度控制有几种方式:一种是内部14位幅度控制字直接设置输出幅度比例,另一种是开OSK(Output Shift Keying)引脚,通过外部引脚控制幅度变化率。在比赛里多数用第一种,因为简单可靠。幅度控制字的含义大致是:输出幅度 = 满幅 × (AMPCTL / 2^14)。需要注意幅度控制字只对DAC输出电流起比例作用,模拟通路上的增益还是靠外部放大器决定,所以控制字设成最大值不代表DAC输出满幅,还要看外围电路匹配。
相位控制是另一个实用功能。AD9910支持14位相位偏移字(POW),粒度约为360°/16384 ≈ 0.022°。这在需要输出两路同频不同相波形时非常有用,比如题目要求输出相位可调的正弦波,或者做BPSK调制。多片AD9910同步还能做多通道相位一致输出,利用SYNC_IN和SYNC_OUT引脚级联即可,这个后面细说。
2.4 无杂散动态范围(SFDR)怎么看
SFDR是衡量DDS输出纯净度的关键指标,反映的是信号中最大杂散分量与基波的差值,单位是dBc。AD9910的SFDR典型值在-50 dBc到-80 dBc之间,取决于输出频率和滤波器设计。比赛时有的题目要求输出信号谐波失真小于-40 dBc,这个指标直接决定了要不要加滤波器、加什么样的滤波器。虽然AD9910内部已经有了DAC重建滤波器,但严格来说那只是用来去掉采样镜像频率,真正要得到干净频谱,外部低通或带通滤波器是少不了的。
3. 硬件设计要点:从原理图到PCB布局
3.1 电源分配与去耦
AD9910对电源质量很敏感,数据手册里明确标注了DVDD(数字3.3V/1.8V)和AVDD(模拟3.3V/1.8V)要分开供电。实际做板子的时候,我用两个独立的LDO分别给数字和模拟部分供电,中间用磁珠单点连接地平面,避免数字开关噪声串到模拟端。每个电源引脚旁边都放0.1uF和10uF的电容组合去耦,特别是靠近DAC输出引脚的高频去耦电容,摆放位置必须紧贴芯片引脚,走线尽量短。电源纹波控制在20mV以内比较稳妥,否则输出信号的相位噪声会明显变大。
3.2 参考时钟设计
AD9910的REF_CLK输入推荐使用差分信号,但实际工程为了省事,很多人直接用单端有源晶振接REF_CLK引脚。官方手册给出了单端转差分的接法,在REF_CLK引脚通过AC耦合电容接入,另一个互补引脚接0.1uF电容到地或者通过电阻分压偏置到合适电平。我实测下来,用50MHz有源温补晶振(TCXO)做参考源,倍频到1GHz,在100MHz输出时相位噪声表现还不错。
这里有个关键细节:REF_CLK经过内部PLL倍频到SYSCLK(系统时钟),PLL的环路滤波参数是通过寄存器配置的,不同的参考频率对应不同的分频比和环路带宽。如果配置不对,输出信号会有很大的抖动,示波器上看就是波形边缘毛糙,频谱仪上则是相噪恶化。我一般建议先固定参考频率,然后根据手册上的表格查对应的PLL配置。
3.3 输出匹配与滤波器
AD9910的DAC输出是电流型,满幅输出约8.7mA(可调)。输出引脚IOUT和IOUT̅是互补的,各通过一个电阻到地,转成电压信号,然后经过变压器或者差分放大器转成单端信号。如果直接测IOUT引脚电压而不做转换,你会看到幅值很小甚至只是一个直流偏置,这是很多初学者容易误判“芯片没输出”的原因。要用Balun(平衡-不平衡变压器)或者运放差分转单端,再接入50欧负载。常用的做法是初级接IOUT和IOUT̅之间接一个电阻当作差分负载,次级阻抗匹配到50欧,再接SMA头。
输出滤波器的截止频率要根据目标频率上限来定。比赛题通常要求最高输出频率是题目给的上限,比如30MHz,那低通滤波器的截止频率取40~50MHz比较合适,既要保证带宽内平坦度,又要对带外镜像和杂散有明显抑制。可以用椭圆滤波器或者切比雪夫滤波器,阶数选5阶或7阶,实际用ADS仿真一下再贴片,效果比盲调强得多。
3.4 PCB布局的几个实际经验
AD9910是LQFP-100封装,引脚密,布局布线如果不注意,板子做出来调试会非常痛苦。我的经验是:芯片底下尽量铺完整的地,不要有信号线横穿;SPI控制线远离DAC输出走线,尤其是串行时钟线,如果离IOUT太近,谐波会被耦合到输出端;DAC输出走线尽量短而直,阻抗控制在50欧;去耦电容靠近芯片电源引脚,过孔直接下到地平面。
还有一点容易忽略的是散热。AD9910满速跑起来功耗大约在1W左右,虽然不至于烫到烧毁,但芯片表面温度明显高于室温。如果做的是便携小盒子,建议在芯片下方增加散热过孔阵列,帮助热量导到背面铜箔。环境温度升高对DAC的线性度会有影响,波形失真会变大,所以别忽略散热。
4. 软件控制与波形实现流程
4.1 SPI通信与寄存器初始化
AD9910支持SPI串行配置和并行配置两种方式。比赛里通常用SPI就够了,频率不高,连线也简单。SPI时序要注意几点:CPOL和CPHA默认是模式0,即时钟空闲为低、数据在上升沿采样;指令字(16位)和有效数据(32位或64位)一起发送,手册里有明确的时序图。我最初调试时SPI总是读回错误数据,后来发现是MCU的SPI时钟速率太高,AD9910虽然是高速器件,但SPI接口最高只能跑到约100MHz,低一些更稳,实际我用的是2MHz,毫无问题。
寄存器初始化的顺序有讲究。一般先复位芯片(通过CFR2中的全局软复位位),等待一段时间后再写其他控制寄存器,最后写频率、相位和幅度控制字。如果复位后立即写数据,芯片内部PLL还没稳定,指令可能被丢弃。稳妥流程是:上电 → 延时100ms → 软复位 → 延时20ms → 配置PLL参数 → 延时10ms → 配置输出模式和工作模式 → 写频率/相位/幅度控制字。
4.2 寄存器地址速查
AD9910的寄存器按Profile方式存取,最常用的是:
- CFR1:地址0x00,控制各种使能位,比如DAC校准使能、DRG使能等
- CFR2:地址0x01,控制PLL分频比、系统时钟来源等
- CFR3:地址0x02,控制频率累加器步进方式(自动清位等)
- 频率/相位/幅度控制字:地址0x04、0x05、0x06,分别对应FTW、POW、AMP
寄存器都是32位宽,但SPI支持对单个字节操作,写的时候用8位模式多次写或者一次性写4字节都行。我习惯用4字节模式一次写入,逻辑清晰,读写也方便。注意如果开了Profile切换模式,控制字要写到Profile0~Profile7对应的寄存器区域,地址是0x0E到0x17这些,需要在初始化时先把Profile优先级等参数设置好。
4.3 如何实现FSK、ASK和扫频输出
AD9910的Profile模式非常适合FSK和ASK。把两个频率控制字分别存入Profile0和Profile1,然后通过PS0和PS1引脚切换当前生效的Profile。外部MCU只需控制这两个引脚的电平,就能瞬间改变输出频率,切换速度是纳秒级的,完全满足FSK调制的要求。
扫频输出利用的是DRG(Digital Ramp Generator)功能。在CFR1里使能DRG,然后设置频率起始值、步进和步进间隔时间。通过寄存器配置将这些参数写入控制字,DRG会根据步进设置自动从起始值累加到目标值,实现线性扫频。步进间隔可以精细调节,最小时钟周期为单位,可以实现几乎连续平滑的扫频效果。比赛要求扫频速度可调的场合,这一套控制很实用。
我分享一下我当时写的一个简单C代码框架,基于STM32的SPI接口:
void AD9910_Init(void) { SPI_Init(); // 配置SPI1,速率2MHz AD9910_Soft_Reset(); delay_ms(100); // CFR1: 使能DAC校准,选择Profile0 uint32_t cfr1_value = 0x00000003; AD9910_WriteReg(0x00, cfr1_value); // CFR2: 设置PLL倍频系数,系统时钟=1GHz // 假设参考时钟50MHz,倍频系数20倍 uint32_t cfr2_value = 0x0A900000; AD9910_WriteReg(0x01, cfr2_value); // CFR3: 自动清除相位累加器 uint32_t cfr3_value = 0x00000010; AD9910_WriteReg(0x02, cfr3_value); // 设置输出频率10MHz,FTW=0x028F5C29 uint32_t ftw = 0x028F5C29; AD9910_WriteReg(0x04, ftw); // 设置幅度为满幅 uint32_t amp = 0x3FFF; AD9910_WriteReg(0x06, amp); } void AD9910_WriteReg(uint8_t addr, uint32_t data) { // 指令字:W/R位为写,地址8位 uint16_t instruction = 0x0000 | (addr << 8); // 先发指令,再发4字节数据,MSB在前 SPI_Send16(instruction); SPI_Send32(data); }这段代码看着简单,但要注意SPI发送数据时字节序,AD9910默认是MSB先行。如果用低端MCU,要注意SPI库函数的字节序设置。我当时在字节序上折腾了很久,后来直接用逻辑分析仪看时序才发现问题。
4.4 与FPGA的配合方案
有些队伍选择用FPGA来驱动AD9910,优点是可以并行写入控制字,频率切换速度更快,适合要求快速跳频的信号源。用FPGA时,原理类似,只是把SPI和引脚控制全部用Verilog或VHDL实现。并行模式占用的引脚多,但胜在速度快,单次写频率控制字只需要一个时钟周期。我比赛时用的是MCU方案,因为代码调试方便,逻辑也直观。如果是做产品化设计或者需要极高速跳频的,再考虑FPGA。
5. 常见问题与排查技巧实录
5.1 上电后没有任何输出
这是最常遇到的问题。排查顺序我一般是这样:
- 检查电源:AVDD和DVDD是否正常,上电顺序是否满足手册要求(先数字后模拟或者同时上电)。AD9910对电源上电顺序有一定要求,如果顺序不对可能锁死。最好用示波器测一下电源引脚的爬升波形。
- 检查SPI写入是否成功:回读寄存器验证。AD9910支持通过SPI回读寄存器内容,写完后读一遍,看看是否和写入一致。
- 检查配置顺序:是不是先复位了但没等待足够时间,PLL还没锁定就写参数了。可以在配置后延时50ms再用示波器看输出。
- 检查输出负载:如果没有接50欧负载,IOUT引脚悬空,DAC处于高阻状态,示波器探头负载又太高,可能测不到正确波形。
5.2 输出波形幅度很小或者失真明显
幅度问题的常见原因:
- DAC输出是电流型,负载电阻太小会导致输出电压摆幅过低。一般IOUT引脚到地接50~200欧电阻,具体阻值根据期望输出电压算。如果接50欧,满幅电流8.7mA,输出只有约0.4V峰值。
- 未做差分转单端,直接测量IOUT对地电压,只有一半的信号能量。
- Balun选型不对,工作频段覆盖不到目标频率,导致高频衰减严重。
- 滤波器截止频率太低,把目标信号也一并滤掉了。
失真问题多半是滤波器没调好或DAC增益设置过高导致削波。另外,AD9910内部有DAC校准功能,初始化时一定别漏掉。如果不校准,输出波形的SFDR会明显变差,高低频增益也不均匀。
5.3 扫频不平坦,幅度起伏大
扫频时输出幅度随频率变化是一个典型问题,原因是后级滤波器在通带内存在纹波,加上DAC本身的输出在接近奈奎斯特频率时会自然衰减。解决办法一是使用具有更平坦通带的滤波器类型,比如巴特沃斯;二是在MCU或上位机做幅度预补偿,根据“频率-增益”的映射关系反推控制输出幅度。
5.4 多片AD9910同步问题
如果要做两路同步输出,需要利用SYNC_IN和SYNC_OUT引脚做菊花链级联。具体做法是:将第一片设为主机,输出SYNC_OUT信号;第二片的SYNC_IN接收这个信号,并配置为从机模式。然后通过寄存器把各片内部PLL的锁定状态对齐。我在调试时会示波器同时看两路输出,仔细调各片的同步延迟,直到两路波形重叠。
6. 一点项目体会与扩展建议
如果说有什么最深刻的总结,那就是:AD9910是个功能很强但初始化细节很多的芯片,成功的关键是“慢和稳”。慢是指SPI时序不用追求高速度,稳妥最重要;稳是指上电和复位流程按部就班,每一步都验证过后再往后走。我第一次调试时因为SPI时序没有严格遵守,卡了两天,最后用逻辑分析仪一帧一帧地查波形,才定位到是字节序和时钟极性配置错了。
这个项目后续还可以扩展的地方不少。如果加上一个宽带VGA(可变增益放大器)做后级,就可以实现任意波形输出的衰减控制;如果用两片AD9910配合正交调制器,还能做出相位连续可调的QPSK信号源;再往上,将AD9910的输出当作本振信号,配合混频器,还可以做上变频,把信号搬移到更高频段,覆盖到GHz以上。这些方向在通信系统、雷达模拟器、测试仪器里都有真实的应用场景。
对于准备电赛的同学,我的建议是:别贪多,先把正弦波从10MHz到100MHz的输出和频率切换做扎实,再考虑调制功能。把AD9910的核心控制逻辑跑通,在后面调试其他模块时,你会发现自己已经掌握了一个非常可靠的信号源工具。
本文还有配套的精品资源,点击获取