隆太威电子网欢迎您!
新闻资讯

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号

作者:    发布时间:2026-04-28 22:18:29    浏览量:

一、AD9954模块简介

AD9954是一款直接数字频率合成器(DDS),采用先进技术内置一个高速高性能DAC,构成完整的数字可编程、高频合成器能够产生高达150MHz的频率捷变模拟输出正弦波。它能够实现快速跳频,同时还具有精密频率调谐(0.01Hz或更高分辨率)和相位谐波(0.022°间隔)默认程序1hz可调用户如需更高精度需自行修改程序通过一个高速串行I/0端口可以进行编程。该器件内置静态RAM支持多种模式下的灵活扫频功能,并且支持用户定义的线性扫描工作模式同时内置一个片内高速比较器,适合要求方波输出的应用。借助片内振荡器和PLL电路,用户可以用多种方法产生器件的系统时钟AD9954的额定工作温度范围为扩展工业温度范围。
特性

  • 模块供电:DC 5V
  • 通讯协议:SPI串行
  • 系统主频:400MHz(MAX)
  • DAC分辨率位数:14位
  • 相位累加器位数:32位
  • 输出信号:正弦波,方波;正弦波带150MHz低通滤波器,方波耦合输出
  • 输出通道:2通道差分,相位差180°,高频输出由于有滤波器,相位会偏移
  • 信号特点:正弦波无耦合输出;输出自带直流分量,接入射频设备请加隔直流器,也可直接使用示波器测量
  • 正弦波,方波最高主频输出:1Hz-140MHz(正弦波),1Hz-120MHz(方波),10MHz以上需要示波器输入阻抗为50欧
  • 输出幅度:正弦波200mVpp,方波1.8Vpp,正弦波随着频率增加幅度减小,方波随着频率增加波 形变化
  • 输出阻抗:75欧

模块应用

  • 频率信号发生,正弦波、方波信号发生,传感器激励

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图1)

二、模块接口说明

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图2)

UPD 上升沿将内部缓冲存储器的内容传输到 I/O 寄存器
PS0,PS1 输入引脚用于选择内部相位/频率配置文件之一。这些引脚上的变化会触发将所选内部缓冲存储器的内容传输到 I/O 寄存器
OSK 输入引脚用于控制编程后形状关键控 (OSK) 功能的方向
SDIO 串行数据 I/O
SCK 串行时钟
CS 片选
SDO 串行数据输出
IOSY 串行端口控制器异步高电平有效复位。当此引脚为高电平时,当前 I/O 操作立即终止,一旦 IOSYNC 返回低电平,即可开始新的 I/O 操作。
RST 硬件复位
PWR 输入引脚用作外部掉电控制

三、功能框图和时序图说明

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图3)

功能框图说明

REFCLK输入
AD9954 支持多种方式生成内部系统时钟。芯片内部集成了振荡器电路,可通过在 REFCLK 引脚外接晶体来产生低频参考时钟;同时也支持直接由外部时钟源驱动 REFCLK 引脚。为了在低频参考时钟条件下仍获得较高的 DDS 运算速率和 DAC 采样率,AD9954 内部集成了基于 PLL 的参考时钟倍频器,可对 REFCLK 进行倍频后作为系统时钟使用。
在对相位噪声要求较高的应用中,建议使用转换速率高、抖动低、稳定性好的外部时钟直接驱动 REFCLK,并旁路内部倍频器,以获得最佳频谱性能。

倍频器
板载锁相环 (PLL) 允许对参考时钟 (REFCLK) 频率进行倍频。倍频系数通过 CFR2<7:3> 设置。当编程值为 0x04 至 0x14(十进制 4 至 20)范围内的值时,PLL 会将 REFCLK 输入频率乘以编程值。用户在编程时必须考虑 PLL 的指定最大频率。如果更改倍频系数,用户必须预留时间让 PLL 锁定(约 1 毫秒)。当该字段编程为其他值时,PLL 被旁路并关闭,REFCLK 直接作为系统时钟输入。
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图4)

频率累加器
用于线性扫描模式;从起始频率 (F0) 到终止频率 (F1) 的转换并非瞬时完成,而是以扫描或斜坡方式实现。这种频率斜坡是通过步进 F0 和 F1 之间的中间频率来实现的。线性扫描模块使用下降和上升增量频率调谐字、下降和上升增量频率斜坡速率以及频率累加器。线性扫描使能位 CFR1<21> 用于使能线性扫描模块。线性扫描无停留位用于设置扫描过程中达到终止频率时要执行的操作。

DDS核心
DDS 的输出频率 (fO) 是系统时钟频率 (SYSCLK)、频率调谐字 (FTW) 的值以及相位累加器容量(本例中为 2³²)的函数。具体关系如下,其中 fs 定义为 SYSCLK 的频率。

fO = (FTW)(fS)/2³²,其中 0 ≤ FTW ≤ 2³¹

每个系统时钟周期,FTW 的值都会加到相位累加器中先前存储的值上。然后,将相位累加器的输出值与用户定义的 14 位相位偏移值 (POW) 相加。最后,通过 cos(x) 功能模块将该和的最高 19 位转换为幅度值。为了降低 DDS 内核的功耗,会截断最低有效位 (LSB)。这种截断不会降低频率分辨率。在某些应用中,需要能够强制输出信号为零相位。简单地将 FTW 设置为 0 并不能实现这一点;它只会使内核停留在其当前的相位值。系统提供了一个控制位,用于强制相位累加器输出为零。上电复位后,相位累加器清零功能默认处于使能状态,但该控制位需通过第一次 I/O UPDATE 才会真正生效。因此,在上电后未执行 I/O UPDATE 之前,相位累加器保持在零相位状态。

