单片机定时器与串口通信

本章内容将结合之前所学的周期、中断的相关知识完成新的实践内容。

内容包括:

利用中断发出1Khz的方波信号,驱动蜂鸣器鸣叫。

LED数码管秒表的制作。

使用定时器实现一个LCD显示时钟。

两个单片机串口通信。

将单片机串口与笔记本电脑串口模块相连,单片机发送内容,笔记本电脑接收。

目录

一、驱动蜂鸣器鸣叫

1、蜂鸣器介绍

2、任务实现

proteus仿真电路原理图:

代码示例: 

 仿真结果:

二、LED数码管秒表的制作

1、LED数码管

2、计时器

2、任务实现

proteus仿真电路原理图:

代码示例: 

三、实现一个LCD显示时钟(采用LCD 1602)

1、LCD 1602

引脚图及功能:

控制指令: 

连接方式 :

2、任务实现

proteus仿真电路原理图:

代码示例:

仿真结果:

四、两个单片机串口通信

1、串口

任务实现

proteus仿真电路原理图:

代码示例:

仿真结果:

五、单片机发送,笔记本接收

任务实现

代码示例:

串口助手:


一、驱动蜂鸣器鸣叫

1、蜂鸣器介绍

蜂鸣器是一种电子元件,通常用于发出嗡嗡声或蜂鸣声。它通常由一个振动器和一个驱动电路组成。振动器产生声音的频率,而驱动电路控制振动器的工作方式和频率。

在嵌入式系统或电子设备中,蜂鸣器常用于发出警报、提醒或产生特定的声音效果。它们可以通过改变驱动电路中的频率和脉冲宽度来产生不同的声音。例如,通过调整驱动信号的频率和持续时间,可以使蜂鸣器发出连续的长蜂鸣声、短促的蜂鸣声或者间隔的蜂鸣声。

常见的蜂鸣器类型:压电蜂鸣器、磁性蜂鸣器。

51单片机上的蜂鸣器是一种常见的外围设备,用于发出声音信号。通常情况下,它是通过将电流传输到蜂鸣器内部的压电元件来产生声音。

2、任务实现

要求:利用T1的中断控制P1.7引脚输出频率为1kHz方波音频信号,驱动蜂鸣器发声。系统时钟为12MHz。方波音频信号周期1ms,因此T1的定时中断时间为0.5 ms,进入中断服务程序后,对P1.7求反。

proteus仿真电路原理图:

代码示例: 


#include <REGX51.H>
 
sbit sound = P3^5;
 
void main()
{
	EA=1;               //开总中断
  	ET1=1;              //允许定时器T1中断        
   	TMOD=0x10; 			//TMOD=0001 000B,使用T1的方式1定时    	
	TH1=0xFE;      			
   	TL1=0x33;      		//设置定时器1的初值为0xFE33,使定时器每次溢出所需的时间为500微秒
   	TR1=1;              //启动T1
	while(1)
	{
	}
}
 
void Timer1_Riutine(void)  interrupt 3
{
	
	sound = ~sound;
	TH1=0xFE;      			
   	TL1=0x33;
 
}

 仿真结果:

二、LED数码管秒表的制作

1、LED数码管

LED数码管是一种常用的数字显示器件,通常由7段LED组成,每段LED表示显示数字的一个部分(如水平线、垂直线等),加上一个小数点LED。它们排列成数字“8”的形状,用于显示数字0到9以及一些特殊字符。LED数码管广泛应用于各种计时器、计数器、温度计等数字显示应用中。LED数码管可分为共阳极和共阴极两种类型,它们的工作原理和接线方式有所不同。共阳极LED数码管中,所有的阳极连接在一起,而共阴极LED数码管中,所有的阴极连接在一起。

2、计时器

计时器是一种电子设备或计算机组件,用于测量和跟踪时间的流逝。它可以在各种应用中使用,从简单的秒表到复杂的定时器和计数器。计时器通常包含一个计时器芯片或是微控制器的计时器模块,用于精确地测量时间。

在微控制器中,计时器是一种硬件模块,用于生成精确的时间延迟或者跟踪时间间隔。它可以用于测量微秒、毫秒、甚至更长时间间隔。通过编程,可以配置计时器以执行各种功能,如生成脉冲、定时中断、PWM(脉冲宽度调制)输出等。

