08:串口通信三

串口中断

1、中断的引出

我们写个代码,实现单片机每隔1s给PC的上位机发送一段字符串,然后我们也能通过上位机控制单片机的LED1的亮灭。

代码①:

向上位机每隔1s不断的发送一段字符串

c 复制代码
#include <REGX52.H>
#include "intrins.h"

sfr AUXR = 0X8E;

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)		//波特率9600
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x40;		//8位数据,可变波特率
	AUXR = 0x01;	
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	TR1 = 1;		//启动定时器1
}

void sendByte(char data_sj)
{
	SBUF = data_sj;//单片机开始将数据从SBUF发送出去
	while(!TI);
	TI = 0;
}

void sendString(char *str)
{
	while(*str != '\0')
	{
		sendByte(*str);
		str++;
	}
}

void main()
{
	UartInit();
	while(1)
	{
		sendString("wohaoshuai\r\n");
		Delay1000ms();
	}
}

代码②:通过上位机来控制LED1的亮灭

c 复制代码
#include <REGX52.H>
#include "intrins.h"

sfr AUXR = 0X8E;
sbit LED1 = P3^7;

void Delay300ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)		//波特率9600
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率,且允许接收数据
	AUXR = 0x01;	
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	TR1 = 1;		//启动定时器1
}

void main()
{
	char mark;
	LED1 = 1;
	UartInit();
	while(1)
	{
		Delay300ms();//先给硬件一点准备时间
		if(RI == 1)//判断单片机的SBUF是否写入数据成功,已经写入成功这变为1,然后个RI手动置0后,以便为第二次写入做标志
		{
		    RI = 0;
		    mark = SBUF;//让mark来承接数据
			if(mark == 'o')
			{
					LED1 = 0; 
			}
			if(mark == 'c')
			{
					LED1 = 1;
			}
		}
	}
}

最终功能实现的代码③:

c 复制代码
#include <REGX52.H>
#include "intrins.h"

sfr AUXR = 0X8E;
sbit LED1 = P3^7;

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay300ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)		//波特率9600
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x40;		//8位数据,可变波特率
	REN = 1;
	AUXR = 0x01;	
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	TR1 = 1;		//启动定时器1
}


void sendByte(char data_sj)
{
	SBUF = data_sj;
	while(!TI);
	TI = 0;
}

void sendString(char *str)
{
	while(*str != '\0')
	{
		sendByte(*str);
		str++;
	}
}

void main()
{
	char mark;
	LED1 = 1;
	UartInit();
	while(1)
	{
		Delay300ms();//先给硬件一点准备的时间
		sendString("wohaoshuai\r\n");
		Delay1000ms();
		if(RI == 1)
		{ 
		    RI = 0;
		    mark = SBUF;
			if(mark == 'o')
			{
			   LED1 = 0; 
			}
			if(mark == 'c')
			{
			   LED1 = 1;
			}
		}
	}
}

其实就是代码①和代码②的结合。

但是我们发现一个BUG,就是上位机个单片机发送一个指令控制LED1时,LED1要过大约1s钟才会做出相应的动作。导致控制LED1的指令不灵敏。
这是为什么喃?

  • 原因:主要因为是主函数里面的Delay1000ms()导致的,因为可能指令到来的时候,单片机还在执行Delay函数。

  • 例如:把单片机比作我们,主函数的代码比作我们要做的事情,sendString()函数比作我们看电视,delay()函数比作我们数数。if()比作出门检测门口邮箱是否来通知。if后面{ }中的内容就是通知要我们做的事情。

  • 整个过程就是:我们看电视->看完电视数1秒钟的数->数完数去检查一下邮箱是否有通知(如果有通知就执行通知里面的事情)->做完通知里面的事然后继续看电视。这样循环往复。

  • 这个通知是外界给我们的,比如邮递员。我们并不知道邮递员什么时候给邮箱里面发送通知,所以我们只能每隔一段时间去查看邮箱里面是否有通知。

  • 当我们还在看电视的时候,通知已经到邮箱里面了,这不代表通知来了,我们就必须去查看邮箱。我们也要等数完数才会去查看邮箱。所以通知里面让我们做的事情,并不会因为通知发送到邮箱而立马发生,也只有我们查看了邮箱后才会发生。

所以,上位机给单片机发送指令(就相当于给邮箱里面发送通知,比如让LED1亮/灭),而发送指令的时候,可能单片机还在执行其他的事情(比如我们看电视),单片机还没有执行到检测if()(我们还没有去查看邮箱),所以LED1灯不会立马做出反应,只有等到单片机执行到if()时,查看到指令,才会让LED1亮/灭。

解决方法:

  • 使用串口中断,中断就相当于邮递员个邮箱里面发送通知的时候,按了一下门铃,给我们一个信息,让我们知道通知来了。让我们先执行通知里面的事情,做完通知里面的事情后,再回来继续接着做上次没有作为的事情。
  • 通知里面的事情就是所谓单片机里面的中断函数里面的程序,一般情况下,在不影响主函数里面的程序,中断函数里面的程序执行速度是十分迅速的。