DAC输出
与许多DAC不同,AD9954的DAC输出参考的是AVDD,而非AGND。两个互补输出提供组合的满量程输出电流(IOUT)。差分输出可降低DAC输出端可能存在的共模噪声,从而提高信噪比。满量程电流由连接在DAC_RSET引脚和DAC接地引脚(引脚49,裸露的电极片)之间的外部电阻(RSET)控制。满量程电流与电阻值成正比,公式为:

RSET = (39.19 / Iout) Ω

组合DAC输出的最大满量程输出电流为15 mA。将输出限制在最大10 mA可获得最佳的无杂散动态范围(SFDR)性能。DAC 输出必须保持在 AVDD ±0.5 V 的合规范围内,超出该范围将导致失真显著增加,甚至可能损坏输出级。实际应用中应通过合理的负载与偏置设计,确保 DAC 输出始终工作在该合规范围之内。

时序与串行接口说明

AD9954 提供一个灵活的同步串行接口,可方便地与多种微控制器或微处理器连接。该接口兼容常见同步串行协议,包括 Motorola 6905/11 SPI® 和 Intel® 8051 SSR。
串行接口支持:

  1. MSB 优先或 LSB 优先数据格式
  2. 单引脚双向 SDIO(2 线接口)
  3. SDIO + SDO 分离(3 线接口)

接口配置由 CFR1 中的相关位控制。可选的 IOSYNC 与 CS 引脚进一步提升了系统集成的灵活性。

AD9954 的串行通信以 寄存器为单位,而非字节为单位。串口控制器在接收到指令字节后,会自动解析目标寄存器地址并完成整个寄存器宽度的数据传输。一个完整的通信周期分为两个阶段:
指令阶段:在前 8 个 SCLK 上升沿写入指令字节,用于指定读/写方向及寄存器地址。
数据阶段:随后的 SCLK 周期用于传输寄存器数据,传输位数由目标寄存器的位宽决定。
所有输入数据在 SCLK 上升沿采样,输出数据在 SCLK 下降沿更新。相关时序如图 25~图 28 所示。
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图5)

MSB/LSB 数据传输
AD9954 串口支持 MSB 优先或 LSB 优先的数据格式。此功能由 LSB 优先位 CFR1<8> 控制。对于 MSB 优先操作,串口控制器首先生成(指定寄存器的)最高有效字节地址,然后生成下一个较低有效字节地址,直到 I/O 操作完成。所有写入(读取)AD9954 的数据都必须采用 MSB 优先顺序。如果 LSB 模式处于活动状态,则串口控制器首先生成最低有效字节地址,然后生成下一个较高有效字节地址,直到 I/O 操作完成。所有写入(读取)AD9954 的数据都必须采用 LSB 优先的格式。

示例操作
例如,考虑将幅度比例因子 (ASF) 寄存器的值写入满量程的 0.5。首先,计算 0.5 的二进制等效值。由于 ASF 为 16 位宽,其十六进制等效值为 0x80。接下来,对于 MSB 优先的格式,发送指令字节 0x02(ASF 的串口地址为 00010(b))。根据该指令,内部控制器轮询此内存位置的寄存器,并注意到 ASF 为 2 字节宽。串口控制器的状态机设置为 16,并等待 SCLK 上的 16 个上升沿和 SDIO 线上的 16 位数据。发送 SCLK 上的 16 个上升沿,以及 SDIO 线上的二进制数据 10000000 00000000。要以 LSB 优先格式写入幅度比例因子寄存器,其过程与 MSB 优先格式相同;但是,数据需要逐字进行按位反转。指令字节为 0x40。幅度比例因子寄存器的二进制数据为 00000000 00000001。
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图6)

四、主要寄存器说明

指令字节
指令字节包含以下信息。
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图7)
R/W——指令字节的第 7 位定义在写入指令字节后发生的是读取还是写入数据传输。逻辑高电平表示读取操作,逻辑 0 表示写入操作。
X, X——指令字节的第 6 位和第 5 位无关紧要。
A4, A3, A2, A1, A0——指令字节的第 4 位、第 3 位、第 2 位、第 1 位和第 0 位确定在通信周期的数据传输阶段访问哪个寄存器。寄存器的地址可以在寄存器映射表的第一列中找到(参见下表)。

寄存器映射表及说明
寄存器映射表列于表 12 和表 13 中。当前活动的寄存器映射表取决于线性扫描使能位的状态;根据器件的工作模式,某些寄存器会被重新映射。具体来说,寄存器 0x​​07、寄存器 0x​​08、寄存器 0x​​09 和寄存器 0x​​0A 会受到影响。由于线性扫描操作的优先级高于 RAM 操作,Analog Devices 公司建议,当使用位 CFR1<21> 启用线性扫描时,应使用位 CFR1<31> 禁用 RAM,以节省功耗。每个寄存器的序列地址采用十六进制格式。尖括号 <> 用于引用特定位或位范围。例如,<3> 表示位 3,<7:3> 表示从位 7 到位 3(含位 7 和位 3)的位范围。注意,RAM使能位CFR1<31>仅激活RAM本身,并不激活RAM段控制字。
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图8)
前7个寄存器为固定映射(与模式无关)
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图9)

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图10)

