简介:一套基于STM32F407的HAL库驱动AD9910 DDS模块输出正弦波的完整工程资源,适合具备一定单片机基础、正在调试DDS波形发生器的开发者。内容覆盖SPI接口初始化、GPIO引脚配置、AD9910控制寄存器写入以及DDS相位累加算法,并配套Keil与IAR工程文件,可直接导入开发环境。压缩包共1114个文件,其中C源码591个、头文件282个,另有链接脚本、HAL库驱动、编译中间文件与HEX烧录文件等,整体约39.36MB,目录结构清晰,便于按模块查找。目前已有2073人学习该资源。借助工程内的驱动代码,可减少反复查阅数据手册的时间,重点关注SPI时序与寄存器配置,即可输出频率和幅度可控的正弦波,对信号发生器、扫频源等实际项目具有直接参考价值。 最近在调一个宽带信号源的项目,核心就是用STM32F407的HAL库去驱动AD9910这个DDS模块。AD9910这块芯片不是那种入门级DDS,它内部有14位DAC、支持1 GSPS级别采样率,还自带数字斜坡、扫频、甚至频率跳变能力,研究明白之后能省掉一大片模拟电路。这篇文章打算把我在实际项目中从接线、SPI初始化、寄存器配置,到输出滤波和校准踩过的坑全部捋一遍,给同样用F407玩AD9910的朋友一份能直接“抄作业”的参考方案。
写这篇文章之前,我特意去翻了一圈网上的资料,发现这芯片的中文教程少得可怜,大部分都是AD9850/AD9834这类低端DDS,真要去查AD9910的寄存器手册又很痛苦。所以这次我尽量把话说得直白些,从原理到代码,再到排查思路,一条线穿起来,适合那种已经跑通HAL库基础驱动、正准备开始啃高速DDS的开发者。
1. 整体设计与思路拆解
1.1 为什么选AD9910而不是普通DDS
先说说AD9910这颗芯片的定位。市面上常见的AD9833输出频率上限大概在12.5MHz,AD9850可以到几十MHz,但对于雷达信号模拟、跳频通信、扫频激励源这类应用来说,带宽还是不太够。AD9910的DAC速率虽然标称1 GSPS,实际要根据参考时钟和PLL倍频来定,但它胜在两点:一是频率调谐字是32位的,频率分辨率做到微Hz级别,二是内部的数字斜坡和profile切换机制非常适合快速扫频和跳频,这在做滤波器扫频测试、锁相环驱动、声呐模拟时特别好用。
如果你只是做简单的低速信号源,AD9910是有点杀鸡用牛刀,F407跑一个SPI去写寄存器,控制起来反而没有AD9833直接。但如果你有“窄带扫频 + 点频跳变 + 相位可控”这类需求,就是AD9910的主场了。我当时的需求是产生一个几百MHz范围的正弦扫频信号,而且扫频不能有回看延时,要求频率连续变化,这种情况下软件一个一个点去改FTW是来不及的,只能交给AD9910内部的数字斜坡引擎。
1.2 F407搭配HAL库的优势
F407在这个组合里的角色就是个“配置者”,不参与高速波形合成,但需要保证配置的实时性和稳定性。选F407而不是G031或者F103,就是因为它主频高、外设丰富,我后面还计划用它的定时器触发DAC或者ADC同步采集,用F103可能CPU占用率会吃紧。
HAL库对比寄存器开发,其实最大的优势不是代码效率,而是CubeMX帮你生成了底层时钟树和外设初始化,DDS驱动代码可以专注于“业务逻辑”。比如SPI的SCK极性、相位、分频系数,以前在标准库要翻寄存器,现在CubeMX界面下拉选一下就行,虽然底层还是那点东西,但出错概率低得多。而且HAL库函数名非常语义化,HAL_SPI_Transmit一看就知道是发SPI数据,比直接操作SPI->DR要直观,这对后期维护和团队协作很友好。
1.3 串行接口和并行接口怎么选
AD9910实际上是支持并行和串行两种配置方式的,并行总线可以一下子把寄存器值全写进去,速度很快,但代价是占用几十个GPIO。F407引脚虽然有100多个脚,但我不想为了这个把其他外设都拆掉,所以选择了串行SPI方式。串行方式配置速度其实也够用,因为正常工作时F407并不需要频繁改寄存器,只有在上电初始化和需要切换模式时才发送一次配置帧。
有一点要注意,AD9910的串行接口是一种通用的3线或4线SPI,支持MSB或LSB优先,还可以工作在CPOL=0、CPHA=0模式下。我实测F407的SPI1配置成模式0完全能驱动,只要你把时序图和数据手册对照一遍。很多朋友调不通,十有八九是SPI相位或极性选错,后面第六部分我会专门列这个问题。
2. 硬件连接和基础配置
2.1 引脚分配与最小系统参考
AD9910模块网上有很多种,引脚定义可能会略有差异,但核心控制引脚基本一致。我这边的模块接口是:SCLK、SDIO、CS、IO_UPDATE、RESET、PROFILE0、PROFILE1(有些模块没有),另外还要接电源和参考时钟。
我用STM32F407VET6,SPI1对应的默认引脚是PA5、PA6、PA7,但DDS模块只需要一根数据线SDIO,所以SPI就配置成“全双工主机”,把MOSI当SDIO用,MISO悬空或者不管。片选CS我用普通GPIO来拉低拉高,而不是SPI硬件NSS,因为软件控制CS在各种时序组合下更灵活,能避免NSS自动模式带来的干扰。IO_UPDATE是更新寄存器到内部动作的关键信号,必须单独找一个GPIO;RESET引脚也要软件控制,方便上电后重新初始化。
| 功能 | AD9910模块引脚 | F407引脚 | 说明 |
|---|---|---|---|
| SPI时钟 | SCLK | PA5 (SPI1_SCK) | 极性/相位模式0 |
| SPI数据 | SDIO | PA7 (SPI1_MOSI) | 3线模式,双向但配置只用写 |
| 片选 | CS | PB12 (GPIO输出) | 软件拉低使能 |
| 更新锁存 | IO_UPDATE | PB13 (GPIO输出) | 上升沿把缓冲寄存器锁存到内部 |
| 复位 | RESET | PB14 (GPIO输出) | 低电平有效,上电拉低复位 |
| Profile选择 | PROFILE0 | PB15 (GPIO输出) | 单频/扫频模式控制 |
如果你的模块是5V供电,千万先确认板载稳压。AD9910核心电压和IO电压都是1.8V到3.3V范围,千万不要直接怼5V到SCLK和SDIO上。F407是3.3V系统,正常连接就好,但要加一个100nF左右的去耦电容在模块电源附近,高速DDS对电源纹波非常敏感。
2.2 参考时钟与内部PLL设置
AD9910内部有一个可以旁路的PLL,它能把外部参考时钟倍频到系统时钟SYSCLK,SYSCLK才是DDS真正的采样时钟来源。比如我的模块外部给了25MHz的参考晶振,通过内部PLL倍频到500MHz作为SYSCLK,那DAC采样率就是500MSPS,输出信号最高可以到200MHz左右。
PLL的倍频系数、电荷泵电流、VCO频率范围都在CFR2寄存器里配置。AD9910的PLL环路滤波器需要在外部接一个电阻电容网络,具体元件值可以在数据手册里按照“参考时钟频率、SYSCLK频率”查表得到。这里我强烈建议大家不要随意改元件,我第一次想手动优化环路噪声,结果PLL直接锁不住,DDS输出完全乱掉。
你在设计系统时最好把参考时钟来源想清楚,是要用一个独立的有源温补晶振,还是从F407的MCO引脚输出一个时钟过来。我一开始尝试从F407的MCO输出25MHz给AD9910,结果是能出波,但相位噪声明显变差,后来老老实实换成独立有源晶振。
2.3 SPI通信时序的几个关键点
AD9910串行通信的指令格式是:先发一个指令字节,高8位是寄存器地址和读写方向,低8位是字节计数值-1,意思是要读或写多少个字节。以写操作举例,发送第一个字节前CS拉低,发送完成后,紧跟着发送的数据就是要写入寄存器的原始内容,字节数由指令字节里的count+1决定。
这个机制意味着如果我想写CFR1(这个寄存器占了4个字节),需要先发送指令字节0x00(地址0x00,写方向,位7=0,count-1=3,所以低4位是0x03),然后连续发送4个寄存器值。这里最容易被忽略的就是“字节计数必须减1”,很多朋友第一次写寄存器发现数据不进去,就是因为把count直接填了4。
用F407的HAL库驱动时,可以用两个长度很短的数组拼一个发送帧,一次HAL_SPI_Transmit发完。注意CS的电平时序,要在SPI发送指领字节之前拉低,所有字节发完后延时几个毫秒再拉高,不要每发一个字节就翻转一次CS。
3. 频率字计算与寄存器配置
3.1 AD9910寄存器地图速览
AD9910的寄存器很多,但裸用的话核心就是几个:CFR1、CFR2、CFR3、FTW0、POW0、ASF、RDW、RRW等。CFR1里包含总控制位,比如SPI的SDO是否使能、LSB优先还是MSB优先、数字斜坡使能、profile模式选择;CFR2主要管PLL;CFR3负责参考时钟倍频和DAC相关校准。
下面这个表格只是关键寄存器索引,实际使用时还是要对照数据手册完整地址,因为不同版本芯片可能有些保留位。
| 名称 | 地址 | 字节数 | 主要功能 |
|---|---|---|---|
| CFR1 | 0x00 | 4 | 全局控制,扫频/Profile/SPI模式 |
| CFR2 | 0x01 | 4 | PLL倍频、电荷泵、参考时钟相关 |
| CFR3 | 0x02 | 4 | 时钟分频、DAC校准相关 |
| FTW0 | 0x04 | 4 | 频率调谐字,Profile0的默认频率 |
| POW0 | 0x05 | 2 | 相位偏移字,Profile0 |
| ASF | 0x06 | 2 | 幅度比例因子,Profile0 |
| RDW | 0x0C | 4 | 数字斜坡上升步进 |
| RRW | 0x0D | 4 | 数字斜坡下降步进 |
| RDF | 0x0E | 1 | 斜坡速率时钟分频 |
每次配置完这些寄存器后,都要给IO_UPDATE引脚一个上升沿,AD9910才会把缓冲寄存器里的数据真正锁存到DDS核心。如果不做这个动作,前面写的所有寄存器都只是“看起来写进去了,实际上没生效”,这是新手最容易懵的地方。
3.2 频率调谐字FTW的计算
AD9910输出频率和频率调谐字的关系是:
[ FTW = \frac{f_{out} \times 2^{32}}{f_{SYSCLK}} ]
反过来:
[ f_{out} = \frac{FTW \times f_{SYSCLK}}{2^{32}} ]
比如SYSCLK取500MHz,想要输出10MHz,那FTW就是:
[ FTW = \frac{10,000,000 \times 4,294,967,296}{500,000,000} \approx 85,899,345.92 ]
取整后约85899346,对应十六进制是0x051D5E90(这个数值可以在代码里用常量算出来,不要手算)。如果SYSCLK是1GHz,则10MHz对应的FTW是42949673。
实际写代码时,我一般先define一个SYSTEM_CLOCK_HZ宏,然后封装一个calc_ftw(double freq_hz)函数,用double算完再强转成uint32_t,避免整数除法带来的误差。注意,要保证计算结果在2^32范围内,也就是输出频率不能超过SYSCLK/2(严格说建议不要超过40%的采样率,镜像抑制会好做些)。
3.3 单频模式配置流程
最简单的配置流程就是单频点输出,等于是把DDS当成一个高精度可编程晶振用。步骤是:
- 对RESET引脚先拉低再拉高,让芯片内部复位。
- 通过SPI写CFR1,把多用途引脚、SPI模式等设置好。我这里用MSB优先,关闭SDO,这样用3线SPI就能写。
- 写CFR2,配置PLL倍频,设置系统时钟。如果不需要倍频,把PLL旁路,SYSCLK直接等于参考时钟,但那样输出频率上限会低不少。
- 写CFR3,按需配置DAC参考电流和时钟输出。
- 写FTW0,把刚刚算出来的频率调谐字写进去。
- 写ASF,设置输出幅度为满量程或你需要的比例。
- 最后给IO_UPDATE一个上升沿,完成锁存。
写CFR1的时序我习惯一次性把4个字节用数组发过去,例如CFR1我用MSB优先、SDO关闭、关闭数字斜坡、普通单频模式,那么寄存器值就是0x00 0x00 0x00 0x00,很多位默认是0就已经足够了。CFR2就复杂些,比如PLL倍频40倍,数据手册会告诉你相应的字段怎么填,这里一定要用现场实测确认。
4. HAL库代码实操
4.1 CubeMX里的SPI与GPIO配置
在CubeMX中打开SPI1,设置Mode为Full-Duplex Master,硬件NSS禁用,参数配置为:
- Data Size: 8 Bits
- First Bit: MSB First
- Prescaler: 8(或16,看你的APB2时钟频率)
- CPOL: Low
- CPHA: 1 Edge
这里的SPI_MODE0就是CPOL=Low、CPHA=1 Edge,对应AD9910默认的串行时序。请注意,CPHA在CubeMX界面里写的是“1 Edge”,不是平时说的CPHA=0或1,严格对照AD9910数据手册的时序图会更稳。分频系数我一般选到10MHz左右,因为写入寄存器量不大,速度足够,太快反而容易因走线质量出问题。
在CubeMX里把PB12设为GPIO_Output,作为CS;PB13作为IO_UPDATE;PB14作为RESET;PB15作为PROFILE0。这样PA5、PA7用SPI,其他控制脚用GPIO,整体接线非常清晰。
4.2 寄存器写入基础函数
HAL库下最核心的就是一个“写AD9910寄存器”的函数,它负责拼装指令字节和数据字节。注意AD9910串行指令字节的格式是:最高位是R/W(写为0),中间几位是寄存器地址,低4位是“字节数减1”。我把几个常用寄存器地址都定义成宏,方便后续复用。
#define AD9910_CFR1 0x00 #define AD9910_CFR2 0x01 #define AD9910_CFR3 0x02 #define AD9910_FTW0 0x04 #define AD9910_POW0 0x05 #define AD9910_ASF 0x06 #define CS_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET) #define CS_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET) void AD9910_WriteRegister(uint8_t addr, uint8_t *data, uint8_t bytes) { uint8_t cmd = (addr << 1) | 0x00; // R/W=0 uint8_t len_field = bytes - 1; cmd |= (len_field & 0x0F); // 低4位是字节数-1 CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); HAL_SPI_Transmit(&hspi1, data, bytes, 100); CS_HIGH(); HAL_Delay(1); }这里有个细节,如果你的寄存器地址不止5位,会超出这个(addr << 1)的范围,但AD9910的实际地址都在7位以内,所以这样写是没问题的。为了更安全,你也可以用cmd = (addr << 1) & 0xFE再加长度。
4.3 单频和扫频的完整初始化代码
单频输出通常就写CFR2、FTW0、ASF三个地方就行。CFR3如果不需要额外功能可以不动。下面这段代码是设置SYSCLK为500MHz,PLL倍频20倍(参考25MHz),输出10MHz的配置。
void AD9910_Init_SingleTone(void) { // 复位AD9910 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET); HAL_Delay(20); // CFR1: MSB first, SDO disabled, 单频模式 uint8_t cfr1[4] = {0x00, 0x00, 0x00, 0x00}; AD9910_WriteRegister(AD9910_CFR1, cfr1, 4); // CFR2: PLL使能,倍频20倍,具体字段需仔细对照手册 uint8_t cfr2[4] = {0x00, 0x42, 0x13, 0x00}; AD9910_WriteRegister(AD9910_CFR2, cfr2, 4); // 等待PLL锁定,大概需要几百us HAL_Delay(10); // FTW0: 10MHz at 500MHz SYSCLK uint32_t ftw = (uint32_t)((10.0e6 / 500.0e6) * 4294967296.0); uint8_t ftw_buf[4]; ftw_buf[0] = (ftw >> 24) & 0xFF; ftw_buf[1] = (ftw >> 16) & 0xFF; ftw_buf[2] = (ftw >> 8) & 0xFF; ftw_buf[3] = ftw & 0xFF; AD9910_WriteRegister(AD9910_FTW0, ftw_buf, 4); // ASF: 满幅输出 uint8_t asf[2] = {0x3F, 0xFF}; AD9910_WriteRegister(AD9910_ASF, asf, 2); // IO_UPDATE latch IO_UPDATE_PULSE(); }代码里CFR2的具体值我故意没有展开成通用解释,因为这个字段受PLL配置影响很大,不同参考频率、不同SYSCLK对应值完全不同。建议你在写CFR2之前,用ADI官方的评估软件AD9910/AD9912等配置工具先模拟一遍,生成寄存器值,再填到代码里。
扫频模式稍微复杂一些,需要把CFR1里的数字斜坡相关位打开,同时设置RDW(上升步进)、RRW(下降步进)、RDF(斜坡速率分频),再设置扫频的下限和上限。基本原理是DDS每次采样时,频率累加器按步进和速率增加,达到上限后再按下降步进递减。F407只需要把这几个寄存器写进去,扫频的实时控制完全在芯片内部完成,这是这个芯片最爽的地方。
4.4 IO_UPDATE函数与常见误区
IO_UPDATE的脉冲宽度有最小要求,必须大于一个SYSCLK周期。我一般在函数里直接拉低、延时、拉高、延时、再拉低,保证信号沿干净:
void IO_UPDATE_PULSE(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); }有朋友问为什么写完寄存器要等1ms再更新,其实次毫秒级延时已经非常保守了。如果没有这个IO_UPDATE,你写进去的FTW和CFR全都不生效,直接查这个点就好。
5. 滤波与校准实战
5.1 DDS输出为什么必须滤波
AD9910输出端是DAC直接输出的模拟信号,本质上带有量化台阶和采样时钟的镜像频率。如果你只做低频应用,可能感觉不到太大问题,但是输出几十MHz甚至更高时,频谱混叠现象会非常明显。比如SYSCLK是500MHz,输出50MHz正弦波,在450MHz处会有一个很大的镜像频率,这个频率如果直接进放大器或者混频器,会让后级电路出现乱七八糟的“幽灵信号”。
所以DDS输出端的低通滤波器不是可选件,而是必须的。滤波器阶数和截止频率要根据你的输出频率范围来定。最简单的做法是买一个现成的LC低通滤波器模块,或者用常见的Mini-Circuits滤波器,但这样做体积大、成本高。我自己的项目里是用一个7阶椭圆滤波器,截止频率设在100MHz,对50MHz以下的输出信号做平坦度补偿,50MHz以上基本就交给扫频应用的其他模块去处理了。
5.2 幅度平坦度和相位校准
即使有了滤波器,AD9910在宽频率范围内的输出幅度也不是完全平坦的,DAC自身的sinc包络、滤波器插损、后级放大器增益波动都会造成幅度偏差。如果需要比较精准的信号源,就一定得做幅度校准。
AD9910内部有ASF字段,可以调整输出幅度比例因子。你可以先让DDS输出每个频点的正弦波,用频谱仪或功率计测实际功率,然后再按差值修改ASF。这里要注意ASF只是在数字域里改变正弦波的归一化幅度,如果幅度差超过6dB,光靠ASF可能不够,要考虑后端加一个数控衰减器或AGC电路。
相位校准往往被忽略。当DDS用于相参信号或脉冲多普勒模拟时,不同频点之间的初始相位不一致会带来麻烦。AD9910有POW字段可以做相位偏移,你可以用示波器或者正交解调器测量每个频点的相位偏差,然后把POW值写进去补偿。
5.3 我遇到的一个滤波坑
刚开始我做了一个很陡的截止频率100MHz低通滤波器,结果发现输出20MHz时波形还很好,但输出60MHz时幅度衰减变成-4dB,相位也明显滞后。后来用网络分析仪扫出来,是滤波器群时延不是常数,对宽带扫频信号来说,这个畸变比幅度不平坦更讨厌。如果你做的是窄带信号源,影响不大;做宽带扫频,一定要先把滤波器的群时延特性搞清楚。
另一个坑是DDS输出端最好串一个放大器做隔离,不要直接把滤波器和后续混频器直接连接,否则阻抗变化会把滤波器特性拉偏。我在后面加了一级50欧姆的宽带放大器,输出平坦度明显改善。
6. 常见问题排查与避坑清单
6.1 寄存器写不进去、输出没反应
遇到这种问题,先按这个顺序查:先用万用表和示波器确认AD9910供电正常,然后看RESET引脚释放没有。很多模块上没有板载复位电路,F407上电后RESET默认是低电平,芯片一直处于复位状态,你写什么都没用。
第二是查SPI时序。AD9910对SCLK空闲电平有要求,如果你的SPI配置成了Mode1或Mode3,虽然看起来也能通信,但写入值经常是错乱的。用逻辑分析仪抓SDIO上的波形,对照数据手册看第一个字节是不是0x00这种指令值。
第三是查片选CS。如果CS有毛刺或者发送完没有拉高,AD9910会认为还在同一个通信周期里,后面所有数据全部错位。我后来把CS控制改成软件GPIO后,明显比硬件NSS稳定。
如果以上都查了还是不行,就把MISO引脚直接空出来,只用MOSI写,因为AD9910默认SDO禁用时SDIO是单向输入,如果你开了SDO却又不读MISO,反而会造成引脚电平冲突。
6.2 输出频率和计算值不一致
频率对不上,九成是SYSCLK和预期不符。要么PLL没锁定,要么参考时钟频率搞错了。先读一下AD9910的状态寄存器,看PLL锁定标志位有没有置位。如果没锁,检查CFR2的电荷泵电流和VCO选择,必要的时候换一个环路滤波器元件值。
还有一种情况是FTW字节序反了。AD9910默认是MSB优先,如果你在SPI配置里不小心设成LSB优先,那么FTW四个字节会倒过来,输出频率变成完全没规律的值。核对SPI的First Bit参数即可。
6.3 扫频不动作或者只在一个点输出
扫频模式最基本的要求是CFR1里要设置数字斜坡使能,不能只在单频配置基础上改FTW。另外要保证profile切换的逻辑正确,如果你把PROFILE0固定为低电平,输出始终停在FTW0,看不到扫频。
扫频速率太慢也会让人误以为没工作,比如RDF分频系数设得很大,扫完整个频带可能要好几秒,这完全是正常的。想判断是否在扫频,可以看DAC输出信号的包络,或者把PROFILE引脚接到示波器上观察切换时刻。
还有一个常见错误是把扫频上限和下限搞反了。AD9910的数字斜坡是从当前频率往上扫,到了上限再往下扫,方向由DRCTL控制。如果初始频率已经高于上限,就会一直不动作,需要先把FTW0设到扫频范围的下限再启动。
6.4 输出波形毛刺和抖动
如果你在示波器上看到正弦波上有周期性毛刺,可能不是AD9910坏了,而是SPI配置过程正好干扰了DDS核心。AD9910在IO_UPDATE时会把缓冲寄存器的数据一次性搬到核心,如果这个过程发生在DAC输出关键相位,会触发很小的相位跳变。解决办法是尽量把IO_UPDATE放在DDS输出信号的过零附近,或者干脆在扫频结束后再更新寄存器,不要连续更新。
电源去耦也特别重要。DDS的模拟输出对数字电源噪声很敏感,建议模拟电源加铁氧体磁珠,数字电源和模拟电源单点相连,PCB布线时让SCLK和SDIO远离DAC输出端。我第一版板子因为是飞线搭的,输出频谱一到200MHz就恶化得不行,重新画了PCB之后好了很多。
7. 一点个人经验
用HAL库调AD9910,最花时间的其实不是驱动代码,而是信号链路里的滤波和校准。如果你第一次玩,别急着上扫频,先把单频模式的频率准确度调好,再一步步扩展。FM和AM调制、数字斜坡、profile切换这些功能都是在单频基础上加出来的,基础通了后面就是查寄存器字段的事。
最后分享一个我自己的调试小技巧:把IO_UPDATE引脚通过一个普通GPIO接到示波器上,每次更新寄存器时观察这个引脚的脉冲宽度,比你在代码里打印日志直观得多。如果你能同时抓到IO_UPDATE上升沿和DDS频率跳变的对应关系,基本上就能确定时序设置没有大问题。AD9910这颗芯片功能很强大,但它的寄存器体系也确实复杂,耐心点一页一页翻数据手册,总能把它驯服。
本文还有配套的精品资源,点击获取