新手学单片坤之定时器与串口

本文围绕单片机定时器与串口展开,介绍利用中断驱动蜂鸣器、制作LED数码管秒表、实现LCD显示时钟等内容。还涉及两单片机串行通信,以及单片机与笔记本电脑串口通信。通过代码实现和仿真、实物效果展示,让新手较浅理解UART通信和串口配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录

新手学单片坤之定时器与串口

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

1、蜂鸣器介绍

蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号

蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器

有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定

无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

蜂鸣器有正负极,顶部印有+号的为正极,若蜂鸣器引脚没剪,则长的为正极
本次任务使用的是无源蜂鸣器,故需要提供脉冲。

(1)脉冲

在这里插入图片描述

脉冲的单位

两个脉冲相继出现的间隔时间,就是脉冲周期,它是频率的倒数;而将在单位时间(1秒)内所产生的脉冲个数称为频率。

频率的单位有:Hz(赫)、kHz(千赫)、MHz(兆赫)、GHz(吉赫)。

其中1GHz=1000MHz,1MHz=1000kHz,1kHz=1000Hz。

假如脉冲频率为50HZ(一秒种发出50个脉冲,每个脉冲占用的时间就是脉冲周期),则脉冲周期 = 1 秒 / 脉冲频率 = 1 / 50 = 0.02 秒(S)

所以,对于本个实验脉冲周期为1ms,所以我们可以对定时器设定0.5ms的中断,进入中断后对蜂鸣器取反,就能得到一个1ms的脉冲周期。

2、代码实现

#include <REGX52.H>
#include "Timer0.h"

sbit Buzzer=P2^5; //单片机连接蜂鸣器的引脚是p2^5,这里就相当于给他重新取一个名字。可以查看开发板原理,查看你的开发板蜂鸣器连接的哪一个口,


void main()
{ 
	  Timer0Init(); //定时器初始化函数
      while(1)
     {
             
     }

}
void Timer0_Routine() interrupt 1  //定时器0中断函数
{		
	TL0 = 0x0C;		//设置定时初值
	TH0 = 0xFE;
	Buzzer=~Buzzer;//对引脚取反,实现振荡脉冲

}   

定时器初始化函数见上一篇博客

3、仿真图

在这里插入图片描述

由图可见,可以看见1ms的脉冲周期。

实物效果就是蜂鸣器发出持续的响。

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

1、代码实现

#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];   P2=discode2[second%10];	//显示秒位0   				  	//显示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;			//启动定时器继续计时
}
}

2、仿真效果

请添加图片描述

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

1、LCD简单函数

在这里插入图片描述

2、代码实现

//主函数模块


#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hour;

void main()
{
	LCD_Init();
	Timer0Init();
	
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowString(2,1,"  :  :");//LCD显示字符串函数
      while(1)
{
	     LCD_ShowNum(2,1,Hour,2);  //LCD显示数字函数
       LCD_ShowNum(2,4,Min,2);    
       LCD_ShowNum(2,7,Sec,2);    	
}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
  TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;	
	T0Count++;    //当T0count满1000时,定时器正好为1s
	if(T0Count>=1000)
	{
		T0Count=0;  //重新从0开始
		Sec++;      //秒加1
		if(Sec>=60)
		{
			Sec=0;    //当秒数大于60,清零
			Min++;    //分钟数清零
			if(Min>=60)
			{
				Min=0;  //分钟数大于60时,清零
				Hour++; //小时加加
				if(Hour>=24)
				{
					Hour=0; 
					
				}
			}
		}
	}
}	



LCD1602函数实现模块
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
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();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
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();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
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));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
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]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
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');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
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');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
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');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
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');
	}
}



Delay函数以及定时器函数可以查看之前博客。

3、效果展示

请添加图片描述

四、甲、乙两单片机进行方式3(或方式2)串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED

1、串行口通信

在这里插入图片描述

2、简单串口模式图

在这里插入图片描述

3、串行口相关寄存器

在这里插入图片描述

(1)SCON寄存器

在这里插入图片描述
在这里插入图片描述

(2)PCON寄存器

在这里插入图片描述
更加详细的可以自行查阅手册,中文版可能翻译有错,推荐看英文版

4、初始化串口(简便方法)

1、打开stc小程序

在这里插入图片描述

其中蓝色方框里的删去,波特率一般选择4800,9600等

当然也可以根据自己需求设置

