实验具体内容

在 Proteus 环境下搭建如下图所示的电路图:

img

控制要求:

本实验利用 LCD1602 和 AD0808 实现简单的交流信号过零检测与频率分析。要求信号幅度变化时(满量程的 5%-95%) ,不影响检测的结果。频率检测的结果通过 LCD1602 的第一行显示出来,信号过零时,能够通过 P2.6 输出一个脉冲宽度为 5us 的脉冲信号。

编程思路

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 理论计算值和实际输出值可能也会有一定的差距,也要进行调整。

流程图

image-20220212125745202

实验过程

① 根据上述实验内容,参考 1.2.2,在 Proteus 环境下建立图3.22 所示原理图,并将其保存为 ADC0808_self.DSN 文件。

② 根据控制要求和编程思路画出流程图,并编写源程序,将其保存为 ADC0808_self.c。

③ 运行 Keil uVision2 开发环境,按照 1.1.3 节介绍的方法建立工程ADC0808_self.uV2,CPU 为 AT89C51,包含启动文件STARTUP.A51。

④ 按照 1.2.2 第(6)节介绍的方法将 C 语言源程序ADC0808_self.c 加入工程ADC0808_self.uV2,并设置工程ADC0808_self.uV2 属性,将其晶振频率设置为12MHz,选择输出可执行文件,仿真方式为选择硬仿真,并选择其中的“PROTEUS VSM MONITOR 51 DRIVER”仿真器。

⑤ 构造(Build)工程 ADC0808_self.uV2。如果输入有误进行修改,直至构造正确,生成可执行程序 ADC0808_self.hex 为止。

⑥ 为 AT89C51 设置可执行程序 ADC0808_self.hex。

⑦ 运行程序,观察计算结果,并验证其是否正确。

⑧ 改变 RV1 的抽头位置,从而改变输入信号的幅值,观察计算结果是否正确。

⑨ 更改信号发射器的频率,再次验证其功能是否正确。(注意:因为是软仿真,所以信号采集的速度受到限制,因此所输入的交流信号频率也不能太高,可以在 200Hz以内尝试)。

实验截图:

5%幅值:

img

95%幅值:

img

150Hz,幅值5V信号频率采集:

img

实验源程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#include <reg51.h>
#include <intrins.h> //包含_nop_()函数
#include <stdio.h>   //包含sprintf()函数
#include <string.h>  //包含各种字符串处理函数
#define uint unsigned int
#define uchar unsigned char
uchar data line_data[16]; //要显示的一行字符
sbit LCD_RS=P2^0;
sbit LCD_RW=P2^1;
sbit LCD_EN=P2^2;
sbit CLK=P2^3;
sbit Start=P2^4;
sbit OE=P2^5;
sbit Out_pulse=P2^6;
sbit EOC = P2^7;
uint Hz_count;
uchar n,count0,data_old,data_new;
bit Hz_sum;
/**********************************延时子程序**********************/
void delay_ms(uint xms)
{
   uint i,j;
   for(i=xms;i>0;i--)
   for(j=110;j>0;j--);
}

/**********************************LCD 忙检查子程序
**********************/
bit lcd_busy()
{
   bit flag;
   LCD_RS = 0;
   LCD_RW = 1;
   LCD_EN = 1;
   _nop_();
   _nop_();
   _nop_();
   _nop_();            //空操作4us,给硬件反应时间
   flag = (bit)(P1&0x80);
   LCD_EN = 0;
   return flag;
}
/****************************定时器1中断服务子程序*********************/
void s_timer1() interrupt 3
{
   CLK=~CLK;
}

/****************************定时器0中断服务子程序*********************/
void s_timer0() interrupt 1
{
   TH0=0x3C;           //定时50ms
   TL0=0x0B0;
   count0++;
   if(count0==20)      //定时1s执行
  {
       Hz_sum = 1;
       count0=0;       //清零count,重新定时1s
  }  
}
/***************************写命令子程序****************************/
void lcd_wcmd(uchar cmd)
{
while(lcd_busy());
LCD_RS=0;
LCD_RW=0;
LCD_EN=0;
_nop_();
_nop_();
P1=cmd;
_nop_();
_nop_();
_nop_();
_nop_();
LCD_EN=1;
_nop_();
_nop_();
_nop_();
_nop_();
LCD_EN=0;
}
/******************************LCD 清屏子程序*************************/
void lcd_clr()
{
lcd_wcmd(0x01);
delay_ms(2);
}
/****************************写数据子程序*************************/
void lcd_wdat(uchar dat)
{
while(lcd_busy());
LCD_RS=1;
LCD_RW=0;
LCD_EN=0;
_nop_();
_nop_();
P1=dat;
_nop_();
_nop_();
_nop_();
_nop_();
LCD_EN=1;
_nop_();
_nop_();
_nop_();
_nop_();
LCD_EN=0;
}
/***************************lcd初始化子程序*************************/
void lcd_init()
{
   delay_ms(15);       //等待LCD电源稳定
   lcd_wcmd(0x30);     //16*1显示,5*7点阵
   delay_ms(5);
   lcd_wcmd(0x0c);     //显示开,无光标
   delay_ms(5);
   lcd_wcmd(0x06);     //移动光标
   delay_ms(5);
   lcd_wcmd(0x01);     //清除LCD显示内容
   delay_ms(5);
}
void sysinit()
{
   Hz_count = 0;      //过零次数初始化为0
   CLK = 0;          
   OE = 0;            //LCD的读写
   TMOD=0x21;         //T0工作在方式1,T1工作在方式 2
   TH1=0xF6;          //50KHz,周期为20us,作10us定时
   TL1=0xF6;
   TH0=0x3C;           //定时器0定时50ms
   TL0=0x0B0;
   EA=1;               //开启总中断
   ET1=1;             //定时器1中断允许
   TR1=1;             //启动定时器1        
   ET0=1;             //定时器0中断允许
   TR0=1;             //启动定时器0
}
uchar sample()           //ADC0808 数据采集触发子程序
{
uchar temp;
   Start = 1; //启动 AD转换
   Start = 0;
   while(EOC==0);
   P0 = 0xff;
   OE = 1;             //打开ADC0808的输出使能
   temp = P0;
   OE = 0;             //关闭ADC0808的输出使能
   return(temp);
}
void main()
{
   uchar i,Hz_result[6];
   delay_ms(10);
   lcd_init();
   sysinit();
   while(1)
  {
         
           data_new = sample(); //采集
           if(((data_new>128)&&(data_old<128))((data_new<128)&&(data_old>128)))
          {
               Hz_count++;          //记录一次过零
               Out_pulse=1;        //产生一个5us的脉冲表示一次过零
               _nop_();
               _nop_();
               _nop_();
               _nop_();
               _nop_();
               Out_pulse=0;        
               data_old = data_new;    //把新采集数据的正负赋给old,以便与下一个新数据比较
          }  

           if(Hz_sum==1)
          {
           lcd_clr();
           Hz_count = Hz_count/2;
           memset(line_data,0,16);
           n = 0;
           lcd_wcmd(0x000x80);       //在lcd第1行第1列显示输出
           line_data[n++]='f';
           line_data[n++]=':';
           sprintf(Hz_result,"%d",Hz_count);
           strcat(line_data,Hz_result);
           while(line_data[++n]!='\0');
           line_data[n++]='H';
           line_data[n++]='z';
           line_data[n]='\0';
           i=0;
           while(line_data[i]!='\0')
          {
               lcd_wdat(line_data[i]);
               i++;
          }
           Hz_sum = 0;
           Hz_count = 0;
          }
  }
}