【51单片机】单片机定时器与串口通信应用
文章目录
一、使用中断驱动蜂鸣器
A.实验分析与实现
(1)利用T1的中断控制P1.7引脚输出频率为1kHz方波音频信号,驱动蜂鸣器发声。
分析:系统时钟为12MHz。方波音频信号周期1ms,因此T1的定时中断时间为0.5 ms,进入中断服务程序后,对P1.7求反。
先计算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
}
效果图:
B.蜂鸣器的使用与简介
1蜂鸣器分类
先来看两组蜂鸣器的图片。
第一张图:
第二张图:
这两幅图都是蜂鸣器,但是存在区别,你看出来了吗?第一张图的蜂鸣器可以看到内部有电路板,而第二张图是浇封起来的。蜂鸣器从驱动方式上来分可以分为无源蜂鸣器和有源蜂鸣器。第一张图就是无源的,第二张图就是有源。
2蜂鸣器的驱动方式
无源蜂鸣器是没有正负之分的,类似于喇叭,只要在两个腿上加载不同的频率的电信号就可以实现发声,根据不同的频率所发出的声音也是不一样的。
有源蜂鸣器是有正负之分的,只需要在两个腿上加上电压信号就会发声,发出的声音音调单一、频率固定。
有源蜂鸣器比无源蜂鸣器内部多了振荡结构,所以有源蜂鸣器在价格上稍微贵一点。
3蜂鸣器的驱动电路
单片机通过三极管驱动蜂鸣器的电路如下所示:
如果要驱动有源蜂鸣器只需要在三极管的基极上给高电平就可以了。如果要在三极管的基极上加载不同频率的信号。信号如下图所示。
单片机只要通过定时器来定时翻转IO口就可以产生不同频率,从而控制无源蜂鸣器发出不同的声音。乐谱与频率的关系如下图所示:
二、LED数码管秒表与LCD1602显示时钟
A.LED数码管秒表
用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清0。再次按一下计时功能键,则重复上述计时过程。
代码实现
-
#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; //启动定时器继续计时 } }
B.LCD1602显示时钟
使用定时器实现一个LCD显示时钟,采用LCD 1602。
代码如下:
#include <REGX52.H>
//LCD引脚配置
sbit LCD_RS=P3^5;
sbit LCD_RW=P3^6;
sbit LCD_EN=P3^7;
#define LCD_DataPort P2
unsigned char Hour=23,Min=59,Sec=55;
//LCD延时函数
void Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
//LCD写命令
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
Delay();
LCD_EN=0;
Delay();
}
//LCD写数据
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
Delay();
LCD_EN=0;
Delay();
}
//LCD设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
//LCD显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
//LCD显示数字
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
//LCD初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
//定时器初始化
void Timer0_Init()
{
TMOD=0x01;
TH0=0xFc;
TL0=0x66;
TF0=0;
TR0=1;
ET0=1;
EA=1;
}
void main()
{
LCD_Init();
Timer0_Init();
LCD_ShowString(1,1," : : ");
while(1)
{
LCD_ShowNum(1,1,Hour,2);
LCD_ShowNum(1,4,Min,2);
LCD_ShowNum(1,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TH0=0xFc;
TL0=0x66;
T0Count++;
if(T0Count==1000)
{
Sec++;
T0Count=0;
}
if(Sec>=60)
{
Sec=0;
Min++;
}
if(Min>=60)
{
Min=0;
Hour++;
}
if(Hour>=24)
{
Hour=0;
}
}
C.数码管与LCD1602简介
一、数码管电路
1、单个数码管
一个数码管是由a、b、c、d、e、f、g、dp八个二极管组成,八个LED一端接在一起,另一端引脚引出来。二极管如果阳极连在一起,就是共阳极数码管,阴极连在一起,就是共阴极数码管。
如上图,是一个共阴极数码管,要使数码管显示不同的数字,只需点亮对应LED即可。如:数码管显示“0”,则a、b、c、d、e、f六个LED亮,g、dp这俩个LED灭,即可显示“0”。
2、多个数码管
上图所示的六个数码管,在使用时,需要程序选定使用哪几个数码管,这就是“位选”,选定数码管后再对选定的数码管进行操作,其操作与单个数码管的操作一致,这就是“段选”。
3.LCD与LCD1602
-
LCD ( Liquid Crystal Display 的简称)液晶显示器。能够同时显示16x2,32个字符,是一种专门用来显示字母、数字、符号等的点阵型液晶模块。
-
LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。该显示屏的优点是耗电量低、体积小、辐射低。
-
LCD1602主要用来显示数字、字母、图形以及少量自定义字符。可以显示2行16个字符,拥有16个引脚,其中8位数据总线D0-D7,和RS、R/W、EN三个控制端口,工作电压为5V,并且带有字符对比度调节V0和背光源AK。
-
LCD1602主要参数
显示字符:16×2个字符
工作电压:4.5~5V
工作电流:2.0mA
工作温度:-20°C~70°C
模块最佳工作电压:5.0V
单个字符尺寸2.95×4.35(W×Hmm)
引脚:16脚
三、串口通信
A.实验与实现
甲、乙两单片机进行 方式3(或方式2)串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED。方式3比方式1多了1个可编程位TB8,该位一般作奇偶校验位。乙机接收到的8位二进制数据有可能出错,需进行奇偶校验,其方法是将乙机的RB8和PSW的奇偶校验位P进行比较,如果相同,接收数据;否则拒绝接收。
甲机代码如下:
#include <reg51.h>
sbit P=PSW^0; //P位为PSW寄存器的第0位,即奇偶校验位
unsigned char Tab[8]= {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf,
0xbf, 0x7f}; //控制流水灯显示数据数组,为全局变量
void main(void) //主函数
{
unsigned char i;
TMOD=0x20; //设置定时器T1为方式2
SCON=0xc0; //设置串口为方式3
PCON=0x00; //SMOD=0
TH1=0xfd; //给T1赋初值,波特率设置为9600
TL1=0xfd;
TR1=1; //启动定时器T1
while(1)
{
for(i=0;i<8;i++)
{
Send(Tab[i]);
delay( ); //大约200ms发送一次数据
}
}
}
void Send(unsigned char dat) // 发送1字节数据的函数
{
TB8=P; // 将偶校验位作为第9位数据发送
SBUF=dat;
while(TI==0); //检测TI,TI=0,未发送完
; // 空操作
TI=0; // 1字节发送完,TI清0
}
void delay (void) // 延时约200ms的函数
{
unsigned char m,n;
for(m=0;m<250;m++)
for(n=0;n<250;n++);
}
乙机代码如下:
#include <reg51.h>
sbit P=PSW^0; // P位为PSW 寄存器的第0位,即奇偶校验位
void main(void) //主函数
{
TMOD=0x20; //设置定时器T1为方式2
SCON=0xd0; //设置串口为方式3,允许接收REN=1
PCON=0x00; // SMOD=0
TH1=0xfd; //给定时器T1赋初值,波特率为9600
TL1=0xfd;
TR1=1; //接通定时器T1
REN=1; //允许接收
while(1)
{
P1= Receive( ); //将接收到的数据送P1口显示
}
}
unsigned char Receive(void) //接收1字节数据的函数
{
unsigned char dat;
while(RI==0); //检测RI,RI=0,未接收完,则循环等待
;
RI=0; //已接收一帧数据,将RI清0
ACC=SBUF; //将接收缓冲器的数据存于ACC
if(RB8==P) //只有偶校验成功才能往下执行,接收数据
{
dat=ACC; //将接收缓冲器的数据存于dat
return dat; //将接收的数据返回
}
}
实验结果:
B.串口通信
串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。典型地,串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
波特率
这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。一般调制速率大于波特率,比如曼彻斯特编码)。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
数据位
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
奇偶校验位
码)。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
数据位
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
奇偶校验位
在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。