寄存器映射 - 当线性扫描使能位为False时(CFR1<21> = 0)
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图11)
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图12)
寄存器映射 - 当线性扫描使能位为True时 (CFR1<21> = 1)
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图13)
功能控制寄存器1 (CFR1) (0X00):用于控制 AD9954 的各种功能、特性和模式。其中:
CFR1<31>:0(默认):关闭 RAM,进入单频/线性扫频模式。1:开启 RAM,可用于 FSK/PSK 或非线性调制模式。用于多频调制场景。
CFR1<30>:0(默认):如果 CFR1<31> 被置位,则 RAM 输出驱动相位累加器(提供 FTW)。1:如果 CFR1<31> 被置位,则 RAM 输出驱动相位偏移加法器(POW)。
CFR1<21>:0:不进行线性扫频 → 直接用 FTW0 单频输出。1:进入线性扫频模式,需要配合 FTW0、FTW1、NLSCW、PLSCW 使用。线性扫频是在频率之间做连续过渡的功能。

功能控制寄存器2 (CFR2) (0X01):用于控制 AD9954 的各种功能、特性和模式,主要与芯片的模拟部分相关。其中:
CFR2<11>:0(默认):高速同步增强功能关闭。1:高速同步增强功能开启。当 SYNC_CLK > 50 MHz (SYSCLK > 200 MSPS) 时,若使用自动同步功能,则应设置此位。
CFR2<7:3>:参考时钟倍频器控制位。此 5 位字控制时钟倍频器 (PLL) 模块的输出倍频值。
CFR2<2>:0(默认):VCO 工作频率范围为 100 MHz 至 250 MHz。1:VCO 工作频率范围为 250 MHz 至 400 MHz。

幅度比例因子寄存器 (ASF) (0X02)
ASF 寄存器存储 2 位自动斜坡速率值和 14 位幅度比例因子,用于输出整形键控 (OSK) 操作。在自动 OSK 操作中,ASF<15:14> 指示 OSK 模块每次增量或减量需要执行多少个幅度步长。ASF<13:0> 设置 OSK 内部乘法器可达到的最大值。在手动 OSK 模式下,ASF<15:14> 无效。ASF<13:0> 直接提供输出比例因子。如果使用CFR1<25> 禁用 OSK,则此寄存器对设备操作没有影响。

幅度斜坡速率寄存器 (ARR)(0x03):ARR 寄存器存储自动 OSK 模式下使用的 8 位幅度斜坡速率。

频率调谐字 0寄存器 (FTW0)(0x04):频率调谐字是一个 32 位寄存器,用于控制 DDS 内核相位累加器的累加速率。

相位偏移字寄存器 (POW)(0x05):相位偏移字是一个 14 位寄存器,用于存储相位偏移值。

频率调谐字 1寄存器 (FTW1)(0x06):频率调谐字是一个 32 位寄存器,用于设置线性扫描操作中的上限频率。

正负线性扫描控制字寄存器 (PLSCW / NLSCW)(0x07~0x08)
当启用线性扫频位时,寄存器0x07提供负线性扫频控制字(NLSCW),而寄存器0x08提供正线性扫频控制字(PLSCW)。每个线性扫频控制字包含一个32位的增量频率调谐字(FDFTW和RDFTW)和一个8位的扫频斜率字(FSRRW和RSRRW)。
负线性扫频控制字(NLSCW):用于控制负向线性扫频操作的相关参数,包括起始频率和终止频率之间的频率差(增量频率调谐字)以及扫频的斜率(扫频斜率字)。这些参数决定了扫频的速率和方向。
正线性扫频控制字(PLSCW):用于控制正向线性扫频操作的参数,包括起始频率和终止频率之间的频率差和扫频的斜率。
NLSCW 和 PLSCW 本身并不定义起始频率或终止频率,而是用于定义线性扫频过程中每一步的频率增量(ΔFTW)以及频率更新的时间间隔。线性扫频的起始频率由 FTW0 指定,终止频率由 FTW1 指定。

五、STM32F103驱动AD9954发生器模块

准备工作

STM32F103C8T6最小系统板,AD9954发生器模块,OLED屏幕,EC11旋转编码器,按键和导线若干。

接线说明

STM32F103C8T6 AD9954
3V3 3V3
GND GND
PA3 UPD
PA4 PS1
PA5 PS0
PA6 OSK
PA7 SDIO
PA11 PWR
PA12 IOSY
PA15 SDO
PB0 SCLK
PB1 CS
PB10 RES
PB8 OLED -> SCL
PB9 OLED -> SDA
PA0 EC11旋转编码器 -> A,调节频率增加
PA1 EC11旋转编码器 -> B,调节频率减少
PA2 EC11旋转编码器 -> S,移位调节
PB14 按键1,切换幅值或相位调节
PB13,15 按键2,3,调节幅值或相位逐步加减
PA8,PB12 按键4,5,调节幅值或相位快速加减

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图14)

代码示例

AD9954.c

#include "AD9954.h"
#include "delay.h"

//系统频率fosc(外部晶振频率),系统频率=fs
#define fosc  20                        //晶振频率 20Mhz
#define PLL_MULTIPLIER  20              //PLL倍频数(4--20)
#define fs  (fosc*PLL_MULTIPLIER)       //系统时钟频率

