一、AD9959模块简介
AD9959可以执行高达16阶的频率、相位或幅度调制(FSK、PSK、 ASK)通过将数据施加到模式引脚,可执行调制。此外,AD9959 还支持线性频率、幅度或相位扫描,适合雷达、仪器仪表等应用。 AD9959的串行I/0端口可支持多种配置,提供了极大的灵活性。与 ADI公司以往DDS产品中提供的SPI工作模式类似,串行I/O端C提 供SPI兼容工作模式,四个数据引脚(SDIO_0/SDIO_1/SDIO.2/ SDIO_3)对应串行/0的四种可编程模式,从而提供了灵活性。
性能参数
- 供电电压/电流:DC 5V 320mA(max)
- 通讯协议:SPI串行
- 系统主频:500MHz
- DCA分辨率位数:10位
- 相位累加器位数:32位
- 输出信号:正弦波(带200MHz低通滤波器)
- 输出通道:4通道(每个通道都可以独立调节频率,幅度,相位)
- 信号特点:耦合输出(模块通电容耦合输出,极低频率输出幅度较小,可以直接使用示波器测量)
- 最高主频输出正弦波:1Hz-200MHz
- 输出幅度:正弦波530mVpp(max),正弦波随着频率增加幅度减少
- 输出高阻:高阻
模块应用
- 多通道信号源,频率信号发生器,正弦波信号发生器,传感器激励
二、AD9959接口图和功能框图
每个DDS通道内部结构:频率控制字FTW + 相位累加器 + 相位偏移 + SIN查找表 + 幅度控制 + DAC。 DDS核心模块作用:
- 相位累加器:每个时钟周期执行:Phase = Phase + FTW,FTW越大,相位增长越快,输出频率越高。
- 正弦查找表:将相位值转换为:sin(phase)得到数字正弦信号。
- 幅度控制:控制输出信号振幅。
- DAC:DAC将数字信号转换为模拟信号输出。AD9959每个通道都有 独立DAC。
三、AD9959串行通讯
AD9959 的串行操作是在寄存器级别进行的,而不是字节级别;也就是说,控制器期望访问寄存器地址中包含的所有字节。可以使用 SYNC_I/O 函数中止 I/O 操作,从而允许访问部分字节。此功能可用于仅对寻址寄存器的一部分进行编程。请注意,只有已完成的字节才会受到影响。
串行通信周期分为两个阶段。阶段 1 是指令周期,它将指令字节写入 AD9959。指令字节的每一位都在 SCLK 的每个相应上升沿被写入。指令字节定义了即将进行的数据传输是写入操作还是读取操作。指令字节包含地址寄存器的串行地址。
I/O 周期的阶段 2 包括串行端口控制器和串行端口缓冲区之间的实际数据传输(写入/读取)。在此通信周期阶段传输的字节数取决于所访问的寄存器。数据传输和指令字节所需的额外 SCLK 上升沿的实际数量取决于寄存器中的字节数和串行 I/O 操作模式。
例如,访问宽度为 3 字节的功能寄存器 1 (FR1) 时,I/O 周期的第二阶段需要传输 3 个字节。在传输完每个指令字节的所有数据字节后,该寄存器的通信周期即告完成。通信周期完成后,AD9959 串行端口控制器期望下一组 SCLK 上升沿作为下一个通信周期的指令字节。所有写入 AD9959 的数据都在 SCLK 上升沿时被读取。数据在 SCLK 下降沿时被读取(参见图 43 至图 49)。图 41 和图 42 的时序规格在表 25 中描述。
阶段 1: 每个通信周期不需要发出 I/O 更新指令。I/O 更新指令将数据从 I/O 端口缓冲区传输到有效寄存器。I/O 更新指令可以在每个通信周期发送,也可以在所有串行操作完成后发送。但是,数据只有在发送 I/O 更新指令后才会生效,通道选择寄存器 (CSR) 中的通道使能位除外。这些通道使能位不需要 I/O 更新指令即可启用。
指令字节描述
指令字节包含以下信息:
指令字节(读/写)的 D7 位决定指令字节写入后是进行读取还是写入数据传输。逻辑高电平表示读取操作,逻辑低电平表示写入操作。指令字节的 D4 位到 D0 位决定在通信周期的数据传输阶段访问哪个寄存器。内部字节地址由 AD9959 生成。
阶段 2: 串行 I/O 操作模式 串行 I/O 端口有四种可编程操作模式:
- 单比特串行 2 线模式(默认模式)
- 单比特串行 3 线模式
- 2 比特串行模式
- 4 比特串行模式(SYNC_I/O 不可用)
表 26 显示了所有六个串行 I/O 接口引脚的功能,具体取决于所编程的串行 I/O 操作模式。
通道选择寄存器 CSR[2:1] 中的两位设置串行 I/O 操作模式,定义见表 27。
单比特串行(2线和3线)模式
单比特串行模式接口允许对配置AD9959的所有寄存器进行读/写访问。支持MSB优先或LSB优先的传输格式。此外,单比特串行模式接口端口可以配置为单引脚I/O(支持2线接口),也可以配置为两个单向输入/输出引脚(支持3线接口)。单比特模式允许使用SYNC_I/O函数。
在单比特串行模式的2线接口操作中,SDIO_0引脚是单路串行数据I/O引脚。在单比特串行模式的3线接口操作中,SDIO_0引脚是串行数据输入引脚,SDIO_2引脚是输出数据引脚。无论接口中使用的导线数量如何,SDIO_3 引脚均配置为输入,并在单比特串行模式和双比特串行模式下作为 SYNC_I/O 引脚工作。在此模式下,SDIO_1 引脚未使用(参见表 26)。
双比特串行模式
双比特串行模式下的 SPI 端口操作与单比特串行模式下的 SPI 端口操作相同,区别在于 SCLK 的每个上升沿会记录两位数据。因此,传输八位信息仅需四个时钟周期。SDIO_0 引脚包含偶数位数据,表示为 D[7:0],SDIO_1 引脚包含奇数位数据。这种奇偶位引脚/数据对齐方式在 MSB 优先和 LSB 优先两种格式中均有效(参见图 44)。
4 位串行模式
4 位串行模式下的 SPI 端口与 1 位串行模式下的 SPI 端口相同,区别在于 SCLK 的每个上升沿会写入 4 位数据。因此,传输 8 位信息仅需两个时钟周期。SDIO_0 和 SDIO_2 引脚包含偶数位数据,表示为 D[7:0],其中 SDIO_0 引脚包含半字节的最低有效位 (LSB)。SDIO_1 和 SDIO_3 引脚包含奇数位数据,其中 SDIO_1 引脚包含要访问的半字节的最低有效位 (LSB)。
请注意,在将设备编程为 4 位串行模式时,务必将 SDIO_3 引脚保持为逻辑 0,直到设备从 1 位串行模式编程出来为止。否则,可能会导致串行 I/O 端口控制器乱序。
图 43 至图 45 分别展示了各种串行 I/O 模式的写入时序图。图中同时显示了 MSB 优先和 LSB 优先两种模式。LSB 优先位以括号标出。图中所示的时钟停顿低/高电平特性并非必需,其作用在于表明数据(SDIO)的建立时间必须相对于 SCLK 的上升沿正确。
图 46 至图 49 分别展示了各种串行 I/O 模式的读取时序图。图中同时显示了 MSB 优先和 LSB 优先两种模式。LSB 优先位以括号标出。图中所示的时钟停顿低/高电平特性并非必需,其作用在于表明数据(SDIO)的建立时间必须相对于 SCLK 的上升沿正确,以确保指令字节和紧随 SCLK 下降沿之后的读取数据的建立时间。
四、AD9959主要寄存器说明
-
通道选择寄存器 (CSR) — 地址 0x00:CSR 通过四个通道使能位的状态来决定通道的启用或禁用状态。默认情况下,所有四个通道均启用。CSR 还决定选择哪种串行操作模式。此外,CSR 还提供 MSB 优先或 LSB 优先两种数据格式选择。
-
功能寄存器 1 (FR1)—地址 0x01:FR1 用于控制芯片的工作模式。
-
通道功能寄存器 (CFR)—地址 0x03
-
信道频率调谐字 0 (CFTW0)—地址 0x04 / 通道相位偏移字 0 (CPOW0)—地址 0x05
-
幅度控制寄存器(ACR)—地址0x06
五、AD9959频率、幅度、相位的转换
计算增量频率、增量相位或增量幅度的 RDW 或 FDW 步长的公式如下:
根据 RSRR 或 FSRR 计算增量时间的公式为:t = (RSRR) × 1 / SYNC_CLK。 在 500 MSPS 操作(SYNC_CLK = 125 MHz)下,步长之间的最大时间间隔为 1/125 MHz × 256 = 2.048 μs。最小时间间隔为 (1/125 MHz) × 1 = 8.0 ns。
六、STM32F103驱动AD9959模块
准备工作
STM32F103C8T6开发板,AD9959模块,OLED显示屏,导线若干。
接线说明
| STM32F103C8T6 | AD9959 |
|---|---|
| PA0 | P0 |
| PA1 | P1 |
| PA2 | P2 |
| PA3 | P3 |
| PA4 | SD0 |
| PA5 | SD1 |
| PA6 | SD2 |
| PA7 | SD3 |
| PB0 | CS |
| PB1 | SCK |
| PB10 | UP |
| PB5 | RST |
| PB6 | PDC |
| PB8 | OLED->SCL |
| PB9 | OLED->SDA |
代码示例
AD9959.c
#include "AD9959.H"
//#include "task_manage.h"
u8 CSR_DATA0[1] = {0x10}; // 开 CH0
u8 CSR_DATA1[1] = {0x20}; // 开 CH1
u8 CSR_DATA2[1] = {0x40}; // 开 CH2
u8 CSR_DATA3[1] = {0x80}; // 开 CH3
u8 FR2_DATA[2] = {0x20,0x00};//default Value = 0x0000
u8 CFR_DATA[3] = {0x00,0x03,0x02};//default Value = 0x000302
u8 CPOW0_DATA[2] = {0x00,0x00};//default Value = 0x0000 @ = POW/2^14*360
u8 LSRR_DATA[2] = {0x00,0x00};//default Value = 0x----
u8 RDW_DATA[4] = {0x00,0x00,0x00,0x00};//default Value = 0x--------
u8 FDW_DATA[4] = {0x00,0x00,0x00,0x00};//default Value = 0x--------
//AD9959初始化
void Init_AD9959(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
u8 FR1_DATA[3] = {0xD0,0x00,0x00};//20倍频 Charge pump control = 75uA FR1<23> -- VCO gain control =0时 system clock below 160 MHz;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE); //PA,PB,PC端口时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;//初始化管脚PA2.3.4.5.6.7.8.9.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; //IO口速度为2MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_10|GPIO_Pin_5|GPIO_Pin_6;//初始化管脚PB0.1.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; //IO口速度为2MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB
Intserve(); //IO口初始化
IntReset(); //AD9959复位
WriteData_AD9959(FR1_ADD,3,FR1_DATA,1);//写功能寄存器1
WriteData_AD9959(FR2_ADD,2,FR2_DATA,1);
// WriteData_AD9959(CFR_ADD,3,CFR_DATA,1);
// WriteData_AD9959(CPOW0_ADD,2,CPOW0_DATA,0);
// WriteData_AD9959(ACR_ADD,3,ACR_DATA,0);
// WriteData_AD9959(LSRR_ADD,2,LSRR_DATA,0);
// WriteData_AD9959(RDW_ADD,2,RDW_DATA,0);
// WriteData_AD9959(FDW_ADD,4,FDW_DATA,1);
//写入初始频率
}
//延时
void delay1 (u32 length)
{
length = length*12;
while(length--);
}
//IO口初始化
void Intserve(void)
{
AD9959_PWR=0;
CS = 1;
SCLK = 0;
UPDATE = 0;
PS0 = 0;
PS1 = 0;
PS2 = 0;
PS3 = 0;
SDIO0 = 0;
SDIO1 = 0;
SDIO2 = 0;
SDIO3 = 0;
}
//AD9959复位
void IntReset(void)
{
Reset = 0;
delay1(1);
Reset = 1;
delay1(30);
Reset = 0;
}
//AD9959更新数据
void IO_Update(void)
{
UPDATE = 0;
delay1(2);
UPDATE = 1;
delay1(4);
UPDATE = 0;
}
/*--------------------------------------------
函数功能:控制器通过SPI向AD9959写数据
RegisterAddress: 寄存器地址
NumberofRegisters: 所含字节数
*RegisterData: 数据起始地址
temp: 是否更新IO寄存器
----------------------------------------------*/
void WriteData_AD9959(u8 RegisterAddress, u8 NumberofRegisters, u8 *RegisterData,u8 temp)
{
u8 ControlValue = 0;
u8 ValueToWrite = 0;
u8 RegisterIndex = 0;
u8 i = 0;
ControlValue = RegisterAddress;
//写入地址
SCLK = 0;
CS = 0;
for(i=0; i<8; i++)
{
SCLK = 0;
if(0x80 == (ControlValue & 0x80))
SDIO0= 1;
else
SDIO0= 0;
SCLK = 1;
ControlValue <<= 1;
}
SCLK = 0;
//写入数据
for (RegisterIndex=0; RegisterIndex<NumberofRegisters; RegisterIndex++)
{
ValueToWrite = RegisterData[RegisterIndex];
for (i=0; i<8; i++)
{
SCLK = 0;
if(0x80 == (ValueToWrite & 0x80))
SDIO0= 1;
else
SDIO0= 0;
SCLK = 1;
ValueToWrite <<= 1;
}
SCLK = 0;
}
if(temp != 0)
IO_Update();
CS = 1;
}
/*---------------------------------------
函数功能:设置通道输出频率
Channel: 输出通道
Freq: 输出频率
---------------------------------------*/
void Write_frequence(u8 Channel,u32 Freq)
{
u8 CFTW0_DATA[4] ={0x00,0x00,0x00,0x00}; //中间变量
u32 Temp;
Temp=(u32)Freq*8.589934592; //将输入频率因子分为四个字节 8.589934592=(2^32)/500000000 其中500M=25M*20(倍频数可编程)
CFTW0_DATA[3]=(u8)Temp;
CFTW0_DATA[2]=(u8)(Temp>>8);
CFTW0_DATA[1]=(u8)(Temp>>16);
CFTW0_DATA[0]=(u8)(Temp>>24);
if(Channel==0)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA0,1);//控制寄存器写入CH0通道
WriteData_AD9959(CFTW0_ADD,4,CFTW0_DATA,1);//CTW0 address 0x04.输出CH0设定频率
}
else if(Channel==1)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA1,1);//控制寄存器写入CH1通道
WriteData_AD9959(CFTW0_ADD,4,CFTW0_DATA,1);//CTW0 address 0x04.输出CH1设定频率
}
else if(Channel==2)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA2,1);//控制寄存器写入CH2通道
WriteData_AD9959(CFTW0_ADD,4,CFTW0_DATA,1);//CTW0 address 0x04.输出CH2设定频率
}
else if(Channel==3)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA3,1);//控制寄存器写入CH3通道
WriteData_AD9959(CFTW0_ADD,4,CFTW0_DATA,1);//CTW0 address 0x04.输出CH3设定频率
}
}
/*---------------------------------------
函数功能:设置通道输出幅度
Channel: 输出通道
Ampli: 输出幅度
---------------------------------------*/
void Write_Amplitude(u8 Channel, u16 Ampli)
{
u16 A_temp;//=0x23ff;
u8 ACR_DATA[3] = {0x00,0x00,0x00};//default Value = 0x--0000 Rest = 18.91/Iout
A_temp=Ampli|0x1000;
ACR_DATA[2] = (u8)A_temp; //低位数据
ACR_DATA[1] = (u8)(A_temp>>8); //高位数据
if(Channel==0)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA0,1);
WriteData_AD9959(ACR_ADD,3,ACR_DATA,1);
}
else if(Channel==1)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA1,1);
WriteData_AD9959(ACR_ADD,3,ACR_DATA,1);
}
else if(Channel==2)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA2,1);
WriteData_AD9959(ACR_ADD,3,ACR_DATA,1);
}
else if(Channel==3)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA3,1);
WriteData_AD9959(ACR_ADD,3,ACR_DATA,1);
}
}
/*---------------------------------------
函数功能:设置通道输出相位
Channel: 输出通道
Phase: 输出相位,范围:0~16383(对应角度:0°~360°)
---------------------------------------*/
void Write_Phase(u8 Channel,u16 Phase)
{
u16 P_temp=0;
P_temp=(u16)Phase;
CPOW0_DATA[1]=(u8)P_temp;
CPOW0_DATA[0]=(u8)(P_temp>>8);
if(Channel==0)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA0,1);
WriteData_AD9959(CPOW0_ADD,2,CPOW0_DATA,1);
}
else if(Channel==1)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA1,1);
WriteData_AD9959(CPOW0_ADD,2,CPOW0_DATA,1);
}
else if(Channel==2)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA2,1);
WriteData_AD9959(CPOW0_ADD,2,CPOW0_DATA,1);
}
else if(Channel==3)
{
WriteData_AD9959(CSR_ADD,1,CSR_DATA3,1);
WriteData_AD9959(CPOW0_ADD,2,CPOW0_DATA,1);
}
}
main.c
#include "stm32_config.h"
#include "stdio.h"
#include "OLED.h"
#include "AD9959.h"
uint32_t SinFre[5] = {10000,10000,50,50}; //调频
uint32_t SinAmp[5] = {1023, 1023, 1023, 1023}; //调幅,范围0~1023,对应0~500mVpp左右
uint32_t SinPhr[5] = {0, 4095*2, 4095*3, 4095}; //调相位,范围0~16383,对应0~360°
int main(void)
{
MY_NVIC_PriorityGroup_Config(NVIC_PriorityGroup_2); //设置中断分组
delay_init(72); //初始化延时函数
OLED_Init();
Init_AD9959();
delay_ms(100);
OLED_ShowString(0,0,"CH0: ",16,1);
OLED_ShowString(0,16,"CH1: ",16,1);
OLED_ShowString(0,32,"CH2: ",16,1);
OLED_ShowString(0,48,"CH3: ",16,1);
while(1)
{
Write_frequence(3,SinFre[3]);
Write_frequence(0,SinFre[0]);
Write_frequence(1,SinFre[1]);
Write_frequence(2,SinFre[2]);
Write_Phase(3, SinPhr[3]);
Write_Phase(0, SinPhr[0]);
Write_Phase(1, SinPhr[1]);
Write_Phase(2, SinPhr[2]);
Write_Amplitude(3, SinAmp[3]);
Write_Amplitude(0, SinAmp[0]);
Write_Amplitude(1, SinAmp[1]);
Write_Amplitude(2, SinAmp[2]);
// OLED_ShowNum(35, 0, SinFre[0], 9, 16, 1);
// OLED_ShowNum(35, 16, SinFre[1], 9, 16, 1);
// OLED_ShowNum(35, 32, SinFre[2], 9, 16, 1);
// OLED_ShowNum(35, 48, SinFre[3], 9, 16, 1);
// OLED_ShowString(110,0,"Hz",16,1);
// OLED_ShowString(110,16,"Hz",16,1);
// OLED_ShowString(110,32,"Hz",16,1);
// OLED_ShowString(110,48,"Hz",16,1);
//
// OLED_ShowNum(35, 0, SinAmp[0], 4, 16, 1);
// OLED_ShowNum(35, 16, SinAmp[1], 4, 16, 1);
// OLED_ShowNum(35, 32, SinAmp[2], 4, 16, 1);
// OLED_ShowNum(35, 48, SinAmp[3], 4, 16, 1);
// OLED_ShowString(70,0,"A",16,1);
// OLED_ShowString(70,16,"A",16,1);
// OLED_ShowString(70,32,"A",16,1);
// OLED_ShowString(70,48,"A",16,1);
OLED_ShowNum(35, 0, SinPhr[0], 5, 16, 1);
OLED_ShowNum(35, 16, SinPhr[1], 5, 16, 1);
OLED_ShowNum(35, 32, SinPhr[2], 5, 16, 1);
OLED_ShowNum(35, 48, SinPhr[3], 5, 16, 1);
OLED_ShowString(80,0,"P",16,1);
OLED_ShowString(80,16,"P",16,1);
OLED_ShowString(80,32,"P",16,1);
OLED_ShowString(80,48,"P",16,1);
OLED_Refresh();
}
}
效果展示
七、注意事项与常见问题
注意事项
(1) 模块电流消耗较大,供电电源需要有一定余量,建议使用5V 1A以上供电。
(2)由于模块是高精度器件,为了避免不必要的干扰,建议使用线性电源供电。
(3)输出信号建议使用SMA转BNC的线直接示波器观测效果,接触不良或劣质的线材可能导致信号衰减或者噪声过大。
(4) 由于功耗发热较大,长时间工作注意通风散热。
(5) 如需简单测试模块功能,建议搭配本店控制板使用,先给 DDS模块供电,再给控制板供电即可产生波形,长按中间键切换功能。
(6) 配送的代码仅为配套主控板使用,不提供单片机教程,额外功能需要自行开发。
常见问题
Q:AD9959模块的4个输出通道可以设置成不同的频率输出吗?
A:可以,AD9959模块的4个输出通道的频率、幅度、相位均可独立调节。
Q:模快的主频是多少?输出幅度可以调节?
A:模块的主频是输入时钟和程序一起决定的,模块可以外部输入时钟,板载默认时钟为25MHz,程序控制倍频为20倍,即默认主频为500MHz,可以通过修改输入时钟和倍频数改变主频,输入时钟必须是在20MHz-30MHz之间。输出幅度可以通过修改10位幅度寄存器控制输出幅度。需要自行修改程序哦。
Q:模块可以实现扫频么?可以产生方波信号吗?
A:模块可以实现扫频,本店提供的代码可支持扫频。需要方波的可以搭配本店高速比较器转换为方波。AD9959只能输出正弦波信号,4通道的频率幅度相位都是独立和可调的。
Q:为什么每次设置完通道间的相位差不是预设值,每次都不一样?
A:这个是写寄存器的顺序导致的,要先写频率寄存器,再写幅度寄存器,最后写相位寄存器,这样相位寄存器的值才是预设值。
Q:怎么实现幅度,相位的调节?
A:本店搭配的主控板可以实现频率幅度相位的调节,长按中间键切换到调节界面和扫频界面。