C51串口通讯--学习总结

C51串口通讯–学习总结

一、知识顺理

串口通讯分为查询和中断两种方式实现。
查询是在主程序中实现串口通讯,即每执行一次程序,就会发送一次。
串口中断就需要用到中断寄存器了。当触发 SBUF 缓冲器,就会执行。
串口通讯的准备工作:设置串口的工作方式
设置波特率( T1 初值)
启动T1 (编程 TCON 中的 TR1 位。REN 位也要启动,允许接收位)
定时器T1的一定要开,这个是串口的波特率的控制。如果需要用扫定时/计数,就用T0。此时,TCOM 会被设置两次,所以在 TCOM 设定时,需要用上“ |= ”。在定时初始化时“ TCOM|=0X _ _ ”,
在串口通讯初始化时“ TCOM|=0X20 ”,串口通讯的 TCOM 一般为定值。

  • 发送 " SBUF= "
  • 接收 " =SBUF "

SBUF缓冲器的缓冲区一次只能接收一个字符。
初始化时可以不管TI,RI,这两个默认为0。

查询
查询的方式要在程序开始时进行串口初始化。用查询方式就不需要开中断寄存器,或者说不能开中断寄存器。
在查询通讯编程时:
缓冲区一次只能接收一个字符,所以,若想要讲接受到的 SBUF 在发送给上位机,需要另设一个新的变量,我不知道直接送给 SBUF 会怎么样,但是为了不让程序乱套,我觉得还是新设变量吧(主要是懒,等什么时候来了兴致再去弄吧)。不过我觉得应该是不行的。发送 SBUF 和接收SBUF 虽然名字一样,但是这两个不是同一个寄存器… …呃… …突然觉得可行,甚至想到了“ SBUF=SBUF ”这个荒唐的想法。

while(1)
	{
		if(RI)//判断接收是否完成
		{
			num = SBUF;//读SBUF,读出串口接收到的数据
			RI = 0;//软件清零接收标志位	
			temp = num;//
			SBUF = temp;//写SBUF,把要发送的数据送给发送缓存器
		}
		if(TI)//判断是否发送完成
			TI = 0;//清零发送完成标志位
	}

上面这段程序执行后,上位机发送一连串字符,51收到的也是一连串字符。而不是一串所发送字符的第一个字符组成的字符串。

中断
中断通讯较之查询方式,就需要打开寄存器。( EA,ES 置位)
中断程序

void chuank()interrur 4()
{
}

此时的串口通信是一个触发中断。只要串口处于开放状态,一经触发就会进入串口中断处理程序。

中断方式中,发送数据可以不用放在中断程序里。用 while(!TI);TI=0; 即可。
但是接收数据就要放在中断程序中。如果需要接收的地方过多,立个 flag (标志位),检测到改值了就执行西药执行的内容。
顺带一提的是,在串口中断里改变变量的值,结束中断后变量的值是改变后的值,前提是该变量是全局变量。
对于接受中断处理,我找到一个博主总结(该博主的全文),他总结了三个容易出错的情况并提出了三个相应的解决方案,我就将其总结了一下:

  1. 发送数据前,先关闭中断
    这种情况在等待发送数据时,如果收到了发送的数据,将不会被触发中断,不会保留这个新收到的数据。
    但是相应的,这也说明了他不会被新的数据打断。
  2. 发送数据前,没有关闭中断
    这个情况会出现的问题是发送数据,当 TI = 1 时,是可以进入中断程序的。但是,却在中断函数中,将 TI 清零!如果主函数后有 while(TI ==0);,将永远等不到发送结束的标志。
  3. 在中断没有区分中断来源
    这会让发送引起的中断,执行了接收中断的程序。

前两种情况就是根据需要来决定是否要先关掉中断 ,不过看情况选择第一个的情况会多一些吧。
第三种情况一般会用在既有接受又有发送的时候。中断程序放接受,发送中断用查询,然后要把flag 处理明白。比如接收到某个数据,经过对比数据,将相应的 flag=1;当使用完 flag 后,根据需要将 flag 清零。

二、一些琐碎的小点

发送接受函数的来源
关于发送接受,在 keil4 里面,它本身有一个库函数文件“stdio.h”
这里面有 puts();gets();printf();scanf(); 等等。
不过,使用 printf(); puts(); 前都应软件置位 TI = 1;
因为 printf(); puts(); 使用 putchar 函数发送字节。而在 putchar 函数中有一个 while(!TI); ,也就是说,你需要将 TI 置位。

void putchar(uchar sbyte )
{
    while(!TI);   //等待发送完

    SBUF=sbyte;

}

所以第一次调用 putchar 前没 TI=1 永远等待。
如果觉得这个很麻烦可以自己写一个接收数据的函数,方便多次调用。

   /*接收一个字符*/
    void tx_byte(uchar str)
    {
    	SBUF=str;
    	while(!TI);
    	TI==0;
    }
    /*接收字符串*/
    void tx_str(uchar *str)
    {
   		while(*str!='\0')
   		{
   			tx_byte(*str++);
   		}
    }
    /*计算字符串长度*/
    uchar rx_str( uchar* Buffer )
    {
    	uchar Length = 0;
    	uint uartRxTimOut = 0x7FFF;	//0111 1111 1111 1111
    								//根据原出处,SCON=0X50;是用T1在16位定时/计数器,当uartRxTimOut减至0,则跳出循环
    	while( uartRxTimOut-- )//自减
    	{
    		if(RI)
    		{
    			RI = 0;	
    			*Buffer = SBUF;
    			Buffer++;
    			Length++;
    			uartRxTimOut = 0x7FFF;
    		}
    	}	
    	return rxLength;
    }