double fH_Num=10.73741824;  
//double fH_Num=11.2204;
//double fH_Num=11.3671588397205;//
//double fH_Num = 11.3025455157895;//频率转换系数:2^32/系统时钟频率


/************************************************************
** 函数名称 :void AD9954_GPIO_Init(void)  
** 函数功能 :初始化控制AD9954需要用到的IO口
** 入口参数 :无
** 出口参数 :无
** 函数说明 :无
**************************************************************/
void AD9954_GPIO_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE);					 

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_11|GPIO_Pin_12;			
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);					 					
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_10;				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;	    		
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	AD9954_RES=0;
	AD9954_CS =0;
	AD9954_SCLK =0;
	AD9954_SDIO=0;
	AD9954_OSK=0;
	PS0=0;
	PS1=0;
	IOUPDATE=0;
	AD9954_IOSY=0;
	AD9954_PWR=0;
	delay_ms(10);

}

/*********************************************************************************************************
** 函数名称 :void AD9954_RESET(void)
** 函数功能 :复位AD9954
** 入口参数 :无
** 出口参数 :无
** 函数说明 :不复位也可以
*********************************************************************************************************/
void AD9954_RESET(void)
{
	AD9954_RES = 1;
	delay_ms(10);
	AD9954_RES = 0;
	AD9954_CS = 0;
	AD9954_SCLK = 0;
	AD9954_CS = 1;
}


/*********************************************************************************************************
** 函数名称 :void UPDATE(void)
** 函数功能 :产生一个更新信号,更新AD9954内部寄存器,
** 入口参数 :无
** 出口参数 :无
** 函数说明 :可以不加任何延时
*********************************************************************************************************/
void UPDATE(void)
{ 
	IOUPDATE = 1;
	delay_us(10);
	IOUPDATE = 0;
}

/*********************************************************************************************************
** 函数名称 :void AD9954_Send_Byte(uint8_t dat)
** 函数功能 :向AD9954发送一个字节的内容
** 入口参数 :待发送字节
** 出口参数 :无
** 函数说明 :AD9954的传输速度最大为25M,所以不加延时也可以
*********************************************************************************************************/
void AD9954_Send_Byte(uint8_t dat)
{
	uint8_t i;
	for (i = 0;i< 8;i++)
	{
		AD9954_SCLK = 0;
		delay_us(10);
		if (dat & 0x80)
		{
			AD9954_SDIO = 1;
		}
		else
		{
			AD9954_SDIO = 0;
		}
		AD9954_SCLK = 1;
		delay_us(10);
		dat < <= 1;
	}
}

/*********************************************************************************************************
** 函数名称 :uint8_t AD9954_Read_Byte(void)
** 函数功能 :读AD9954一个字节的内容
** 入口参数 :无
** 出口参数 :读出的一个字节数据
** 函数说明 :
*********************************************************************************************************/
uint8_t AD9954_Read_Byte(void)
{
	uint8_t i,dat=0;
	for (i = 0;i< 8;i++)
	{
		AD9954_SCLK = 0;
		delay_us(2);
		dat|=AD9954_SDO;
		AD9954_SCLK = 1;
		delay_us(2);
		dat < <= 1;
	}
	return dat;
}

/************************************************************
** 函数名称 :void AD9954_Write_nByte(uint8_t RegAddr,uint8_t *Data,uint8_t Len)
** 函数功能 :向AD9954指定的寄存器写数据
** 入口参数 :RegAddr: 寄存器地址
						*Data: 数据起始地址
						Len: 要写入的字节数
** 出口参数 :无
** 函数说明 :无
**************************************************************/
void AD9954_Write_nByte(uint8_t RegAddr,uint8_t *Data,uint8_t Len)
{  	
	uint8_t t=0;
	
	AD9954_Send_Byte(RegAddr);
	for(t=0;t< Len;t++)
	{
		AD9954_Send_Byte(Data[t]);
	}												    
}
/*********************************************************************************************************
** 函数名称 :uint32_t AD9954_Read_nByte(uint8_t ReadAddr,uint8_t Len)
** 函数功能 :读AD9954寄存器数据
** 入口参数 :ReadAddr:要读出的寄存器地址
							Len:要读出数据的长度1-4
** 出口参数 :读出的数据
** 函数说明 :
*********************************************************************************************************/
uint32_t AD9954_Read_nByte(uint8_t ReadAddr,uint8_t Len)
{
	uint8_t t=0;
	uint32_t temp=0;
	AD9954_CS=1;
	AD9954_Send_Byte(ReadAddr);
	for(t=0;t< Len;t++)
	{
		temp< <=8;
		temp+=AD9954_Read_Byte();
	}
	AD9954_CS=1;
	return temp;
}

/************************************************************
** 函数名称 :uint32_t Get_FTW(double Real_fH)
** 函数功能 :频率数据转换
** 入口参数 :Freq,需要转换的频率,0-140000000hz
** 出口参数 :频率数据值
** 函数说明 :
**************************************************************/
uint32_t Get_FTW(double Real_fH)
{
		return (uint32_t)(fH_Num*Real_fH);
}