5、波特率

波特率(bandrate),指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。比如每秒钟可以传输9600个二进制(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600。

6、方式三

在这里插入图片描述

由于方式三多了奇偶校验位,所以我们要对奇偶校验位进行配置

**奇偶校验位:**当这个位的数时与前面1的个数的和是奇数还是偶数。奇数就是奇校验,偶数就是偶校验。

要配置奇偶校验位,我们就需要用到PSW寄存器中的P

PSW

在这里插入图片描述

从图中可发现P是奇偶标志位,注意区分奇偶校验位与奇偶标志位的区别。

7、代码实现

//发送机

#include <reg51.h>
sbit Ps=PSW^0;		//P位为PSW寄存器的第0位,即奇偶校验位
unsigned char Tab[8]= {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf,  0xbf, 0x7f};   //控制流水灯显示数据数组,为全局变量						
void Send(unsigned char dat);
void delay(void);

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,未发送完// 空操作	// 1字节发送完,TI清0
TI=0;
}
void delay(void)
{unsigned char m,n;
for(m=0;m<250;m++)for(n=0;n<250;n++);
}



//接收机
#include <reg51.h>
sbit Ps=PSW^0;// P位为PSW 寄存器的第0位,即奇偶校验位
unsigned char Receive(void);
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;//将接收的数据返回
}
}

TB8=P;// 将偶校验位作为第9位数据发送,这里为什么说是偶检验位?而不是奇校验位?

你可以这样理解

1、PSW寄存器中的P位就是监测寄存器里数据1的个数(不算上P本身),如果1的个数为奇数,p=1,为偶数,p=0。

2、将P给TB8,如果P=1,再加上这TB8本身,1的个数就为偶数了。如果P=0,加上TB8本身,1的个数还是偶数。所以这里就确定为偶校了。

8、仿真图

请添加图片描述

9、实物效果

实物由于没有两个单片机,就换一种表达形式

#include <REGX52.H>
#include "UART.h"
void main()
{ 
	   UART_Init();
      while(1)
{     
}
}

void UART_Routine() interrupt 4
{
	if(RI==1)
	{
		P2=SBUF; //将串口数据发送给P2
		RI=0;
	}
}
void UART_Init()//4800bps@12.000MHz
{
	SCON=0x50;   //串行控制寄存器配置,将SM1置一与REN置一
	PCON|=0x80;
	//TMOD=0x01;  //0000 0001,特殊功能寄存器里,通过赋值选择定时器工作模式
	//使用8位自动重装定时器,就是模式3,选用定时器1
	TMOD&=0x0F;//把TMOD高四位清零,低四位保持不变
	TMOD|=0x20;//
  TL1 = 0xFA;		//设定定时初值
	TH1 = 0xFA;		//设定定时器重装值

	ET1=0;   //因为不需要中断,就禁止中断
	TR1=1; //启动定时器1
	EA=1;
	ES=1;//启动串口中断
}//触发串口中断函数

效果如下

在这里插入图片描述

f0对应的二进制就为11110000,同理ff对应的二进制就为11111111.

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

1、代码实现

#include <REGX52.H>
#include "UART.h"
#include "Delay.h"

unsigned char array[20]="Hello C51  ";//定义一个字符串
unsigned char a=1;//定义一个全局变量,a用来判断是否继续发送字符的依据

void main()
{  
	UART_Init();//串口初始化
	while(1)
	{
		if(a==1)//当a==1时,就调用字符串以及延时函数
		{ UART_SendString(array);
		  Delay(2000);
		}
		if(a==0)//当a==0时,就进入死循环
		{
			while(1)
				if(a==1)break;//判断a是否等于1,等于1就跳出当前的死循环,去执行外循环
		}		
	}

}

void UART_Routine() interrupt 4
{
	if(RI==1)
	{
		P2_0=SBUF; //将串口数据发送给P2_0端游,正好来看向单片机发送数据是否成功
		if(P2_0==0)//如果P2_0=0,对应单片机上第一个led会点亮
		{
			a=0;    //再将a赋值,去主函数里面进行判断是否要进行发送字符串函数
		}if(P2_0==1)
		{
			a=1;
		}
		RI=0;
	}
}

2、效果

请添加图片描述
可以对照着单片机上的第一个led灯来确定是否发送成功

六、总结

较浅的理解到了UART通信,学会了串口的配置等

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值