尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32控制AD9850 DDS频率合成器:从控制字到低通滤波

STM32控制AD9850 DDS频率合成器:从控制字到低通滤波 简介面向嵌入式开发者这是一份基于STM32控制AD9850芯片实现DDS直接数字频率合成的完整工程与教程资源。资源从DDS基本原理出发讲解相位累加器、频率控制字计算及低通滤波器设计覆盖接口连接、GPIO配置、芯片初始化和实时调频等关键环节适合需要快速上手DDS信号发生器项目的学生或工程师。包内共170个文件包含STM32标准外设库的c/h源码、启动汇编文件、编译生成的o/hex/axf文件以及uvproj/uv2工程配置和说明文档压缩后大小2.47MB可直接打开工程对照学习或二次开发。目前已有723人学习下载资源结构清晰既有代码实现也有硬件设计思路可帮助读者理解STM32与AD9850协同工作的完整流程并掌握频率控制字计算和波形输出的工程方法。1. 用STM32给AD9850写频率控制字难点不在SPI而在时序做信号发生器或者扫频源时最省事的方案就是MCU加DDS芯片。AD9850这颗老芯片至今仍被大量用在射频前端、函数发生器和仪器仪表里原因很简单接口是并口加串口双模式频率分辨率做到0.0291Hz输出最高能到40MHz左右价格还便宜。STM32控制AD9850的难点并不在于写频率控制字——那个公式一页纸就能讲完真正容易翻车的是时序、相位截断和滤波器的阻抗匹配。本文基于一个实际的STM32工程包含mygpio.uvproj.bak等标准库工程文件、stm32f10x_tim.c和stm32f10x_flash.c模块从DDS原理到40位控制字的逐位拆解再到HAL库和标准库两套初始化代码最后落在低通滤波器和调试技巧上。适合正在做DDS信号源、频率可调激励源或者想搞明白相位累加器到底怎么工作的嵌入式工程师。2. DDS频率合成原理与AD9850的控制模型2.1 相位累加器为什么能决定输出频率DDS的核心不是查表而是相位累加器。AD9850内部是一个32位相位累加器每来一个参考时钟脉冲累加器就加上一个频率控制字。累加器的溢出频率就是输出频率公式是f_out (ΔPhase × f_clk) / 2^32其中ΔPhase是频率控制字f_clk是参考时钟频率。AD9850的数据手册标称最高支持125MHz参考时钟但实际工程里常用有源晶振100MHz或者直接拿STM32的MCO引脚输出72MHz或者50MHz来驱动。参考时钟越低同样的频率控制字能得到的输出频率分辨率越低所以建议至少用100MHz有源晶振。从这个公式能看出一个关键点相位累加器只是相位增量幅度是查ROM表得到的。ROM表里存的是正弦波的幅度值相位累加器的高14位作为查表地址。所以输出波形质量取决于ROM表的位数和后面低通滤波器的阶数跟相位累加器的位数没有直接关系。2.2 AD9850的40位控制字结构AD9850的控制字总共40位一次串行写入需要40个时钟周期。这40位的分布是第0位到第31位是频率控制字第32位到第34位是相位控制字第35位到第39位是控制位。控制位的定义是第35位是电源休眠位1为休眠第36、37位是出厂测试位必须为0第38位是LVDS使能位一般置0第39位是串行/并行选择位1为串行0为并行。相位控制字虽然占了3位但实际只有5位有效分辨率对应180度的相位调节范围。5位相位控制字每步对应11.25度3位只是在硬件上预留了更多位数据手册说明只使用低5位。写入顺序是从高位到低位也就是先写控制位第39位到第35位再写相位控制字第34位到第32位最后写频率控制字第31位到第0位。串行时序要求W_CLK上升沿锁存数据FQ_UD上升沿把40位移位寄存器里的内容更新到内部寄存器。2.3 参考时钟的选择与STM32系统时钟的配合STM32控制AD9850时建议把参考时钟独立出来不要用STM32的MCO引脚直接驱动。原因是MCO引脚的输出质量受GPIO配置和系统时钟精度影响相位噪声可能不理想。如果一定要用MCO需要把GPIO配置为AF_PP模式并且检查MCO输出频率是否精确等于系统时钟的整数分频。我自己实测过用100MHz有源晶振直接给AD9850输出1MHz正弦波时的SFDR无杂散动态范围大约在65dBc左右。如果换成STM32的MCO输出72MHz同样输出1MHzSFDR会掉到55dBc附近。这个差异主要来自参考时钟的抖动DDS对参考时钟的相位噪声极其敏感。AD9850的复位逻辑也有讲究。芯片上电后需要先给RESET引脚一个高电平脉冲然后拉低再给FQ_UD一个上升沿芯片才会进入正常工作状态。如果省略这步芯片可能处于并行模式或者未定义状态写入的频率控制字不会生效。3. STM32 GPIO模拟串行时序的工程实现3.1 硬件连接与引脚分配这个工程里用的是STM32F103系列标准库工程文件里的GPIO配置可以直接参考。我建议的引脚分配是PB0接AD9850的W_CLKPB1接FQ_UDPB2接DATAPB3接RESETPB4接CLKEN。CLKEN直接拉高即可不需要MCU控制。硬件连接的关键点是电平匹配。AD9850的数据引脚是5V容忍的但STM32的GPIO是3.3V输出。查AD9850数据手册可以看到高电平输入阈值是2.0VVDD5V时3.3V的高电平输出足够满足要求。但为了保险起见可以在数据线上串联33欧姆电阻做阻抗匹配和限流。连接方式如下STM32引脚AD9850引脚说明PB0W_CLK串行时钟输入上升沿锁存数据PB1FQ_UD频率更新上升沿生效PB2DATA串行数据输入高位在前PB3RESET复位信号高电平有效3.3VCLKEN时钟使能直接接高电平GNDGND共地RESET引脚建议串一个10k欧姆下拉电阻防止上电瞬间高电平毛刺导致芯片误复位。CLKEN直接接3.3V即可官方文档说CLKEN为高时内部时钟正常工作低电平时时钟关闭不需要MCU控制。3.2 软件模拟SPI写入40位控制字AD9850的串行接口不是标准SPI它的时序更接近移位寄存器。W_CLK的上升沿把DATA引脚上的电平锁存进内部移位寄存器40个时钟周期后FQ_UD的上升沿把移位寄存器里的40位数据一次性更新到DDS内核。标准库代码实现如下#define W_CLK_GPIO GPIOB #define W_CLK_PIN GPIO_Pin_0 #define FQ_UD_GPIO GPIOB #define FQ_UD_PIN GPIO_Pin_1 #define DATA_GPIO GPIOB #define DATA_PIN GPIO_Pin_2 #define RESET_GPIO GPIOB #define RESET_PIN GPIO_Pin_3 void AD9850_WriteByte(uint8_t data) { for (uint8_t i 0; i 8; i) { if (data 0x80) { GPIO_SetBits(DATA_GPIO, DATA_PIN); } else { GPIO_ResetBits(DATA_GPIO, DATA_PIN); } data 1; GPIO_SetBits(W_CLK_GPIO, W_CLK_PIN); GPIO_ResetBits(W_CLK_GPIO, W_CLK_PIN); } } void AD9850_WriteFreq(uint32_t freq_ctrl_word, uint8_t phase) { GPIO_ResetBits(FQ_UD_GPIO, FQ_UD_PIN); // 先写控制字节bit39串行使能1bit380bit37:3600bit350 AD9850_WriteByte(0x80); // 相位控制字低5位有效放到高5位对齐 AD9850_WriteByte(phase 3); // 频率控制字低32位先写高字节 AD9850_WriteByte((freq_ctrl_word 24) 0xFF); AD9850_WriteByte((freq_ctrl_word 16) 0xFF); AD9850_WriteByte((freq_ctrl_word 8) 0xFF); AD9850_WriteByte(freq_ctrl_word 0xFF); // FQ_UD上升沿数据加载到内部寄存器 GPIO_SetBits(FQ_UD_GPIO, FQ_UD_PIN); GPIO_ResetBits(FQ_UD_GPIO, FQ_UD_PIN); } void AD9850_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin W_CLK_PIN | FQ_UD_PIN | DATA_PIN | RESET_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // 复位AD9850 GPIO_SetBits(RESET_GPIO, RESET_PIN); GPIO_ResetBits(RESET_GPIO, RESET_PIN); GPIO_SetBits(FQ_UD_GPIO, FQ_UD_PIN); GPIO_ResetBits(FQ_UD_GPIO, FQ_UD_PIN); // 上电后写入一个默认频率防止输出异常 AD9850_WriteFreq(CalculateFreqWord(1000000, 100000000), 0); }代码里有个细节容易被忽略AD9850_WriteByte函数在写入数据后立刻拉高再拉低W_CLK这个顺序不能反过来。如果先拉低W_CLK再改变DATA会导致上一字节的最后一位被重复锁存。还有FQ_UD的上升沿必须在W_CLK为低的时刻给出否则可能造成数据保持时间不够。3.3 40位数据组包的位运算逻辑串行写入的数据顺序是控制字8位、相位字8位、频率字高字节8位、频率字中高字节8位、频率字低字节8位。但注意相位字只有5位有效所以写入相位字时要把5位数据左移3位对齐到字节的高5位低3位补0。控制字节的0x80含义是最低位bit39为1表示串行模式。如果这个位错误地写0AD9850会进入并行模式此时芯片会忽略后续的串行数据导致频率设置完全失效。这是新手最容易踩的坑。频率控制字的计算方式用下面这个函数uint32_t CalculateFreqWord(uint32_t freq, uint32_t ref_clk) { double word ((double)freq * 4294967296.0) / (double)ref_clk; return (uint32_t)(word 0.5); // 四舍五入 }4294967296就是2^32。这个计算用浮点运算可以接受因为STM32F103有硬件FPU的在F4上但F103用软件浮点每次调频率大概消耗几十微秒。如果对频率切换速度有要求可以改成纯整数运算但要注意64位中间变量的溢出问题。4. 频率控制字计算与动态调频策略4.1 从目标频率到32位控制字的映射关系以100MHz参考时钟为例频率分辨率是100MHz除以2的32次方约等于0.0233Hz。这意味着在100MHz参考时钟下任何目标频率都能用整数频率控制字逼近到0.0233Hz以内这个精度远超大多数应用的需求。但要注意AD9850在低频段的输出幅度会出现波动。这是因为DDS内部的DAC在低频时会受到积分非线性INL影响特别是输出频率低于10kHz时SFDR会明显下降。如果目标频率低于10kHz建议采用以下几个措施一是提高参考时钟频率让频率控制字变大减少DAC量化误差的影响二是在输出端加一级有源滤波器三是结合DDS的正弦查表方式理解低位相位累加器不会触发ROM表地址跳变所以实际输出的相位噪声比理论值略好。计算频率控制字的代码要特别注意类型转换。如果直接写uint32_t freq_word (freq * 4294967296) / ref_clk那freq和4294967296的乘积会先按uint32_t计算溢出后结果完全错误。必须先把freq转成double或者uint64_t。4.2 线性扫频的实现方法与定时器配合STM32控制DDS实现线性扫频并不复杂核心思路是每隔固定时间更新一次频率控制字。频率步进和更新间隔决定了扫频的线性度。举个例子用TIM3产生100us的中断每次中断把频率增加1kHz从1MHz扫到10MHz需要9万个中断总共9秒每步1kHz扫频精度由AD9850的0.0233Hz分辨率保证。TIM更新中断代码volatile uint32_t current_freq 1000000; // 起始频率 1MHz void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); current_freq 1000; // 每步1kHz if (current_freq 10000000) { current_freq 1000000; // 回到起点 } uint32_t word CalculateFreqWord(current_freq, 100000000); AD9850_WriteFreq(word, 0); } }这段代码有个性能问题CalculateFreqWord用了浮点除法在F103的72MHz主频下大约耗时20到30us。如果扫频步进间隔只有100us那浮点计算就占掉了接近三分之一的CPU时间。优化方案是把浮点除法换成64位整数运算uint32_t CalculateFreqWordInt(uint32_t freq, uint32_t ref_clk) { uint64_t word (uint64_t)freq 32; word / ref_clk; return (uint32_t)word; }freq左移32位等效于乘以2的32次方再用64位除法得到结果。在F103上64位除法是软件实现的大约耗时3到5us比浮点快一个数量级。4.3 频率切换时的相位连续性和毛刺抑制DDS的一个优势是频率切换时相位连续不会像PLL那样有锁定时间。AD9850内部的相位累加器不会因为写入新的频率控制字而清零而是在当前相位基础上继续累加新的增量所以输出波形没有相位跳变。不过硬件层面有个陷阱FQ_UD信号线上的毛刺可能导致芯片在非预期时刻更新频率。FQ_UD引脚对毛刺的敏感度很高如果FQ_UD走线过长或者旁边有高频信号就可能出现随机触发。解决办法是在FQ_UD引脚加一个10nF电容到地构成低通滤波器把纳秒级的毛刺滤掉。代价是FQ_UD上升沿变缓频率更新的延迟增加几十纳秒对绝大多数应用无影响。另外如果需要在关键时刻精确切换频率不能用软件延时控制FQ_UD应该用STM32的定时器输出比较模式在精确时刻翻转FQ_UD引脚。这个做法的精度取决于定时器时钟和APB分频误差控制在几个系统时钟周期内。5. HAL库版本代码与标准库的移植要点5.1 HAL库的GPIO配置差异现在的STM32CubeIDE环境下很多新项目直接用HAL库。HAL库操作GPIO的方式和标准库差别不大但初始化结构体和API名称完全不同。AD9850控制不需要任何外设只用GPIO模拟时序所以HAL库版本的核心代码非常干净void AD9850_Init_HAL(void) { __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); } void AD9850_WriteFreq_HAL(uint32_t freq_ctrl_word) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // FQ_UD低 // 先写控制字节和相位再写频率 AD9850_WriteByte_HAL(0x80); // 并行模式 0, 串行使能 1 AD9850_WriteByte_HAL(0x00); // 相位 0 AD9850_WriteByte_HAL((freq_ctrl_word 24) 0xFF); AD9850_WriteByte_HAL((freq_ctrl_word 16) 0xFF); AD9850_WriteByte_HAL((freq_ctrl_word 8) 0xFF); AD9850_WriteByte_HAL(freq_ctrl_word 0xFF); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // FQ_UD拉高 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // FQ_UD再拉低 } static void AD9850_WriteByte_HAL(uint8_t data) { for (uint8_t i 0; i 8; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, (data 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET); data 1; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); } }HAL库的GPIO操作函数是函数调用不像标准库那样直接操作寄存器所以单次操作耗时更多。实测在72MHz主频下HAL_GPIO_WritePin一次大概需要1.5us写入40位数据需要40次循环乘3次IO操作加上循环开销总共大约200us。这比标准库慢不少因为标准库的GPIO_SetBits/ResetBits是宏定义直接改写BSRR/BRR寄存器单次操作耗时不到0.1us。如果你在HAL库工程里需要高频率更新DDS输出建议在需要速度的关键路径上直接用寄存器操作绕过HAL的封装。比如把AD9850_WriteByte_HAL里的HAL_GPIO_WritePin改成直接操作GPIOB-BSRR和GPIOB-BRR速度提升非常明显。// 快速版写入直接用寄存器操作 #define W_CLK_HIGH() GPIOB-BSRR GPIO_PIN_0 #define W_CLK_LOW() GPIOB-BRR GPIO_PIN_0 #define DATA_HIGH() GPIOB-BSRR GPIO_PIN_2 #define DATA_LOW() GPIOB-BRR GPIO_PIN_2 #define FQ_UD_HIGH() GPIOB-BSRR GPIO_PIN_1 #define FQ_UD_LOW() GPIOB-BRR GPIO_PIN_1用寄存器操作后写入40位数据的总耗时可以控制在20us以内。如果还需要更快可以开启GPIO的复用推挽输出配合DMA直接控制ODR寄存器但工程上很少需要做到这个程度。5.2 标准库工程文件里的模块划分原始工程压缩包里有stm32f10x_tim.c、stm32f10x_flash.c和mygpio.uvproj.bak等Keil工程备份文件。stm32f10x_tim.c的用途是生成扫频定时中断stm32f10x_flash.c可能是用来保存校准数据的——比如把上次设定的频率存到Flash下次上电自动恢复。Flash写入操作要注意先擦后写STM32F103的Flash按页擦除每页1KB或者2KB取决于型号。如果你要在运行时保存频率参数建议用最后一页避免覆盖主程序代码区。这里给出一个简洁的Flash读写示例void SaveFreqToFlash(uint32_t freq) { uint32_t addr 0x0800FC00; // 最后一页起始地址 FLASH_Unlock(); FLASH_ErasePage(addr); FLASH_ProgramWord(addr, freq); FLASH_Lock(); } uint32_t LoadFreqFromFlash(void) { uint32_t addr 0x0800FC00; return (*(volatile uint32_t *)addr); }Flash写入要谨慎不要在系统运行中频繁调用Flash擦写寿命只有1万次左右。如果需要在运行中保存频率快照建议先在RAM里缓存只有掉电前或手动保存时才写入Flash。5.3 GPIO速度等级对时序的影响STM32的GPIO配置为50MHz输出速度时信号上升沿大约几纳秒这对AD9850的串行接口来说太快了。虽然AD9850的数据手册没有规定输入信号最大上升沿速度但过陡的边沿可能造成振铃和过冲。如果发现写入数据后频率不对可以先把GPIO_Speed从50MHz改成2MHz。这样信号边沿变缓虽然单次翻转耗时增加但信号的完整性更好尤其在飞线连接的情况下。接线的长度也要控制。AD9850的串行时钟最高可以跑到几十MHz但那是芯片极限STM32模拟时序时W_CLK频率通常不到1MHz所以普通杜邦线完全够用。倒是DATA和W_CLK之间的等长关系要注意如果DATA线明显长于W_CLK线可能造成建立时间不足。I2C模式的AC时序参数在数据手册里有详细定义串行模式下W_CLK的最小高电平时间是3.5ns对STM32来说非常宽松。真正需要担心的是软件延时逻辑// 数据建立时间不足导致的典型错误写法 static void WriteBit_Slow(uint8_t bit) { HAL_GPIO_WritePin(CS_IOC, DATA_PIN, bit); HAL_GPIO_WritePin(CS_IOC, W_CLK_PIN, GPIO_PIN_SET); // 紧跟着拉时钟 HAL_GPIO_WritePin(CS_IOC, W_CLK_PIN, GPIO_PIN_RESET); }这种写法在HAL库下会导致DATA信号还没有稳定在引脚上W_CLK的上升沿就到了锁存的是上一个数据的残留电平。正确做法是在DATA写入和W_CLK上升沿之间插入至少几个空操作或一个微秒级延时。6. 输出波形质量优化低通滤波器与调试验证6.1 简易LC低通滤波器的设计与阻抗匹配AD9850的输出是内置DAC的电流输出需要外接一个电阻把电流转成电压通常用200欧姆电阻接到IOUT引脚可以得到大约0.5V的电压摆幅。这个电压信号不能直接驱动后级电路需要经过低通滤波器滤除DAC的采样输出导致的高频镜像频率。DDS输出频谱包含多个镜像频率最明显的是参考时钟频率减去输出频率。比如参考时钟100MHz输出10MHz那么在90MHz位置会有一个大的镜像分量。输出频率越高越难滤除镜像干扰这就是为什么AD9850的实际可用频率范围通常限制在40MHz以下。一个常见的7阶椭圆低通滤波器设计可以在ADI的应用笔记里找到配合AD9850和AD9852使用。对于实验板可以先搭一个简单的二阶LC低通截止频率设在40MHz元件参数位置L1470nH串联在信号路径上C122pFL1之后对地L2220nHC1之后串联C210pF输出端对地这个滤波器的-3dB截止频率大约在3040MHz带内插损约0.5dB带外抑制在100MHz处大约有-40dBc。如果输出频率低于10MHz可以在这个基础上把谐振频率调低元件值相应放大。电感要选高频贴片电感避免使用工字电感因为工字电感的自谐振频率低会在数百MHz处引入额外损耗。电容用NP0材质不要用X7RX7R在射频下容值会随电压变化。6.2 用示波器预测并验证频率与幅度调试时第一件事是确认AD9850是否真的输出了目标频率。示波器直接测量IOUT引脚上的信号注意探头要打到10x档因为1x档的输入电容约100pF会严重加载滤波器的输出端导致幅度衰减和频率偏移。如果示波器带宽只有100MHz观察40MHz以上的信号幅度会显示偏低这是示波器带宽造成的不是电路问题。要精确测量高频信号幅度需要使用带宽至少是信号频率5倍的示波器也就是至少200MHz带宽。频率准确性的验证方法没有示波器也能做用STM32的定时器输入捕获功能测量AD9850输出信号的周期。TIM2的CH1配置为输入捕获上升沿模式每次捕获读取CNT值两次捕获的差值就是信号周期。6.3 常见故障排查顺序与经验值AD9850不上电工作或者输出频率不对按以下顺序排查第一检查RESET时序。用逻辑分析仪抓RESET、FQ_UD和W_CLK三个引脚的电平变化确认复位后FQ_UD有一个上升沿且该上升沿发生在W_CLK为低电平期间。如果逻辑分析仪显示FQ_UD在复位后一直是低初始化代码没执行完整。第二确认串行模式是打开的。把控制字节打印出来检查bit39是否为1。如果芯片工作在并行模式串行数据会被忽略但芯片仍然输出某个固定频率——因为并行模式下数据引脚的电平组合决定了频率控制字悬空引脚的电平不定输出频率完全随机。第三用示波器测量W_CLK引脚的波形看是否有时序毛刺。最常见的问题是W_CLK拉高后紧接着拉低时代码里有额外操作导致W_CLK引脚电平反转两次。比如在AD9850_WriteByte函数里循环外面多写了一次W_CLK翻转导致每个字节的最后一拍是多余的。第四检查晶振是否真正起振。很多AD9850模块板载晶振没有焊接电容或者晶振负载电容和模块设计不匹配导致不起振。用示波器测量晶振引脚应该能看到正弦波。没有示波器的话可以用万用表交流档测晶振引脚有数百毫伏的交流电压说明起振了。第五验证AD9850的参考时钟接入方式。部分淘宝模块上AD9850的CLKIN引脚默认接地需要飞线到有源晶振输出。如果模块上的CLKIN接了地无论怎么写控制字都不会有输出。一个值得记住的经验值是AD9850输出频率在1MHz到20MHz范围内经过合理滤波后幅度平坦度可以做到正负0.5dB以内。如果幅度波动超过2dB优先检查滤波器元件容差和IOUT电阻的焊接质量而不是怀疑DDS芯片本身。6.4 用Flash保存校准表实现输出幅度平坦化有些应用要求输出幅度恒定比如做扫频测试的激励源。AD9850的DAC输出幅度会随频率升高而轻微下降这是DAC的sinc函数响应特性决定的。可以在每个预设频率点先用ADC测量输出幅度然后调整AD9850的相位控制字来补偿——但相位控制字不能调幅度这个思路是错的。正确做法是在输出端加一个数控衰减器或者可编程增益放大器STM32根据当前输出频率查表得到对应的衰减器控制码。校准表可以存放在Flash里每次开机时加载到RAM。生成校准表的流程是先用STM32控制AD9850输出1MHz正弦波用ADC以足够的采样率采集输出波形幅度记录该频率下的幅度值依次扫过所有频率点最后把所有幅度值换算成衰减器的补偿码。衰减器用数字电位器最方便比如AD5242它的阻值和频率对应关系可以预先标定。这个方法能把输出幅度平坦度从正负2dB改善到正负0.2dB。如果要求更高就要考虑在模拟域用AGC电路但这超出了DDS控制的范畴。本文还有配套的精品资源点击获取
返回列表