目录
定时器的工作原理在上一篇文章略有说明。
一、定时器的应用
1.1 利用T1控制发出1kHZ的音频型号
应用:利用T1的中断控制P1.7引脚输出频率为1kHz方波音频信号,驱动蜂鸣器发声。系统时钟为12MHz。方波音频信号周期1ms,因此T1的定时中断时间为0.5 ms,进入中断服务程序后,对P1.7求反。电路见图7-18。
先计算T1初值,系统时钟为12MHz,则机器周期为1µs。1kHz音频信号周期为1ms,要定时计数的脉冲数为a。则T1初值:
TH1=(65 536 −a) /256;
TL1=(65 536 −a) %256
代码
#include<reg51.h> //包含头文件
sbit sound=P1^7; //将sound位定义为P1.7脚
#define f1(a) (65536-a)/256 //定义装入定时器高8位时间常数
#define f2(a) (65536-a)%256 //定义装入定时器低8位时间常数
unsigned int i=500;
unsigned int j=0;
void main(void)
{
EA=1; //开总中断.
ET1=1; //允许定时器T1中断 .
TMOD=0x10; //TMOD=0001 000B,使用T1的方式1定时 TH1=f1(i); //给T1高8位赋初值.
TL1=f2(i); //给T1低8位赋初值.
TR1=1; //启动T1
while(1)
{ //循环等待
i=460;
while(j<2000);
j=0;
i=360;
while(j<2000);
j=0;
}
}
void T1(void) interrupt 3 using 0 //定时器T1中断函数
{
TR1= 0; //关闭T1
sound=~sound; //P1.7输出求反
TH1=f1(i); //T1的高8位重新赋初值.
TL1=f2(i); //T1的低8位重新赋初值.
j++;
TR1=1; //启动定时器T1
}
原理图
仿真效果
仿真效果为左下角的蜂鸣器发声,在普中开发板上验证也是同样的效果。
1.2 LED数码管秒表的制作
数码管一种是半导体发光器件,数码管可分为七段数码管和八段数码管,区别在于八段数码管比七段数码管多一个用于显示小数点的发光二极管单元DP(decimal point),其基本单元是发光二极管。
我们本次实验用到的为七段数码管。七段数码管是基于发光二极管(LED)封装的显示器件,分为共阳极和共阴极2种结构。共阳极的七段数码管(7SEG-MPX1-CA)的正极(或阳极)为八个发光二极管的共有正极,其他接点为独立发光二极管的负极(或阴极),使用者只需把正极接电,不同的负极接地就能控制七段数码管显示不同的数字。共阴极的七段数码管(7SEG-MPX1-CC)与共阳极的只是接驳方法相反而已。
应用:用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清0。再次按一下计时功能键,则重复上述计时过程。
本秒表应用定时器模式,计时范围0.1~9.9s。此外还涉及如何编写控制LED数码管显示的程序。
原理图
代码
#include<reg51.h> //头文件
unsigned char code discode1[]= {0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};
//数码管显示0~9的段码表, 带小数点
unsigned char code discode2[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//数码管显示0~9的段码表,不带小数点
unsigned char timer=0; //timer记录中断次数
unsigned char second; //second储存秒
unsigned char key=0; //key记录按键次数
main() //主函数
{
TMOD=0x01; //定时器T0方式1定时
ET0=1; //允许定时器T0中断
EA=1; //总中断允许
second=0; //设初始值
P0=discode1[second/10]; //显示秒位0
P2=discode2[second%10]; //显示0.1s位0
while(1) //循环
{
if((P3&0x80)==0x00) //当按键被按下时
{
key++; //按键次数加1
switch(key) //根据按键次数分三种情况
{
case 1: //第一次按下为启动秒表计时
TH0=0xee; //向TH0写入初值的高8位
TL0=0x00; //向TL0写入初值的低8位,定时5ms
TR0=1; //启动定时器T0
break;
case 2: //按下两次暂定秒表
TR0=0; //关闭定时器T0
break;
case 3: //按下3次秒表清0
key=0; //按键次数清
second=0; //秒表清0
P0=discode1[second/10]; //显示秒位0 P2=discode2[second%10]; //显示0.1s位0
break;
}
while((P3&0x80)==0x00); //如果按键时间过长在此循环
}
}
}
void int_T0() interrupt 1 using 0 //定时器T0中断函数
{
TR0=0; //停止计时,执行以下操作(会带来计时误差)
TH0=0xee; //向TH0写入初值的高8位
TL0=0x00; //向TL0写入初值的低8位,定时5ms
timer++; //记录中断次数
if (timer==20) //中断20次,共计时20*5ms=100ms=0.1s
{
timer=0; //中断次数清0
second++; //加0.1s
P0=discode1[second/10]; //根据计时,即时显示秒位 P2=discode2[second%10]; //根据计时,即时显示0.1s位 }
if(second==99) //当计时到9.9s时
{
TR0=0; //停止计时
second=0; //秒数清0
key=2; //按键数置2,当再次按下按键时, //key++,即key=3,秒表清0复原
}
else //计时不到9.9s时
{
TR0=1; //启动定时器继续计时
}
}
仿真效果
普中开发板
#include<REGX52.h>
typedef unsigned char uchar;
uchar code leddata[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
char s=56,m=59,h=23;
int n;
void Delay(unsigned int xms){
unsigned char i, j;
while(xms--){
i = 2;
j = 239;
do{
while (--j);
} while (--i);
}
}
void init(){
TMOD=0x01;//??? 0 ??? 1,?? 4ms 0000 0001
TH0=(65536-4000)/256;
TL0=(65536-4000)%256;
ET0=1;
EA=1;
TR0=1;
}
void display(uchar hour,uchar minute,uchar second){
int i;
for(i=1;i<=8;i++){
switch(i){
case 1: P2_4=0;P2_3=0;P2_2=0;P0=leddata[second%10];break;
case 2: P2_4=0;P2_3=0;P2_2=1;P0=leddata[second/10];break;
case 3: P2_4=0;P2_3=1;P2_2=0;P0=0x40;break;//?? —
case 4: P2_4=0;P2_3=1;P2_2=1;P0=leddata[minute%10];break;
case 5: P2_4=1;P2_3=0;P2_2=0;P0=leddata[minute/10];break;
case 6: P2_4=1;P2_3=0;P2_2=1;P0=0x40;break;//?? —
case 7: P2_4=1;P2_3=1;P2_2=0;P0=leddata[hour%10];break;
case 8: P2_4=1;P2_3=1;P2_2=1;P0=leddata[hour/10];break;
}
Delay(1);
P0=0;
}
}
void key_scan(){
static uchar flag=0;
if(P3_1==0){
Delay(20);
while(P3_1==0)
/*display(h,m,s)*/;
Delay(20);
TR0=0;//??????
flag++;
if(flag==4){
flag=0;
TR0=1;
}
}
if(flag!=0){
switch(flag){
case 1:if(P3_0==0){ Delay(20);while(P3_0==0)display(h,m,s);Delay(20);h++; if(h>23) h=0;}break;
case 2:if(P3_0==0){ Delay(20);while(P3_0==0)display(h,m,s);Delay(20);m++; if(m>59) m=0;}break;
case 3:if(P3_0==0){ Delay(20);while(P3_0==0)display(h,m,s);Delay(20);s++; if(s>59) s=0;}break;
default:break;
}
switch(flag){
case 1:if(P3_2==0){ Delay(20);while(P3_2==0)display(h,m,s);Delay(20);h--; if(h<0) h=23;}break;
case 2:if(P3_2==0){ Delay(20);while(P3_2==0)display(h,m,s);Delay(20);m--; if(m<0) m=59;}break;
case 3:if(P3_2==0){ Delay(20);while(P3_2==0)display(h,m,s);Delay(20);s--; if(s<0) s=59;}break;
default:break;
}
}
}
void main(){
init();
while(1){
display(h,m,s);
key_scan();
}
}
void timer0() interrupt 1
{
TH0=(65536-4000)/256;
TL0=(65536-4000)%256;
n++;
if(n==250)
{
n=0;
s++;
if(s>=60)
{
s=0;
m++;
if(m>=60)
{
m=0;
h++;
if(h>=24)
h=0;
}
}
}
}
1.3 LCD时钟的设计
LCD1602(Liquid Crystal Display)是一种常见的字符型液晶显示屏,它能显示两行共16个字符。
LCD1602模块通常由以下几个主要部分组成:
1)液晶显示屏:显示字符和图形。
2)控制器:例如HD44780U,负责处理来自微控制器的命令和数据,并将其转换为屏幕上的显示。
3)背光模块:为液晶显示屏提供背景照明。
4)接口引脚:包括数据引脚(D0-D7)和控制引脚(如RS - 数据/命令选择,R/W - 读/写选择,E - 使能)。
LCD1602的引脚具体如下所示:
应用:使用定时器实现一个LCD显示时钟。采用LCD 1602,最小计时单位是秒,如何获得1s的定时?可将T0定时时间定为50ms,采用中断方式进行溢出次数累计,满20次,则秒计数变量second加1;若秒计满60,则分计数变量minute加1,同时将秒计数变量second清0;若分钟计满60,则小时计数变量hour加1;若小时计数变量满24,则将小时计数变量hour清0。
原理图
代码
#include"reg51.h"
sbit RS=P3^0;
sbit RW=P3^1;
sbit E=P3^2;
unsigned char s[]={"0123456789"};
unsigned char s1[]={"clock:"};
unsigned char num=0,sec=0,min=25,hour=12;
unsigned char temp0=0,temp1=0,temp2=0,temp3=0,temp4=0,temp5=0;
void delay(unsigned int m)
{
unsigned int i=0,j=0;
for(i=0;i<m;i++)
{
for(j=0;j<120;j++);
}
}
void writecom(unsigned char com)
{
RS=0;
RW=0;
E=0 ;
P2=com;
delay(5);
E=1;
E=0;
}
void writedat(unsigned char dat)
{
RS=1;
RW=0;
E=0 ;
P2=dat;
delay(5);
E=1;
E=0;
}
void initlcd()
{
writecom(0x38);
writecom(0x0c);
writecom(0x06);
writecom(0x01);
}
void inittime()
{
TMOD=0x01;//???0
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
EA=1;
ET0=1;
TR0=1;
}
void display()
{
unsigned int i=0;
temp0=sec%10;
temp1=sec/10;
temp2=min%10;
temp3=min/10;
temp4=hour%10;
temp5=hour/10;
writecom(0x80+0x46);
delay(5);
writedat(s[temp5]);
delay(5);
writedat(s[temp4]);
delay(5);
writedat(':');
delay(5);
writedat(s[temp3]);
delay(5);
writedat(s[temp2]);
delay(5);
writedat(':');
delay(5);
writedat(s[temp1]);
delay(5);
writedat(s[temp0]);
delay(5);
writecom(0x80);
delay(5);
while(s1[i]!='\0')
{
writedat(s1[i]);
i++;
}
}
void main()
{
initlcd();
inittime();
while(1)
{
display();
}
}
void inittime_isr() interrupt 1
{
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
num++;
if(num==20)
{
sec++;
num=0;
}
if(sec==60)
{
min++;
sec=0;
}
if(min==60)
{
hour++;
min=0;
}
if(hour==24)
{
hour=0;
}
}
仿真效果
二、串口通信的应用
2.1 串口通信的工作原理
串口通信(Serial Communication)是一种基于串行通信的数据传输方式,它通过单一的数据线逐位地传输数据。串口通信的工作原理可以从以下几个方面来理解:
1)数据传输模式:串口通信使用串行传输模式,这意味着数据是按位顺序发送的,一次只能发送一个位。这与并行通信不同,后者同时通过多个线路发送多个位。
2)数据格式:串口通信通常涉及定义数据的起始位、数据位、校验位(可选)和停止位。起始位标志着数据传输的开始,数据位是实际传输的数据,校验位用于错误检测,停止位表示一个数据单元的结束。
3)波特率:波特率(Baud Rate)是指每秒钟传输的符号数,它是串口通信中的一个重要参数。波特率决定了数据传输的速度,常见的波特率有9600、19200、38400等。
4)信号线:串口通信至少需要两根信号线,一根用于发送(TX),一根用于接收(RX)。在某些情况下,还会使用额外的线,如地线(GND)和握手线(如RTS - 请求发送,CTS - 清除发送)。
5)同步与异步通信:
异步通信:不需要时钟信号来同步数据。发送和接收设备通过预定义的波特率来同步。每个数据包都是独立的,由起始位和停止位界定。
同步通信
同步通信:需要时钟信号来同步数据。发送和接收设备通过共享的时钟信号来同步数据传输。这种通信方式通常用于高速数据传输。
异步通信
6)流控制:在数据传输过程中,流控制是一种机制,用于防止数据丢失或溢出。常见的流控制方法有软件流控制和硬件流控制。软件流控制通常使用XON/XOFF字符,而硬件流控制使用RTS/CTS信号。
7)错误检测与校正:串口通信中可能会出现错误,如噪声干扰导致的数据位错误。为了检测这些错误,可以使用校验位(如奇偶校验)来验证数据的完整性。在某些情况下,还可以使用更复杂的错误校正码(如循环冗余校验CRC)。
串口通信由于其简单性和灵活性,在嵌入式系统、工业控制和计算机通信中被广泛使用。随着技术的发展,串口通信也逐渐被更高速的通信接口如USB所取代,但在许多应用中,串口通信仍然是一个重要的通信手段。
2.2 甲乙两个单片机串口通信
甲、乙两单片机进行串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED。乙机接收到的8位二进制数据有可能出错,需进行奇偶校验,其方法是将乙机的RB8和PSW的奇偶校验位P进行比较,如果相同,接收数据;否则拒绝接收。本实验使用一个虚拟终端来观察甲机串口发出的数据。
原理图
代码
甲机发送程序
#include <reg51.h>
sbit T_P=PSW^0;
unsigned char Tab[8]= {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f};
void Send(unsigned char dat)
{
TB8=T_P;
SBUF=dat;
while(TI==0);
TI=0;
}
void delay (void)
{
unsigned char m,n;
for(m=0;m<250;m++)
for(n=0;n<250;n++);
}
void main(void)
{
unsigned char i;
TMOD=0x20;
SCON=0xc0;
PCON=0x00;
TH1=0xfd;
TL1=0xfd;
TR1=1;
while(1)
{
for(i=0;i<8;i++)
{
Send(Tab[i]);
delay( );
}
}
}
乙机接收程序
#include <reg51.h>
sbit R_P=PSW^0;
unsigned char Receive(void)
{
unsigned char dat;
while(RI==0);
RI=0;
ACC=SBUF;
if(RB8==R_P)
{
dat=ACC;
return dat;
}
}
void main(void)
{
TMOD=0x20;
SCON=0xd0;
PCON=0x00;
TH1=0xfd;
TL1=0xfd;
TR1=1;
REN=1;
while(1)
{
P1= Receive( );
}
}
仿真效果
三、总结
此次学习让我认识到了理论与实践相结合的重要性,理论知识需要通过实践进行巩固。此外,通过本次学习,我了解到了51单片机定时器与串口通信的基本概念和用法。定时器能够帮助我们实现精准的时间控制,而串口通信则是实现数据交互的重要手段。文章存在许多不足,欢迎大家指正!