资讯动态

STM32L552驱动AD9102任意波形发生器:完整代码与避坑指南

发布时间:2026/10/5 1:34:34 来源:尧图企业网站定制
很多人第一次拿到AD9102的数据手册时心里会松一口气一颗集成了DDS和DAC的芯片SPI配上就能出波形看起来比搭分立DAC方案省心多了。等真正把STM32L552和它连在一起才发现能出波形和出对波形完全是两码事。这篇博客把我用STM32L552驱动AD9102做任意波形发生器的完整过程、完整代码和避坑经历整理出来希望能给打算在这套组合上做信号源、超声激励源、气体分析仪驱动的硬件工程师省下几个加班的夜晚。1. 选型复盘STM32L552配AD9102这套组合到底适合干什么1.1 为什么不用MCU查表刷DAC要选集成DDS的AD9102做信号发生器很多人的第一反应是MCU内部查正弦表然后通过SPI不断刷新外部DAC。这个思路在输出频率几十kHz到一两MHz的时候完全够用但再往上走就吃力了MCU的SPI时钟速度、中断响应时间、DAC建立时间、GPIO翻转速率每一项都在拖后腿。AD9102这颗芯片很不一样它把DDS直接数字频率合成器和12位、180MSPS的DAC封装在一起内部还有一个32k采样点的SRAM存储区。做规则波形时MCU只需要通过SPI把频率调谐字、相位字、增益这些参数写进去DDS就会持续输出对应频率的信号波形数据根本不需要MCU实时干预。这等于说MCU从搬运工变成了指挥官。如果只是做一个1MHz正弦波用普通MCU查表刷DAC也许还行但要做几MHz、几十MHz的正弦波或者要做带包络的超声激励脉冲查表刷DAC的架构基本就废了。AD9102自带的DDSSRAM混合模式可以DDS做高频载波、SRAM里的自定义序列做包络这种能力用纯MCU方案很难实现。1.2 STM32L552在系统里承担什么角色STM32L552不是那种一眼看上去很强壮的MCU110MHz主频放在一众M7、M4里面不算亮眼但它的定位从来不是拼算力而是拼能效。Cortex-M33内核超低功耗系列待机电流能做到很低这让它非常适合做便携式仪器、手持超声设备、现场信号源这类对供电敏感的产品。放在这个项目里STM32L552的SPI外设用来配置AD9102GPIO用来控制复位和触发信号串口接收上位机指令再加上ADC采集一些反馈电压一套系统主控的工作就全包了。它的GPIO翻转速度虽然比不上专用逻辑电路但做控制绰绰有余甚至后面我会说连触发信号这种极其讲究时序的活它用定时器输出也能干得很好。还有一点L5系列自带TrustZone安全功能这个在普通仪器里用不太上但如果你的产品需要做代码保护、防抄板这颗芯片天然比普通M0/M3系列多一层保障。对我来讲选择L552的核心理由是静态功耗足够低而且一套代码里既要跑显示交互又要跑配置算法Cortex-M33的处理能力刚好够用。1.3 整体链路拆解整个系统的信号链其实是这样的外部参考晶振或者TCXO产生REFCLK时钟进入AD9102的时钟引脚经过内部PLL倍频后得到DAC采样时钟。MCU通过SPI配置AD9102的工作模式、DDS参数或者写入SRAM波形数据配置完成后通过触发信号让AD9102开始输出IOUT和IOUT-这对差分电流输出经过外部I/V转换和重建滤波器最后变成示波器上看到的电压波形。这里有个很多人容易忽略的点AD9102不是上电就能用的它的内部寄存器在复位后处于默认状态PLL也没有锁定必须由MCU完成一整套初始化流程。也就是说AD9102功能再强它也不会自己跑程序所有行为都依赖SPI指令所以你会发现折腾这颗芯片的时候多半时间不是耗在电路上而是耗在SPI时序和寄存器初始化顺序上。2. AD9102的寄存器体系波形生成核心逻辑逐一拆解2.1 三种工作模式先分清AD9102的工作模式大体可以分成三类DDS模式、SRAM模式、DDSSRAM混合模式。DDS模式适合产生正弦波、三角波这类周期信号。内部相位累加器在每一个DAC采样时钟周期累加一次频率调谐字查表输出对应幅度值这个模式下波形连续、相位连续频率分辨率可以达到很细因为内部相位累加器是32位的。SRAM模式则是把任意波形的采样点提前写入片内SRAM然后从起始地址循环读到终止地址。这种模式没有DDS的频率连续性限制你可以灌入自己算好的任意波形比如一个猝发指数衰减脉冲、一个非对称的斜坡信号都可以。DDSSRAM混合模式更有意思典型应用就是超声成像里的激励脉冲DDS产生一个高频正弦载波SRAM里的包络数据作为幅度调制因子两者相乘或叠加后输出。这个模式下单一芯片就能完成复杂的脉冲激励信号生成省掉了外部模拟乘法器。选择哪种模式取决于你的最终波形形态不要在DDS模式下硬要输出任意波也别在SRAM模式下追求连续相位变化。2.2 DDS频率调谐字和相位字怎么配DDS模式下的输出频率和频率调谐字FTW之间是严格的线性关系$$f_{out} \frac{FTW \times f_{DAC}}{2^{32}}$$反推一下就是$$FTW \frac{f_{out} \times 2^{32}}{f_{DAC}}$$举个例子DAC采样时钟180MHz想生成1MHz正弦波FTW算出来约23888888十六进制是0x016C94B8需要拆成两个16位字写进寄存器。频率调谐字是32位无符号整数相位控制字是16位的可以直接设置初始相位动态范围。这里要特别注意字节拆分顺序。AD9102的频率参数寄存器是分高字、低字分开写的而且存在影子寄存器更新机制——低字节写入的瞬间才会把整个32位配置真正锁存。也就是说必须先写高16位再写低16位顺序一旦反了中间会出现一段频率不正确的过渡状态而这段过渡状态可能正好被你触发输出最后示波器上看到的波形频率就像抽风一样乱跳。2.3 SRAM模式的加载流程SRAM模式的核心流程是设置SRAM访问模式把内部地址计数器指到起始地址然后连续写入16位采样点数据写完再设置终止地址最后触发输出。写SRAM的时候有一个容易踩的bugSPI连续写几十几百个数据中间任何一次CS时序不干净都可能导致一个采样点丢失或者错位。波形前面的点错了你肉眼根本发现不了直到最后看时域波形突然冒出一个不该有的毛刺。所以我在驱动里加了一个校验策略写完一批SRAM数据之后把数据回读出来和原始缓冲逐点比对。这个操作虽然多花几百微秒但对批量交付的产品来说能拦掉大量偶发性问题。另外SRAM模式的输出触发不止一种方式。可以软件触发也可以用外部TRIG引脚硬触发。外部触发对相位的重复性有要求这一点在后面踩坑部分详细讲。2.4 DAC输出配置与上电时序AD9102的输出是电流型差分输出IOUT和IOUT-满量程电流由外部Rset电阻和寄存器里的DAC增益位共同决定。Rset越小满量程越大DAC增益位可以进一步微调。输出级设计上需要注意IOUT引脚不能直接拿示波器看波形因为它输出的是电流必须在外部接端接电阻或者运放I/V转换电路。如果直接把差分输出接到示波器高阻探头你会觉得芯片没输出其实是负载压根没形成电流回路。上电时序这块数据手册明确要求VDDIO先上电、DAC模拟电源后上电如果你的板子两路电源不是分轨控制那至少要在软件复位流程里做足文章上电后等电源稳定再拉低复位引脚几百微秒确保芯片在已知状态。有些设计图省事RESET引脚直接悬空或者只接一个小电容这样芯片很容易受上电浪涌干扰导致寄存器状态不确定这点我到第4部分会用实例讲。3. SPI底层时序与HAL驱动封装细节3.1 CubeMX配置和物理连接我用到的硬件连接大概是这几根线信号STM32L552引脚AD9102引脚说明SCLKPB13SCLKSPI时钟SDIOPB15SDIO主出从入SDOPB14SDO主入从出接上方便回读CSPB12CS软件控制片选RESETPB10RESET低电平复位TRIGPA2TRIG硬件触发CubeMX里把SPI2配置为全双工主机模式、8位数据长度、MSB先行。时钟极性和相位我最终用的是CPOL0、CPHA1但你这块板子未必和我的完全一样最好的办法是拿着数据手册时序图对照SCLK的采样沿慢慢调别照抄别人的配置。CS不要用硬件NSS用普通GPIO控制就够了。硬件NSS在多从机情况下牵涉到各种时序冲突软件控制反而简单可靠。SPI时钟速度一开始也不要拉满我调试阶段用的是4MHz左右等全链路跑通了再考虑提速。杜邦线连接情况下高速SPI很容易在信号沿上产生振铃回读数据就可能错位。3.2 写寄存器与读回校验AD9102的SPI写操作是16位指令字加上16位数据。指令字bit15是读写标志bit12到bit0是寄存器地址数据字就是你要写入的值。写寄存器函数的本质就是把这两段16位拼成4个字节一次性通过HAL_SPI_Transmit发出去。这里有一个很实用的习惯每次写完之后立即读回哪怕只读一个字节对比。因为AD9102是SPI从机你自己写的命令到底有没有被正确接收没有任何中断引脚告诉你回读是唯一可靠的确认手段。我的驱动代码中会带一个自检函数初始化完成后把所有关键寄存器的值读回来和期望值做比对不匹配就返回错误码。这样在产线上烧录完程序不需要额外仪器就能判断芯片和PCB有没有基本问题。3.3 DDS正弦波配置与触发流程配置DDS输出正弦波的完整流程是这样的:拉低RESET引脚保持至少100us再释放让芯片回到已知态。配置PLL倍频系数等待PLL锁定标志位置位。按公式计算频率调谐字先写高16位。再写低16位触发影子寄存器更新。写相位字。配置DAC增益和输出模式。发出触发信号软件或硬件波形开始输出。PLL锁定这一步特别重要。芯片刚上电时PLL没锁内部DAC时钟并不是你预期的180MHz如果不等锁定就配置DDS并触发输出频率会和你算的完全对不上而且带有很大抖动。在代码里加一个等待超时循环等PLL锁定寄存器位被置为有效再往下走。3.4 低功耗唤醒后SPI外设必须重新初始化如果你用的是STM32L552看中的就是它的低功耗特性那系统一定会进入STOP模式或者STANDBY模式。低功耗唤醒后SPI外设的寄存器状态很可能已经丢失或者被复位HAL库不会自动帮你恢复全部状态。我踩过的情形是系统从STOP2唤醒后直接调用HAL_SPI_Transmit返回值正常但AD9102那边就是没反应因为SCLK根本没有被正确输出。排查后发现问题出现在GPIO复用配置上没有恢复SPI外设虽然被HAL库重新初始化了但对应的PB13、PB15引脚还停留在模拟输入状态时钟自然过不去。解决方案很简单唤醒后把SPI的DeInit和Init全部重新执行一遍并且按顺序恢复GPIO复用配置然后再跑AD9102的初始化流程。这件事最好封装成一个函数操作系统唤醒回调里直接调用别指望HAL库帮你兜底。4. 实战踩坑记录四条完整的排查链路4.1 寄存器写不进去最后发现是RESET引脚悬空最开始调试时板子焊好程序烧进去读回任何寄存器都是0写进去也像石沉大海。SPI波形用示波器抓了CS、SCLK、SDIO电平都对电源电压也对VDDIO和AVDD都正常芯片表面温度也没有异常。当时我排查了一整夜最后无意中摸到RESET引脚发现3.3V电平看起来不像是低电平复位状态。但问题是从楼上接了一根飞线到地碰一下RESET引脚芯片就恢复了说明它在正常工作只是不知道什么时候被干扰拉低又自动释放。再看原理图才发现RESET上面只画了一个0.1uF电容没有任何电阻。低电平有效且悬空这在强电磁环境或者上电瞬间是致命的轻微噪声就可能让芯片不断复位SPI当然写不进任何寄存器。后面我改为RESET引脚接10k上拉到VDDIO并且在软件初始化时先拉低200us再释放这个问题彻底消失。这条排查链路给我最大的教训是芯片看起来没复位不代表复位电路可靠凡是低有效复位引脚必须要有明确的上拉或者驱动源不能靠芯片内部上拉电。4.2 读回一直是0xFFSPI极性和相位差了一级寄存器能写但读回全是0xFF这个问题和上一个正好相反。初始化流程跑完VDDIO正常CS时序正常写操作之后马上发读指令结果SDO线回传的一直是0xFF。一开始我怀疑SDO引脚没焊好量了通路没问题用示波器去抓SDO线上的数据发现确实有翻转信号但MCU采样到的全是1。这时候才意识到是SPI的时钟极性和相位配置和AD9102输出数据的时序差了一拍。SCLK默认空闲电平不一样、采样沿位置不一样都会导致发送端和接收端错开半个周期。我把SCLK改为另外一种CPOL/CPHA组合后回读值立即正常。这里不要试图硬背哪个模式是对的不同批次、不同封装的器件手册描述可能有细微差异正确做法是先看手册时序图确定数据是在SCLK上升沿还是下降沿更新、在哪个沿被主机采样再去CubeMX里把对应的CPOL和CPHA设好。4.3 正弦波频率偏了败在影子寄存器的写入顺序现象是DDS已经配置好寄存器回读确认没有错误但示波器上输出正弦波的频率和计算值差了接近10倍。一开始怀疑DAC采样时钟不对回头检查PLL配置寄存器发现PLL倍频系数确实被覆盖过。继续深挖发现真正的原因是初始化顺序里先写了低16位频率字、再写高16位频率字。AD9102某些寄存器的更新是在低字写入时触发的低字先写就会导致一次不完整的32位配置被锁存高字后面再写虽然能修正最终值但中间状态已经影响了PLL或其他相关配置。改正方法一句话就能讲明白凡是分高字和低字的寄存器永远先写高字最后写低字。低字写入时一次性更新整个32位配置保证中间不会出现半个字的错误值。类似这种影子寄存器机制在ADI的各种DDS芯片里很常见写代码前先看寄存器描述里有没有说明更新触发条件比踩完坑再去猜高效得多。4.4 波形起点抖动别再用GPIO翻转做触发源波形本身稳定周期正确幅度正确但每次一按触发按钮波形在示波器上的水平位置都会微微晃动虽然只有几ns级别但对于要做相位相关测量的场景来说这个抖动完全不能忍。排查的时候一度怀疑AD9102内部的触发边沿检测不稳定后来用定时器直接输出PWM触发信号抖动瞬间消失。真正的问题出在MCU的GPIO翻转上我用HAL_GPIO_TogglePin翻转一个引脚作为触发脉冲这个函数调用本身要经过函数跳转、寄存器读写时间不确定再加上中断、总线调度的干扰触发沿相对AD9102的DAC时钟来说相位是随机浮动的。AD9102的触发边沿和单片机GPIO翻转的抖动叠加以后脉冲出现的时刻不在固定的相位点上波形出来就表现为水平抖动。正确做法是用定时器PWM产生干净的单脉冲经过一个上升沿送给TRIG引脚这样触发边沿相对DAC采样时钟有了确定性相位稳定度立刻提升。5. 完整驱动代码与PCB布局要点5.1 驱动代码组织我的驱动代码分成ad9102.h和ad9102.c两个文件核心逻辑围绕寄存器读写、初始化、DDS配置、SRAM加载来组织。具体寄存器地址请以你手上的数据手册为准下面代码里的地址是示例逻辑不要把地址序号当成通用答案直接抄一定要先对一遍手册。ad9102.h主要定义引脚宏、寄存器地址和对外接口函数原型。ad9102.c里实现底层SPI通信、复位、初始化、DDS正弦波配置、SRAM写入、软件触发等函数。5.2 关键源码先看寄存器读写函数。写操作必须遵守指令字数据字拼成4字节连发的规则中间CS绝对不能抬起。uint8_t AD9102_WriteReg(uint16_t reg, uint16_t val) { uint8_t buf[4]; /* 指令字bit15 0 表示写bit12~bit0 为寄存器地址 */ buf[0] (uint8_t)((reg 0x1FFF) 8); buf[1] (uint8_t)(reg 0xFF); buf[2] (uint8_t)(val 8); buf[3] (uint8_t)(val 0xFF); AD9102_CS_LOW(); HAL_SPI_Transmit(hspi2, buf, 4, 10); AD9102_CS_HIGH(); return 0; }读回函数逻辑类似注意要先把SDIO切换到输入方向或者像我这样接四线SDO才能读到数据uint8_t AD9102_ReadReg(uint16_t reg, uint16_t *val) { uint8_t cmd[2], rx[2]; /* bit15 1 表示读 */ cmd[0] (uint8_t)((0x8000 | (reg 0x1FFF)) 8); cmd[1] (uint8_t)(reg 0xFF); AD9102_CS_LOW(); HAL_SPI_Transmit(hspi2, cmd, 2, 10); HAL_SPI_Receive(hspi2, rx, 2, 10); AD9102_CS_HIGH(); *val ((uint16_t)rx[0] 8) | rx[1]; return 0; }DDS正弦波配置函数重点看FTW计算和先高后低的写入顺序uint8_t AD9102_ConfigDDS(uint32_t freq, uint32_t dac_clk, uint16_t phase) { uint32_t ftw; if (dac_clk 0) { return 1; } ftw (uint32_t)(((uint64_t)freq 32) / dac_clk); /* 先写高16位 */ AD9102_WriteReg(AD9102_REG_DDS_TUNEWORD0, (uint16_t)(ftw 16)); /* 低16位最后写触发更新 */ AD9102_WriteReg(AD9102_REG_DDS_TUNEWORD1, (uint16_t)(ftw 0xFFFF)); AD9102_WriteReg(AD9102_REG_DDS_PHASE0, (uint16_t)(phase 16)); AD9102_WriteReg(AD9102_REG_DDS_PHASE1, (uint16_t)(phase 0xFFFF)); /* 切换到DDS模式并启动 */ AD9102_WriteReg(AD9102_REG_MODE, 0x0001); return 0; }这段代码里有几处需要核对你手册的地方TUNEWORD0、TUNEWORD1、PHASE0、PHASE1、MODE这些寄存器的实际地址编号以及模式字里使能DDS的位定义。寄存器地址以手册为准但写高在先、写低在后的原则是通用的。5.3 PCB布局经验硬件调试能顺不顺往往在画板阶段就决定了。SPI的SCLK、SDIO、SDO三根线在所有走线里要优先保证完整性和短距离不要在中间穿过其他大电流回路。我在实际板卡上习惯在MCU端的SPI线上串联33R电阻靠近MCU放置这个电阻配合走线寄生电容能显著降低信号振铃。模拟地和数字地不能直接混在一起。AD9102的AVDD、DVDD、VDDIO最好是分开走再通过0欧电阻或者磁珠在电源入口处单点汇合。DAC输出IOUT和IOUT-这两根线要尽量短差分等长底层参考地挖空干净周围不要铺乱七八糟的其他信号线。REFCLK参考时钟的走线更是重点能用短走线就不要拉长有条件的话在REFCLK附近加一个小电容到地做滤波。晶振负载电容取值可以让晶体厂商的规格书来定别自己估算误差太大会引起频率偏差。去耦电容方面每个电源引脚都放一个0.1uF陶瓷电容位置紧贴芯片引脚稍大容量的10uF或者22uF电容放在板子电源入口处。别把所有去耦电容堆成一排放在芯片同一侧那样等于没放。5.4 调试验证清单文章最后把我自己在项目里用的验证清单分享一下每一条都是在实际调试中总结出来的步骤步骤操作通过标准1测量VDDIO、AVDD上电电压电压稳定无明显跌落2回读芯片ID寄存器与手册预期值一致3回读全部关键寄存器与写入值完全匹配4DDS模式输出正弦波频率误差在允许范围内5SRAM写入后回读比对数据逐点一致6硬件触发测试多次触发波形相位无抖动7低功耗唤醒后重新初始化再次触发输出正常调试阶段其实最有帮助的还是读回校验这个习惯。AD9102不像串口设备有应答机制你不主动读就很难知道SPI命令到底执行没有。每次初始化之后把关键寄存器全部读回来打印出来很多隐藏问题在波形出现之前就提前暴露了。如果后面还有人要在这套组合上做多通道同步或者相位连续调频可以继续在DDS动态重配上下文章。AD9102支持通过SPI在运行中更新频率字配合定时器精确控制更新时间就能实现跳频、线性调频这类高级功能。不过这些就需要更精细的时序测试了等下次有空再单独写一篇展开聊。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