目的:利用PROTUES仿真软件、串口调试助手、虚拟串口,搭建单片机与PC通信仿真平台,熟悉单片机串口的配置及与PC机的通信方法;尝试制定通信协议,单片机根据通信协议解析接收到的内容,并根据接收的指令执行相应的操作。

1、proteus仿真实验电路:

fceaccdb591b4072bd36dc15f600adf8.png

dff226392be6431b939fa0ea4d6d88b6.png

 2、单字符的接收和发送,串口通信控制单片机

源码:通过PC端发送单个字符控制单片机,实现根据发送的字符指令控制数码管显示时钟“暂停(输入P)和开始(输入S)”,“清零(C)”,显示当前数码管显示的计时(R)

#include <reg52.h>

#define u8 unsigned char
#define u16 unsigned int	

u8 WeiMa[6]={0xFE,0xFD,0xFB,0xF7,0xEF,0xDF};
u8 DuanMa[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

//函数声明
void Delay_ms(u16 xms);
void ShuMaGuan(u8 wei,u8 duan);
void Display_Timer(u8 hour,u8 min,u8 sec);
u8 Key_Scan();
void PutChar(u8 n);
void UartInit();
void PutString(u8 *p);


//引脚定义
sbit SW1=P3^2;
sbit SW2=P3^3;
sbit SW3=P3^4;

//函数功能:定时器初始化
void Time0init()
{
	TMOD|=0x01;				//设置定时器模式
	TF0=0;					//清除TF0标志
	TH0=(65536-50000)/256;  //设置定时初值
	TL0=(65536-50000)%256;	//设置定时初值
	TR0=1;					//定时器0允许计时
	ET0=1;					//中断允许
	EA=1;					//CPU中断允许位打开
}

//串口初始化
void UartInit()  //9600bps@11.0592MHz
{
 
	 PCON &= 0x8F;  //波特率倍速
	 SCON = 0x50;  //8位数据,可变波特率
	 TMOD &= 0x0F;  //清除定时器1模式位
	 TMOD |= 0x20;  //设定定时器1为8位自动重装方式
	 TL1 = 0xFD;   //设定定时初值
	 TH1 = 0xFD;  //设定定时器重装值
	 TR1 = 1;  //启动定时器1
	 EA=1;
	 ES=1;
}

u8 Hour,Min,Sec;//全局变量,时分秒
u8 mode;//全局变量:状态切换,0:时钟显示,1:调节时;2:调节分;3:调节秒
bit flash_tip=1;//数码管闪烁标志,为0时数码管熄灭,为一时数码管显示
u8 Data;//接收数据

//主函数
void main()
{
	u8 keynum;
	Time0init();//定时器
	UartInit(); 
	Hour=0;
	Min=0;
	Sec=0;
	mode=0;//初始化
	PutString("开始");
	while(1)
	{
		keynum=Key_Scan();//按键返回值
		if(keynum)   //非0表示有按键按下
		{
			switch(keynum)  //判断是哪个按键按下,按键一调节模式,按键2自加,按键3自减
			{
				case 1:if(++mode>=4) mode=0;break;  //++mode为先自增再判断是否大于4
				case 2:
					if(mode==1) if(++Hour>=24) Hour=0;//++mode为先自增再判断是否大于4
					if(mode==2) if(++Min>=60) Min=0;//++Min先自增再判断是否大于60
					if(mode==3) if(++Sec>=60) Sec=0;//++Sec先自增再判断是否大于60
					break;										
				case 3:
					if(mode==1)	if(--Hour==255) Hour=23;//--Hour先自增再判断是否溢出
					if(mode==2) if(--Min==255) Min=59;//--Min先自增再判断是否大溢出
					if(mode==3) if(--Sec==255) Sec=59;//--Sec先自增再判断是否大溢出
					break;
				default:break;
			}
		}
		Display_Timer(Hour,Min,Sec);
	}
}

//软件延时函数
//xms为延时多少毫秒
void Delay_ms(u16 xms)
{
	
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 135;
		do
		{
			while (--j);
		} while (--i);
	}
}

