锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

2021年电赛 | 手把手带你玩转DDS

时间:2022-10-29 15:00:01 31k6电阻29k4贴片电阻

摘要:原定于7月28日发布的设备清单将于26日提前2天发布。我觉得我们现在应该猜测,与其猜题,不如静下心来做题。。盲目猜测问题是没有必要的。熟悉相关设备尤为重要。不要到处去水群聊天。听风是雨。如果你能猜到这个话题,你只能说这个话题很差,我们可以猜到。

既然你猜不到,就不要浪费时间。相反,充分利用这些时间,继续完成你手中的事情,购买你应该购买的组件。如果你买得晚,它会很贵。购买后,每个组件应尽可能熟悉使用,并准备相关程序。

1、电赛清单

事实上,国赛年的电赛仪器设备和主要部件清单基本相同,只有很小的变化。2019年国赛年也有DDS今年还有模块DDS模块。

2、什么是DDS

DDS直接数字频率综合器 DDS(Direct Digital Synthesizer),它实际上是一种分频器:通过编程频率控制字来分频系统时钟(SYSTEM CLOCK)产生所需的频率。DDS一方面有两个突出的特点,DDS在数字域工作,一旦频率控制字更新,输出频率相应变化,跳频率高;另一方面,由于频率控制字的宽度(48bit 或更高),频率分辨率高。说人话:可以理解为信号源,即信号发生器。

高配版DDS模块

电赛肯定不会让你做这样的仪器作为信号源,用你的DDS代替它。

3、DDS工作原理

DDS主要分成3 部分:相位累加器 , 相位幅度转换 , 数模转换器(DAC)。

  • 相位累加器

正弦波,虽然其幅度不是线性的,但其相位是线性的。DDS 利用这一特性产生正弦信号。DDS频率控制字的位数N,把 360° 平均分为2N次方等份。

  • 相位幅度转换

通过相位累加器,我们得到了合成Fout 相位信息对应于频率,然后相位幅度转换器 0°~360°相位转换为相应相位的幅度值。例如,当DDS选择为2Vp-p的输出时,45°相应的幅度值为 0.707V,该值以二进制的形式输入DAC。通过查表完成相位到幅度的转换。

  • DAC输出

二进制数字信号的代表范围被发送到DAC并将其转换为模拟信号输出。DAC位数不影响输出频率的分辨率。输出频率的分辨率由频率控制字的位数决定

4.怎样做一个?DDS

注意电赛清单说的是:DDS芯片或模块。也就是说,你可以自己买芯片设计电路板,也可以自己买DDS模块。当然,如果你有能力直接购买芯片,自己画板子,你就可以这样做DDS一定要直接买DDS模块的学生更有优势。当然,如果你觉得很难买一个DDS模块吧!

聪明如我

如何选择DDS

如何选择哪一个?DDS芯片仍然取决于你自己的预算和你的需求。今天主要讲的是DDS安富莱家的模块AD9833这一款DDS模块。至于为什么选择一个,因为19年电赛买了这个,价格还是便宜的,电路和编程都比较简单。强调这不是广告!

5、AD9833简介

AD9833是ADI公司生产的低功耗可编程波形发生器可产生正弦波、三角波和方波输出。波形发生器广泛应用于各种测量、激励和时域响应领域,AD9833不需要外部元件,输出频率和相位可通过软件编程,易于调节,频率寄存器为28位,主频时钟为25MHz时,精度为0.1Hz,主频时钟为1MHz精度可达0.004Hz。

数据可以通过三个串行接口写入AD983,这三个串口的最高工作频率可达40MHz,易于与DSP兼容各种主流微控制器。AD9833的工作电压范围为2.3V-5.5V。

AD9833还具有休眠功能,例如,如果使用,可以使未使用的部分休眠,减少这部分的电流损失AD作为时钟源,9833输出可以让DAC为了减少功耗,休眠电路采用10引脚MSOP型面贴片封装,体积小。