/*********************************************************************************************************
** 函数名称 :void AD9954_Init(void))
** 函数功能 :初始化AD9954的管脚和最简单的内部寄存器的配置,
** 入口参数 :无
** 出口参数 :无
** 函数说明 :板上的晶振为20MHz,最大采用了20倍频,为400M
*********************************************************************************************************/
void AD9954_Init(void)
{
	uint8_t CFR1_data[4]={0,0,0,0};
	uint8_t CFR2_data[3]={0,0,0};

	AD9954_GPIO_Init();
	AD9954_RESET();
	delay_ms(300);
	AD9954_CS = 0;
	
	CFR1_data[0]=0X02;//此处:0x02- >OSK使能;0X00- >OSK关闭。在OSK模式使能的前提下,幅度寄存器(0X02)生效
										//见英文数据手册,page 31 :Amplitude Scale Factor (ASF)部分
										//In manual OSK
										//mode, ASF< 15:14 > has no effect. ASF< 13:0 > provide the output
										//scale factor directly. If the OSK is disabled using CFR1< 25 >,
										//this register has no effect on device operation.
	CFR1_data[1]=0X00;
	CFR1_data[2]=0X00;
	CFR1_data[3]=0x00;//比较器启用,方波输出;0x40,比较器禁用方波无输出
	AD9954_Write_nByte(CFR1,CFR1_data,4);//数据写入控制功能寄存器1

	CFR2_data[0]=0X00;
	CFR2_data[1]=0X00;
	if(fs >400)
		;//系统频率超过芯片最大值
	else if(fs >=250)
		CFR2_data[2]=PLL_MULTIPLIER< < 3|0x04|0X03;
	else CFR2_data[2]=PLL_MULTIPLIER< < 3;
	AD9954_Write_nByte(CFR2,CFR2_data,3);//数据写入控制功能寄存器2
	
	AD9954_CS=1;
}


/*********************************************************************************************************
** 函数名称 :void AD9954_Set_Fre(double fre)
** 函数功能 :设置AD9954当前的频率输出,采用的是单一频率输出
** 入口参数 :fre:欲设置的频率值 0-140000000hz
** 出口参数 :无
** 函数说明 :因为采用的浮点数进行计算,转换过程中会出现误差,通过调整可以精确到0.1Hz以内
*********************************************************************************************************/
void AD9954_Set_Fre(double fre)//single tone
{
	uint8_t date[4] ={0x00,0x00,0x00,0x00};	//中间变量
	uint32_t Temp=0;   
	
	Temp=Get_FTW(fre);
	date[0] =(uint8_t)(Temp > > 24);
	date[1] =(uint8_t)(Temp > > 16);
	date[2] =(uint8_t)(Temp > > 8);
	date[3] =(uint8_t)Temp;
	
	AD9954_CS = 0;
	AD9954_Write_nByte(FTW0,date,4);//写频率控制字
	AD9954_CS=1;
	UPDATE();
}

/*********************************************************************************************************
** 函数名称 :void AD9954_Set_Amp(uint16_t Ampli)
** 函数功能 :设置AD9954输出幅度
** 入口参数 :Ampli:0-16383,最大峰峰值约500mv
** 出口参数 :无
** 函数说明 :
*********************************************************************************************************/
void AD9954_Set_Amp(uint16_t Ampli)
{
	uint8_t date[2] ={0x00,0x00};	
	
	AD9954_CS = 0;
	date[0]=(uint8_t)(Ampli > > 8);
	date[1]=(uint8_t)Ampli;
	AD9954_Write_nByte(ASF,date,2);
	AD9954_CS = 1;
	UPDATE();
}

/************************************************************
** 函数名称 :void AD9954_Set_Phase(uint8_t Channel,uint16_t Phase)
** 函数功能 :设置通道的输出相位
** 入口参数 :Phase:	输出相位,范围:0~16383(对应角度:0°~360°)
** 出口参数 :无
** 函数说明 :无
**************************************************************/
void AD9954_Set_Phase(uint16_t Phase)//写相位
{
	uint8_t date[2] ={0x00,0x00};	
	
	AD9954_CS = 0;
	date[0]=(uint8_t)(Phase > > 8);
	date[1]=(uint8_t)Phase;
	AD9954_Write_nByte(POW0,date,2);
	AD9954_CS = 1;
	UPDATE();
}