//静态显示一位
//参数:wei控制位选duan控制段选,表示要显示的一个数字
void ShuMaGuan(u8 wei,u8 duan)
{
	P1=WeiMa[wei];    //位选
	P2=DuanMa[duan];  //段选
	Delay_ms(1); //间隔一段时间扫描
	P1=0xFF;    
	P2=0xFF;	   //消隐
}


//函数功能:数码管动态显示
//flash_tip为数码管闪烁标志,为0时数码管熄灭,为一时数码管显示
//flash_tip每4.5秒进行取反
void Display_Timer(u8 hour,u8 min,u8 sec)
{
	if(mode!=1 || flash_tip==1) //mode=1时,左边的条件一直为假,当flash_tip=1时,或运算为真,进入if,数码管显示
	{
		ShuMaGuan(5,hour/10);
		ShuMaGuan(4,hour%10);
	}
	else P1=0xFF;
	
	
	if(mode!=2 || flash_tip==1)//mode=2时,左边的条件一直为假,当flash_tip=1时,或运算为真,进入if,数码管显示
	{
		ShuMaGuan(3,min/10);
		ShuMaGuan(2,min%10);
	}
	else P1=0xFF; 
	
	
	if(mode!=3 || flash_tip==1)//mode=3时,左边的条件一直为假,当flash_tip=1时,或运算为真,进入if,数码管显示
	{
		ShuMaGuan(1,sec/10);
		ShuMaGuan(0,sec%10);
	}
	else P1=0xFF;  
}


//独立按键检测
u8 Key_Scan()
{
	static u8 key_up=1; //按键按松开标志
	if(key_up && (SW1==0 || SW2==0 || SW3==0))
	{
		Delay_ms(10); //去抖动
		key_up=0; //松手标志为0,那么下次再检测,if结果为0,则不会进入这里的语句
		if(SW1==0) return 1;
		if(SW2==0) return 2;
		if(SW3==0) return 3;
	}
	else if(SW1 == 1 && SW2 == 1 && SW3 == 1) key_up=1; //松手标志
	return 0; // 无按键按下
}

//发送一个字符
void PutChar(u8 n)
{
	 SBUF=n;
	 while(!TI);
	 TI=0;
}

//发送字符串
void PutString(u8 *p)
{
	while(*p!='\0')
	{
		PutChar(*p);
		p++;
	}
}

//串口中断
void uart() interrupt 4
{
	if(RI==1)
	{
		 RI=0;
		 Data=SBUF;
		 if(Data=='P')  //向单片机发送p-->停止计时
		 {
			 TR0=0;
		 }
		 else if(Data=='S')  //s-->开始计时
		 {
			 TR0=1;
		 }
		 else if(Data=='C')  //c-->时钟清零
		 {
			 Hour=0;
			 Min=0;
			 Sec=0;
		 }
		 else if(Data=='R')   //查询当时时间
		 {
			 PutString("当前时间为:");
			 PutChar(Hour/10+48);  //字符
			 PutChar(Hour%10+48);
			 PutChar(':');
			 PutChar(Min/10+48);
			 PutChar(Min%10+48);
			 PutChar(':');
			 PutChar(Sec/10+48);
			 PutChar(Sec%10+48);
			 PutString('\n');
		 }
	}
}

//定时器中断服务函数
void Time0() interrupt 1
{
	static unsigned char flag_1,flag_2; 
	TH0=(65536-50000)/256;  
	TL0=(65536-50000)%256;//重新赋初值
	
	if(mode==0)flag_1++;  //mode为0时,数码管正常显示
	else flag_2++;   
	
	if(flag_1==20 && mode==0)  //每秒执行一次
	{
		flag_1=0;
		if(++Sec>=60) //++Sec先自增再判断是否大于60  
		{
			Sec=0;
			if(++Min>=60)//++Min先自增再判断是否大于60
			{
				Min=0;
				if(++Hour>=24)//++Hour先自增再判断是否大于60
				{
					Hour=0;
				}
			}
		}
	}
	if(flag_2==9)
	{
		flash_tip=~flash_tip;//每4.5秒进行取反
		flag_2=0;
	}

}

3、字符串的接收和发送

基本功能:

1.时分秒的动态显示。2.用三个按键实现时分秒的修改,调节的数字闪烁提示。
3.串口控制时钟的暂停、开始、清零、读取
输入指令(回车结束):
暂停计时:stop
开始计时:start
时钟清零:reset
读取当前时间:read

       思路: 串口通信时,发送数据是一个一个字符发送和接收的,所以可以每次接收到一个字符就保存在字符数组保存,再进行下一步处理。每次存储好一个字符,同时记录存储的长度,单片机内存有限,所以读取完成子后就从首地址重新写入。字符串的结束标志为‘’,用这点判断输入的长度。

(1)效果图: 

8c9559bac1b44f49a259100f3c1f316e.png

3e2728ab47094cdea6acd8e86414f036.png (2)源码

/*******************************************************************************
程序功能:1.时分秒的动态显示。2.用三个按键实现时分秒的修改,调节的数字闪烁提示。
3.串口控制时钟的暂停、开始、清零、读取
输入指令(回车结束):
暂停计时:stop
开始计时:start
时钟清零:reset
读取当前时间:read
*******************************************************************************/

#include <reg52.h>		 //包含需要的头文件
#include <string.h>		 //包含需要的头文件

#define u8 unsigned char
#define u16 unsigned int	

u8 WeiMa[6]={0xFE,0xFD,0xFB,0xF7,0xEF,0xDF};
u8 DuanMa[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

//函数声明
void Delay_ms(u16 xms);
void ShuMaGuan(u8 wei,u8 duan);
void Display_Timer(u8 hour,u8 min,u8 sec);
u8 Key_Scan();
void PutChar(u8 n);
void UartInit();
void PutString(u8 *p);
void Key_Timer_Set();
void Uart_Timer_Set();
void crlf();
void Pintf_Uart();


//引脚定义
sbit SW1=P3^2;
sbit SW2=P3^3;
sbit SW3=P3^4;

u8 Hour=0,Min=0,Sec=0;//全局变量,时分秒
u8 mode=0;//全局变量:状态切换,0:时钟显示,1:调节时;2:调节分;3:调节秒
bit flash_tip=1;//数码管闪烁标志,为0时数码管熄灭,为一时数码管显示

#define Data_SIZE 15 //数据长度
u8 USART_RX_BUF[Data_SIZE]; //接收缓冲,最大Data_SIZE个字节.末字节为换行符 
u8 Data_length=0;   //数据长度
u8 USART_RX_STA=0;  //接收状态标记	

//函数功能:定时器初始化
void Time0init()
{
	TMOD|=0x01;				//设置定时器模式
	TF0=0;					//清除TF0标志
	TH0=(65536-50000)/256;  //设置定时初值
	TL0=(65536-50000)%256;	//设置定时初值
	TR0=1;					//定时器0允许计时
	ET0=1;					//中断允许
	EA=1;					//CPU中断允许位打开
}

//串口初始化
void UartInit()  //9600bps@11.0592MHz
{
 
	 PCON &= 0x8F;  //波特率倍速
	 SCON = 0x50;  //8位数据,可变波特率
	 TMOD &= 0x0F;  //清除定时器1模式位
	 TMOD |= 0x20;  //设定定时器1为8位自动重装方式
	 TL1 = 0xFD;   //设定定时初值
	 TH1 = 0xFD;  //设定定时器重装值
	 TR1 = 1;  //启动定时器1
	 EA=1;
	 ES=1;  //打开接收中断
}

/*******************************************************************************
* 函 数 名: void main()
* 函数功能: 主函数
*******************************************************************************/
void main()
{
	
	
	Time0init();//定时器
	UartInit(); //串口
	Pintf_Uart();//输入提示
	while(1)
	{
		Key_Timer_Set();//按键控制时钟
		Uart_Timer_Set();//按键调节时钟
		Display_Timer(Hour,Min,Sec);//数码管显示
		
	}
}

/*******************************************************************************
* 函 数 名: void Pintf_Uart()
* 函数功能: 串口助手输入指示
*******************************************************************************/
void Pintf_Uart()
{
	/***************输入指示*******************/
	PutString("请输入指令");
	PutString("(回车结束):");
	crlf();
	PutString("暂停计时:stop");
	crlf();
	PutString("开始计时:start");
	crlf();
	PutString("时钟清零:reset");
	crlf();
	PutString("读取当前时间:read");
	crlf();
	/*******************************************/
}

/*******************************************************************************
* 函 数 名: void Key_Timer_Set()
* 函数功能: //按键调节时钟
*******************************************************************************/
void Key_Timer_Set()
{
	u8 keynum;
	keynum=Key_Scan();//按键返回值
	if(keynum)   //非0表示有按键按下
	{
		switch(keynum)  //判断是哪个按键按下,按键一调节模式,按键2自加,按键3自减
		{
			case 1:if(++mode>=4) mode=0;break;  //++mode为先自增再判断是否大于4
			case 2:
				if(mode==1) if(++Hour>=24) Hour=0;//++mode为先自增再判断是否大于4
				if(mode==2) if(++Min>=60) Min=0;//++Min先自增再判断是否大于60
				if(mode==3) if(++Sec>=60) Sec=0;//++Sec先自增再判断是否大于60
				break;										
			case 3:
				if(mode==1)	if(--Hour==255) Hour=23;//--Hour先自增再判断是否溢出
				if(mode==2) if(--Min==255) Min=59;//--Min先自增再判断是否大溢出
				if(mode==3) if(--Sec==255) Sec=59;//--Sec先自增再判断是否大溢出
				break;
			default:break;
		}
	}	
}
/*******************************************************************************
* 函 数 名: void Key_Timer_Set()
* 函数功能: 串口调节时钟
//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。
//如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
*******************************************************************************/
void Uart_Timer_Set()
{
	static char str[10];//串口数据缓存区
	u8 i;
	if(USART_RX_STA) //如果串口接收到数据
	{
		for(i=0;i<Data_length;i++)  //将数据存入数组
		{
			str[i]=USART_RX_BUF[i];
		}			
		USART_RX_STA=0; //接收完毕
		if(strstr(str,"stop"))  //暂停计时
		{
			TR0=0;
			PutString("已暂停");
			crlf();
		}
			
		else if(strstr(str,"start"))  //暂停计时
		{
			TR0=1;
			PutString("已开始");
			crlf();
		}
		else if(strstr(str,"reset"))  //暂停计时
		{
			 Hour=0;
			 Min=0;
			 Sec=0;
			 PutString("已清零");
			 crlf();
		}
		else if(strstr(str,"read"))  //暂停计时
		{
			 PutString("当前时间为:");
			 PutChar(Hour/10+48);  //转化ASCII码字符,0为48,1为48+1=49.....
			 PutChar(Hour%10+48);
			 PutChar(':');
			 PutChar(Min/10+48);
			 PutChar(Min%10+48);
			 PutChar(':');
			 PutChar(Sec/10+48);
			 PutChar(Sec%10+48);
			 crlf();
		}
		else
		{
			PutString("指令错误!请重新输入");
			crlf();
		}			
			
		ES=1;//打开接收中断
	}
}

/*******************************************************************************
* 函 数 名: void Delay_ms(u16 xms)
* 函数功能: 软件延时函数,xms为延时多少毫秒
*******************************************************************************/
void Delay_ms(u16 xms)
{
	
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 135;
		do
		{
			while (--j);
		} while (--i);
	}
}

