简易数字合成信号发生器(附代码)
一、设计目标:”简易数字合成信号发生器”,产生正弦波信号
设计指标要求:
- 频率范围10Hz-1KHz,递进控制;幅度0.2V-2V P-P,递进控制;能驱动100Ω负载。
- 频率数值和幅度数值的设定可以采用加、减按键步进控制或者直接输入数字完成,幅度和频率通过LED数码管显示。
频率按10Hz、20Hz、50Hz、100Hz、200Hz、500Hz、1kHz递进
幅度按0.2V、0.4V、0.6V、0.8V、1.0V、1.2V、1.4V、1.6V、1.8V、2.0V递进
- 对于频率和幅度的控制如果能在前面指定范围(10Hz-1KHz,2V P-P)内输入任意数值且测试结果较为精确,可酌情加分。
- 如果能增加标准AM幅度调制功能,要求调制信号频率为100Hz,载频为1KHz,调制度为50%,可酌情加分。
二、电子系统设计实验平台板原理图
软件模块说明
一、程序实现的整体功能介绍
本程序采用同时显示AM与FM的显示方法,数码管L5、L4显示AM的值,其他的数码管显示FM的值。总共用了五个按键,分别为KEY11、KEY12、KEY13、KEY14和KEY21,它们的功能为:
KEY11:AM与FM切换键,即在频率与幅度之间切换。
KEY12:更新DAC输出按键,就是使DAC输出当前数码管显示的幅度与频率的正弦信号。
KEY13:加键,如果是处于AM可调状态,则加的对象即为AM 的值并同步在数码管上显示。
KEY14:减键,如果是处于AM可调状态,则减的对象即为AM 的值并同步在数码管上显示。
KEY21:模式切换键,即在正弦信号输出与标准AM幅度调制之间切换。
除了这些基本的功能之外,我们还进行了一些优化和容错处理:
1、我们用LED灯提示当前处于哪个状态,这样看到那边的LED灯亮就知道是在什么状态下了。比如最左边的灯亮(就是与数码管显示AM的那一侧),则处于AM可调状态,此时的加减按键是对AM进行操作,若是最右边的灯亮,则为FM可调状态。
2、在按下KEY12按键的时候,我们做了一个动态的LED灯光效果,当看到这个效果的时候就表示DAC的输出已经更新了。
3、我们对AM与FM的取值进行了限定,即当AM的值小于0.2V或者大于2.0V时,我们的四个LED灯会闪烁来提示出错了,当FM的值小于10HZ或者大于1KHZ时也会同样报错。
4、在DAC的输出处理中,我们采用了缓冲机制,即数码管上的值与DAC的输出并不同步,这样在我们调节AM或者FM的时候DAC不会胡乱的输出并不是我们想要的信号,并且采用了这样的缓冲机制可以防止在其他按键按下的时候会对波形产生影响的情况,这个缓冲的实现利用的便是局部变量与全局变量的原理了。
通过这些优化与容错处理,我们可以不用示波器只看实验板就能知道当前的状态,输出的是什么信号了,这样真的的达到了一个独立的数字合成信号发生器的设计要求。
二、编程的原理
1、波形实现原理
从任务脚本四我们可以知道DAC的原理,DAC的最大输出为2.4V,间隔为256个。让DAC0H或者DAC1H等于某一个值则DAC就可以输出对应的电压了。因此,我们对正弦函数进行采样,采了若干点(可根据自己的需求,我们采了256个点,原因见频率原理的分析),然后对这些点进行量化,使样值的大小最大为256,然后将这些样值除以2.4,这样便可以得到DAC输出幅度为1V的正弦函数采样表。我们是用matlab实现的,具体代码如下:
t=linspace(0,1,256);y=sin(2*pi*t);y1=uint8(127+127*y);y1=uint8(y1/2.4);plot(y1,'k.')
同样的,标准AM幅度调制的波形也可以这么实现,matlab代码如下:
t=linspace(0,1,256);y=(1+0.5.*sin(2*pi*t)).*sin(2*pi*10*t);y1=uint8(127+127/1.5.*y);plot(y1,'k.');
2、幅度实现原理
在波形实现原理中我们已经得到了DAC输出幅度为1V的正弦函数采样表,因此只要我们将AM的值乘以这个表便可以得出对应幅度的正弦信号输出。
3、频率实现原理
由任务脚本四可知我们的硬件系统使用了 24MHz 晶体振荡器,经12分频后作为定时器 0 的计数时钟,即每计数一次用0.5us的时间。所以我们一个正弦信号的周期为T=X*N*T0,其中T0为两个数值输出之间的间隔,X为寄存器产生中断的计数次数,N为输出的正弦函数采样表中的数据个数。在这里我们的X=78,若将整个正弦函数采样表全部输出,每中断一次输出一个数值,则T=78*256*0.5=10000us,因此F=100HZ。
1、对于频率大于100HZ的频率,我们只需要减小N的值即可实现,因此我们设置了一个采样间隔DACFM=FM/100,每次中断输出一个数值。
2、对于频率小于100HZ的频率,我们只需要增大T0的值即可实现,采样间隔DACFM=FM/10,每10次中断输出一个数值。
6、问题与解决
一、软件问题
1、延时函数Loop_Delay()会随着频率的改变延时时间也会改变,从而导致按键在一些频率下不能响应。解决的办法是自己写了一个延时10ms的程序Delay10ms用在按键程序的去抖动中。
2、(256-X)中的X过小时,频率最小只能达到200多HZ,原因是中断中的程序执行也需要时间,而这个时间大于了下一次中断进入的时间。解决方法是将中断程序中的计算过程写到主程序中或者增大X的值。
二、硬件问题
1、在我们的第一个电路中,滤波后小于50HZ的信号毛刺很多,原因是模拟的滤波是在10~1KHZ而实际电路是50~1KHZ,我们的解决办法是从新设计了电路。
2、放大后的信号出现负峰切割失真,解决的办法是调整三极管的静态工作点使VBE=0.6~0.7V之间,此时三极管工作在放大状态
3、F4测试点的信号幅度正常但F5的幅度值只有正常值的一半,原因是两个100欧的电阻之间的电容过小,更换了更大的电容后便可以解决。
7、实测与分析
1)测试方法说明(使用的设备、连接图、基本原理)
A. 静态工作点测量
使用设备:数字万用表(直流电压挡)、直流稳压电源
测量方法:通过直流稳压电源接入10V直流电压,用数字万用表的直流电压挡测量三极管发射极电压,为9.68V,满足要求。
B. 阶梯波测量
使用设备:PC机、示波器
测量方法:将PC机与单片机相连,在PC机上运行程序,载入至单片机中,使用示波器测量DAC输出波形,观察不同频率的波形是否为阶梯波。
C. 正弦波测量
使用设备:示波器
测量方法:在上述测量的基础上,将硬件电路板与单片机相连,使用示波器测量滤波电路的输出端,观察不同频率的输出波形是否为无失真的正弦波。
D. 波形放大测量
使用设备:示波器
测量方法:在上述测量的基础上,使用示波器测量电路输出端,观察不同频率的正弦波幅度是否放大,波形是否稳定。
2)实测数据表格或曲线
根据验收指标对输出端波形进行测量。
A. 1Vp-p条件下,不同频率的测量
a. 表格:
1Vp-p | 额定 | 10Hz | 20Hz | 50Hz | 100Hz | 200Hz | 500Hz | 1000Hz |
信号频率(Hz) % | 实测 | 10.02 | 20.00 | 49.50 | 96.53 | 192.3 | 473.9 | 934.6 |
误差 | 0.20 | 0.00 | 1.00 | 3.47 | 3.85 | 5.22 | 6.54 | |
频率响应(V) % | 实测 | 0.880 | 0.984 | 1.01 | 1.06 | 1.04 | 0.992 | 0.880 |
误差 | 12.00 | 1.60 | 1.00 | 6.00 | 4.00 | 0.80 | 12.00 |
b. 曲线
10Hz 20Hz
50Hz 100 Hz
200Hz 500Hz
B. 200Hz条件下,不同幅度的测量
a. 表格:
200Hz | 额定 | 0.2V | 0.4V | 0.6V | 0.8V | 1.0V | 1.2V | 1.4V | 1.6V | 1.8V | 2.0V |
信号幅度(V) % | 实测 | 208mV | 408mV | 596mV | 832mV | 1.04 | 1.24 | 1.45 | 1.62 | 1.78 | 1.96 |
误差 | 4.00 | 2.00 | 0.67 | 4.00 | 4.00 | 3.33 | 3.57 | 1.25 | 1.11 | 2.00 | |
信号频率(Hz) % | 实测 | 191.9 | 194.9 | 190.8 | 194.6 | 191.9 | 191.6 | 193.8 | 195.7 | 197.6 | 194.9 |
误差 | 4.05 | 2.55 | 4.60 | 2.70 | 4.05 | 4.20 | 3.10 | 2.15 | 1.20 | 2.55 |
b. 曲线:
0.6V 0.8V
1.0V 1.2V
1.4V 1.6V
1.8V 2.0V
C. AM调制:调制信号频率为100Hz,载频为1KHz,调制度为50%
调制信号频率为200Hz,载频为2KHz
调制信号频率为500Hz,载频为5KHz
3)数据分析
A. 1Vp-p条件下,不同频率的测量
a. 从表格可看出,测得的信号频率和频率响应与真实值基本相差不大,较大或较小频率条件下,误差较大。这是由于电路本身的衰减造成的
b. 从波形图中可以看出,波形没有失真,其中100Hz的波形最好。
B. 200Hz条件下,不同幅度的测量
a. 从表格可看出,测得的信幅度与真实值相差不大,在误差允许范围内。
b. 从波形图中显示的数据可以看出,测得的信号频率和频率响应与真实值相差不大,由于电路本身的衰减,频率与,各幅度的波形无明显失真。
C. AM调制:
从波形图中可以看出,调制信号基本无失真。信号幅度超过一定范围时,波形将产生失真。
6、问题与解决
一、软件问题
1、延时函数Loop_Delay()会随着频率的改变延时时间也会改变,从而导致按键在一些频率下不能响应。解决的办法是自己写了一个延时10ms的程序Delay10ms用在按键程序的去抖动中。
2、(256-X)中的X过小时,频率最小只能达到200多HZ,原因是中断中的程序执行也需要时间,而这个时间大于了下一次中断进入的时间。解决方法是将中断程序中的计算过程写到主程序中或者增大X的值。
二、硬件问题
1、在我们的第一个电路中,滤波后小于50HZ的信号毛刺很多,原因是模拟的滤波是在10~1KHZ而实际电路是50~1KHZ,我们的解决办法是从新设计了电路。
2、放大后的信号出现负峰切割失真,解决的办法是调整三极管的静态工作点使VBE=0.6~0.7V之间,此时三极管工作在放大状态
3、F4测试点的信号幅度正常但F5的幅度值只有正常值的一半,原因是两个100欧的电阻之间的电容过小,更换了更大的电容后便可以解决。
三、程序
/*====================================================电子系统设计平台实验板演示程序作者:梁,黄,姬Release 2016=====================================================*/#include "compiler_defs.h"#include "C8051F020_defs.h"#include "driver\common.h"#include "driver\io_config.h"#include "driver\osc.h"#include "driver\uart.h"SBIT (TP0, SFR_P0, 7);SBIT (TP1, SFR_P0, 6);static unsigned char LED_BUF[6]={0xc0,0xf9,0xc0,0xc0,0xa4,0x40}; // LED数码管显示缓冲区,共6字节static double DACAM=0.0;static unsigned int DACFM=0;static unsigned int tun=0; //将频率以100HZ为中心,分为两部分static unsigned int Mu_cnt=0; //sin函数数据表static unsigned char SinData[256]={ 53,54,55,57,58,60,61,62,63,65,66,67,68,70,71,72,73,75,75,77,78,79,80,81,83,83,85,85,87,88,89,90,90,91,92,93,94,95,95,96,97,98,98,99,100,100,101,101,102,103,103,103,104,104,104,105,105,105,105,105,105,106,106,106,106,106,106,106,105,105,105,105,105,105,104,104,103,103,103,102,102,101,100,100,100,99,98,98,97,96,95,94,93,93,92,91,90,89,88,87,86,85,84,83,82,81,80,78,78,76,75,74,73,71,70,69,68,66,65,64,63,61,60,59,58,56,55,54,52,51,50,48,47,46,45,43,42,41,40,38,37,36,35,33,32,31,30,28,28,26,25,24,23,22,21,20,19,18,17,16,15,14,13,13,12,11,10,9,8,8,7,6,6,5,5,4,4,3,3,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,2,2,2,3,3,3,4,5,5,5,6,7,8,8,9,10,10,11,12,13,14,15,15,16,17,18,19,20,21,23,23,25,25,27,28,29,30,31,33,34,35,36,38,39,40,41,43,44,45,46,48,49,50,52,53};static unsigned char AmData[256]={ 127,148,168,186,201,212,218,218,213,202,187,167,145,121,97,74,55,39,28,23,25,32,46,65,88,114,141,167,191,212,228,238,242,238,228,211,189,163,134,105,77,51,30,15,6,5,11,24,44,68,97,127,158,186,212,232,246,253,252,244,228,207,180,150,119,89,60,36,17,5,1,4,14,31,54,82,112,142,172,199,221,237,247,249,244,232,214,190,163,134,105,78,54,34,20,13,13,20,33,52,74,100,127,154,178,199,215,226,231,229,221,208,190,169,145,121,98,77,59,46,38,36,39,47,60,77,96,117,137,157,174,189,199,204,205,201,193,181,166,149,131,114,98,84,73,65,62,62,66,74,85,98,112,127,141,154,166,174,180,182,181,176,169,160,149,136,124,112,101,92,85,81,80,82,86,92,101,111,122,132,143,152,160,166,169,170,168,164,157,149,140,130,119,109,100,93,88,85,84,86,91,98,106,116,127,138,148,158,165,171,174,174,171,166,158,148,136,124,111,100,89,81,75,72,73,77,84,94,106,120,134,149,163,175,184,190,193,191,185,176,164,148,131,114,96,80,67,57,51,49,52,60,72,88,107,127};/* 用户代码(定时器0中断服务,100us周期) */void t0_isr_callback(void){static unsigned int cnt=0;static unsigned int tun_cnt=0;static unsigned char led_cnt=0;static unsigned int Sincut=0; TP0=!TP0; // TP0 翻转,用来指示中断触发频率或周期 TP1=1; // TP1 置高,TP1的高电平持续时间代表中断服务程序执行时间if(tun_cnt>10) //确保能进入波形输出程序{tun_cnt=0;}tun_cnt++;if(tun_cnt==tun){tun_cnt=0;if(Mu_cnt==0){DAC0H=SinData[Sincut]*DACAM;}else{DAC0H=AmData[Sincut]*DACAM;}Sincut+=DACFM; //抽样,实现频率的变化}if(Sincut>255) //周期{Sincut=0;}DAC1H+=1; // DAC1 输出锯齿波,递减// 每十次中断执行一次以下代码,扫描一位LEDif(cnt==10){cnt=0;switch(led_cnt){case 0:P2=0x01;P1=LED_BUF[0];led_cnt=1;break;case 1:P2=0x02;P1=LED_BUF[1];led_cnt=2;break;case 2:P2=0x04;P1=LED_BUF[2];led_cnt=3;break;case 3:P2=0x08;P1=LED_BUF[3];led_cnt=4;break;case 4:P2=0x10;P1=LED_BUF[4];led_cnt=5;break;case 5:P2=0x20;P1=LED_BUF[5];led_cnt=0;break;default:break;}cnt++;}else{cnt++;}TP1=0; // TP1 置低,TP1的高电平持续时间代表中断服务程序执行时间}/* 用户代码(主循环)*/int main(void){unsigned int i,j;/unsigned int key=0;unsigned int TranAFM=0;int Integer=0;int DeNum=0;double AM_Value=0.2;double Test_AM=0.2;int cut_FM=0;unsigned int FM_Value=10;unsigned int Test_FM=0;unsigned int FM_Bit[6]={0,0,0,0,0,0};unsigned char LED_FM[6]={0xc0,0xf9,0xc0,0xc0,0xff,0xff}; //频率显示缓存,初始显示10unsigned int Values_FM[7]={10,20,50,100,200,500,1000}; //频率的可能取值unsigned char LED_AM[6]={0xff,0xff,0xff,0xff,0xa4,0x40}; //幅度显示缓存,初始显示0.2unsigned int Samp=0; //Samp为采样间隔unsigned int Fun=0;unsigned int Mu=0;/ Osc_Init_Parameter_t Osc_Init_Parameter; // 振荡器初始化参数// 用于键盘线扫描: D7=0, D6=0, D5=0, D4=0 unsigned char LINE[4]={ 0x7F, 0xBF, 0xDF, 0xEF};// 字符对应的段数值: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "." "8." " " unsigned char DIGI[13]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x7f,0x00,0xff};unsigned char TEXT[6]={0xc0,0xc0,0xc0,0xc0,0xc0,0xc0};// 键值 D0=0, D1=0, D2=0, D3=0unsigned char KEY[4]={ 0x0e, 0x0d, 0x0b, 0x07};// 中断全局关闭EA=0; //关闭开门狗定时器WDTCN = 0xDE;WDTCN = 0xAD;//初始化振荡器Osc_Init_Parameter.Source_Select = EXTERNAL_OSC;Osc_Init_Parameter.External_Mode = CRYSTAL;Osc_Init_Parameter.Frequency = 24000000; Osc_Init(&Osc_Init_Parameter);Loop_Delay(100);io_config();io_init();uart0_init();//设置串口0所占用的IO口:P0.0和P0.1XBR0=Reg_Field_Set(XBR0,BIT2);//使能交叉开关XBR2=Reg_Field_Set(XBR2,BIT6);//使能内部电压基准REF0CN=Reg_Field_Set(REF0CN,BIT1);REF0CN=Reg_Field_Set(REF0CN,BIT0);//使能DAC0,DAC0H: 8bit; DAC0L: 4bitDAC0CN=Reg_Field_Set(DAC0CN,BIT7);DAC0CN=Reg_Field_Set(DAC0CN,BIT2);//使能DAC1,DAC1H: 8bit; DAC1L: 4bitDAC1CN=Reg_Field_Set(DAC1CN,BIT7);DAC1CN=Reg_Field_Set(DAC1CN,BIT2);DAC0L=0;DAC0H=0;DAC1L=0;DAC1H=0;/*启动信息*/printf("\n");printf("Tianjin University\n");printf("Electronic System Design Kit\n");printf("Version 1.0 [C]2012-2016\n");printf("----------------------------\n");printf("MCU: C8051F020/24MHz\n");printf("PWR: 3.3V/100mA\n");printf("\n");////// Timer 0/1 initialization////// BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0// ---------------------------------------------------------------------// TCON: TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0// 0 0 0 1 0 0 0 0// // TMOD: GATE1 C/T1 T1M1 T1M0 GATE0 C/T0 T0M1 T0M0// 0 0 0 0 0 0 1 0//// IE: EA IEGF0 ET2 ES0 ET1 EX1 ET0 EX0// 1 0 0 0 0 0 1 0TL0=(256-78); // when 24MHz & SYSCLK/12, 10kHz timeoutTH0=(256-78); // reloadEA=1;ET0=1;TMOD=0x02;TR0=1;//#if 1while(1){P3=LINE[0]; //扫描第一行键盘Delay10ms;if((P3&0x0f)==KEY[0]) //第一个按键是否按下,AMFM转换{Delay10ms; //去抖动if((P3&0x0f)==KEY[0]) {key++;TranAFM=key%2; while((P3&0x0f)==KEY[0]); //直到按键松开} }Delay10ms;if((P3&0x0f)==KEY[1]) //更新DAC显示{Delay10ms; //去抖动if((P3&0x0f)==KEY[1]) {DACAM=AM_Value;DACFM=Samp; tun=Fun;for(i=0;i<3;i++) //更新提示{P4=0xF3;Loop_Delay(20);P4=0xF0;Loop_Delay(20);}while((P3&0x0f)==KEY[1]); //直到按键松开} }if(TranAFM==0) //显示幅度{//立即显示幅度的值LED_BUF[5]=LED_AM[5]; //LED_AM为幅度显示缓存LED_BUF[4]=LED_AM[4];P4=0xF7;Test_AM=AM_Value; //Test_AM保存AM_Value的初值if((P3&0x0f)==KEY[2]) //检测KEY13是否按下,KEY13为加按键{Delay10ms;if((P3&0x0f)==KEY[2]){AM_Value=AM_Value+0.2; //AM_Value为幅度值,double型数while((P3&0x0f)==KEY[2]);}if(AM_Value>2.2) //容错处理,保持显示值不变,闪灯提示{AM_Value=AM_Value-0.2;for(i=0;i<4;i++){P4=0xFF;Loop_Delay(100);P4=0xF0;Loop_Delay(100);}}} Delay10ms;if((P3&0x0f)==KEY[3]) //检测KEY14是否按下,KEY14为减按键{Delay10ms;if((P3&0x0f)==KEY[3]){AM_Value=AM_Value-0.2; //AM_Value为幅度值,double型数while((P3&0x0f)==KEY[3]);}if(AM_Value<0.2) //容错处理,保持显示值不变,闪灯提示{AM_Value=AM_Value+0.2;for(i=0;i<4;i++){P4=0xFF;Loop_Delay(100);P4=0xF0;Loop_Delay(100);}}} if(Test_AM!=AM_Value) //如果有按键按下则刷新LED的值{Integer=AM_Value;DeNum=(AM_Value-Integer)*10;LED_AM[5]=DIGI[Integer]&DIGI[10];LED_AM[4]=DIGI[DeNum];}} else //显示频率{for(i=0;i<4;i++) //立即显示频率的值{LED_BUF[i]=LED_FM[i]; //LED_FM为频率显示缓存}P4=0xFE;if((P3&0x0f)==KEY[2]) //检测KEY13是否按下{Delay10ms;if((P3&0x0f)==KEY[2]){cut_FM++;while((P3&0x0f)==KEY[2]);}if(cut_FM>6) //容错处理,保持显示值不变,闪灯提示{cut_FM--;for(i=0;i<4;i++){P4=0xFF;Loop_Delay(100);P4=0xF0;Loop_Delay(100);}}}if((P3&0x0f)==KEY[3]) //检测KEY14是否按下{Delay10ms;if((P3&0x0f)==KEY[3]){cut_FM--;while((P3&0x0f)==KEY[3]);}if(cut_FM<0) //容错处理,保持显示值不变,闪灯提示{cut_FM++;for(i=0;i<4;i++){P4=0xFF;Loop_Delay(100);P4=0xF0;Loop_Delay(100);}}}Test_FM=FM_Value; //Test_FM保存FM_Value的初值FM_Value=Values_FM[cut_FM];if(Test_FM!=FM_Value) //如果有按键按下,则刷新LED的值{FM_Bit[3]=FM_Value/1000;FM_Bit[2]=(FM_Value%1000)/100;FM_Bit[1]=(FM_Value%100)/10;FM_Bit[0]=FM_Value%10;for(i=0;i<4;i++){j=FM_Bit[i];LED_FM[i]=DIGI[j];}}}if(FM_Value<100) //将频率以100HZ为中心分成两部分{Fun=10;Samp=FM_Value/10;}else{Fun=1;Samp=FM_Value/100;}P3=LINE[1]; //扫描第一行键盘Delay10ms;if((P3&0x0f)==KEY[0]) //第一个按键是否按下,AMFM转换{Delay10ms; //去抖动if((P3&0x0f)==KEY[0]) {Mu++;while((P3&0x0f)==KEY[0]); //直到按键松开}Mu_cnt=Mu%2; } }#endif// 前台程序完成,进入中断事件驱动状态while(1);}/* system call: */void putchar (char c){while (!TI); /* assumes UART is initialized */TI = 0;SBUF = c;}/* interrupt entrances */void t0_isr(void) __interrupt (1) // 定时器0中断服务程序{t0_isr_callback();}/* end */void Delay10ms (void){unsigned int a;for(a=0;a<10;a++){Delay1ms;}}