- 什么是DDS函数发生器?
- 了解功能生成器IC AD9833的工作原理
- 构建基于AD9833的函数发生器所需的组件
- 基于AD9833的函数发生器-原理图
- 基于AD9833的函数发生器-Arduino代码
- 测试基于AD9833的函数发生器
- 进一步增强
如果您是像我这样的电子发烧友,并且想要调整不同的电子电路,那么有时必须配备一个体面的函数发生器。但是拥有一个是一个问题,因为这样的基本设备可能会花费一笔巨款。建立自己的测试设备不仅更便宜,而且是提高知识水平的好方法。
因此,在本文中,我们将使用Arduino 和AD9833 DDS函数发生器模块构建一个简单的信号发生器,该信号发生器可以在输出端产生最大12 MHz的正弦波,方波和三角波。最后,我们将在示波器的帮助下测试输出频率。
以前,我们已经在基本模拟电路的帮助下构建了一个简单的正弦波发生器,一个方波发生器和一个三角波发生器。如果您正在寻找一些基本的波形发生器电路,则可以检查一下。另外,如果您想在不使用AD9833模块的情况下构建更便宜的Arduino函数发生器,则可以查看DIY Arduino波形发生器项目。
什么是DDS函数发生器?
顾名思义,函数发生器是一种可以在设置时以特定频率输出特定波形的设备。例如,假设您有一个LC滤波器要测试其输出频率响应,则可以在函数发生器的帮助下轻松地做到这一点。您所需要做的就是设置所需的输出频率和波形,然后可以上下摇动它以测试响应。这只是一个示例,随着列表的进行,您可以使用它做更多的事情。
DDS代表直接数字合成。它是一种波形发生器,它使用数模转换器(DAC)从零开始构建信号。此方法专门用于生成正弦波。但是我们使用的IC可以产生方波或三角波信号。DDS芯片内部发生的操作是数字操作,因此它可以非常快速地切换频率,也可以非常快速地从一个信号切换到另一个信号。该设备具有良好的频率分辨率和宽频谱范围。
了解功能生成器IC AD9833的工作原理
我们项目的核心是由模拟器件设计和开发的AD9833可编程波形发生器IC。它是一款低功耗可编程波形发生器,能够产生最大12 MHz的正弦波,三角波和方波。这是一个非常独特的IC,仅通过一个软件程序就可以改变输出频率和相位。它具有3线SPI接口,因此与该IC的通信变得非常简单容易。该IC的功能框图如下所示。
该IC的工作非常简单。如果我们看一下上面的功能框图,我们会发现我们有一个相位累加器,它的工作是存储从0到2π的正弦波的所有可能数字值。接下来,我们有SIN ROM,其作用是将相位信息转换为以后可以直接映射为幅度的信号。 SIN ROM使用数字相位信息作为查找表的地址,并将相位信息转换为幅度。最后,我们有一个10位数模转换器,其作用是从SIN ROM接收数字数据并将其转换为相应的模拟电压,这就是我们从输出中获得的电压。在输出处,我们还有一个开关,只需少量软件代码即可打开或关闭。我们将在本文后面讨论。您在上面看到的详细信息是IC内部发生情况的精简版本,您在上面看到的大多数详细信息摘自AD9833数据手册,您也可以查看它以获得更多信息。
构建基于AD9833的函数发生器所需的组件
下面列出了构建基于AD9833的函数发生器所需的组件,我们使用非常通用的组件设计了该电路,这使得复制过程非常容易。
- Arduino纳米-1
- AD9833 DDS函数发生器-1
- 128 X 64 OLED显示屏-1
- 通用旋转编码器-1
- 直流桶式千斤顶-1
- LM7809稳压器-1
- 470uF电容器-1
- 220uF电容器-1
- 104pF电容器-1
- 10K电阻器-6
- 触觉开关-4
- 螺丝端子5.04mm-1
- 女头-1
- 12V电源-1
基于AD9833的函数发生器-原理图
AD9833和基于Arduino的函数发生器的完整电路图如下所示。
我们将结合使用AD9833和Arduino生成所需的频率。在本节中,我们将在原理图的帮助下解释所有细节。让我给您简要介绍一下电路的状况。让我们从AD9833模块开始。 AD9833模块是函数发生器模块,根据原理图与Arduino连接。为了给电路供电,我们使用了带有适当去耦电容器的LM7809稳压器IC,这是必要的,因为电源噪声会干扰输出信号,从而导致不良的输出。与往常一样,Arduino是该项目的大脑。为了显示设定的频率和其他有价值的信息,我们连接了一个128 X 64 OLED显示模块。为了改变频率范围,我们使用了三个开关。第一个将频率设置为Hz,第二个将输出频率设置为KHz,第三个将频率设置为MHz,我们还有另一个按钮可用于启用或禁用输出。最后,我们有旋转编码器,并且必须附加一些上拉电阻,否则这些开关将无法工作,因为我们正在检查池化方法上的按钮按下事件。旋转编码器用于更改频率,旋转编码器内部的触觉开关用于选择设置的波形。
基于AD9833的函数发生器-Arduino代码
该项目中使用的完整代码可以在页面底部找到。添加所需的头文件和源文件后,您应该能够直接编译Arduino文件。您可以从下面给出的链接下载ad9833 Arduino库和其他库,也可以使用板管理器方法安装该库。
- 下载Bill Williams的AD9833库
- 下载Adafruit的SSD1306 OLED库
- 下载Adafruit GFX库
ino 中的代码说明 。 文件 如下。首先,我们首先包含所有必需的库。首先是AD9833 DDS模块的库,然后是OLED的库,我们的某些计算需要数学库。
#include // AD9833模块的库#include
接下来,我们为按钮,开关,旋转编码器和OLED定义所有必需的输入和输出引脚。
#define SCREEN_WIDATA_PINH 128 // OLED显示宽度以像素为单位#define SCREEN_HEIGHT 64 // OLED显示高度以像素为单位#define SET_FREQUENCY_HZ A2 //按钮以Hz设置频率#define SET_FREQUENCY_KHZ A3 //按钮以Khz设置频率#define SET_FREQUENC A6 //设置频率(Mhz)的按钮#define ENABLE_DISABLE_OUTPUT_PIN A7 //启用/禁用输出的按钮#define FNC_PIN 4 // AD9833模块所需的Fsync #define CLK_PIN 8 //编码器的时钟引脚#define DATA_PIN 7 / /编码器的数据引脚#define BTN_PIN 9 //编码器上的内部按钮
此后,我们定义此代码中所需的所有必需变量。首先,我们定义一个整数变量计数器,该计数器将存储旋转编码器值。接下来的两个变量clockPin和clockPinState存储了解编码器方向所需的引脚状态。我们有一个时间变量,用于保存当前的计时器值,该变量用于按键去抖。接下来,我们有一个无符号的长变量moduleFrequency,它保存将要应用的计算出的频率。接下来,我们有去抖动延迟。可以根据需要调整此延迟。接下来,我们有三个布尔变量set_frequency_hz,set_frequency_Khz和 set_frequency_Mhz 这三个变量用于确定模块的当前设置。我们将在本文后面详细讨论它。接下来,我们有一个变量来存储输出波形的状态,默认输出波形是正弦波。最后,我们有编码器_btn_count变量,该变量保存用于设置输出波形的编码器按钮计数。
整数计数器= 1; //如果将旋转编码器设置为int clockPin,则此Counter值将增加或减少; //旋转编码器使用的引脚状态占位符int clockPinState; //旋转编码器使用的引脚状态占位符,无符号长时= 0; //用于消除未签名的long moduleFrequency; //用于设置输出频率长反跳= 220; //防抖延迟bool btn_state; //用于启用AD98333模块的禁用输出bool set_frequency_hz = 1; // AD9833模块的默认频率布尔值set_frequency_khz; bool set_frequency_mhz;字符串waveSelect =“ SIN”; //模块的启动波形int encoder_btn_count = 0; //用于检查是否按下编码器按钮下一步,我们有两个对象,一个用于OLED显示屏,另一个用于AD9833模块。Adafruit_SSD1306显示(SCREEN_WIDATA_PINH,SCREEN_HEIGHT,&Wire,-1); AD9833 gen(FNC_PIN);
接下来,我们有了setup()函数,在该设置函数中,我们首先启用串行调试功能。我们借助begin()方法初始化AD9833模块。接下来,我们将所有分配的旋转编码器引脚设置为输入。并且我们将时钟引脚的值存储在clockPinState变量中,这是旋转编码器的必要步骤。
接下来,我们将所有按钮引脚设置为输入,并借助 display.begin() 方法启用OLED显示,并且还使用 if语句 检查是否有任何错误。完成后,我们清除显示并打印启动初始屏幕,添加2秒的延迟,这也是初始屏幕的延迟,最后,我们调用update_display()函数来清除屏幕并更新再次显示。 update_display() 方法的详细信息将在本文后面讨论。
void setup(){Serial.begin(9600); //启用串行@ 9600波特gen.Begin(); //这必须是声明AD9833对象pinMode(CLK_PIN,INPUT);之后的第一个命令。 //将Pins设置为输入pinMode(DATA_PIN,INPUT); pinMode(BTN_PIN,INPUT_PULLUP); clockPinState = digitalRead(CLK_PIN); pinMode(SET_FREQUENCY_HZ,INPUT); //将引脚设置为输入pinMode(SET_FREQUENCY_KHZ,INPUT); pinMode(SET_FREQUENCY_MHZ,输入); pinMode(ENABLE_DISABLE_OUTPUT_PIN,INPUT); if(!display.begin(SSD1306_SWITCHCAPVCC,0x3C)){// 128x64 Serial.println(F(“ SSD1306分配失败”))的地址0x3D为(;;); } display.clearDisplay(); //清除屏幕display.setTextSize(2); //设置文字大小display.setTextColor(WHITE); //设置LCD彩色显示器。setCursor(30,0); //设置光标位置display.println(“ AD9833”); //打印此文本显示。setCursor(17,20); //设置光标位置display.println(“ Function”); //打印此文本display.setCursor(13,40); //设置光标位置display.println(“ Generator”); //打印此文本display.display(); //更新显示延迟(2000); //延迟2秒SEC update_display(); //调用update_display函数}
接下来,我们有了loop()函数,所有主要功能都写在loop部分中。
首先,我们读取旋转编码器的Clock引脚,并将其存储在我们之前声明的clockPin变量中。接下来,在 if 语句中,我们检查该引脚的先前值和该引脚的当前值是否相似,并且还检查该引脚的当前值。如果都为真,则检查数据引脚,如果为真,则表示编码器正在逆时针旋转,并借助counter--命令减小计数器值。否则,我们用counter ++命令增加计数器的值。最后,我们把另外 ,如果 语句设置最小值为1。接下来,我们更新了clockPinState当前clockPin将来使用的价值。
void loop(){clockPin = digitalRead(CLK_PIN); if(clockPin!= clockPinState && clockPin == 1){if(digitalRead(DATA_PIN)!= clockPin){计数器-; }否则{计数器++; //编码器正在旋转CW,因此应递增}如果(counter <1)counter = 1; Serial.println(counter); update_display(); }
接下来,我们有用于检测按钮按下的代码。在本节中,我们借助一些嵌套的if语句if(digitalRead(BTN_PIN)== LOW && millis()-time> denounce),检测到编码器内部的按钮, 在此语句中,我们首先检查按钮是否引脚是否为低电平,如果为低电平,则按下。然后,再次使用去抖延迟检查计时器值,如果两个语句都为真,则如果成功,则将其声明为成功的按钮按下操作,如果这样我们就增加了encoder_btn_count值。接下来,我们声明另一个if语句以将最大计数器值设置为2,我们需要它,因为我们使用它来设置输出波形。连续的三个if语句执行该操作,如果该值为零,则选择正弦波形;如果为1,则为方波;如果值为2,则为三角波。在所有这三个if语句中,我们使用update_display() 函数更新显示 。最后,我们使用当前计时器计数器值更新时间变量。
///如果检测到LOW信号,则如果(digitalRead(BTN_PIN)== LOW && millis()-time> debounce){ //如果(encoder_btn_count> 2)则增加值// //如果值大于2,则将其重置为0 {coder_btn_count = 0; } if(encoder_btn_count == 0){//如果值为0,则选择正弦波waveSelect =“ SIN”; //使用sin值更新字符串变量update_display(); // //更新显示} if(encoder_btn_count == 1){//如果选择的值为1方波waveSelect =“ SQR”; //使用SQR值update_display()更新字符串变量。 // //更新显示} if(encoder_btn_count == 2){//如果值为1,则选择Triangular wave waveSelect =“ TRI”; //使用TRI值更新字符串变量update_display();//更新显示内容} time = millis(); //更新时间变量}
接下来,我们定义设置所有具有反跳延迟的按钮所需的所有必要代码。当按钮连接到Arduino的模拟引脚时,如果模拟读取值达到30以下,我们将使用模拟读取命令来识别按钮按下,然后我们检测到按钮按下成功,然后等待200 ms检查它是实际按下按钮还是仅发出噪音。如果此语句为真,则为布尔变量分配用于设置函数发生器的Hz,Khz和Mhz值的值。接下来,我们更新显示并更新时间变量。我们对与Arduino连接的所有四个按钮执行此操作。
if(analogRead(SET_FREQUENCY_HZ)<30 && millis()-时间>去抖动){set_frequency_hz = 1; //更新布尔值set_frequency_khz = 0; set_frequency_mhz = 0; update_display(); //更新显示时间= millis(); //更新时间变量} if(analogRead(SET_FREQUENCY_KHZ)<30 && millis()-time> debounce){set_frequency_hz = 0; //更新布尔值set_frequency_khz = 1; set_frequency_mhz = 0; moduleFrequency =计数器* 1000; update_display(); //更新显示时间= millis(); //更新时间变量}如果(analogRead(SET_FREQUENCY_MHZ)<30 && millis()-time> debounce){//检查模拟引脚的去抖动延迟set_frequency_hz = 0; //更新布尔值set_frequency_khz = 0; set_frequency_mhz = 1; moduleFrequency =计数器* 1000000; update_display();// //更新显示时间= millis(); //更新时间变量} if(analogRead(ENABLE_DISABLE_OUTPUT_PIN)<30 && millis()-time> debounce){//用去抖动延迟btn_state =检查模拟引脚! btn_state; //反转按钮状态gen.EnableOutput(btn_state); //根据按钮状态启用/禁用函数发生器的输出update_display(); //更新显示时间= millis(); //更新时间变量}}//更新时间变量}}//更新时间变量}}
最后,我们有update_display()函数。在此功能中,我们所做的不仅是更新此显示器,还因为它无法在OLED中更新显示器的某些部分而做了很多事情。要更新它,您必须使用新值重新粉刷它。这使编码过程变得更加困难。
在此功能内部,我们先清除显示内容。接下来,我们设置所需的文本大小。此后,我们设置光标并使用display.println(“ Function Function”);打印函数发生器。命令。再次使用display.setCursor( 0,20 ) 函数将文本大小设置为2,并将光标设置为(0,20) 。
在这里,我们可以打印出什么波形的信息。
display.clearDisplay(); //首先清除显示内容display.setTextSize(1); //设置文字大小display.setCursor(10,0); //设置光标位置display.println(“ Function Generator”); //打印文本display.setTextSize(2); //设置文本大小display.setCursor(0,20); //设置光标位置
接下来,我们检查布尔变量的频率详细信息,并更新moduleFrequency变量中的值。我们对Hz,kHz和MHz值执行此操作。接下来,我们检查waveSelect变量并确定选择了哪个波。现在,我们有了用于设置波形类型和频率的值。
if(set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0){//检查是否按下了以Hz为单位的频率设置模块。 // //用当前计数器值更新moduleFrequency变量}如果(set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0){//检查是否按下了以KHz设置频率的按钮moduleFrequency = counter * 1000; //使用当前计数器值更新moduleFrequency变量,但我们将1000乘以在KHZ上进行设置}如果(set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1){//检查是否按下了以MHz设置频率的按钮=计数器* 1000000;如果(moduleFrequency> 12000000){moduleFrequency = 12000000;//不要让频率偏高12Mhz counter = 12; }} if(waveSelect ==“ SIN”){//选择了正弦波display.println(“ SIN”); gen.ApplySignal(SINE_WAVE,REG0,moduleFrequency); Serial.println(moduleFrequency); }如果(waveSelect ==“ SQR”){//选择了Sqr wave display.println(“ SQR”); gen.ApplySignal(SQUARE_WAVE,REG0,moduleFrequency); Serial.println(moduleFrequency); }如果(waveSelect ==“ TRI”){//选择了Tri wave display.println(“ TRI”); gen.ApplySignal(TRIANGLE_WAVE,REG0,moduleFrequency); //更新AD9833模块。 Serial.println(moduleFrequency); }}如果(waveSelect ==“ SQR”){//选择了Sqr wave display.println(“ SQR”); gen.ApplySignal(SQUARE_WAVE,REG0,moduleFrequency); Serial.println(moduleFrequency); }如果(waveSelect ==“ TRI”){//选择了Tri wave display.println(“ TRI”); gen.ApplySignal(TRIANGLE_WAVE,REG0,moduleFrequency); //更新AD9833模块。 Serial.println(moduleFrequency); }}如果(waveSelect ==“ SQR”){//选择了Sqr wave display.println(“ SQR”); gen.ApplySignal(SQUARE_WAVE,REG0,moduleFrequency); Serial.println(moduleFrequency); }如果(waveSelect ==“ TRI”){//选择了Tri wave display.println(“ TRI”); gen.ApplySignal(TRIANGLE_WAVE,REG0,moduleFrequency); //更新AD9833模块。 Serial.println(moduleFrequency); }
我们再次设置光标并更新计数器值。再次,我们检查布尔值以更新显示器上的频率范围,我们必须这样做,因为OLED的工作原理非常奇怪。
display.setCursor(45,20); display.println(counter); //在显示屏上打印计数器信息。 if(set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0){display.setCursor(90,20); display.println(“ Hz”); //在显示器display.display();上显示Hz // //当所有set都更新显示时} if(set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0){display.setCursor(90,20); display.println(“ Khz”); display.display(); // //当所有set都更新显示时} if(set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1){display.setCursor(90,20); display.println(“ Mhz”); display.display(); //当所有设置都更新显示时}
接下来,我们检查按钮的按下变量以将输出打印输出到OLED。再次由于OLED模块而需要这样做。
如果(btn_state){display.setTextSize(1); display.setCursor(65,45); display.print(“ Output ON”); //将输出打印到显示器上display.display(); display.setTextSize(2); } else {display.setTextSize(1); display.setCursor(65,45); display.print(“ Output OFF”); //将输出打印到显示器上display.display(); display.setTextSize(2); }
这标志着我们编码过程的结束。如果您对此感到困惑,则可以检查代码中的注释以进一步了解。
测试基于AD9833的函数发生器
要测试电路,请使用上述设置。如您所见,我们已将12V DC电源适配器连接至DC桶式插孔,并将Hantek示波器连接至电路的输出。我们还将示波器连接到笔记本电脑以可视化并测量输出频率。
完成此操作后,我们将在旋转编码器的帮助下将输出频率设置为5Khz,然后测试输出正弦波,可以肯定的是,在输出端它是5Khz正弦波。
接下来,我们将输出波形更改为三角波,但频率保持不变,输出波形如下所示。
然后我们将输出更改为方波并观察输出,这是一个完美的方波。
我们还更改了频率范围并测试了输出,效果很好。
进一步增强
该电路仅是概念证明,还需要进一步增强。首先,我们需要高质量的PCB和一些高质量的BNC连接器作为输出,否则我们将无法获得更高的频率。模块的幅度非常低,因此,为了增强这一点,我们需要一些运算放大器电路来放大输出电压。可以连接一个电位计以改变输出幅度。可以连接一个补偿信号的开关。这也是必备功能。而且,由于有一些小问题,代码需要大量改进。最后,需要更改OLED显示器,否则无法编写易于理解的代码。
这标志着本教程的结束,希望您喜欢这篇文章并学到一些新东西。如果您对本文有任何疑问,可以将其留在下面的评论部分,也可以使用我们的电子论坛。