简易频率特性测试仪的设计说明.docx
《简易频率特性测试仪的设计说明.docx》由会员分享,可在线阅读,更多相关《简易频率特性测试仪的设计说明.docx(51页珍藏版)》请在冰豆网上搜索。
简易频率特性测试仪的设计说明
简易频率特性测试仪的设计
加在前面:
术业有专攻。
一般写一些东西我也不会在空间瞎发,弄的别人以为自己瞎显摆。
不过我觉得我们电子设计的过程确实值得其他小组学习一下,比如说老葛焊板子那种芯片的布局,还有我们用4个按键解决所有数字的设置的思想。
我希望大家看到文章的时候不是觉得怎么吊炸天,其实我们这种水平比我们吊炸天的多了去。
我们之所以有敢厚着脸皮把这么次的设计思想分享出来,主要希望能把其中的某一些发光点分享给大家,同时希望他人给我们的更宝贵的意见和建议。
----end----
电子设计三中,仪器仪表组的第一个题目,是简易频率特性测试仪的设计。
这个题目取自2013年的E题:
简易频率特性测试仪(E题)。
为了纪念近一个月的工作,特撰以此文纪念我们第七小组历经了的艰辛岁月。
在此,感谢组长葛家瑾大神、还有范一华同学的辛勤付出,还有李煜及其他一些学长的帮助。
特发上图,以作纪念。
在本次完成题目的过程中,葛大神早早完成了公式推导、电路理论和原理的分析,并组织我们在工作上分工(虽然他好像对“被我和范一华排挤去焊电路板”很不满意私下抱怨并耿耿于怀,哈哈)。
下面我简单的回顾一下我们的这次设计:
其中,有关硬件电路的部分是葛大神负责的,我只是略懂了原理,故仅仅略述。
我主要承担的是AD采样部分的程序,还有就是通过操作液晶屏和按键实现的程序的总体逻辑控制程序。
范一华同学主要完成的是AD9854部分的程序,正弦波输出及其幅度补偿,还有扫频部分的程序。
下面,我从入手这道题目的开始状态,来一步步回顾一下。
下面,先把题目贴出来:
/*=======================开始贴题目=======================*/
【本科组】
一、任务
根据零中频正交解调原理,设计并制作一个双端口网络频率特性测试仪,包括幅频特性和相频特性,其示意图如图1所示。
二、要求
1.基本要求
制作一个正交扫频信号源。
(1)频率范围为1MHz~40MHz,频率稳定度≤10^-4;频率可设置,最小设置单位100kHz。
(2)正交信号相位差误差的绝对值≤5º,幅度平衡误差的绝对值≤5%。
(3)信号电压的峰峰值≥1V,幅度平坦度≤5%。
(4)可扫频输出,扫频范围及频率步进值可设置,最小步进100kHz;要求
连续扫频输出,一次扫频时间≤2s。
2.发挥部分
(1)使用基本要求中完成的正交扫频信号源,制作频率特性测试仪。
a.输入阻抗为50Ω,输出阻抗为50Ω;
b.可进行点频测量;幅频测量误差的绝对值≤0.5dB,相频测量误差的绝对值≤5º;数据显示的分辨率:
电压增益0.1dB,相移0.1º。
(2)制作一个RLC串联谐振电路作为被测网络,如图2所示,其中Ri和Ro分别为频率特性测试仪的输入阻抗和输出阻抗;制作的频率特性测试仪可对其进行线性扫频测量。
a.要求被测网络通带中心频率为20MHz,误差的绝对值≤5%;有载品质因数为4,误差的绝对值≤5%;有载最大电压增益≥-1dB;
b.扫频测量制作的被测网络,显示其中心频率和-3dB带宽,频率数据显示的分辨率为100kHz;
c.扫频测量并显示幅频特性曲线和相频特性曲线,要求具有电压增益、相移和频率坐标刻度。
(3)其他。
/*=======================贴题目结束=======================*/
AD9854实验板的程序,我们直接有学长找来的代码,我们需要做的工作只是移植。
然而,源程序对应的IO口用到的均为位操作,而我们使用的F020单片机不能直接对P6、P7口直接进行位操作,所以需要将位操作均用“|=bitx”或者“&=!
bitx”的方式来置位或者复位。
这一段程序由范一华同学完成移植,在此不贴出了。
硬件部分最值得一说的是AD835解调板,该板子由葛大神焊成。
他共焊接了两次,第一次半途而废,因为确实太渣渣了。
第二次,板子正面元器件布局很好,但是最终测试的时候,发现还是效果不行,原因可能是高频信号的其板子背面走线,尤其是AD9854的双正弦波输入附近位置的不佳处,受到的影响较大(具体我不清楚为什么)。
你们会发现,途中下面的两块转接板上你看不到芯片,这并不是没有焊芯片,而是焊接时候就把板子反面了一下。
这样的话,芯片引脚的布局就和电路的原理图上一样啦,在走线的时候将方便许多,这个小技巧大家可以学习一下。
老哥说了下次还是他要来焊了啦,要给他一个挽回的机会哈哈。
现在贴出AD835解调板原理图:
解调板左边的四个接口,分别对应如下:
Q-AD9854cos路,I-AD9854sin路,IN-RLC被测网络输入端,OUT-LC被测网络输出端。
右侧分别为Ain0和Ain1路的采样,输给F020单片机的ADC0.
输入解调板的两路正交的sin信号I和Q,期中I路经过RLC网络后分别与I路和Q路相乘。
通过AD835完成乘法,其输出:
分别给偏置电压0.125mV和0.25mV。
通过TL431给出稳定的偏置。
分别通过低通滤波器,得到直流分量。
这里低通的指标是按照截止频率100KHz做得,其实具体是多少,只要足够小就行。
为了让F020能够采样到合适范围的电压值,通过低通滤波器后,再将信号放大10倍。
实际上,我们会发现,因为F020板子上参考电压已经与内部相连,我们无法改变,最大电压只能采样到2.48V,幸好最大电压只能是2.5V,我们在这种情况下可以视为2.5V来算。
但是我们设想,如果将电压放大的倍数略设置小一些,如8倍,将解决该问题。
最后,就是在程序中,根据公式算出实际的被测网络的K(幅度衰减)和Fi(相位)值啦。
经过滤波器之后,两路直流分量为:
,
由此推出:
,由此再推出K,再代回某个式子推出Fi,这里就略了。
这里建议用sin函数推导(代回Q路公式),我们可以发现asin函数的值域[-90度,+90度]对应的值正好为[-1,+1],这样我们方便在程序中计算。
Atan函数毕竟精确度没有asin好,这个自己画函数图像便知。
硬件电路就说这么多了,下面我来show一下我们的代码。
这里补充一些大家在画幅频、相频曲线的时候可能会遇到的问题:
如果你们图像画的不标准,可能有这些原因:
1、公式形式问题。
最开始我们公式中用的是mV做电压的单位,发现计算出来的数值打在屏上是错的。
后来发现是因为mV本来就是10^3,再需要平方等等,可能已经造成了数据类型的溢出。
后来我们改在计算之前转换成V作为电压的单位,结果就对了。
其实我们不难想到,当我们做小数乘法的时候,就算数据类型有溢出,也是向后溢出的,缺失的是我们不太关心的极小的部分。
2、可能是你们的硬件电路有问题。
我们的硬件电路跑在我们自己的软件上,画出来的相频曲线明显不对,根本都看不出来是什么。
但是换成吴天涯组他焊的板子,结果图像就特别好,是一个从90度逐渐减小的趋势。
相频我们的还行。
但是由此还是可见,就算程序写的没有问题,如果AD835解调板焊接不行,还是会影响你的软件显示,造成怀疑自己程序错误的假象。
3、可能是你们的公式推导问题:
我们最早把峰峰值和电压幅度值弄混淆的写在一起计算,这样你会发现得出的结果比理论上差个2倍或者多个2倍。
这个问题细心看一下就解决了。
--------
关于ADC采样值转化为实际电压值的一些问题:
ADC0H+ADC0L中如果数据为4095,表示的电压:
超过了2.48或者引脚悬空的时候都是4095。
问:
如果adc采样后,寄存器里面的值为temp,那么电压就是vin=temp/4095*2.48,对吗?
答:
如果你这样处理,是不好的。
我们知道,AD采样在低于180mV电压和高于2.4V电压的时候是不够准确的,2.48只是一个手册里面给的参考电压。
我们组的做法是,用电源输入一个1v的电压,记录现在的ADC采样数值。
再输入一个2V电压,记录现在采样数值。
然后,就可以画出一条线性的曲线,以后任意采样到的ADC值,都可以转化成实际的电压。
====================================================
Main.c函数
#include"lcdsys.h"
#include"key.h"
#include"lcd.h"
#include"Device.h"
intmain()
{
Device_Init();
KeyPort_Init();
newLCDInit();
//WelcomePics.
Clear();
DrawcharS("OurAD9854sys",1,0);
DrawcharS("-toBeNo.1",2,4);
while(0==KeyScan()){};
////GointoOurSystem.
initlcdsys();
while
(1)
{
sysfuntion(KeyScan());
}
}
上面是我们的main函数,我们主要就是进来初始化所有需要用到的外围电路,然后就进了一个sysfuntion(KeyScan());函数,这就是我们的LCDsys。
当然了,我们的LCDsys.c有600+行,所以接下来关于这段程序的解读,我会直接在代码里面写。
我个人认为我一个比较好的编程习惯就是注释写得非常详细,当然,和别的大牛比起来就很渣渣啦,不过通过注释让隔了一段时间后自己还看得懂自己的程序是非常必要的。
然后,我写此文时对程序的解读,我就用【】扩起来表示。
Lcdsys.c函数
#include"lcdsys.h"
#include"DataType.h"
#include"math.h"
#include"string.h"
#include"lcd.h"
#include"key.h"
#include"ADDA.h"
#include"AD9854.h"
#include"c8051F020.h"//c8051f020单片机头文件
【上方都是头文件就没什么说的啦,当然,如果你经常在把别人的程序挪给自己用的时候出错,那么你就需要专门XX一下.c程序的头文件.h需要怎么写了。
】
#defineVppadjust0.819
#defineVppFangDaBeiShu10.0
#defineVppPianZhiValue1250.0
#defineGNDBuChang13.0
【如果你有把不需要改变的值定义成变量的坏习惯,建议你定义他们为常量。
这样你不会在程序中不小心的改变他们,更不会让Keil不智能的编译器不知为什么的就让你的程序跑飞。
F020单片机的存储单元在你写了一大串自己都不太记得什么用的变量和大量的借鉴了别人的代码后都不知道别人有什么变量的情况下存储空间的资源将会是十分极其以及相当的紧张的,所以你需要有这个习惯】
unsignedintf;//幅度(整型)给AD9854的时候*1000.0
unsignedinta;//幅度(整型)给AD9854的时候*Vppadjust(即0.819)
unsignedintxdatatmp[5];//记录采集出来的ADC0的12位数据
bitJiaoZhunBit;
floatxdataad0bc[5];//补偿值数据
floatxdataad1bc[5];//补偿值数据
floatxdataMaxValue;//最大相位或K取值
intxdataMaxPoint;//最大相位或K的频率点、
//floatxdataMinValue;//最小相位或K取值
//intxdataMinPoint;//最小相位或K的频率点、
floatxdataValue[120];
intxdataThreeDb[2];
【给自己定义的变量注释清楚到底是用作什么用的】
/*========对应关系========
0-5000k(5M)
1-10000k(10M)
2-20000k(20M)
3-30000k(30M)
4-40000k(40M)
========对应关系========*/
unsignedintadtmp,adtmp0,adtmp1;//记录采集出来的ADC0的12位数据
longdoublead0,ad1,sinFi,Du,K;//将12位数据转化为实际电压值,保存为浮点数据//cosFi已经不用了
unsignedcharwei,count,incount;//LCDsys用的:
数据标志位、全局计数位、内嵌套计数位
unsignedcharcmd;//画图时候:
1-频率特性绘图,2-幅度特性曲线,3-校准,4-退出
//扫频时候:
1-步进,2-起始频率,3-截止频率
bitAorF;//用于标志:
设置频率或者幅度、的标志位。
unsignedintmo=1;//扫频使用scanusing...
unsignedlongFreq1=1000000.0;//扫频使用scanusing...
unsignedintscanstep=1;//扫频使用scanusing...扫频步进
voidinitlcdsys()//LCDsys初始化函数
{
ADCcount=0;
f=100;//kHz
a=2000;//mV
wei=0;AorF=0;
Clear();
fun_ok();
JiaoZhunBit=0;
MaxValue=0;
MaxPoint=0;
//MinValue=0;
//MinPoint=0;
for(count=0;count<120;count++){Value[count]=0;}
ThreeDb[0]=0;ThreeDb[1]=0;
}
【其实最好是写一个初始化函数,给自己定义了的变量赋一个初值】
【下面是一个寻找5个数字的中值得函数,用到了冒泡排序,还记得不?
】
unsignedintmid5(unsignedintnum1,unsignedintnum2,unsignedintnum3,unsignedintnum4,unsignedintnum5){
unsignedinta[6],i,start;
a[1]=num1;a[2]=num2;a[3]=num3;a[4]=num4;a[5]=num5;
for(start=5;start>1;start--){
for(i=1;iif(a[i]>a[i+1]){a[0]=a[i];a[i]=a[i+1];a[i+1]=a[0];}
}
}
returna[3];
}
【下面是一个将一位的整型转化成字符串的函数】
//将1位的int型数据转换,返回‘字符’的子函数
charint2char(intinput)
{
return0x30+input;
}
【在液晶屏上画无符号整型的函数】
voiddrawint(unsignedintnum,unsignedcharrow,unsignedcharcol)
{
Drawchar(int2char((num/1)%10),row,col+4);
Drawchar(int2char((num/10)%10),row,col+3);
Drawchar(int2char((num/100)%10),row,col+2);
Drawchar(int2char((num/1000)%10),row,col+1);
Drawchar(int2char((num/10000)%10),row,col+0);
}
【在液晶屏上画有符号整型的函数】
voiddrawsignedint(intinput,unsignedcharrow,unsignedcharcol)
{
intnum;
if(input<0){Drawchar('-',row,col+0);num=-input;}else{Drawchar('+',row,col+0);num=input;}
Drawchar(int2char((num/1)%10),row,col+4);
Drawchar(int2char((num/10)%10),row,col+3);
Drawchar(int2char((num/100)%10),row,col+2);
Drawchar(int2char((num/1000)%10),row,col+1);
}
【在液晶屏上画有符号浮点数的函数】
voiddrawsignedfloat2_2(floatinput,unsignedcharrow,unsignedcharcol)
{
floatnum;
if(input<0){
Drawchar('-',row,col+0);
num=-input;
}else{
Drawchar('+',row,col+0);
num=input;
}
Drawchar(int2char((int)(num/10.0)%10),row,col+1);
Drawchar(int2char((int)(num/1.0)%10),row,col+2);
Drawchar('.',row,col+3);
Drawchar(int2char((int)(num*10.0)%10),row,col+4);
Drawchar(int2char((int)(num*100.0)%10),row,col+5);
}
【这个函数是我们打主屏(F和A设置时候的函数),贴出图片来看看
】
voidfun_ok()
{
if(AorF){
DrawcharS("ChangeA...",0,0);
drawint(a,1,0);
DrawcharS("mV",1,5);
DrawcharS("whenF=",3,0);
drawint(f,3,7);
DrawcharS("kHz",3,12);
}else{
DrawcharS("ChangeF...",0,0);
drawint(f,1,0);
DrawcharS("kHz",1,5);
DrawcharS("whenA=",3,0);
drawint(a,3,7);
DrawcharS("mV",3,12);
}
DrawcharS("=",2,wei);
}
【这是画扫频菜单时候的函数。
。
。
靠。
。
。
找不到照片。
。
。
可能没拍吧。
。
。
】
voidfun_scanmsg()
{Clear();
DrawcharS("Scaning...",0,0);
DrawcharS(">",cmd+1,0);
DrawcharS("scanStep=",1,1);drawint(100*scanstep,1,10);DrawcharS("k",1,15);
DrawcharS("Start=",2,1);drawint(adtmp0*100,2,7);DrawcharS("kHz",2,12);
DrawcharS("Stop=",3,1);drawint(adtmp1*100,3,6);DrawcharS("kHz",3,11);
}
【下面是两路ADC的补偿函数,根据不同的频率,将电压分别在接直通网络自动校准的时候,分别校准位1250mV和2500mV】
/*========对应关系========
0-5000k(5M)
1-10000k(10M)
2-20000k(20M)
3-30000k(30M)
4-40000k(40M)
========对应关系========*/
longdoublead0BuChang(longdoubleinputVpp,unsignedintinputf)//(相/频)度数补偿函数
{
longdoubleBuChangVpp,k;
BuChangVpp=inputVpp;
if
(1){//修改判断的值,以决定是否进行校正。
if(1elseif(10000elseif(20000elseif(30000}
returnBuChangVpp;
}
longdoublead1BuChang(longdoubleinputVpp,unsignedintinputf)//(相/频)度数补偿函数
{
longdoubleBuChangVpp,k;
BuChangVpp=inputVpp;
if
(1){//修改判断的值,以决定是否进行校正。
if(1elseif(10000elseif(20000elseif(30000