一、实验题目
3.8 ADC0808信号采集实验
二、实验要求
1、画出实验的流程图
2、编写源程序并进行注释
3、记录实验过程
4、记录程序运行结果截图
三、实验过程及结果分析
利用LCD1602和AD0808实现简单的交流信号过零检测与频率分析。要求信号幅度变化时(满量程的5%—95%),不影响检测到结果。频率检测的结果通过LCD1602的第一行显示出来,信号过零时,能够通过P2.6输出一个脉冲宽度为5μs的脉冲信号。
- 根据上述实验内容,在Proteus 环境下建立图1所示原理图,并将其保存为ADC0808_self.DSN 文件。
图1:实验电路图
- 编写源程序,将其保存为ADC0808_self.c,运行Keil开发环境,建立工程ADC0808_self.uV2,CPU 为AT89C51,包含启动文件STARTUP.A51。
- 将C 语言源程序ADC0808_self.c 加入工程ADC0808_self.uV2,并设置工程ADC0808_self.uV2 属性,将其晶振频率设置为12MHz,选择输出可执行文件,仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。
4.构造(Build)工程ADC0808_self.uV2。如果输入有误进行修改,直至构造正确,生成可执行程序ADC0808_self.hex 为止。为AT89C51 设置可执行程序ADC0808_self.hex。
- 运行程序,观察计算结果,并验证其是否正确。改变RV1 的抽头位置,从而改变输入信号的幅值,观察计算结果是否正确。更改信号发射器的频率,再次验证其功能是否正确。(注意:因为是软仿真,所以信号采集的速度受到限制,因此所输入的交流信号频率也不能太高,可以在200Hz以内尝试)。
实验结果如下:
1)将电位器至于满量程的90%,此时信号的幅值就是最大值的90%
图2:电位器调整到满量程的90%
2)将电位器至于满量程的50%,此时信号的幅值为最大值的一半
图3:电位器调整到满量程的50%
3)电位器调整到满量程的10%,此时信号的幅值为最大值的10%
图4:电位器调整到满量程的10%
四、实验流程图
图5:实验流程图
LCD1602的控制方法按3.7节所示方法进行ADC0808的控制方法按3.8.1所示方法进行。这里主要是过零点的检测方法如何实现。不能采用判断所采集到的数据是否为 0 的方法来实现,因为你的采集时刻不一定能够严格对准过零时刻。但是,我们注意到在 0 点的两边信号的极性是发生变化的,我们可以利用这一特点来实现过零检测。正弦波每个周期有两个过零点,因此,1s 内过零次数除以 2 就是信号的频率。
因此,在程序中可以这样实现。当每次采集到一个新的数据之后都要看一下这个数据是正数还是负数。当这个数大于 128 时是正数,当它小于 128 时是负数。判断当前数据的正负极性和上一个数据的正负极性是否一致,如果不一致,则说明经过了一次过零点,将其记录入次数计数器。
ADC0808 的 CLK 仍然用定时器 T1 来实现,可以将其设置为 50kHz(硬件实现时可以更高,软件仿真再高将难以实现)。利用定时器 T0 实现 50ms 定时,并配合软件实现 1s 钟定时。采用 12M 晶振时,T0 采用方式 1,则处置应为(TH0=0x3C,TL0=0xB0)。
但是,由于中断处理函数需要一定的响应时间,因此这个参数只是理论计算结果,要根据实测情况稍作调整。 同样 T1 理论计算值和实际输出值可能也会有一定的差距,也要进行调整。
五、实验源代码
include "reg51.h" // 单片机寄存器头文件
include "intrins.h" // _ nop_()等函数定义的头文件
sbit LCD_RS=P2^0; //寄存器选择位,将RS位定义为P2.0引脚
sbit LCD_RW=P2^1; // 读写选择位,将RW位定义为P2.1引脚
sbit E=P2^2; //使能信号位,将E位定义为P2.2引脚
sbit BF=P1^7; // P0.7为忙碌标志位
sbit CLK=P2^3; // 设置时钟端口
sbit start=P2^4;
sbit oe=P2^5;
sbit eoc=P2^7;
sbit out_pulse=P2^6; // P2^6端口输出5us脉冲
sbit p30=P3^0;
define uchar unsigned char
define uint unsigned int
uchar n=0;
uchar flag=0;
void delay1ms()// 定义延时1ms函数
{
unsigned char i,j;
for(i=0;i<10;i++)
for(j=0;j<33;j++);
}
void delay(unsigned int n) //定义延时nms函数
{
unsigned int i;
for(i=0;i<n;i++)
delay1ms();
}
bit BusyTest(void) //检查子程序,为1时繁忙,为0时不忙
{
bit result;
LCD_RS=0;
LCD_RW=1;
E=1;
_nop_();
_nop_();
_nop_();
_nop_();
result=BF;
E=0;
return result;
}
void Write_com (uchar cmd) //写命令子程序
{
while(BusyTest()==1);
LCD_RS=0;
LCD_RW=0; // RS/RW都是0的时,写指令
E=0;
_nop_();
_nop_();
P1=cmd; // 写指令操作
_nop_();//运行一个机器周期
_nop_();
_nop_();
_nop_();
E=1;
_nop_();
_nop_();
_nop_();
_nop_();
E=0; // E由高跳低写入
}
void WriteAddress(unsigned char x)
{
Write_com(x|0x80); //显示位置为"80H+地址码x"
}
void WriteData(unsigned char y) //写数据子程序
{
while(BusyTest()==1);
LCD_RS=1;
LCD_RW=0;
E=0;
P1=y; // 执行操作
_nop_(); //运行一个机器周期
_nop_();
_nop_();
nop_();
E=1;
_nop_();
_nop_();
_nop_();
_nop_();
E=0;
}
void LcdInt(void) //初始化LCD
{
delay(15); // 延时15ms,让电源稳定
Write_com(0x38); //设置显示,即16×2显示,5×7点阵,8位数据接口
delay(5); //延时5ms
Write_com(0x0c); //设置显示:显示开,有光标,光标闪烁
delay(5);
Write_com(0x06); D=1,S=0地址自加1
delay(5);
Write_com(0x01); // 清屏幕操作
delay(5);
}
void sysinit() //系统初始化
{
TMOD = 0x21; // 设定定时器T1工作方式为自动重装8位计数器,T0工作方式为方式1(16位计数器)
TH1=0xfd; //利用中断产生CLK信号
TL1=0xfd;
EA=1; //开总中断
ET1=1; //T1中断允许
TR1=1; //启动定时器T1
TH0=0x45; // 置初值
TL0=0x00; //50MS定时
ET0=1; //T0中断允许
TR0=1; //启动定时器T0
start=0; //A/D转换启动信号起始为0
oe=0; //数据输出允许信号起始为0
}
void t0(void) interrupt 1 //T0中断服务程序
{
ET0=0; // 关中断
TH0=0X45;
TL0=0X00;
n++;
if(n==20) {flag=1;n=0;} //循环20次,即完成1S定时
ET0=1; //开T0中断允许
}
void t1(void) interrupt 3 // 定时器T1中断服务程序
{
ET1=0; // 关闭中断
CLK=~CLK; // 每中断一次,取上一次clk信号的反,如此便产生了50KHz的周期性方波信号
ET1=1; //开启中断
}
unsigned char adc()//数据采集子程序
{
unsigned char Temp;
start=1;
start=0; //启动信号
while (!eoc); // A/D转换结束信号,0代表转换完成,可以输出
P0=0xff; // 读之前先写入1
oe=1;
Temp=P0; //读取采集数据
oe=0;
return(Temp);
}
void display(uint a)//显示子函数
{
uint bai,shi,ge;
bai=a/100;
shi=(a-bai*100)/10;
ge=a%10;
WriteAddress(2);
WriteData(0x30+bai); //显示百位
WriteData(0x30+shi); //显示十位
WriteData(0x30+ge); //显示个位
}
void main() // 定义主程序
{
uint temp1,temp2;
uchar f=0;
LcdInt();
delay(5);
sysinit();
CLK=0;
WriteAddress(0); //从第0行开始显示
WriteData('f'); //显示 f ; 等
WriteData(':');
WriteAddress(5); //从第5行开始显示
WriteData('H'); //显示H
WriteData('z'); //显示z
while(1) //一直循环
{
temp2=temp1;
temp1=adc();//ad采集
if(((temp1>=128)&&(temp2<=128))||((temp1<=128)&&(temp2>=128))) //过零时两次采集极性相反
{
f++;
out_pulse=1; //过零时输出脉冲信号
_nop_(); //运行一个机器周期
_nop_();
_nop_();
_nop_();
_nop_();
out_pulse=0; //产生5us的脉冲信号
}
if(flag==1) //1s后计算频率
{
flag=0; // 标志位清0
f=f/2; // 一个周期两次过零点
display(f); //显示频率
f=0;
}
}
}