/*******************************************************************************
* 函 数 名: void ShuMaGuan(u8 wei,u8 duan)
* 函数功能: 静态显示一位,参数:wei控制位选duan控制段选,表示要显示的一个数字
*******************************************************************************/
void ShuMaGuan(u8 wei,u8 duan)
{
	P1=WeiMa[wei];    //位选
	P2=DuanMa[duan];  //段选
	Delay_ms(1); //间隔一段时间扫描
	P1=0xFF;    
	P2=0xFF;	   //消隐
}

/*******************************************************************************
* 函 数 名: void Display_Timer(u8 hour,u8 min,u8 sec)
* 函数功能:数码管动态显示
  flash_tip为数码管闪烁标志,为0时数码管熄灭,为一时数码管显示
  flash_tip每4.5秒进行取反
*******************************************************************************/
void Display_Timer(u8 hour,u8 min,u8 sec)
{
	if(mode!=1 || flash_tip==1) //mode=1时,左边的条件一直为假,当flash_tip=1时,或运算为真,进入if,数码管显示
	{
		ShuMaGuan(5,hour/10);
		ShuMaGuan(4,hour%10);
	}
	else P1=0xFF;
	
	
	if(mode!=2 || flash_tip==1)//mode=2时,左边的条件一直为假,当flash_tip=1时,或运算为真,进入if,数码管显示
	{
		ShuMaGuan(3,min/10);
		ShuMaGuan(2,min%10);
	}
	else P1=0xFF; 
	
	
	if(mode!=3 || flash_tip==1)//mode=3时,左边的条件一直为假,当flash_tip=1时,或运算为真,进入if,数码管显示
	{
		ShuMaGuan(1,sec/10);
		ShuMaGuan(0,sec%10);
	}
	else P1=0xFF;  
}