2、串口中断



  • 如图上图串口中断源是UART,其中序列号位4。中断的标志位是TI和RI
  • 开启串口中断:ES = 1;EA = 1;
  • 由上可知,中断函数的序列号只有一个,所以可见,串口发送数据发生的中断和串口接收数据发生的中断是同一个函数
  • 单片机串口接收数据中断的触发:并不是将SBUF里面的数据承接完给mark开始的。而是由上位机给SBUF写入数据完毕的那个时候开始
  • 单片机串口发送数据中断的触发:是单片机向SBUF里面写入数据完毕开始的。可见都是向SBUF里面写入数据完毕开始的。

如上图所示,串口中断有2个标志位,如果开启的串口中断,定义了中断函数,当单片机接收到数据的时候,RI会自动变为1,所以我们通过判断RI的值来执行的发送数据的中断还是接收数据的中断。

c 复制代码
#include <REGX52.H>
#include "intrins.h"

sfr AUXR = 0X8E;
sbit LED1 = P3^7;
char mark;

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay300ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)		//波特率9600
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x40;		//8位数据,可变波特率
	REN = 1;
	AUXR = 0x01;	
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	TR1 = 1;		//启动定时器1
	
	ES = 1;//开启串口中断
	EA = 1;//开启中断总开关
}


void sendByte(char data_sj)
{
	SBUF = data_sj;
	while(!TI);
	TI = 0;
}

void sendString(char *str)
{
	while(*str != '\0')
	{
		sendByte(*str);
		str++;
	}
}

void main()
{
	LED1 = 1;
	UartInit();
	while(1)
	{
		Delay300ms();
		sendString("wohaoshuai\r\n");
		Delay1000ms();
	}
}

void UART_Handler() interrupt 4//从上位机给SBUF里面写入数据,触发中断函数
{
		if(RI == 1)				//上位机写入数据完毕后,RI变为1
		{
		    RI = 0;				//让RI变为0以便下次写入数据数据
		    mark = SBUF;		//将SBUF里面的数据开始承接给mark,注意是开始。也就是单片机开始接收数据
			if(mark == 'o')
			{
			   LED1 = 0; 
			}
			if(mark == 'c')
			{
			   LED1 = 1;
			}
		}
}

3、发送字符串控制LED1的亮灭

c 复制代码
#include <REGX52.H>
#include "intrins.h"
#include <string.h>

sfr AUXR = 0X8E;
sbit LED1 = P3^7;
char mark[12];//定义mark数组用来承接字符串

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;
	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay300ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)		//波特率9600
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x40;		//8位数据,可变波特率
	REN = 1;
	AUXR = 0x01;	
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	TR1 = 1;		//启动定时器1
	
	ES = 1;//开启串口中断
	EA = 1;//开启中断总开关
}


void sendByte(char data_sj)
{
	SBUF = data_sj;
	while(!TI);
	TI = 0;
}

void sendString(char *str)
{
	while(*str != '\0')
	{
		sendByte(*str);
		str++;
	}
}

void main()
{
	LED1 = 1;
	UartInit();
	while(1)
	{
		Delay300ms();
		sendString("wohaoshuai\r\n");
		Delay1000ms();
	}
}

void UART_Handler() interrupt 4
{
	static int i = 0;//定义一个静态变量
		if(RI == 1)
		{
			RI = 0;
	    mark[i] = SBUF;
	     i++;
			if(i == 12)
				i = 0;
			if(strstr(mark,"op"))
			{
					LED1 = 0;
						i = 0;
				memset(mark,'\0',12);
			}
			if(strstr(mark,"cl"))
			{
					LED1 = 1;
					i = 0;
				memset(mark,'\0',12);
			}
		}
}
void UART_Handler() interrupt 4
{
static int i = 0;//定义一个静态变量
   if(RI == 1)
   {
	 RI = 0;
     mark[i] = SBUF;
     i++;
	 if(i == 12)
	  i = 0;
	  if(strstr(mark,"op"))
	   {
		  LED1 = 0;
		  i = 0;
		  memset(mark,'\0',12);
	   }
	   if(strstr(mark,"cl"))
	   {
		   LED1 = 1;
		   i = 0;
		   memset(mark,'\0',12);
	  }
}

假设上位机发送open字符串,上位机也是将open的每个字符一个一个按顺序的写入SBUF的。将第一个o写入后,执行中断函数,SBUF将o存放在mark数组的第一位,然后上位机将第二个数据p写入SBUF,执行中断函数,SBUF将p存放在mark数组的第二位。这样依次进行。由此可见,每写入一个数据,就会执行一次中断函数。同理:单片机每次向外发送数据后,都会调用一次中断函数

相关推荐
网易独家音乐人Mike Zhou4 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵4 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
lantiandianzi11 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网11 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式11 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈12 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代1361006839313 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi13 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件
嵌入式知识大讲堂13 小时前
HDMI数据传输三种使用场景
单片机
黑客呀14 小时前
[系统安全]Rootkit基础
stm32·单片机·系统安全