/*********************************************************************************************************
** 函数名称 :void AD9954_SetFSK(double f1,double f2,double f3,double f4,uint16_t Ampli)   
** 函数功能 :四相FSK信号输出参数设置
** 入口参数 :f1:频率1  0-140000000hz
** 	      		f2:频率2
** 	      		f3:频率3
** 	      		f4:频率4
**					 Ampli幅度:0-16383,最大峰峰值约500mv
** 出口参数 :无
** 隐含控制   			PS0: 0   1	0	 1
** 管脚参数: 			PS1: 0   0	1	 1
** 对应控制 RAM段:		 0   1	2	 3
** 函数说明 :在四个RAM区各设置了一个频率值,通过改变PS0和PS1的电平选择对应的RAM端输出相应的频率值来实现FSK,也可以实现二项的FSK;
**            通过设置控制PS0,PS1管脚的电平就可以将二进制的编码转化为FSK信号输出
*********************************************************************************************************/
void AD9954_SetFSK(double f1,double f2,double f3,double f4,uint16_t Ampli)   
{
	uint32_t FTW_Vau=0;
	uint8_t fdata[4]={0,0,0,0};	
	uint8_t CFR1_data[4]={0,0,0,0};
	uint8_t data[5]={0,0,0,0,0};
	
	AD9954_CS = 0;

	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x00;	//终止地址:0x0000   低8位
	data[3]=0x00;	//0:1终止地址:高2位,2:7起始地址低6位:0x000;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM0工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW0,data,5);
		
	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x01;	//终止地址:0x0001   低8位
	data[3]=0x04;	//0:1终止地址:高2位,2:7起始地址低6位:0x0001;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM1工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW1,data,5);
	
	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x02;	//终止地址:0x0002   低8位
	data[3]=0x08;	//0:1终止地址:高2位,2:7起始地址低6位:0x0002;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM2工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW2,data,5);
	
	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x03;	//终止地址:0x0003   低8位
	data[3]=0x0c;	//0:1终止地址:高2位,2:7起始地址低6位:0x0003;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM3工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW3,data,5);    	
	
	AD9954_CS = 1;
	UPDATE();
	
	AD9954_CS = 0;
	PS1=0;PS0=0;
	FTW_Vau=Get_FTW(f1); 
  fdata[0]=FTW_Vau >>24;
  fdata[1]=FTW_Vau >>16;
  fdata[2]=FTW_Vau >>8;
  fdata[3]=FTW_Vau;
	AD9954_Write_nByte(RAM,fdata,4);    	

	
	PS1=0;PS0=1;
	FTW_Vau=Get_FTW(f2); 
  fdata[0]=FTW_Vau >>24;
  fdata[1]=FTW_Vau >>16;
  fdata[2]=FTW_Vau >>8;
  fdata[3]=FTW_Vau;
	AD9954_Write_nByte(RAM,fdata,4);  
	
	PS1=1;PS0=0;
	FTW_Vau=Get_FTW(f3); 
  fdata[0]=FTW_Vau >>24;
  fdata[1]=FTW_Vau >>16;
  fdata[2]=FTW_Vau >>8;
  fdata[3]=FTW_Vau;
	AD9954_Write_nByte(RAM,fdata,4);  
	
	PS1=1;PS0=1;
	FTW_Vau=Get_FTW(f4); 
  fdata[0]=FTW_Vau >>24;
  fdata[1]=FTW_Vau >>16;
  fdata[2]=FTW_Vau >>8;
  fdata[3]=FTW_Vau;
	AD9954_Write_nByte(RAM,fdata,4);  
	
	AD9954_CS = 1;
	UPDATE();

	AD9954_CS = 0;
	CFR1_data[0]=0X82;//打开RAM控制位驱动FTW
	CFR1_data[1]=0X00;
	CFR1_data[2]=0X00;
	CFR1_data[3]=0x00;//比较器启用,方波输出;0x40,比较器禁用方波无输出
	AD9954_Write_nByte(CFR1,CFR1_data,4);//数据写入控制功能寄存器1
	AD9954_CS = 1;
	UPDATE();

	AD9954_Set_Amp(Ampli);//设置幅度

}   

/*********************************************************************************************************
** 函数名称 :void AD9954_SetPSK(uint16_t Phase1,uint16_t Phase2,uint16_t Phase3,uint16_t Phase4,double fre,uint16_t Ampli)
** 函数功能 :四相PSK信号输出参数设置
** 入口参数 :Phase1:相位1 范围:0-16383 对应0-360度
** 	      		Phase2:相位2
** 	      		Phase3:相位3
** 	      		Phase4:相位4
** 						fre:频率    0-140000000hz
**					 Ampli幅度:0-16383,最大峰峰值约500mv
** 出口参数 :无
** 隐含控制   			PS0: 0   1	0	 1
** 管脚参数: 			PS1: 0   0	1	 1
** 对应控制 RAM段:		 0   1	2	 3
** 函数说明 :在四个RAM区各设置了一个频率值,通过改变PS0和PS1的电平选择对应的RAM端输出相应的频率值来实现PSK,也可以实现二项的PSK;
**            通过设置控制PS0,PS1管脚的电平就可以将二进制的编码转化为PSK信号输出
*********************************************************************************************************/
void AD9954_SetPSK(uint16_t Phase1,uint16_t Phase2,uint16_t Phase3,uint16_t Phase4,double fre,uint16_t Ampli)
{
	uint8_t pdata[4]={0,0,0,0};	
	uint8_t CFR1_data[4]={0,0,0,0};

	uint8_t data[5]={0,0,0,0,0};
	
	AD9954_CS = 0;

	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x00;	//终止地址:0x0000   低8位
	data[3]=0x00;	//0:1终止地址:高2位,2:7起始地址低6位:0x000;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM0工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW0,data,5);
		
	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x01;	//终止地址:0x0001   低8位
	data[3]=0x04;	//0:1终止地址:高2位,2:7起始地址低6位:0x0001;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM1工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW1,data,5);
	
	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x02;	//终止地址:0x0002   低8位
	data[3]=0x08;	//0:1终止地址:高2位,2:7起始地址低6位:0x0002;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM2工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW2,data,5);
	
	data[0]=0x00;	//低8位 斜率=0x0000  
	data[1]=0x00;	//高8位 斜率=0x0000
	data[2]=0x03;	//终止地址:0x0003   低8位
	data[3]=0x0c;	//0:1终止地址:高2位,2:7起始地址低6位:0x0003;   
	data[4]=0x00;	//0:3起始地址高4位  5:7,RAM3工作于模式0;  4:不停留位没有激活  
	AD9954_Write_nByte(RSCW3,data,5);    	
	
	AD9954_CS = 1;
	UPDATE();
	
	Phase1< <=2;
	Phase2< <=2;
	Phase3< <=2;
	Phase4< <=2;
	
	AD9954_CS = 0;
	PS1=0;PS0=0;
  pdata[0]=(uint8_t)(Phase1 >>8);
  pdata[1]=(uint8_t)Phase1;
	pdata[2]=0x00;
  pdata[3]=0x00;
	AD9954_Write_nByte(RAM,pdata,4);    	

	PS1=0;PS0=1;
  pdata[0]=(uint8_t)(Phase2 >>8);
  pdata[1]=(uint8_t)Phase2;
	pdata[2]=0x00;
  pdata[3]=0x00;
	AD9954_Write_nByte(RAM,pdata,4);  
	
	PS1=1;PS0=0;
  pdata[0]=(uint8_t)(Phase3 >>8);
  pdata[1]=(uint8_t)Phase3;
	pdata[2]=0x00;
  pdata[3]=0x00;
	AD9954_Write_nByte(RAM,pdata,4);  
	
	PS1=1;PS0=1;
  pdata[0]=(uint8_t)(Phase4 >>8);
  pdata[1]=(uint8_t)Phase4;
	pdata[2]=0x00;
  pdata[3]=0x00;
	AD9954_Write_nByte(RAM,pdata,4);  
	
	AD9954_CS = 1;
	UPDATE();

	AD9954_CS = 0;
	CFR1_data[0]=0Xc2;//打开RAM控制位驱动POW相位
	CFR1_data[1]=0X00;
	CFR1_data[2]=0X00;
	CFR1_data[3]=0x00;//比较器启用,方波输出;0x40,比较器禁用方波无输出
	AD9954_Write_nByte(CFR1,CFR1_data,4);//数据写入控制功能寄存器1
	AD9954_CS = 1;
	UPDATE();
	AD9954_Set_Amp(Ampli);//设置幅度
	AD9954_Set_Fre(fre);
}

