如果您是像我这样想要调整不同电子电路的电子爱好者,那么拥有一个像样的函数发生器有时是必不可少的。但是拥有一个是个问题,因为这样的基本设备可能要花一大笔钱。构建自己的测试设备不仅更便宜,而且是提高知识的好方法。
因此,在本文中,我们将使用Arduino和AD9833DDS函数发生器模块构建一个简单的信号发生器,它可以在输出端产生最大频率为12MHz的正弦波、方波和三角波。最后,我们将在示波器的帮助下测试输出频率。
什么是DDS函数发生器?
顾名思义,函数发生器是一种可以通过设置输出特定频率的特定波形的设备。例如,假设您有一个想要测试输出频率响应的LC滤波器,您可以在函数发生器的帮助下轻松做到这一点。您需要做的就是设置所需的输出频率和波形,然后您可以调低或调高它以测试响应。这只是一个例子,随着列表的继续,你可以用它做更多的事情。
DDS代表直接数字合成。它是一种波形发生器,使用数模转换器(DAC)从头构建信号。此方法专门用于生成正弦波。但是我们使用的IC可以产生方波或三角波信号。DDS芯片内部发生的操作是数字的,因此它可以非常快速地切换频率,也可以非常快速地从一个信号切换到另一个信号。该设备具有良好的频率分辨率和宽频谱。
了解AD9833函数发生器IC的工作原理
我们项目的核心是由模拟设备设计和开发的AD9833可编程波形发生器IC。它是一种低功耗、可编程波形发生器,能够产生最大频率为12MHz的正弦波、三角波和方波。这是一款非常独特的IC,只需一个软件程序即可改变输出频率和相位。它有一个3线SPI接口,这就是为什么与这个IC通信变得非常简单和容易的原因。该IC的功能框图如下所示。
该IC的工作非常简单。如果我们看一下上面的功能框图,我们会发现我们有一个相位累加器,它的工作是存储从0到2π的所有可能的正弦波数字值。接下来,我们有SINROM,其工作是将相位信息转换为以后可以直接映射到幅度的信息。SINROM使用数字相位信息作为查找表的地址,并将相位信息转换为幅度。最后,我们有一个10位数模转换器,它的工作是从SINROM接收数字数据并将其转换为相应的模拟电压,这就是我们从输出中得到的电压。在输出端,我们还有一个开关,只需一点软件代码就可以打开或关闭它。我们将在本文后面讨论。AD9833数据表,您也可以查看它以获取更多信息。
构建基于AD9833的函数发生器所需的组件
下面列出了构建基于AD9833的函数发生器所需的组件,我们使用非常通用的组件设计了这个电路,这使得复制过程非常容易。
Arduino纳米-1
AD9833DDS函数发生器-1
128X64OLED显示屏-1
通用旋转编码器-1
DC桶形千斤顶-1
LM7809稳压器-1
470uF电容-1
220uF电容-1
104pF电容-1
10K电阻-6
轻触开关-4
螺丝端子5.04mm-1
女头-1
12V电源-1
基于AD9833的函数发生器-原理图
AD9833和基于Arduino的函数发生器的完整电路图如下所示。
我们将使用带有Arduino的AD9833来生成我们想要的频率。在本节中,我们将借助原理图解释所有细节;让我简要概述一下电路发生的情况。让我们从AD9833模块开始。AD9833模块为函数发生器模块,按照原理图与Arduino连接。为了给电路供电,我们使用了LM7809稳压器IC,它带有一个不错的去耦电容,这是必要的,因为电源噪声会干扰输出信号,从而导致不需要的输出。与往常一样,Arduino是这个项目的大脑。为了显示设定频率和其他有价值的信息,我们连接了一个128X64OLED显示模块。为了改变频率范围,我们使用了三个开关。第一个将频率设置为Hz,第二个将输出频率设置为KHz,第三个将频率设置为MHz,我们还有另一个按钮可用于启用或禁用输出。最后,我们有旋转编码器,我们必须连接一些上拉电阻,否则这些开关将不起作用,因为我们正在检查池方法上的按钮按下事件。旋转编码器用于改变频率,旋转编码器内部的轻触开关用于选择设定的波形。
基于AD9833的函数发生器-Arduino代码
此项目中使用的完整代码可在此页面底部找到。添加所需的头文件和源文件后,应该可以直接编译Arduino文件了。您可以从下面给出的链接下载ad9833Arduino库和其他库,或者您可以使用板管理器方法安装库。
ino中的代码说明。文件如下。首先,我们首先包含所有必需的库。AD9833DDS模块库首先是OLED库,我们的一些计算需要数学库。
#include//AD9833模块库#include//OLED线库#include//OLED支持库#include//OLED库#include//数学库
接下来,我们为按钮、开关、旋转编码器和OLED定义所有必要的输入和输出引脚。
#defineSCREEN_WIDATA_PINH128//OLED显示宽度(以像素为单位)#defineSCREEN_HEIGHT64//OLED显示高度,以像素为单位#defineSET_FREQUENCY_HZA2//以Hz为单位设置频率的按钮#defineSET_FREQUENCY_KHZA3//以Khz为单位设置频率的按钮#defineSET_FREQUENCY_MHZA6//以Mhz为单位设置频率的按钮#defineENABLE_DISABLE_OUTPUT_PINA7//启用/禁用输出的按钮#defineFNC_PIN4//AD9833模块需要的Fsync#defineCLK_PIN8//编码器的时钟引脚#defineDATA_PIN7//编码器的数据引脚#defineBTN_PIN9//编码器上的内部按钮
接下来,我们有我们的setup()函数,在该setup函数中,我们首先启用Serial进行调试。我们借助begin()方法初始化AD9833模块。接下来,我们将所有分配的旋转编码器引脚设置为输入。而我们将时钟引脚的值存储在clockPinState变量中,这是旋转编码器的必要步骤。
接下来,我们将所有按钮引脚设置为输入,并在display.begin()方法的帮助下启用OLED显示,我们还使用if语句检查是否有任何错误。完成后,我们清除显示并打印启动启动画面,我们添加2秒的延迟,这也是启动画面的延迟,最后,我们调用update_display()函数清除屏幕并更新再次显示。update_display()方法的细节将在本文后面讨论。
接下来,我们有我们的loop()函数,所有主要功能都写在循环部分。
无效循环(){clockPin=digitalRead(CLK_PIN);if(clockPin!=clockPinState&&clockPin==1){if(digitalRead(DATA_PIN)!=clockPin){柜台-;}别的{counter++;//编码器顺时针旋转,因此递增}如果(计数器<1)计数器=1;Serial.println(计数器);更新显示();}
最后,我们有了update_display()函数。在此功能中,我们所做的不仅仅是更新此显示器,因为显示器的某些部分无法在OLED中更新。要更新它,您必须使用新值重新绘制它。这使得编码过程变得更加困难。
在这个函数中,我们从清除显示开始。接下来,我们设置所需的文本大小。此后,我们设置光标并使用display.println("FunctionFunction");打印函数生成器;命令。在display.setCursor(0,20)函数的帮助下,我们再次将文本大小设置为2,将光标设置为(0,20)。
这是我们打印波浪信息的地方。
display.clearDisplay();//首先清除显示display.setTextSize(1);//设置文字大小display.setCursor(10,0);//设置光标位置display.println("函数生成器");//打印文本display.setTextSize(2);//设置文字大小display.setCursor(0,20);//设置光标位置
接下来,我们检查布尔变量以获取频率详细信息并更新moduleFrequency变量中的值。我们对Hz、kHz和MHz值执行此操作。接下来,我们检查waveSelect变量并确定选择了哪个波。现在,我们有了设置波形类型和频率的值。
我们再次设置光标并更新计数器值。我们再次检查布尔值以更新显示器上的频率范围,我们必须这样做,因为OLED的工作原理非常奇怪。
display.setCursor(45,20);display.println(计数器);//在显示器上打印计数器信息。如果(set_frequency_hz==1&&set_frequency_khz==0&&set_frequency_mhz==0){display.setCursor(90,20);display.println("Hz");//在显示器上打印Hz显示.显示();//当所有设置更新显示}如果(set_frequency_hz==0&&set_frequency_khz==1&&set_frequency_mhz==0){display.setCursor(90,20);display.println("Khz");显示.显示();//当所有设置更新显示}if(set_frequency_hz==0&&set_frequency_khz==0&&set_frequency_mhz==1){display.setCursor(90,20);display.println("Mhz");显示.显示();//当所有设置更新显示}
接下来,我们检查按钮按下变量以将输出打开/输出关闭到OLED。由于OLED模块,这再次需要完成。
如果(btn_state){display.setTextSize(1);display.setCursor(65,45);display.print("输出开启");//打印输出到显示器显示.显示();display.setTextSize(2);}别的{display.setTextSize(1);display.setCursor(65,45);display.print("输出关闭");//打印输出到显示器显示.显示();display.setTextSize(2);}
这标志着我们编码过程的结束。如果此时有疑惑,可以查看代码中的注释进一步理解。
测试基于AD9833的函数发生器
为了测试电路,使用上述设置。如您所见,我们已将12V直流电源适配器连接到DC筒形插孔,并将Hantek示波器连接到电路的输出端。我们还将示波器连接到笔记本电脑,以可视化和测量输出频率。
完成后,我们在旋转编码器的帮助下将输出频率设置为5Khz,并测试输出正弦波,果然,输出端是5Khz正弦波。
接下来,我们将输出波形改为三角波,但频率保持不变,输出波形如下图所示。
然后我们把输出改成方波,观察输出,是一个完美的方波。
我们还改变了频率范围并测试了输出,它运行良好。
进一步增强
该电路只是概念验证,需要进一步增强。首先,我们需要一块优质的PCB和一些优质的BNC连接器用于输出,否则我们无法获得更高的频率。模块的幅度非常低,因此为了增强它,我们需要一些运算放大器电路来放大输出电压。可以连接电位计以改变输出幅度。可以连接一个用于抵消信号的开关;这也是必备功能。此外,代码需要大量改进,因为它有点错误。最后,OLED显示器需要更换,否则无法编写易于理解的代码。
#include//AD9833模块
库#include//OLED线库
#include//OLED支持库
#include//OLED库
#include//数学库
#defineSCREEN_WIDATA_PINH128//OLED显示屏宽度,以像素为单位
#defineSCREEN_HEIGHT64//OLED显示屏高度,以像素为单位
#defineSET_FREQUENCY_HZA2//设置频率的按钮,以Hz为单位
#defineSET_FREQUENCY_KHZA3//以Khz为单位设置频率
的按钮#defineSET_FREQUENCY_MHZA6//以Mhz为单位设置频率的按钮
#defineENABLE_DISABLE_OUTPUT_PINA7//启用/禁用输出的按钮
#defineFNC_PIN4//AD9833模块所需的Fsync
#defineCLK_PIN8//编码器时钟引脚
#defineDATA_PIN7//编码器数据引脚
#defineBTN_PIN9//编码器内部按钮
intcounter=1;//如果旋转编码器转动
intclockPin;//这个Counter值会增加或减少//旋转编码器使用的占位符por引脚状态
intclockPinState;//旋转编码器使用的占位符por引脚状态
unsignedlongtime=0;//用于去抖动
unsignedlongmoduleFrequency;//用于设置输出频率
在
longdebounce=220;//去抖动延迟
boolbtn_state;//用于启用AD98333模块的禁用输出
boolset_frequency_hz=1;//AD9833模块的默认频率
boolset_frequency_khz;
boolset_frequency_mhz;
字符串waveSelect="SIN";//模块启动波形
intencoder_btn_count=0;//用于检查编码器按钮按下
Adafruit_SSD1306display(SCREEN_WIDATA_PINH,SCREEN_HEIGHT,&Wire,-1);
AD9833gen(FNC_PIN);
无效设置(){
序列.开始(9600);
pinMode(CLK_PIN,INPUT)后的第一个命令;
pinMode(DATA_PIN,输入);
pinMode(BTN_PIN,INPUT_PULLUP);
clockPinState=digitalRead(CLK_PIN);
pinMode(SET_FREQUENCY_HZ,输入);
pinMode(SET_FREQUENCY_KHZ,输入);
pinMode(SET_FREQUENCY_MHZ,输入);
pinMode(ENABLE_DISABLE_OUTPUT_PIN,输入);
if(!display.begin(SSD1306_SWITCHCAPVCC,0x3C)){//地址0x3Dfor128x64
Serial.println(F("SSD1306allocationfailed"));
为了(;;);
}
display.clearDisplay();//清屏
display.setTextSize(2);//设置文本大小
display.setTextColor(WHITE);//设置LCD颜色
display.setCursor(30,0);//设置光标位置
display.println("AD9833");//打印这个文本
display.setCursor(17,20);//设置光标位置
display.println("Function");//打印这个文本
display.setCursor(13,40);//设置光标位置
display.println("Generator");//打印这个文本
显示.显示();//更新显示
延迟(2000);//延迟2秒
update_display();//调用update_display函数
voidloop()
{
clockPin=digitalRead(CLK_PIN);
if(clockPin!=clockPinState&&clockPin==1){
if(digitalRead(DATA_PIN)!=clockPin){
计数器--;
else{
counter++;//编码器顺时针旋转,所以递增
if(counter<1)counter=1;
Serial.println(计数器);
更新显示();
时钟引脚状态=时钟引脚;//记住最后的CLK_PIN状态
//如果我们检测到LOW信号,按钮被按下
if(digitalRead(BTN_PIN)==LOW&&millis()-time>debounce){
encoder_btn_count++;//增加值
if(encoder_btn_count>2)//如果值大于2将其重置为0
encoder_btn_count=0;
if(encoder_btn_count==0){//如果值为0则选择正弦波
waveSelect="SIN";//用sin值更新字符串变量
update_display();//更新显示
if(encoder_btn_count==1){//如果值为1则选择方波
waveSelect="SQR";//使用SQR值更新字符串变量
if(encoder_btn_count==2){//如果值为1则选择三角波
waveSelect="TRI";//用TRI值更新字符串变量
//使用analogread方法检查按钮按下动作
//稍微延迟以帮助消除读数
if(analogRead(SET_FREQUENCY_HZ)<30&&millis()-time>debounce){//检查analogpin有去抖延迟
//更新布尔值
set_frequency_hz=1;
设置频率khz=0;
set_frequency_mhz=0;
if(analogRead(SET_FREQUENCY_KHZ)<30&&millis()-time>debounce){//使用debouncedelay检查analogpin
set_frequency_hz=0;
set_frequency_khz=1;
模块频率=计数器*1000;
if(analogRead(SET_FREQUENCY_MHZ)<30&&millis()-time>debounce){//使用debouncedelay检查analogpin
//update布尔值
set_frequency_mhz=1;
模块频率=计数器*1000000;
if(analogRead(ENABLE_DISABLE_OUTPUT_PIN)<30&&millis()-time>debounce){//使用debouncedelay检查analogpin
btn_state=!btn_state;//反转按钮状态
gen.EnableOutput(btn_state);//根据按钮状态启用/禁用函数发生器的输出
voidupdate_display()
display.clearDisplay();//首先清除显示
display.setTextSize(1);//设置文本大小
display.setCursor(10,0);//设置光标位置
display.println("FunctionGenerator");//打印文本
display.setCursor(0,20);//设置光标位置
if(set_frequency_hz==1&&set_frequency_khz==0&&set_frequency_mhz==0){//检查是否按下了以Hz为单位设置频率的按钮
moduleFrequency=counter;//更新模块频率变量与当前计数器值
if(set_frequency_hz==0&&set_frequency_khz==1&&set_frequency_mhz==0){//检查是否按下了设置KHz频率的按钮
moduleFrequency=counter*1000;//更新模块频率变量与当前计数器值但我们相乘1000tosetitonKHZ
if(set_frequency_hz==0&&set_frequency_khz==0&&set_frequency_mhz==1){//检查设置频率的按钮是否被按下
moduleFrequency=counter*1000000;
if(moduleFrequency>12000000)
moduleFrequency=12000000;//不要让频率大于12Mhz
计数器=12;
if(waveSelect=="SIN"
display.println("SIN");
gen.ApplySignal(SINE_WAVE,REG0,moduleFrequency);
Serial.println(moduleFrequency);
if(waveSelect=="SQR"){//选择Sqr波
display.println("SQR");
gen.ApplySignal(SQUARE_WAVE,REG0,moduleFrequency);
if(waveSelect=="TRI"){//选择三波
display.println("TRI");
gen.ApplySignal(TRIANGLE_WAVE,REG0,moduleFrequency);//更新AD9833模块。
display.setCursor(45,20);
display.println(计数器);//在显示屏上打印计数器信息。
if(set_frequency_hz==1&&set_frequency_khz==0&&set_frequency_mhz==0){
display.setCursor(90,20);
display.println("Hz");//在显示器上打印Hz
display.display();//当所有设置更新显示
if(set_frequency_hz==0&&set_frequency_khz==1&&set_frequency_mhz==0){
display.println("Khz");
显示.显示();//当所有设置更新显示
if(set_frequency_hz==0&&set_frequency_khz==0&&set_frequency_mhz==1){