在电子应用中,计时器通常用于实现各种功能,例如控制设备的运行时间、测量事件的持续时间、生成定时器中断以执行特定任务等。在你描述的LED数码管秒表任务中,使用计时器可以让你以百毫秒为单位精确地测量时间,并实现秒表的功能。

 上图为AT89S51定时器/计数器结构,如图所示,定时器/计数器T0由特殊功能寄存器TH0、TL0构成,T1由特殊功能寄存器TH1、TL1构成。(具体相关知识请查阅相关资料或浏览往期博客进行了解学习)

2、任务实现

要求:用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清0。再次按一下计时功能键,则重复上述计时过程。

proteus仿真电路原理图:

代码示例: 

#include <REGX51.H>
typedef unsigned int uint;
typedef unsigned char uchar;
 
uchar discode1[]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};//第一个
uchar discode2[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//第二个
 
uchar timer = 0;//中断次数
uchar second;//秒数
uchar key = 0;//按键次数
 
sbit keyif = P3^7;//定义按键引脚
 
void Delay1ms(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}//延时函数,按键消抖
 
void main()
{
	TMOD = 0x01;
	ET0 = 1;
	EA = 1;
	second = 0;
	P0=discode1[second/10];   		//显示秒位0
	P2=discode2[second%10];   		//显示0.1s位0
	while(1)
	{
	  if(keyif == 0)
	  {
		Delay1ms(5);
		  if(keyif == 0)
		  {
			key++;
			switch(key)
			{
				case 1:
				TH0 = 0xEE;
				TL0 = 0x00;
				TR0 = 1;
				break;
				case 2:
				timer = 0;
				TR0 = 0;
				break;
				case 3:
				key = 0;
				second = 0;
				P0 = discode1[0];
				P2 = discode2[0];
				break;	
			}
			while(keyif == 0); //如果按键时间过长在此循环
 
			}
		}		  
	}
}
 
void timer0() interrupt 1
{
	TR0 = 0;
	timer++;
	if(timer == 20)
	{
		second++;
		P0=discode1[second/10]; 	//根据计时,即时显示秒位		
		P2=discode2[second%10]; 	//根据计时,即时显示0.1s位
		timer = 0;
	}
	if(second == 99)//计时到达9.9s
	{
		TR0 = 0;//停止计时
		second = 0;//秒数清零
		key = 2;//停止计时
	}
	else
	{
		TR0 = 1;//继续计时
	}
 
}

三、实现一个LCD显示时钟(采用LCD 1602)

1、LCD 1602

LCD 1602(也称为16x2 LCD)是一种常见的字符型液晶显示模块,其名称指的是其具有16个字符宽度和2行显示的特性。在使用LCD 1602时,用户可以通过编程指令来控制显示内容、光标位置、背光状态等。它广泛应用于各种嵌入式系统、DIY项目和学术实验中,用于显示信息、状态、菜单等。

引脚图及功能:

编号

符号

引脚说明

标号

符号

引脚说明

1

GND

接地

9

D2

数据

2

VCC

电源正极

10

D3

数据

3

VL

液晶显示偏压

11

D4

数据

4

RS

数据/命令选择

12

D5

数据

5

RW

读/写选择

13

D6

数据

6

E

使能信号

14

D7

数据

7

D0

数据

15

BL+

背光源正极

8

D1

数据

16

BL-

背光源负极

控制指令: 

序号

指令

RS

R/W

D7

D6

D5

D4

D3

D2

D1

D0

1

清屏

0

0

0

0

0

0

0

0

0

1

2

光标复位

0

0

0

0

0

0

0

0

1

x

3

输入方式设置

0

0

0

0

0

0

0

1

I/D

S

4

显示开关控制

0

0

0

0

0

0

1

D

C

B

5

光标或字符移位控制

0

0

0

0

0

1

S/C

R/L

x

x

6

功能设置

0

0

0

0

1

DL

N

F

x

x

7

字符发生存储器地址设置

0

0

0

1

字符发生存储器地址

8

数据存储器地址设置

0

0

1

显示数据存储器地址

9

读忙标志或地址

0

1

BF

计数器地址

10

写入数据至CGRAM或DDRAM

1

0

要写入的数据内容

11

从CGRAM或DDRAM中读取数据

1

1

读取的数据内容

连接方式 :

​LCD1602与单片机的连接有两种方式,一种是直接控制方式,另一种是间接控制方式。它们的区别只是所用的数据线的数量不同。

1.直接控制方式 LCD1602的8根数据线和3根控制线E,RS和R/W与单片机相连后即可正常工作。一般应用中只须往LCD1602中写入命令和数据,因此,可将LCD1602的R/W读/写选择控制端直接接地,这样可节省1根数据线。VO引脚是液晶对比度调试端,通常连接一个10kΩ的电位器即可实现对比度的调整;也可采用将一个适当大小的电阻从该引脚接地的方法进行调整,不过电阻的大小应通过调试决定。

2.间接控制方式 间接控制方式也称为四线制工作方式,是利用HD44780所具有的4位数据总线的功能,将电路接口简化的一种方式。为了减少接线数量,只采用引脚DB4~DB7与单片机进行通信,先传数据或命令的高4位,再传低4位。采用四线并口通信,可以减少对微控制器I/O的需求,当设计产品过程中单片机的I/O资源紧张时,可以考虑使用此方法。

2、任务实现

要求:使用定时器实现一个LCD显示时钟。采用LCD1602,最小计时单位是秒,如何获得1s的定时?可将T0定时时间定为50ms,采用中断方式进行溢出次数累计,满20次,则秒计数变量second加1;若秒计满60,则分计数变量minute加1,同时将秒计数变量second清0;若分钟计满60,则小时计数变量hour加1;若小时计数变量满24,则将小时计数变量hour清0。

proteus仿真电路原理图:

代码示例:

说明:在使用LCD之前需要初始化,同时在使用过程中还需要写指令,写数据等操作,相应操作步骤及对应代码如下。

写指令步骤:

1:将RS置0;
2:将RW置0;
3:将指令(Command)写入LCD_DataPort;
4:将EN置1;
5:延时1ms;
6:将EN置0;
7:延时1ms;

//写指令
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

写数据步骤:

1:将RS置0;
2:将RW置0;
3:将数据(Data)写入LCD_DataPort;
4:将EN置1;
5:延时1ms;
6:将EN置0;
7:延时1ms;

//写数据
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

 下述代码为该任务总代码:

#include <REGX51.H>
 
 
typedef unsigned char uchar;
typedef unsigned int uint;
 
uchar Hour=23,Min=59,Sec=55;
 
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P3
 
void LCD_Delay()
{
	unsigned char i, j;
 
	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}
 
void LCD_WriteCommand(unsigned char Command)//LCD写命令
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}
 
