基于STM32F103 并行驱动AD9851模块 DDS函数信号发生器输出正弦波/方波信号

45 阅读12分钟

一、AD9851模块简介

  AD9851是一款高度集成的器件,采用先进的DDS技术,再加上内部高速,高性能的D/A转换器和比较器,形成一个数字可编程频率合成器和时钟发生器功能。当参考准确的时钟源时,AD9851产生稳定的频率并进行相位可编程的数字化模拟输出正弦波。该正弦波可以直接用作频率源,或内部转换为方波敏捷时钟生成器应用程序。 AD9851的创新高速DDS内核接受32位频率调谐字,导致输出调谐分辨率约为系统时钟为180MHz时为0.04Hz。AD9851包含独特的 6XREFCLK乘法器电路消除了需要一个高速参考振荡器。 6XREFCLK乘法器对SFDR和相位噪声特性的影响最小。 特性

  • 通讯协议:SPI串行/并行
  • 180MHz时钟频率,可选6位参考时钟
  • DAC分辨率:10位
  • 相位累加器:32位
  • 输出信号:正弦波,方波。正弦波带65MHz低通滤波器,方波耦合输出
  • 输出通道:2通道差分。相位差180度,高频输出由于有滤波器,相位会偏移
  • 信号特点:正弦波无耦合输出。输出带自带直流分量,接入射频设备请加隔直器也可直接使用示波器测量
  • 输出正弦波/方波最高主频:1Hz-65M/30MHz,方波10MHz以上需要示波器输入阻抗为50欧,方波占空比可调
  • 输出幅度:正弦波1000mVpp,方波5Vpp,正弦波随频率增加幅度减小,方波随着频率增加波形变化
  • 输出阻抗:75欧

在这里插入图片描述

二、AD9851引脚说明

D0-D78 位数据输入。用于加载 32 位频率字和 8 位相位/控制字的数据端口。D7 = MSB,D0 = LSB。D7也用作 40 位串行数据字的输入引脚。
W_CLK串行时钟输入。上升沿将并行或串行频率/相位/控制字异步加载到 40 位输入寄存器中。
FQ_UD频率更新。上升沿异步传输 40 位输入寄存器的内容,以供 DDS 内核处理。当已知输入寄存器的内容仅包含有效且允许的数据时,应发出 FQ_UD。
RSET主复位引脚,高电平有效。
OUTDAC差分输出

三、功能框图和时序图分析

时序简单分析:

  1. 复位 RESET 拉高 → 相位累加器清零,默认进入 并行模式,PLL 关闭。
  2. 并行方式 每次通过 W0–W4 装载 40 位数据(8 位一组),W0 含控制字与相位字,W1–W4 为频率调谐字。 WCLK 上升沿锁存数据,最后 FQ_UD 上升沿触发更新。
  3. 串行方式 首先在并行方式下写入特殊码 xxxxx011 → 切换至串行。 串行移入 40 位数据,LSB 先行,最后 FQ_UD 上升沿更新。
  4. 延迟 频率更新延迟:约 18 个 SYSCLK 周期。 相位更新延迟:约 13 个 SYSCLK 周期。

四、并行/串行装载 40位控制字分配

  并行装载:W0 是“控制 + 相位”字;W1~W4 是 32 位频率调谐字(FTW,W1 放 MSB)。其中W0位定义为:     D7…D3:Phase[4:0](D7 是相位 MSB,D3 是相位 LSB,步进 11.25°)     D2:低功耗     D1:逻辑 0(仅在“请求进入串行模式”时临时使用;进入后必须再写回 0)     D0:6× REFCLK 倍频使能   串行装载:40-bit 一次性移入,先 32 位频率,再控制,再相位。 进入串行模式需要留意的地方:   上电复位后默认并行模式。在并行端口写入 W0 时,把 D1 置 1(仅用于“请求串行”),D0 按你是否要启用 ×6 设置;随后按手册 Figure 17 的时序切换到串行移位口。进入后务必再把并口 W0 的 D1 写回 0。串行移位是按位 LSB-first;若用 MCU 的 SPI(通常 MSB-first),要么“反相/反序”配置 SPI,要么对每个字节做逐字节位反转再发。

五、STM32F103驱动AD9851

准备工作

  STM32F103ZET6开发板、AD9851模块、OLED显示屏、EC11旋转编码器。

接线说明

STM32F103ZET6AD9851模块
PA3FQ
PA4CLK
PA6REST
PC0D0
PC1D1
PC2D2
PC3D3
PC4D4
PC5D5
PC6D6
PC7D7
PA0旋转编码器-A ,用于调节输出频率
PA1旋转编码器-B ,用于调节输出频率
PA2旋转编码器-S,移位调节
PB8OLED-SCL
PB9OLED-SDA
5V5V
GND共地