(参考程序出处)
以上就是一组关于接收的程序。

如果想直接用keil4自带的程序。要注意使用规则。规则查看方法=help(菜单栏)—>μvision help。

使用flag成功使用串口中断的接受
这是我自己写的一个程序的一部分。

while(1)
{   
	uint k=0,n=1;
	for(j=0;j<i&&flag==1;j++)
	{
		/*语句块*/
	}
	flag=0;
	if(i==100)i=0;
}

串口中断处

void send()interrupt 4
{
	ES = 0;                //关闭串行中断
	if(RI)//判断接收是否完成
	{
		/**/
		RI = 0;//软件清零接收标志位	
		/**/
	}  
	if(TI)//判断是否发送完成
		TI = 0;//清零发送完成标志位
	flag=1;
	ES = 1;    //允许串口中断
}

我用是中断时关掉中断开关。虽然这两段代码看起来很简单的,很容易理解,但是这里边有一个小点折腾了我很久,没错,就是 flag 。2333… …
这个程序要用到 lcd1602,但是我开始的时候只能打开液晶屏,但是想要输的内容液晶屏上并没有,我首先想到是会不会51根本没有收到,但是改完后,这个接受区刷刷刷更新内容,液晶屏上是一点动静也没有,可把我愁坏了。做标志位处理后,就可以实现要求了。

不过我写的另外一个用到 lcd1602 的程序(实现的功能不一样)没用用到标志位也照样实现了功能。不过我分析了一下,是我设置的那个全局变量在中断中改变了。这个全局变量起到了标志位作用,都是作为一个判断下一步该往哪个方向实施的标志。

后来我又看到一种实现方法,用另一个定时器去实现。就是 T1 去提供波特率,T0 计时,定时执行某一个程序。虽然这个设想是可行的,但是碰到那种麻烦的大程序可能就不那么美丽了。

这个 flag 标志位的事情也是非常的多。
在串口中断中将标志位置位后,也不只是串口中断,只要是中断程序,然后用到了flag在中断外部处理一段程序。这个就要注意了,这个flag判断要放在一个死循环中,不然程序会跑飞(运行过程不受用户代码控制)。

我有一个工程是在while(1);里面处理的标志位。就是在这个死循环中一直检测这个标志位的值并使用,因为我原本没有把它定义成标志位,所以我在写其他类似程序的时候并没有注意到这点,所以就出现了完全进不去标志位所在的 函数。
还有另外一个工程,确实使用到了标志位,但是,还有一个条件与它一起控制那个循环。
总结一下就是,在自定义的死循环中使用标志位,不然会跑飞。
此外,main本身不是死循环,没有死循环程序跑完了以后大概又重新复位了。我看到有总结说,死循环是业界的标准做法。

外部中断,定时器中断,串口中断三个中断,我觉得定时器中断是最简单的,基本上就是定时计数。使用外部中断,最好是设立一个标志位,然后只有对标志位的操作,反正我尽量会这么干,因为我在中断中对其他变量操作,要么是进不去标志位所在语句块,要么就是进入中断后一直在中断里没有办法结束中断。除非你在外部中断里是对全局变量操作并且这个全局变量总会被扫描到。至于串口中断……我现在还在头疼。

void chuank()interrupt 4
{
	uchar i;
	EA=0;
	if(RI)
	{
		/*发送输入提示给上位机*/
		if(SBUF)
		{
		/*从上位机接收数据*/
		}
	} 	
	EA=1; 
	flag=1;
}

这个接收数据我是一定要放在串口中断这里的,但是这里面除了串口中断也没有其他的了啊,然后我在主函数中判断标志位,希望继续往下执行,结果,除了标志位所在语句块,其他的都执行了。目前还处理中… …
找到一篇文章,他说C51其实有32个中断号,即允许32个中断。他指出,中断程序不能有值传递的情况,不能进行参数传递,不能有返回值。(上面那段代码并没有参数参与,然而也不知道是结束不了中断,还是进入不了主函数)

emmm… …

串口中断肯定是有值发生变化,因为要接收数据。我觉得把这个接收用的变量设置为全局变量最好,应该是说一定要是全局变量(不然会报错…)。

感觉串口通讯所需要注意点差不多也就这些了。至于在细碎的东西,也就只能靠经验和强大的度娘了。

题外话
SBUF 传输数据大小
char 字符型占 1 byte 即 8 位
一个 char 型数据(例如:a、#、!之类的)用了 1 个字节来存储
unsigned char 无符号的字符型 占 1 byte 即 8 位。 它主要是为了能够兼容扩展 ASCII 码,由于 char 由8 位表示表示范围为 -128 - -+127,无法表示带上扩展 ASCII 码总共 256 个字符所以如果把 8 位中的最高位符号位也用来计数,就可以正好表示256个字符,unsigned char 表示范围为 0 - 255 正好 256 个数可以对应包含扩展 ASCII 码在内的共计 256 个 ASCII 字符

volatile类型
翻找解决方法时,看到了这个 volatile类型 名词,第一次听说,但是简单看了下,好像用途非常广,非常的好用。先贴一下链接,等什么时候能够有更深一步了解时就看一下。

中断中调用了其他函数则被调用函数所使用的寄存器必须与中断函数相同,被调函数最好设置为可重入的。

  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七炳八 辡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值