资源描述
第四届电子设计竞赛复试实验报告
正弦电压信号的产生与有效值测量
*********************************************************************
复试题目:
设计一个频率为1000Hz的正弦波信号发生器,输出幅值为1V左右。用单片机搭建一个系统,精确地测量该信号的有效值。并通过串口送到PC机中,通过串口调试助手软件显示该有效值。
题目要求:
1、设计一个1000Hz的正弦波振荡器,输出幅度转换为1V。
2、用单片机自带10位AD作为模数转换芯片,不允许扩展其它AD。
3、串口以9.6K波特率向PC机传输数据,在串行调试助手中,以10进制格式显示该正弦波的有效值。
********************************************************************
********************************************************************
摘要:通过一RC振荡电路,产生1KHz的正弦波,然后经过峰值检波电路,得到其峰值送入Atmega16单片机,由其内部自带ADC处理,并在软件中得到其有效值,经串口发给PC机,并在串口调试助手上显示电压有效值。
关键字:峰值检波 有效值 ADC 串口
*********************************************************************
*********************************************************************
*******************************论文正文******************************
*********************************************************************
一、正弦波发生电路
正弦波发生电路需要四部分:
放大电路:保证电路能够有从起振到动态平衡的过程,使电路获得一定幅值的输出量,实现能量的控制。
选频网络:确定电路的振荡频率,使电路产生单一频率的振荡,即保证电路产生正弦波振荡。
正反馈网络:引入正反馈,作用是使输入信号等于反馈信号。
稳幅环节:也就是非线性环节,作用是使输出信号幅值稳定。
在电路中,可将选频网络和正反馈网络“合二为一”;而且,一般电路中也没有另加稳幅环节,而是依靠运放等的非线性起到稳幅作用。
振荡电路可以有以下三种方案:
方案一:RC桥式正弦波振荡电路
实用的RC正弦波振荡电路有多种多样,我们选择了最典型的RC桥式正弦波振荡电路。此方法简单实用,容易选择器件和电路的调试。它适用于低频振荡,一般用于生产1Hz~1MHz的低频信号。易于起振,成本低廉。我们考虑到题目的要求,所以采用了此方法。
方案二:LC并联谐振回路
采用LC谐振回路作为选频网络的振荡电路称为LC振荡电路,它主要用来生产高频正弦振荡信号,一般在1MHz以上。根据反馈形式的不同,LC振荡电路可分为变压器反馈式和三点式振荡电路。它产生的是高频信号,我们不予考虑。
方案三:石英晶体正弦波振荡电路
当晶片产生振动时,机械振动的惯性等效为电感。考虑题目要求,我们在这里就不讨论了。
综上所述,我们的振荡电路选择了RC桥式正弦波振荡电路。
RC桥式正弦波振荡电路:
本电路中,C1、C2、R3、R4组成选频网络和正反馈网络,正弦波振荡频率1/(2*PI*R*C),33K电阻和4700pF电容均为标准值。振荡出的峰值由VCC的不同而不同,然后靠R5和R6两者适当的分压来得到1V的1000Hz的正弦信号。R1和(R2+R7)组成放大电路,放大倍 数Au=1+(R2+R7)/R1,通过调节R7,可以满足振荡电路放大要求。
二、信号处理电路
要得到正弦信号的有效值可以有以下三种方案:
方案一:得到其峰值,由单片机对其进行处理,即可得到其有效值:
方案二:对正弦波进行整流、滤波处理,使其变成直流,送入单片机自带的A/D进行处理,得到其有效值。由于本题目中的信号幅值较小,仅为1V,因此不能用普通的整流桥电路。但整流后的的直流与有效值的关系难以确定。
方案三:直接把正弦信号送入单片机进行处理。本题目中信号为1KHz,一个周期内采128个点,则AD采样速率须达到128K,单片机内部AD速度达不到。
综上所述,采用峰值检波电路。
峰值检波电路:
如图所示,Ui为1KHz的1V正弦波电压信号输入。电路原理如下:
输出初始值为0,当待测信号幅度高于0时,前级的运放输出高(相当于比较器),二极管导通。后级运放相当于跟随器,输出跟随输入的增大,同时电容充电。当待测信号幅度大于输出时,二极管导通,输出跟随输入,电路工作在“跟随状态”。当待测信号幅度小于输出时,二极管截止,但由于电容之前充电,存储的能量是之前最大幅度时的,因此后级跟随器依然保持之前的最大幅度,电路工作在“保持状态”。
实际中要注意的问题:
1、由于二极管导通电压的存在,最后的峰值要加上导通电压才准确;
2、运算放大器的带宽决定了输入信号的最大频率;
3、由于电容在此处起电能储存的作用,因此要求电容贮能好,简易使用CBB电容,而不应采用像电解电容之类漏电较大的,或者至少采用瓷片电容等。
4、要在外反馈环加电阻R1,否则有时候会产生问题。
5、为了能够进行放电,故并入电阻R2,但选值时要慎重,权衡时间常数。
三、单片机电路
本单片机系统采用外部晶振,使用ISP下载方式,配有具有上电复位和手动复位的复们电路。一般来说,微处理器的电源接入处都加一滤波电容以去除干扰。由于单片机所用为TTL电平,而PC机为RS232电平,因些需用一SP232(或MAX232)电平转换芯片将mega16的串口与PC机的串口相连。
四、软件编程处理
1、Atmega16内部ADC特点:
10位精度;0.5 LSB的非线性度;±2 LSB的绝对精度;65 - 260 µs的转换时间;最高分辨率时采样率高达15 kSPS;8路复用的单端输入通道;7路差分输入通道;2路可选增益为10x与200x的差分输入通道;可选的左对齐ADC读数;0 - VCC的ADC输入电压范围;可选的2.56V ADC参考电压;连续转换或单次转换模式;通过自动触发中断源启动ADC转换;ADC转换结束中断;基于睡眠模式的噪声抑制器。
2、由于本次处理数据为峰值为1V的电压信号,故可考虑使用ADC内部基准,但有一点要注意,为了增加内部VREF的稳定性,就在32管脚VREF处接一电容,如单片机电路图。
以下是采用ADC0通道进行A/D转换的功能函数:
unsigned int mega16_ad()
{
unsigned int addata;
DDRA&=~BIT(PA0);//设PA0口为输入
PORTA&=~BIT(PA0);//不带上拉
ADMUX=0XC0;//使用片内基准电压源,右对齐,选用ADC0通道
ADCSRA=0X80;//
ADCSRA|=BIT(ADSC);//启动AD
while(!(ADCSRA&(BIT(ADIF))));//等待AD转换结束
//读数据寄存器的值
addata=ADCL;
addata=addata+ADCH*256;//0000 00 00 0000 0000
return addata;
}
3、串口发送有两种方式,一种是轮询,一种是中断;考虑到本题目的实际情况和实时性的要求,可采用轮询发送,每隔1秒单片机向PC机发一次数据。串口初始化的程序可以使用用ICCAVR开发环境自带的编程向导完成。
4、考虑到题目对实时性要求不强,因此可以每隔1秒单片机向PC机发一次数据;可以采取两种方案:1)在程序中加1000ms的延时函数,但这样占用太多的CPU资源;2)在程序中,采用单片机的定时器1进行数据发送的控制,这样虽然占用中断资源,但不影响CPU的工作,程序中我们采用此方案。
5、程序流程大致辞如下:单片机ADC接收外部电压信号并进行处理,为了更加精确将多个数据进行平均,当到1秒时产生定时器中断,在Timer1中断服务程序里将ADC处理得到的数据能过串口发送程序发送。具体程序详见附录。
五、误差分析
1、正弦波信号振荡频率和幅值
(1)本电路中正弦波信号频率由公式:1/(2*PI*R*C)决定,电阻电容本身的不准确性将会使振荡频率有误差,但考虑到本题目对精度的要求,频率误差可以不予考虑。
(2)由于振荡电路产生的幅值与运算放大器所加的电源电压有关(运放电源为±5V或±12V,故振荡出的正弦波幅值肯定大于1V,所以应该降压),因此我们用一简单而实用的电阻分压电路对其进行处理得到1V的正弦波。在实际电路中,图一中的R5为1KΩ电阻,R6为1KΩ的滑线变阻器。
2、在峰值检波电路中,二极管会造成一定的电压损耗,这一点可以在软件编程时进行补偿;而在硬件中,也加入了R1反馈回路以减小误差。
3、如图二中,R2和C1构成一放电回路,电路中R2=10KΩ,C1=10µF,其时间常数τ=R2*C1=0.1S。
4、由于检波电路所得电压仍有一定的脉动,因此在软件中进行了多次采样,求平均值以减小波动的思想。由前所述,内部ADC的转换时间为65 - 260 µs,
而所采信号是1KHz的正弦信号进行峰值检波得到了,脉动的周期也应为1KHz,即周期为1ms,我们可以采100次后求期平均(100次连续转换时间为6.5ms-26ms),得到较为稳定的输出,然后再由串口发送。
5、实际电路中,图二中的C1用的是10uF的电解电容,电解电容泄露较大,会给峰值检波结果带来影响。
六、方案的改进
电路如图所示:
我们在前级运放的输出端加一个二极管,与运放的负输入端相连,
1、由于A2是跟随器,正负两个输入端电位是相等的,Rf等于把被充在C1上的电压反馈回了A1的负输入端,跟Ui做比较。
2、当Ui过了峰值,低于C1电压时,D2导通让A1成了跟随器。没有D2的话 A1就成了纯粹的比较器,当Ui低于峰值时输出负电压,给D1造成很大的反压,而D1会在反压下增加漏电流,使得C1的峰值保持性能下降。
3、D1截止时(Ui不一定是负,只是低于峰值而已)C1上的电荷因无处释放,峰值电压将被保持,通过跟随器A2对外输出。所谓“减小D1的非线性误差”想必是相对于把Rf从A1输出引入的做法而言的,这时C1上的电压值比Ui少了D1的正向压降,而且这个压降是非线性的。如果不接D2,也就是A1开环后,当Ui低于峰值时A1输出为负饱和,一则给D1造成很大的反压造成漏电流,对C1保持峰值不利,二则当Ui再度上升至峰值时运算退出饱和状态需有个恢复时间,动作上会产生瞬间脱节。
七、测试数据分析
我们首先对峰值进行了AD采样测试,
实际峰值(V)
0.82
0.90
1.01
1.10
1.22
串口显示(V)
0.71
0.81
0.92
0.99
1.12
差值(V)
0.11
0.09
0.09
0.11
0.10
由上表可以看出,AD采到的峰值比实际峰值小0.1V左右,因此在最终我们编写程序计算有效值时人为的对峰值加了0.1进行了软件修订,以使结果更加准确。
【参考文献】
[1]Atmel Corp.Atmega16 Data Book.
[2]童诗白、华成英.模拟电子技术基础(第三版).北京:高等教育出版社,2001
[3]马潮.高档8位单片机Atmega128原理与开发应用指南(上).北京:北京航空航天大学出版社,2004
[4]百度搜索引擎:
【附:原程序】
//ICC-AVR application builder : 2009-5-26 9:52:27
// Target : M16
// Crystal: 11.059Mhz
#include <iom16v.h>
#include <macros.h>
#include <math.h>
unsigned char table[]="0123456789";
float adc0;//保存有效值
void delay_1ms(void)
{
unsigned int i;
for (i=0;i<1580;i++);//143*crystal-2
}
void delay(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
}
void port_init(void)
{
PORTA = 0x00;
DDRA = 0x00;
PORTB = 0x00;
DDRB = 0x00;
PORTC = 0x00; //m103 output only
DDRC = 0x00;
PORTD = 0x00;
DDRD = 0x03;
}
//UART0 initialize
// desired baud rate: 9600
// actual: baud rate:9600 (0.0%)
// char size: 8 bit
// parity: Disabled
void uart0_init(void)
{
UCSRB = 0x00; //disable while setting baud rate
UCSRA = 0x00;
UCSRC = BIT(URSEL) | 0x06;
UBRRL = 0x47; //set baud rate lo
UBRRH = 0x00; //set baud rate hi
UCSRB = 0x18;
}
void uart_sendB(unsigned char data)//发送数据程序
{
while(!(UCSRA&(BIT(UDRE)))) ;//查询寄存器是否为空
UDR=data;
while(!(UCSRA&(BIT(TXC))));//查询发送是否结束
UCSRA|=BIT(TXC);//清零
}
unsigned int mega16_ad()
{
unsigned int addata;
DDRA&=~BIT(PA0);//设PA0口为输入
PORTA&=~BIT(PA0);//不带上拉
ADMUX=0XC0;//使用片内基准电压源,右对齐,选用ADC0通道0100 0000
ADCSRA=0X80;//
ADCSRA|=BIT(ADSC);//启动AD
while(!(ADCSRA&(BIT(ADIF))));//等待AD转换结束
//读数据寄存器的值
addata=ADCL;
addata=addata+ADCH*256;//0000 00 00 0000 0000
return addata;
}
//TIMER1 initialize - prescale:256
// WGM: 0) Normal, TOP=0xFFFF
// desired value: 1Hz
// actual value: 1.000Hz (0.0%)
void timer1_init(void)
{
TCCR1B = 0x00; //stop
TCNT1H = 0x57; //setup
TCNT1L = 0x41;
OCR1AH = 0xA8;
OCR1AL = 0xBF;
OCR1BH = 0xA8;
OCR1BL = 0xBF;
ICR1H = 0xA8;
ICR1L = 0xBF;
TCCR1A = 0x00;
TCCR1B = 0x04; //start Timer
}
#pragma interrupt_handler timer1_ovf_isr:9
void timer1_ovf_isr(void)
{
unsigned int adc[5],i,k1,k2,k3;
//TIMER1 has overflowed
TCNT1H = 0x57; //reload counter high value
TCNT1L = 0x41; //reload counter low value
//发送实际的电压
k1=(int)adc0;
adc[0]=table[k1];
adc[1]='.';
k2=(int)((adc0-k1)*10);
adc[2]=table[k2];
k3=(int)((adc0-k1-k2*0.1)*100);
adc[3]=table[k3];
adc[4]='V';
for(i=0;i<5;i++)
uart_sendB(adc[i]);
uart_sendB(0x0D);
uart_sendB(0x0A); //结尾发送回车换行
}
//call this routine to initialize all peripherals
void init_devices(void)
{
//stop errant interrupts until set up
CLI(); //disable all interrupts
port_init();
timer1_init();
uart0_init();
MCUCR = 0x00;
GICR = 0x00;
TIMSK = 0x04; //timer interrupt sources
SEI(); //re-enable interrupts
//all peripherals are now initialized
}
//
void main(void)
{
unsigned char i,k1,k2,k3,ad;
float adc1[100],sum=0;
init_devices();
//insert your functional code here...
while(1)
{
for(i=0;i<100;i++)
{
ad=mega16_ad();//读取ADC0的电压
adc1[i]=ad*2.56/1024;//得到峰值
sum+=adc1[i];//累加100次的峰值,以备在后面求平均
}
adc0=(sum/100+0.1)/sqrt(2);//对结果进行修正,并得到有效值
sum=0;
}
}
展开阅读全文