其中,当使用串行驱动时,DO、D1、D2脚需输入(110)固定电平,D3-6可以悬空不接,D7用作40位串行数据输入引脚。

示例代码

AD9851.c

//***************************************************//
//函数1:           ad9851_reset()                    //
//函数2:           ad9851_reset_serial()             //
//函数3:           ad9851_wr_parrel(unsigned char w0,double frequence)//
//函数4:           ad9851_wr_serial(unsigned char w0,double frequence)//
//***************************************************//
//                  子程序说明                       //
//***************************************************//
//函数1:  ad9851_reset()
//        复位ad9851,之后为并口写入模式        
//函数2:  ad9851_reset_serial()
//        复位ad9851,之后为串口写入模式
//函数3:  ad9851_wr_parrel(unsigned char w0,double frequence)
//        并口写ad9851数据,w0为ad9851中w0的数据,frequence
//        为写入的频率	
//函数4:  ad9851_wr_serial(unsigned char w0,double frequence)
//        串口写ad9851数据,w0为ad9851中w0的数据,frequence
//        为写入的频率	
//需定义的位:
         //ad9851_w_clk    ;
         //ad9851_fq_up    ;
         //ad9851_rest     ;
         //ad9851_bit_data ;
//例:
         //sbit ad9851_w_clk    =P2^2;
         //sbit ad9851_fq_up    =P2^1;
         //sbit ad9851_rest     =P2^0;
         //sbit ad9851_bit_data =P1^7;
//***************************************************//
//                 写数据说明                        //
//***************************************************//
//写数据例:
//       ad9851_reset()
//       wr_lcd02_data(unsigned char x)
//       ad9851_wr_parrel(0x01,1000)
//       ad9851_wr_serial(0x01,1000)
//***************************************************//
//---------------------------------------------------//
//                   程序                            //
//---------------------------------------------------//
# include <AD9851.h>
# include <stdio.h>

u8 AD9851_FD=0x00; //倍频数
void (*_AD9851_Setfq)(u8 w0,double frequence);
//P1为8位数据口
//***************************************************//
//              ad9851复位(并口模式)                 //
//---------------------------------------------------//
void ad9851_reset()
{
ad9851_w_clk=0;
ad9851_fq_up=0;
//rest信号
ad9851_rest=0;
ad9851_rest=1;
ad9851_rest=0;
}
//***************************************************//
//              ad9851复位(口模式)                 //
//---------------------------------------------------//
void ad9851_reset_serial()
{
ad9851_w_clk=0;
ad9851_fq_up=0;
//rest信号
ad9851_rest=0;
ad9851_rest=1;
ad9851_rest=0;
//w_clk信号
ad9851_w_clk=0;
ad9851_w_clk=1;
ad9851_w_clk=0;
//fq_up信号
ad9851_fq_up=0;
ad9851_fq_up=1;
ad9851_fq_up=0;
}
//***************************************************//
//          向ad9851中写命令与数据(并口)             //
//---------------------------------------------------//
void ad9851_wr_parrel(u8 w0,double frequence)
{
u32 w;
long int y;
double x;
//计算频率的HEX值
x=4294967295/180;//适合180M晶振/180为最终时钟频率(或30M六倍频)
//如果时钟频率不为180MHZ,修改该处的频率值,单位MHz !!!
frequence=frequence/1000000;
frequence=frequence*x;
y=frequence;
//写w0数据
w=w0;   
AD9851_DataBus=w|(w^0xff)<<16;     //w0
ad9851_w_clk=1;
ad9851_w_clk=0;
//写w1数据
w=(y>>24);
AD9851_DataBus=w|(w^0xff)<<16;     //w0
ad9851_w_clk=1;
ad9851_w_clk=0;
//写w2数据
w=(y>>16);
AD9851_DataBus=w|(w^0xff)<<16;     //w0
ad9851_w_clk=1;
ad9851_w_clk=0;
//写w3数据
w=(y>>8);
AD9851_DataBus=w|(w^0xff)<<16;     //w0
ad9851_w_clk=1;
ad9851_w_clk=0;
//写w4数据
w=(y>>=0);
AD9851_DataBus=w|(w^0xff)<<16;     //w0
ad9851_w_clk=1;
ad9851_w_clk=0;
//移入始能
ad9851_fq_up=1;
ad9851_fq_up=0;
}
//***************************************************//
//          向ad9851中写命令与数据(串口)             //
//---------------------------------------------------//
void ad9851_wr_serial(u8 w0,double frequence)
{
unsigned char i,w;
long int y;
double x;
//计算频率的HEX值
x=4294967295/125;//适合180M晶振/180为最终时钟频率(或30M六倍频)
//如果时钟频率不为180MHZ,修改该处的频率值,单位MHz  !!!
frequence=frequence/1000000;
frequence=frequence*x;
y=frequence;
//写w4数据
w=(y>>=0);
for(i=0;i<8;i++)
{
ad9851_bit_data=(w>>i)&0x01;
ad9851_w_clk=1;
ad9851_w_clk=0;
}
//写w3数据
w=(y>>8);
for(i=0;i<8;i++)
{
ad9851_bit_data=(w>>i)&0x01;
ad9851_w_clk=1;
ad9851_w_clk=0;
}
//写w2数据
w=(y>>16);
for(i=0;i<8;i++)
{
ad9851_bit_data=(w>>i)&0x01;
ad9851_w_clk=1;
ad9851_w_clk=0;
}
//写w1数据
w=(y>>24);
for(i=0;i<8;i++)
{
ad9851_bit_data=(w>>i)&0x01;
ad9851_w_clk=1;
ad9851_w_clk=0;
}
//写w0数据
w=w0;   
for(i=0;i<8;i++)
{
ad9851_bit_data=(w>>i)&0x01;
ad9851_w_clk=1;
ad9851_w_clk=0;
}
//移入始能
ad9851_fq_up=1;
ad9851_fq_up=0;
}
void AD9851_IO_Init(void)
{
   GPIO_InitTypeDef GPIO_InitStructure ; 
	
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC, ENABLE);	 //使能PB,PE端口时钟

	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 ; 
	GPIO_Init(GPIOC ,&GPIO_InitStructure) ;
	
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_3|GPIO_Pin_4| GPIO_Pin_6; 
	GPIO_Init(GPIOA ,&GPIO_InitStructure) ;

}
void AD9851_Setfq(double fq)
{
	if(ad9851_ad9850) 
	{
		fq *= 1.44;
		AD9851_FD = 0;
	}
	_AD9851_Setfq(AD9851_FD,fq);
}
//***************************************************//
//                   测试程序1000Hz                  //
//---------------------------------------------------//
//输入:mode  ad9851_parallel 并口    ad9851_serial 串口
//FD:0:不倍频 1:2倍频
void AD9851_Init(u8 mode,u8 FD)
{
	AD9851_IO_Init();
	AD9851_DataBus=0x00|(~0x00)<<16;
	
//	ad9851_reset_serial();
//	ad9851_wr_serial(0x00,1000000);
	if(mode==ad9851_parallel){_AD9851_Setfq=ad9851_wr_parrel;ad9851_reset();}
	else {_AD9851_Setfq=ad9851_wr_serial;ad9851_reset_serial();}
	if(FD==1)AD9851_FD=0x01;
//	AD9851_Setfq(1000000);
}