AD9833特点

  • 数字编程频率和相位数字编程

  • 工作电压为3V功耗只有20mW

  • 输出频率范围为OHz-12.5MHz

  • 28位频率寄存器(25位)Mz参考时钟下,精度为0.1Hz)

  • 正弦波、三角波、方波输出

  • 没有外部元件

  • 3线SPI接口

  • 温度范围为-40℃- 105℃

总结一下:通过这个模块和单片机SPI通信方式,模块的数据频率和相位可以通过操作芯片内行调整。可输出的频率范围为0-12.5MHZ。可输出正弦波、三角波和方波。

AD9833模块电路图

波形电路
操作放大输出电路

为了让大家更好地理解,我直接截取了成品模块原理图。

可以看到AD9833是全集成在一起DDS,高达12个外部参考时钟,1个低精度电阻器和一个解耦电容器.5Mz的正弦波。AD933的核心是28位相位累加器,由加法器和相位寄存器组成。每1小时,相位寄存器逐步增加,相位寄存器输入正弦查询表地址。正弦查询表包含正弦波一个周期的数字幅度信息,每个地址对应正弦波0°-360°范围内的一个相位点。

下图来自AD9833的数据手册,可以看到每个引脚的功能说明都非常详细,再配合上图的电路原理图就可以一目了然了!

管脚功能描述

接下来是单片机如何连接芯片的引脚,以及如何编写驱动代码。

6、AD9833驱动代码

一般来说,当你在网上购买模块时,卖家会给你实例代码。也许实例代码不同于您使用的单片机型号。但一般的想法框架是相同的。以下是安富莱的AD9833代码为例。

说明:他的平台是STM32F也许你用的407F103系列或者MSP430,但是驱动代码都是。您可以完全使用驱动代码.c和.h将文件导入您的项目。

功能描述

AD9833有三条串行接口线SPI、QSPI、DSP接口标准兼容,串口时钟SCLK数据以16位的方式加载到设备上,FSYNC引脚是选择能引脚、电平触发、低电平有效的片材。串行数据传输时,FSYNC引脚必须置低,要注意 FSYNC有效到SCLK建立下降边缘的最小值。FSYNC放低后,16个SCLK沿数据发送下降AD第16个输入移位寄存器为9833SCLK的下降沿FSYNC可以放高,但要注意SCLK下降沿到FSYC数据保持上升边的最小和最大值。当然,也可以 FSYNC在低电平时,连续加载多个16位数据,仅在最后一个数据的第16位SCLK下降边缘的时间 FSYNC最后要注意的是,写数据的时候SCLK时钟是高低电平脉冲,但在 FSYNC(即将开始写数据时),SCLK必须为高电平(注意t参数11)。

当AD9833初始化时,为了避免DAC产生虚假输出,RESET必须置为1(RESET不复位频率、相位和控制寄存器),直到配置完成,需要输出 RESET置为0;RESET为0后的8-9个MCLK时钟周期可以在DAC波形观察到输出端。

AD9833将数据写入输出端以获得响应。中间有一定的响应时间。每次将新数据加载到频率或相位寄存器中,将有7-8个MCIK时钟周期延迟后,输出端的波形会发生变化,有一个MCLK由于数据加载到目的寄存器时,时钟周期的不确定性,MCLK上升沿位置不确定。

既然模块要连接到单片机,首先要确定使用哪些引脚,因为它们通过三线SPI通信方式。

初始化GPIO

*定义GPIO端口*/ #defineRCC_SCLKRCC_AHB1Periph_GPIOB #definePORT_SCLKGPIOB #definePIN_SCLKGPIO_Pin_3  #defineRCC_SDATARCC_AHB1Periph_GPIOB #definePORT_SDATAGPIB
#define PIN_SDATA GPIO_Pin_5

/* 片选 */
#define RCC_FSYNC  RCC_AHB1Periph_GPIOF
#define PORT_FSYNC GPIOF
#define PIN_FSYNC GPIO_Pin_7

