STC89C52通过串口控制流水灯亮灭

时间:2021-12-24 19:59:51

本次笔记包含两个方面:

1.只是控制LED的亮灭,不返回数值

2.控制LED的亮灭,并返回数值

看了几讲的视频,都是在讲串口的方式1。其他的还没接触,这里也只用串口的方式1实现这两个功能。串口里面需要计算的地方是根据所要使用的波特率求定时器的初始值。定时器使用的是方式2,可以自动装初始值,避免赋值语句装初始值时出现误差。

以9800bps,定时器使用方式2,串口使用方式1,晶振波特率为 11.0592MHZ,求TH1跟TL1的值。

方式1的波特率 = (2^SMOD/32)xT1溢出率。单片机复位后,电源管理寄存器PCON全部清零,SMOD作为其中一位自然也清零。

波特率已经知道了。这样就剩下T1溢出率了。

假设初值为X,则定时器每次计256-X个数溢出一次(定时器为8位,最大为255 。256时发生溢出)。每计一个数的时间为一个机器周期,机器周期 = T时钟周期 X 12 。于是溢出的时间为 = 个数X 每个时间 = ( 256 - X) * 12/Fosc . 那么基础率就是溢出时间的倒数。

于是结合公式“方式1的波特率 = (2^SMOD/32)xT1溢出率”,式子可以总结为:

9600 = 2^0 /32 * Fosc / (256 - X)*12     带入全部已知数据得到     9600 = 2^0 /32 * 11059200/ (256 - X)*12    =====》》》》  求得的X为: 253  .

在此基础上,如果把SMOD 设为1 ,则 求得波特率为  :

波特率 = (2^1/32) * 11059200 / (256 - 253 )  = 2 * [  1/32 * 11059200 / (256 - 253)] = 2 * 9600 =  19200  。即变为原来的两倍。

如果把晶振换成12MHZ再求初值,求得的X为: 252.744792…… 无穷小数。这样就会产生误差。以前一直感觉整数的晶振挺好,现在才知道为什么会有11.0592MHZ这种晶振的存在了,呵呵。

这样计算得到了初值,下面贴代码。

只是控制LED的亮灭,不返回数值

实现这个又分为查询和中断两种方法。

A。先用查询。感觉叫判断更好些,因为是用if判断来实现的

#include <reg52.h>

void main()
{  
		//设置参数
		TMOD = 0x20; //设定定时器1的工作方式为方式2
		TH1 = 0xfd;
		TL1 = 0xfd;	 //装载TH1、TL1
		TR1 = 1; //启动定时器1

		REN = 1; //允许串行接收位
		SM0 = 0;
		SM1 = 1; //设定串口工作方式为方式1
/*
*    	       EA = 1; //全局中断允许位
*		ES = 1; //串口中断允许位
*		此处使用的是查询法判断接收中断标志位,所以即便不开启中断允许位,也可以
*
*/
	while(1)
	{	 	
		//查询法检测RI
		if(RI == 1)//RI为接收中断标志位。硬件置为1,必须软件清0
		{
			P1 = SBUF;
			RI = 0;
		} 		
	}

}
B 中断法

#include <reg52.h> 
void main()
{  
		//设置参数
		TMOD = 0x20; //设定定时器1的工作方式为方式2
		TH1 = 0xfd;
		TL1 = 0xfd;	 //装载TH1、TL1
		TR1 = 1; //启动定时器1

		REN = 1; //允许串行接收位
		SM0 = 0;
		SM1 = 1; //设定串口工作方式为方式1

		EA = 1; //全局中断允许位
		ES = 1; //串口中断允许位

		while(1) ;	//等待中断的发生
		
}

//中断检测RI
void ser() interrupt 4
{
	   P1 = SBUF;
	   RI = 0;
}

这两个除了代码,感觉就是是否开启中断允许了。因为RI置为1是硬件自动执行的。即便是不开启中断允许位,照样可以用if进行判断。


上面这两个是单方向的,再来个双向的。

/*
 *通过串口给下位机发送数据,并使之显示在P1口的流水灯上。
 *同时单片机返回接收到的数据,显示在串口助手上
 */
#include <reg52.h>

unsigned char flag;

void main()
{  
		//设置参数
		TMOD = 0x20; //设定定时器1的工作方式为方式2
		TH1 = 0xfd;
		TL1 = 0xfd;	 //装载TH1、TL1
		TR1 = 1; //启动定时器1

		
		SM0 = 0;
		SM1 = 1; //设定串口工作方式为方式1
		REN = 1; //允许串行接收位

    	EA = 1; //全局中断允许位
		ES = 1; //串口中断允许位

	while(1)
	{	
		/* 刚开始单片机缓冲寄存器为空,无数据可以显示
		 * 先从串口接收数据,再返回该数据
		 * 在中断中接收数据,同时将flag标志位置为1.说明接收到了数据
		 * 若接收到数据(flag == 1),说明接收到了;否则说明未接收到数据,不显示。继续判断flag数值
		 */
		
		if(flag == 1) 
		{						
			//发送数据
			ES = 0; //关闭串口中断,发送数据
			SBUF = P1; //数据写入SBUF寄存器
			while(!TI); //等待
			TI = 0;
			ES = 1; 
			flag = 0;
		
		}		
	}

}

void ser() interrupt 4
{
	//接收数据
	P1 = SBUF;
	flag = 1;
	RI = 0;
}

主函数里面那个flag = 0  。 一定不能少了。否则只要一小会儿的功能,串口助手就卡了。。。。

这个例子里还有两条语句比较关键:

P1 = SBUF;//把SBUF寄存器中的数值赋给P1

SBUF = P1;//把P1的数值写入到SBUF

SBUF是这么写的:SBUF 串行数据缓冲寄存器,一个发送缓冲寄存器,一个接收缓冲寄存器。两个公用一个地址99H,但在物理上是两个独立的寄存器。那么如何区分是发送还是接收呢?就用语句来区分了。

控制流水灯的话,需要发送十六进制格式的。

比如发送FB(1111,1011)。在我的开发板上是L2灯亮。如果发送字符,就不太好控制了。如果用2中的例程,以字符方式发送“fb”,单片机返回串口助手并用十六进制显示为“62” .这个,嗯,目前不会算 :P


没啥值得纪念的图片,还是几个流水灯。不过此时的流水灯,非彼时的流水灯。现在的流水灯,可是我从电脑上就能控制开发板上的了:D

只是不知道下次自己写个上位机是什么时候了,呵呵