void LCD_WriteData(unsigned char Data)//LCD写数据
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}
 
void LCD_SetCursor(unsigned char Line,unsigned char Column)//LCD设置光标位置
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}
 
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)//LCD显示字符
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}
 
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)//LCD显示字符串
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}
 
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)//LCD显示数字
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}
 
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}
 
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}
 
void LCD_ShowBinNum(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(2,i-1)%2+'0');
	}
}
 
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}
 
void Timer0_Init()		//定时器初始化
{
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值,与通过计算的得到有一定误差。
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	ET0=1;
	EA=1;//将中断打通即令ET0=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//定时器0的中断程序
{
		static unsigned int T0Count;//静态变量,让局部变量变为全局变量。
		TL0 = 0x18;		
		TH0 = 0xFC;//重新赋值,计时器重新开始运行。
		T0Count++;
		if(T0Count>=1000)//定时为1s
		{
			T0Count=0;
			Sec++;
			if(Sec>=60)
			{
				Sec=0;
				Min++;
				if(Min>=60)
				{
					Min=0;
					Hour++;
					if(Hour>=24)
					{
						Hour=0;
					}
				}
			}
		}//每隔1s。
		
}//中断函数

仿真结果:

四、两个单片机串口通信

1、串口

串口是一种用于数据通信的通用接口标准,通常用于在计算机和外部设备之间传输数据。串口允许设备以序列化的方式在物理链路上发送和接收数据。串口可以是硬件实现,也可以是通过软件模拟的虚拟串口。串口通信可以是同步的或异步的。在同步串口通信中,发送方和接收方使用共同的时钟信号进行数据传输。而在异步串口通信中,数据通过起始位、数据位、校验位和停止位来进行传输,不需要共享时钟信号。

常见的串口标准:RS-232、RS-422、RS-485等。

其中RS-232是最常见的串口标准之一,它定义了信号电平、连接器和数据传输协议。串口通常具有多个引脚,包括数据线、控制线和地线等。

串口在许多应用中仍然广泛使用,尤其是在嵌入式系统、网络设备、工业控制和通信设备中。它们通常用于连接调制解调器、打印机、传感器、GPS接收器等外部设备。

51单片机中的串口:

在51系列单片机中,串口通常被称为UART(通用异步收发器),它是一种常见的串行通信接口。UART允许单片机与外部设备进行异步的串行数据通信,而不需要共享时钟信号。

UART通常由两个主要寄存器控制:SBUF(串行数据缓冲器)SCON(串行控制寄存器)

如下为其结构图:

任务实现

要求:甲、乙两单片机进行串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED。方式3比方式1多了1个可编程位TB8,该位一般作奇偶校验位。乙机接收到的8位二进制数据有可能出错,需进行奇偶校验,其方法是将乙机的RB8和PSW的奇偶校验位P进行比较,如果相同,接收数据;否则拒绝接收。

proteus仿真电路原理图:

代码示例:

甲单片机代码:

#include <REGX51.H>
sbit T_P=PSW^0;
unsigned char code 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 Delay1ms(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}
void main()
{
	unsigned char i;
	TMOD=0x20;
	SCON=0xc0;
	PCON&=0x7f;
	TH1=0xfd;
	TL1=0xfd;
	TR1=1;
	while(1)
	{
		for(i=0;i<8;i++)
		{
			Send(Tab[i]);
			Delay1ms(200);
		}
	}
}


乙单片机代码:

#include <REGX51.H>
 
sbit R_P=PSW^0;
 
unsigned char Receive()//接收一字节数据
{
	unsigned char dat;
	while(RI==0);//检测RI,RI=0,未接收完
	RI=0;			//接收数据完成RI手动清0
	ACC=SBUF;		//将接收缓冲器的数据存于ACC
	if(RB8=R_P) 	//只有偶检验成功才能往下执行,接收数据
	{
		dat=ACC;	//将ACC数据存于dat
		return dat;	//将接收的数据返回
	}
}
 
void main()
{
	TMOD=0x20;  
	SCON=0xd0;	
	PCON&=0x7f;
	TH1=0xfd;	
	TL1=0xfd;
	TR1=1;
	while(1)
	{
		 P2=Receive();	
	}
}

仿真结果:

LED依次亮起(注意LBL标注)

五、单片机发送,笔记本接收

任务实现

要求:将单片机串口与笔记本电脑串口模块相连,单片机每隔2秒发送“Hello C51”,笔记本电脑用串口助手软件接收。 如果串口助手发送字符“0" 给单片机,则单片机停止发送; 如果单片机收到“1”,则继续每隔2秒发送“Hello C51”。

代码示例:

#include <REGX51.H>
#include "stdio.h"
 
unsigned char a;
unsigned char Flag=1;
void Delay1ms(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}
void UartInit(void)		//9600bps@12.000MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	EA = 1;
	ES = 1;
}
 
 
void UartSend()
{
		TI=1;
		puts("Hello C51");
		while(!TI);
		TI=0;
		Delay1ms(2000);
}
 
void main()
{
	UartInit();
	while(1)
	{
		if(Flag==1)
		UartSend();
	}	
}
 
 
//串口中断函数模板
void UART_Routine()	interrupt 4 //串口中断
{
	if(RI==1)
	{
		RI=0;
		a=SBUF;
		if(a=='1')Flag=1;
		if(a=='0')Flag=0;
	}
}

串口助手:

注:上述为个人的理解和学习借鉴所得内容,如有错误或内容不全之处,请各位指出

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值