/* 定义口线置0和置1的宏 */
#define FSYNC_0() PORT_FSYNC->BSRRH = PIN_FSYNC
#define FSYNC_1() PORT_FSYNC->BSRRL = PIN_FSYNC

#define SCLK_0() PORT_SCLK->BSRRH = PIN_SCLK
#define SCLK_1() PORT_SCLK->BSRRL = PIN_SCLK

#define SDATA_0()  PORT_SDATA->BSRRH = PIN_SDATA
#define SDATA_1()  PORT_SDATA->BSRRL = PIN_SDATA

void bsp_InitAD9833(void)
{
 GPIO_InitTypeDef GPIO_InitStructure;

 FSYNC_1(); /* FSYNC = 1 */

 /* 打开GPIO时钟 */
 RCC_AHB1PeriphClockCmd(RCC_SCLK | RCC_SDATA | RCC_FSYNC, ENABLE);

 /* 配置几个推挽输出IO */
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  /* 设为输出口 */
 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  /* 设为推挽模式 */
 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; /* 上下拉电阻不使能 */
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; /* IO口最大速度 */

 GPIO_InitStructure.GPIO_Pin = PIN_SCLK;
 GPIO_Init(PORT_SCLK, &GPIO_InitStructure);

 GPIO_InitStructure.GPIO_Pin = PIN_SDATA;
 GPIO_Init(PORT_SDATA, &GPIO_InitStructure);

 GPIO_InitStructure.GPIO_Pin = PIN_FSYNC;
 GPIO_Init(PORT_FSYNC, &GPIO_InitStructure);
}

这几句话大家应该不陌生,首先是使用宏定义来定义使用的GPIO口的时钟、端口和输出的高低电平,然后就是最基本的初始化相关的GPIO口了,这里使用的是PB3、PB5、PF3作为控制引脚,你需要根据你的MCU选择合适的引脚进行控制。

写数据

/*
********************************************************
* 函 数 名: AD9833_Write_16Bits      
* 功能说明: 向SPI总线发送16个bit数据   发送控制字
* 形    参: _cmd : 数据
* 返 回 值: 无
*******************************************************
*/
void AD9833_Write_16Bits(uint16_t _cmd)
{
 uint8_t i;
 SCLK_1(); //在时钟上升沿下操作
 FSYNC_0(); //片选打开
 /* AD9833  SCLK时钟高达40M,因此可以不延迟 */
 for(i = 0; i < 16; i++)
 {
  if (_cmd & 0x8000)
  {
   SDATA_1();
  }
  else
  {
   SDATA_0();
  }
  SCLK_0();
  _cmd <<= 1;
  SCLK_1();
 } 
 FSYNC_1();//片选关闭
}

FSYNC置低后,在16个SCLK的下降沿数据被送到AD9833的输入移位寄存器,采用for循环将数据发送出去。

选择输出波形

void AD9833_SelectWave(uint8_t _Type) 
{
 FSYNC_1();  //宏定义
 SCLK_1();
 if(_Type == 0)
 {
  AD9833_Write_16Bits(0x2028); /*频率寄存器输出方波*/
 }
 else if(_Type == 1)
 {
  AD9833_Write_16Bits(0x2002); /*频率寄存器输出三角波*/
 }
 else if(_Type == 2)
 {
  AD9833_Write_16Bits(0x2000); /*频率寄存器输出正弦波*/
 }
 else if(_Type == 3)
 {
  AD9833_Write_16Bits(0x00C0); /*无输出*/
 }
}

这里根据输入的形参来选择输出的波形,至于这里为什么写0x2002就输出三角波,写0x2000就输出正弦波,那就要看芯片的数据手册对于寄存器这一张章节的说明了。

芯片手册第九页
芯片手册第十页