/*******************************************************************************
* 函 数 名: u8 Key_Scan()
* 函数功能: 独立按键检测,按键按下分别返回1.2.3
*******************************************************************************/
u8 Key_Scan()
{
	static u8 key_up=1; //按键按松开标志
	if(key_up && (SW1==0 || SW2==0 || SW3==0))
	{
		Delay_ms(10); //去抖动
		key_up=0; //松手标志为0,那么下次再检测,if结果为0,则不会进入这里的语句
		if(SW1==0) return 1;
		if(SW2==0) return 2;
		if(SW3==0) return 3;
	}
	else if(SW1 == 1 && SW2 == 1 && SW3 == 1) key_up=1; //松手标志
	return 0; // 无按键按下
}

/*******************************************************************************
* 函 数 名: void PutChar(u8 n)
* 函数功能: 发送一个字符
*******************************************************************************/
void PutChar(u8 n)
{
	 SBUF=n;
	 while(!TI);
	 TI=0;
}

/*******************************************************************************
* 函 数 名: void PutString(u8 *p)
* 函数功能: 发送字符串
*******************************************************************************/
void PutString(u8 *p)
{
	while(*p!='\0')
	{
		PutChar(*p);
		p++;
	}
}

/*******************************************************************************
* 函 数 名: void crlf()
* 函数功能: 换行函数通过输出两个ASCII字符实现
*******************************************************************************/
void crlf()
{
	PutChar(0x0D);
	PutChar(0x0A);
}

/*******************************************************************************
* 函 数 名: void uart() interrupt 4
* 函数功能: 串口中断服务函数,单片机接收数据并存入USART_RX_BUF[]数组中
*******************************************************************************/
void uart() interrupt 4
{
	static u8 Data_count=0;
	u8 Data;
	if(RI==1)
	{
		 RI=0;
		 Data=SBUF;
		 if(Data!='\n') //判断是否接收到结束符
		 {
            USART_RX_BUF[Data_count]=Data;//数据还没结束发送,就存到USART_RX_BUF[]数组中
            Data_count++;
        }
        else
        {
            Data_length=Data_count;//记录其数据长度
            Data_count=0;
			USART_RX_STA=1;//接收完成
			ES=0;
        }
	}
}

/*******************************************************************************
* 函 数 名: void Time0() interrupt 1
* 函数功能: 定时器0中断服务函数,时钟效果
*******************************************************************************/
void Time0() interrupt 1
{
	static unsigned char flag_1,flag_2; 
	TH0=(65536-50000)/256;  
	TL0=(65536-50000)%256;//重新赋初值
	
	if(mode==0)flag_1++;  //mode为0时,数码管正常显示
	else flag_2++;   
	
	if(flag_1==20 && mode==0)  //每秒执行一次
	{
		flag_1=0;
		if(++Sec>=60) //++Sec先自增再判断是否大于60  
		{
			Sec=0;
			if(++Min>=60)//++Min先自增再判断是否大于60
			{
				Min=0;
				if(++Hour>=24)//++Hour先自增再判断是否大于60
				{
					Hour=0;
				}
			}
		}
	}
	if(flag_2==9)
	{
		flash_tip=~flash_tip;//每4.5秒进行取反
		flag_2=0;
	}

}

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