/*********************************************************************************************************
** 函数名称 :void AD9954_Set_LinearSweep(double Freq_Low,double Freq_High,double  UpStepFreq, 
**																			uint8_t UpStepTime,double	DownStepFreq, uint8_t DownStepTime,uint8_t mode)
** 函数功能 :线性扫描输出模式
** 函数说明 :使频率按预置的模式线性扫描上去,详细参见官方PDF
** 入口参数 :Freq_Low:起始频率 0-140000000hz
** 			  		Freq_High:终止频率; 0-140000000hz
**						UpStepFreq:向上扫频步进, 0-140000000hz
**						UpStepTime:向上扫频的步进间隔时间;1-255
**            DownStepFreq:向下扫频步进, 0-140000000hz
**						DownStepTime:向下扫频的步进间隔时间;1-255
**						mode:线性扫描无停留功能,No_Dwell不停留,输出扫频到终止频率回到起始频率;Dwell停留,输出扫频到终止频率后保持在终止频率。
** 出口参数 :无
** 函数说明 :需要保证,Freq_Low< Freq_High
**						步进间隔时间 T = StepTime*10 ;例:UpStepTime=100,则向上扫频的步进间隔时间T=1000nS=1us
**						扫频总时间=总扫描频点数*T
**						PS0脚控制扫频方向,PS0=1向上扫频,PS0=0向下扫频
*********************************************************************************************************/
void AD9954_Set_LinearSweep(double Freq_Low,double Freq_High,double  UpStepFreq, uint8_t UpStepTime,double	DownStepFreq, uint8_t DownStepTime,uint8_t mode)//linear sweep mode
{	
	uint8_t date[5] ={0x00,0x00,0x00,0x00,0x00};	//中间变量
	uint8_t CFR1_data[4] ={0x00,0x00,0x00,0x00};
	uint32_t Temp=0; 
	
	PS1=0;PS0=0;
	AD9954_CS=0;
	
	CFR1_data[0]=0x00;//31-24
	CFR1_data[1]=0x20;//23-16
	CFR1_data[2]=0x00;
	CFR1_data[3]=0x00|mode;//比较器启用,方波输出;0x40,比较器禁用方波无输出
	AD9954_Write_nByte(CFR1,CFR1_data,4);//数据写入控制功能寄存器1
	
	Temp=Get_FTW(Freq_Low);
	date[0] =(uint8_t)(Temp > > 24);
	date[1] =(uint8_t)(Temp > > 16);
	date[2] =(uint8_t)(Temp > > 8);
	date[3] =(uint8_t)Temp;
	AD9954_Write_nByte(FTW0,date,4);//写频率控制字
	
	Temp=Get_FTW(Freq_High);
	date[0] =(uint8_t)(Temp > > 24);
	date[1] =(uint8_t)(Temp > > 16);
	date[2] =(uint8_t)(Temp > > 8);
	date[3] =(uint8_t)Temp;
	AD9954_Write_nByte(FTW1,date,4);//写频率控制字
	
	Temp=Get_FTW(DownStepFreq);
	date[0] =DownStepTime;//下降的扫描斜率
	date[1] =(uint8_t)(Temp > > 24);
	date[2] =(uint8_t)(Temp > > 16);
	date[3] =(uint8_t)(Temp > > 8);
	date[4] =(uint8_t)Temp;//下降的频率增量
	AD9954_Write_nByte(NLSCW,date,5);
	
	Temp=Get_FTW(UpStepFreq);
	date[0] =UpStepTime;//上升的扫描斜率
	date[1] =(uint8_t)(Temp > > 24);
	date[2] =(uint8_t)(Temp > > 16);
	date[3] =(uint8_t)(Temp > > 8);
	date[4] =(uint8_t)Temp;//上升的频率增量
	AD9954_Write_nByte(PLSCW,date,5);
	
	AD9954_CS=1;
	UPDATE();
}