看起来是不是感觉无从下手,但是我们不需要了解这些,因为具体要输出哪种波形配置什么样的参数已经告诉你了,直接用就可以。

设置频率值

接下来就是编写配置输出各种频率的函数了,具体操作如下:

void AD9833_SetFreq(uint32_t _freq)
{
 uint32_t freq;
 uint16_t lsb_14bit;
 uint16_t msb_14bit;
 uint8_t freq_number = 0;
 freq = (uint32_t)(268435456.0 / AD9833_SYSTEM_CLOCK * _freq);
 lsb_14bit = (uint16_t)freq;
 msb_14bit = (uint16_t)(freq >> 14);
 if(freq_number == FREQ_0)
 {
  lsb_14bit &= ~(1U<<15);//0111 1111 1111 1111 先把第15位清0,其他位不变
  lsb_14bit |= 1<<14;    //0100 0000 0000 0000 再把第14位置1,其他位不变 结果就是01xx xxxx xxxx xxxx
  msb_14bit &= ~(1U<<15); //同上
  msb_14bit |= 1<<14;
 }
 else
 {
  lsb_14bit &= ~(1<<14); //1011 1111 1111 1111 先把第14位清0,其他位不变
  lsb_14bit |= 1U<<15;   //1000 0000 0000 0000 再把第15位置1,其他位不变 结果就是10xx xxxx xxxx xxxx
  msb_14bit &= ~(1<<14); //同上
  msb_14bit |= 1U<<15;
 }
 AD9833_Write_16Bits(lsb_14bit);
 AD9833_Write_16Bits(msb_14bit);
}

这里面有一句关键的代码就是freq = (uint32_t)(268435456.0/AD9833_SYSTEM_CLOCK * _freq);结合芯片的数据手册知道。另外这里面用到了寄存器操作的置位和清0操作。如果对寄存器操作比较熟悉得小伙伴一眼就能看出来,这里就不过多解释了。

lsb_14bit &= ~(1U<<15);//0111 1111 1111 1111 先把第15位清0,其他位不变
lsb_14bit |= 1<<14;    //0100 0000 0000 0000 再把第14位置1,其他位不变 结果就是01xx xxxx xxxx xxxx

计算公式

到这里底层驱动函数已经基本完成了,接下来就是写main函数了。在主函数中我们可以增加几个按键进行调频操作。