main.c

#include "stm32_config.h"
#include "stdio.h"
#include "Encoder.h"
#include "OLED.h"
#include "ad9851.h"

u32 SweepMinFre = 1000;
u32 SweepMaxFre = 1000000;

void SweepFre(void)  
{
	if(SweepMinFre > SweepMaxFre) SweepMinFre = 1000;
	SweepMinFre += SweepMinFre;
	AD9851_Setfq(SweepMinFre);
}

int main(void)
{
	MY_NVIC_PriorityGroup_Config(NVIC_PriorityGroup_2);	//设置中断分组
	delay_init(72);	//初始化延时函数
	
	OLED_Init();
	Encoder_Init();
	delay_ms(300);
	
	AD9851_Init(ad9851_parallel, 1);
	AD9851_Setfq(1000);
//	ad9851_wr_serial(0,1000);
//	delay_ms(100);
	
	OLED_ShowString(40,5,"AD9851",16,1);
	OLED_ShowString(95,160,"Hz",16,1);
	OLED_Refresh();
	
	while(1)
	{	
		Encoder_Value_Update();
		Encoder_Display();
		
		uint32_t value = Encoder_GetValue();
		
		if(value > 65000000) value = 1000;
		AD9851_Setfq(value);
		OLED_ShowNum(20, 160, value, 9, 16, 1);
		OLED_Refresh();
		delay_ms(10);
		
//		SweepFre();
//		delay_ms(200);
	}
}

效果展示

注意事项和常见问题

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

常见问题
Q:AD9851模块的2个输出口是什么关系,可以设置成不同的频率输出吗?
A:AD9851模块的2个输出口不能设置成不同的频率输出。OUT1与 OUT1相位固定相差180°。

Q:模快的主频是多少?输出幅度可以调?
A:模块的主频是输入时钟和程序一起决定的,板载默认时钟为30MHz,程序控制倍频为6倍,即默认主频为180MHz。 输出幅度是固定的,没有办法程控。

Q:模块可以实现扫频么?方波占空比可调吗?
A:模块可以实现扫频。本店提供的代码可支持扫频。模块方波占空比可以调节。

Q:两通道可以独立调节吗?可以做调制吗?
A:两个通道固定相差180度,频率高了由于滤波器作用可能相移,不可独立调节。默认代码是点频信号,其他模式需要自行编程。