main.c

#include "stm32_config.h"
#include "stdio.h"
#include "key.h"
#include "Encoder.h"
#include "OLED.h"
#include "ad9954.h"
#include "timer.h"

uint8_t KeyNum, key;
uint32_t last_value = 140000000;
uint32_t Fre = 1000;
uint32_t Amp = 16383;
uint32_t Phase = 0;

int main(void)
{
	MY_NVIC_PriorityGroup_Config(NVIC_PriorityGroup_2);	//设置中断分组
	delay_init(72);	//初始化延时函数
	
	Key_Init();
	Timer_Init();
	OLED_Init();
	Encoder_Init();
	
	AD9954_Init();				//初始化控制AD9954需要用到的IO口,及寄存器
	
	delay_ms(100);
	
	OLED_ShowString(0,5,"AD9954",16,1);
//	OLED_ShowChinese(70,5,0,16,1);
//	OLED_ShowChinese(86,5,1,16,1);
	OLED_ShowString(95,150,"Hz",16,1);
	OLED_Refresh();
	
//	AD9954_Set_LinearSweep(100000,500000,1,200,1,200,Dwell); //扫频,Dwell停留,输出扫频到终止频率后保持在终止频率。No_Dwell不停留,输出扫频到终止频率后回到起始频率;
							//起始100000Hz,终止500000Hz,上扫频步进1hz,上扫频时间约800ms;下扫频步进1hz,下扫频时间约800ms;扫频时间计算参考函数注解
	
	while(1)
	{
		Encoder_Value_Update();
		Encoder_Display();
		
		Fre = Encoder_GetValue();
		if (Fre >= last_value)
		{
			Fre = last_value;
		}
				
		KeyNum += Key_GetNum();
		if(KeyNum > 2) KeyNum = 1;
		
		switch (KeyNum)
		{
			case 1:		// 调节幅值
				
				if(Amp >= 16383) Amp = 16383;
			
				OLED_ShowChinese(70,5,0,16,1);
				OLED_ShowChinese(86,5,1,16,1);
				Amp += Key_GetValue();
				break;
			case 2:		// 调节相位
				if(Phase >= 16383) Phase = 16383;
			
				OLED_ShowChinese(70,5,2,16,1);
				OLED_ShowChinese(86,5,3,16,1);
				Phase += Key_GetValue();
				break;
		}
		
		AD9954_Set_Fre(Fre);//设置输出频率1000Hz
		AD9954_Set_Amp(Amp);//设置幅度,范围0~16383 (对应幅度约:0~500mv)
		AD9954_Set_Phase(Phase);//设置相位,范围0~16383(对应角度:0°~360°)
		
		OLED_ShowNum(20, 150, Fre, 9, 16, 1);
		OLED_ShowNum(10, 175, Amp, 5, 16, 1);
		OLED_ShowNum(70, 175, Phase, 5, 16, 1);
		OLED_Refresh();
		delay_ms(10);
		
		
		//扫频
//		PS0=1;//PS0控制扫频方向;高电平:从起始值扫到结束值
//		delay_ms(500);	
//		PS0=0;//低电平:从结束值扫到起始值
//		delay_ms(500);
		
	}
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		key++;
		if(key > 20)
		{
			Key_Loop();
			key = 0;
		}
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

效果展示

基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图15)
双向线性扫频模式
基于STM32F103驱动AD9954 高速DDS信号发生器模块输出波形信号(图16)

六、注意事项与常见问题

注意事项
(1)模块为低功耗模块,供电电源不超过5.5V。
(2)由于模块是高精度器件,为了避免不必要的干扰,建议使用线性电源供电。
(3)输出信号建议使用SMA转BNC的线直接示波器观测效果,接触不良或劣质的线材可能导致信号衰减或者噪声过大。
(4)如需简单测试模块功能,建议搭配本店控制板使用,先给DDS模块供电,再给控制板供电即可产生波形,长按中间键切换功能。

常见问题
Q:模块的主频和输出幅度可以调节吗?
A:模块的主频是输入时钟和程序一起决定的,模块可以外部输入时钟,板载默认时钟为20MHz,程序控制倍频为20倍,即默认主频为 400MHz,可以通过修改输入时钟和倍频数改变主频,输出幅度可以通过修改14位幅度寄存器控制输出幅度。

Q:模块可以实现扫频么?方波占空比可调吗?
A:模块可以实现扫频。提供的例程可支持扫频。模块方波幅度和占空比都不能调节,正弦波频率、幅度可以调节。

Q:可以做AM等调制吗?
A:可以通过改变幅度寄存器实现低速的AM调制,并且具有多种扫频模式,默认代码是软件单音扫频,其他模式需要自行编程。

审核编辑 黄宇