int main(void)
{
 uint8_t ucKeyCode;
 uint8_t ucChange;
 uint32_t freq;
 bsp_Init();//先初始化各种外设,自己写
 freq = 100000;
 AD9833_SetFreq(freq);   /*频率freq单位HZ*/
 ucChange = 1;
 while (1)
 {

  if (ucChange == 1)
  {
   ucChange = 0;
   AD9833_SetFreq(freq); /*设置频率值  */   
   /* 打印当前的频率值 */
   if (freq < 1000)
   {
    printf("freq =%8dHz\r", freq); 
   }
   else if (freq >= 1000 && freq < 1000000)
   {
    printf("freq =%3d.%03dKHz\r", freq / 1000, (freq % 1000) ); 
   }
   else if (freq >= 1000000)
   {
    printf("freq =%3d.%03d %03d %dMHz\r", freq / 1000000, (freq % 100000) / 1000,
      ((freq % 1000000) / 1000) / 100,  freq % 10) ; 
   }   
  }
  /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  if (ucKeyCode != KEY_NONE)
  {
   switch (ucKeyCode)
   {
    case KEY_DOWN_K1:   /* K1键按下,输出方波 */
     AD9833_SelectWave(0); 
     ucChange = 1;
     break;     
    case KEY_DOWN_K2:   /* K2键按下,输出三角波 */ 
     AD9833_SelectWave(1);
        ucChange = 1;
     break;
    case KEY_DOWN_K3:   /* K3键按下,输出正弦波 */
     AD9833_SelectWave(2);
        ucChange = 1;
     break;    
    case KEY_DOWN_K4:   /*  K4键按下 */
     if (freq >= 1000000) /* 1MHz 以上 */
     {
      if (freq < 25000000)
      {
       freq += 1000000;
       ucChange = 1;
      }
     }
     else if (freq > 1000)  /* 1KHz 以上 */
     {
      freq += 1000;
      ucChange = 1;
     }
     else if (freq > 100)  /* 100Hz 以上 */
     {
      freq += 100;
      ucChange = 1;
     }
     else
     {
      freq += 1;
      ucChange = 1;
     }     
     break;
    case KEY_DOWN_K5:   /*  K5键按下 */
     if (freq >= 1000000) /* 1MHz 以上 */
     {
      freq -= 1000000;
      ucChange = 1;
     }
     else if (freq > 1000)  /* 1KHz 以上 */
     {
      freq -= 1000;
      ucChange = 1;
     }
     else if (freq > 100)  /* 100Hz 以上 */
     {
      freq -= 100;
      ucChange = 1;
     }
     else if (freq > 0)       /*(0,100HZ)*/
     {
      freq -= 1;
      ucChange = 1;
     }
     break;
    case KEY_DOWN_K6:   /*  K6键按下*/
     if (freq > 1000)
     {
      freq -= 100;
      ucChange = 1;
     }
     else if (freq > 100)  /* 100Hz 以上 */
     {
      freq -= 10;
      ucChange = 1;
     }
     else if (freq > 0)       /*(0,100HZ)*/
     {
      freq -= 1;
      ucChange = 1;
     }     
     break;
    case KEY_DOWN_K7:   /*  K7键按下 */
     if (freq > 1000)  /*1KHZ以上*/
     {
      freq += 100;
      ucChange = 1;
     }
     else if (freq > 100)  /* 100Hz 以上 */
     {
      freq += 10;
      ucChange = 1;
     }
     else if (freq > 0)       /*(0,100HZ)*/
     {
      freq += 1;
      ucChange = 1;
     }
     break;

    case KEY_DOWN_K8:   /*  K8键按下 */
     AD9833_SelectWave(3);
     ucChange = 1;
     break;
    default:
     /* 其它的键值不处理 */   
     break;
   }
  }
 }
}

这里面的逻辑就很简单了,按下对应的按键执行相应的操作,按键的个数与步进值可以根据自己的情况自己改写。

7、DDS在线工具

最后给大家推荐一个帮助理解和用好DDS的一个在线工具—simDDS

通过这个工具,你可以根据自己要实现的指标,来确定需要的滤波器的阶数,然后借助滤波器的设计工具、仿真软件就可以设计出满足你系统系统要求的模拟电路部分。

利用DAC的镜像,可以实现通信中的上变频功能,从而省去了本振、上变频、滤波器等复杂的模拟电路。比如你要产生一个150MHz载频的FM信号,可以使用160MHz的主时钟,产生一个10MHz的FM信号,自然就会通过DAC镜像得到一个150MHz和一个170MHz的FM信号,在150MHz处加一个带通滤波器就可以得到你需要的FM信号。电路将变得非常简单。利用这个方法,可以获得更高频率的调制信号。

电赛专栏   

关于21年电赛,这些一定要熟悉!

关于清单,有几点我觉得比较重要。

关于2021年电赛的一些想法,看到就是赚到! 

「2020年电赛」电源题详细技术方案,立即收藏! 

2020年电赛题目,命题专家权威解析! 

如何准备电赛?19年电赛经验总结! 

电赛 | 19年全国一等奖,北航学子回忆录(上)

电赛 | 19年全国一等奖,北航学子回忆录(下)

「电赛分享」电源题,省一等奖! 

2019年电赛综合测评怎么搞?国一师兄带你终极大测评!

电赛 | 电源题软件如何准备?

「2020年电赛」电源题详细技术方案,立即收藏!

-END-

大家好,我是张巧龙,一名教电子的大学老师,欢迎关注!

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章