STC89C51 UART串口通信 发送数据 接收数据

前言

  51单片机内部集成了一个全双工串行通信口,设有两个相互独立但地址相同的数据缓冲器,用于缓存接收到的数据,以及将数据发送出去。UART串口通信相较于I2C,SPI等其他通信方式较为简单,也是最适合入门的一种通信方式。接下来我们具体了解一下UART的工作方式以及如何在单片机上配置串口通信从而实现单片机与主机之间的通信。


相关概念

  首先介绍一些简单的概念。

同步、半同步、异步通信

同步通信:同步通信需要一个共同的时钟信号,发送方和接收方都根据这个时钟信号来确定数据的发送和接收时机。时钟信号通常由一个主设备提供,或者通过专门的时钟线路传输。在同步通信中,数据以连续的比特流形式传输。发送方在时钟信号的每个时钟周期发送一个数据位,接收方在相同的时钟周期接收数据位。由于数据的传输与时钟信号同步,接收方可以准确地确定每个数据位的接收时间,从而保证数据的正确接收。
异步通信:异步通信是一种通信方式,其中数据的传输不依赖于时钟信号的同步。在异步通信中,数据以字符为单位进行传输。每个字符由起始位、数据位、校验位(可选)和停止位组成。起始位用于标志一个字符的开始,通常是一个逻辑 “0” 信号。停止位用于标志一个字符的结束,通常是一个逻辑 “1” 信号。接收方通过检测起始位来确定一个字符的开始,并在接收到停止位后认为一个字符传输完成。异步通信的传输速率通常用波特率来表示,即每秒钟传输的字符数。
半同步通信:半同步通信是一种结合了同步通信和异步通信特点的通信方式。半同步通信中通常有一个基本的同步时钟信号,用于控制数据传输的节奏。这个时钟信号可以由一个主设备提供,也可以由专门的时钟发生器产生。与同步通信不同的是,半同步通信允许接收方在需要额外时间处理数据时,向发送方发送一个等待信号(Wait 信号)。当发送方接收到等待信号时,会暂停数据的发送,直到等待信号被取消。


全双工、半双工、单工通信

全双工通信:在全双工通信中,通信双方都有独立的发送和接收通道,可以同时进行数据的发送和接收。这意味着双方可以在同一时间内互相发送和接收数据,就像两个人在进行对话一样,双方可以同时说话和倾听。
半双工通信:在半双工通信中,通信双方使用一个共享的通信信道。当一方发送数据时,另一方只能接收数据;当一方接收数据时,另一方只能发送数据。双方不能同时进行发送和接收操作。
单工通信:在单工通信中,通信双方只有一个方向的传输通道。数据只能从发送端流向接收端,而不能反向传输。例如,广播电台向听众发送广播信号,听众只能接收信号,而不能向电台发送信息。


串口通信

  串口通信是属于上面的全双工,异步通信。也就是说,对于通信双方,都可以向对方发送和接收数据,但是不要求双方统一时钟,但是既然时钟统一不了,就需要统一双方的波特率了,也就是数据发送的速率。这里解释一下为什么一定需要一个统一的东西,比如同步通信的时钟或异步通信的波特率。我们已经知道,对于发送方,数据在串行线路中是一位一位发送的,那么同样地,对于接收方,也需要一位一位地去接收,那么如果发送方一秒钟发送8位数据,接收方也就必须每秒钟同样接收8个位的数据,可以把接收数据理解为从线路上对信号进行采样,一秒钟采样8次,这样才能保证数据接收的完整一致性。
  如果接收方采样速率比需要的慢,例如接收方一秒钟只能接收4位,那么会导致什么情况?是不是就会导致接收到的数据是原本数据长度的一半,并且由于采样速率是原本的一半,所以实际接收到的数据是原本数据一位隔着一位的,例如发送方发送的数据为11001010,而接收方接收到的就是1011,从右往左数的第1357位分别丢失。
 如果接收方采样速率比需要的块,例如接收方一秒钟能接收16位,那么就会导致接收方在原本一个数据位上采样两次,还是以上面的发送的数据为例,此时接收方接收到的数据是原本数据长度的2倍,1111000011001100。
  那么经过上面的说明,相信你也了解了对于发送和接收双方,有一个同步的信号将直接决定信息传输的成败。而对于半同步通信,其实它也有一个同步的信号,只不过这个同步的信号并不是周期性的或者固定的什么信息,而是通信双方各自主动发出的信号,就好比甲方发出命令,乙方去做,只有甲方满意了,说你可以不用再继续改进了,你就可以停止这个动作了,这也是一种同步信号。只不过这种同步信号是由其中一方发出的,而另一方只需要在后面的一段时间内与发出方保持同步即可,而不需要在其余无关的时间段内与甲方同步。


定时器

  好了,铺垫了这么多,终于进入正题了,当然,我认为这些铺垫都是十分必要的,就像接下来要讲的定时器,如果没有上面的铺垫,直接就进入定时器的讲解,相信你很有可能会一头雾水。
经过上面的分析,你又看到这一小节开始讲定时器,所以你可能很快联想到,是不是在串口通信中,通信双方用定时器来进行时钟同步的!那么恭喜你,你掉坑了。虽然定时器确实是用来用于同步的,但是并不是用作同步时钟的,而是同步波特率的。对于主机,我们可以使用串口通信助手来与单片机相连接,在串口通信助手中,我们可以直接设置主机的波特率,然而,我们是没办法直接设置单片机的波特率的,因此,我们就必须通过手动配置单片机来实现单片机波特率与我们手动设置的主机的波特率一样的结果。
  那么定时器是怎样实现这个效果的呢?通过之前的学习,我们知道,定时器会计数,不断加1,加到最大值时会产生溢出,那么这里的溢出是十分重要的,因为我们的波特率的计算就和溢出这一概念有关。有关波特率的计算公式具体如下:

机器周期 T = 12 / 系统频率 定时器一次定时时间  t = ( 定时器最大值 − 定时器初值 ) ∗ T 定时器溢出率 = 1 / t 波特率 = ( 2 S M O D / 32 ) ∗ 定时器溢出率 机器周期T=12/系统频率\\ 定时器一次定时时间\ t=(定时器最大值-定时器初值)*T\\ 定时器溢出率=1/t\\ 波特率=(2^{SMOD}/32)*定时器溢出率 机器周期T=12/系统频率定时器一次定时时间 t=(定时器最大值定时器初值)T定时器溢出率=1/t波特率=(2SMOD/32)定时器溢出率

SMOD是PCON寄存器中的波特率选择位,SMOD=0时,波特率不加倍,SMOD=1时,波特率加倍,这里的波特率加倍不加倍什么的,如果不理解就不要去试着理解了,就记住一般都是SMOD=0,波特率不加倍。
  定时器的溢出率就是指定时器1s产生溢出的次数,假设定时器从初值不断加1,到溢出一次的时间是10us,那么溢出率就是1/10us=100000次/s。
  在上面的我们自己假设的条件下,SMOD=0,溢出率=100000,那么就可以通过波特率的计算公式计算得到波特率,具体是多少这里就不算了。当然,这里只是给定了条件,在实际情况中,我们除了自己可以设置SMOD的值外,其他的值都是需要动手去算的,接下来我们具体看一下如果去计算。
  假设现在单片机的频率是11.0592MHz,目标是将单片机的波特率设置为4800(这个值你可以自己定),那么机器周期T=12/11.0592=1.085MHz ,因为串口中断需要用到定时器1,所以这里以定时器1的溢出率为例进行计算,此外还要选择定时器的工作模式,这里为了方便,选择定时器的模式2,8位自动重装模式。现在计算定时器一次定时间 t=(256 - 高八位初值)*T,这里T已知,高八位初值不知道,如果知道高八位初值,就可以计算出定时时间t,从而得到溢出率,最终可以计算出波特率。但是,不要忘了我们的目标是什么,是要将波特率设为4800,那么我们看公式中的最后一个式子,现在波特率的值是4800,SMOD=0,只有定时器溢出率不知道,那么我们何不直接把溢出率单独提出来,得到: 溢出率 = ( 2 S M O D / 32 ) ∗ 波特率 溢出率=(2^{SMOD}/32)*波特率 溢出率=(2SMOD/32)波特率,将数据代入后得到溢出率=152600,由溢出率的公式可以知道1/t=152600,所以可以计算出定时器一次定时时间t=6.5104us。在8位自动重装模式下,高八位的最大值是255,现在知道了定时的时间,根据第一个公式就可以计算出高八位的初值=255-定时时间/机器周期 +1,将数据代入后可以得到高八位初值=0xFA。而我们知道,定时器的高八位初值我们是可以手动设置的,因此,我们只需要将定时器1的高八位的值设为0xFA即可。
  通过上面的分析我们发现,其实配置单片机波特率最终需要解决的问题就一个,也就是定时器初值的确定


串口通信相关寄存器

  在串口通信中,用到的主要的寄存器有SCON寄存器、SBUF寄存器、PCON寄存器,其中SBUF寄存器不需要对位进行操作,只需要在读数据时从中取出来,发送数据时往里写入即可。

PCON

PCON寄存器的数据位格式如下:
在这里插入图片描述
  PCON寄存器不可位寻址,在PCON寄存器中,我们需要使用到的位有SMOD位和SMOD0位。
  SMOD:该位用于设置波特率是否加倍,SMOD=0时,波特率不加倍,SMOD=1时,波特率加倍。通常该位置0;
  SMOD0:该位为帧错误检测有效控制位,当SMOD0=1时,SCON寄存器(下面会讲到SCON寄存器)中的SMO0/FE位用于进行帧错误检测;当SMOD0=0时,SCON寄存器中的SM0/FE位用于指定串口的工作方式(SM0与SM1组合在一起指定四种工作模式)。

SCON

SCON寄存器的数据位格式如下:
在这里插入图片描述
  SCON寄存器可位寻址,在该寄存器中需要使用到的位有SM0/FE位、SM1位、REN位、TI位、RI位。可以单独设每一位的值,也可以直接给SCON赋值。
  SM0/FE:上面讲过,当PCON中的SMOD0=1时,该位用于进行帧错误检测;当PCON中的SMOD0=0时,该位与SM1一起组合设定串口的工作模式(具体每个工作模式下面会讲到)。具体规则设定模式如下:

工作模式SM0SM1
模式000
模式101
模式210
模式311

  SM1:如上所述。
  REN:接收数据使能,REN=0时,串口不接收数据;当REN=1时,串口允许接收数据。
  TI:发送数据完成中断标志位,当SBUF中的数据发送完成后,该位会由硬件置1,置1后不会自动恢复0,必须由软件手动恢复。
  RI:接收数据完成中断标志位,当SBUF中的数据接收完成后,该位会由硬件置1,置1后不会自动恢复0,必须由软件手动恢复。

SBUF

  我们知道,发送数据和接收数据都各自有一个对应的数据缓存器,在51单片机中,这两个数据缓冲器的地址是相同的,都是99H,当你需要写入数据到SBUF中时,直接使用赋值语句即可,例如:

SBUF=0x3F;

这样就实现了数据的写入。如果你需要从SBUF中读取数据,那么同理,直接从SBUF取出即可:

unsigned char data=SBUF;

  那么可能到这里有的同学就有疑问了:不对啊?直接就能同时实现读和写两种不同的操作?它怎么知道我是要读数据还是要写数据的呢?这个其实咱们就不用过于关心了,该缓存器是存在响应的硬件电路帮我们直接实现了这一功能了的,我们只需要告诉它我现在是要读数据还是写数据,它自己就能完成响应的操作并把结果给你。

串口4种工作方式

串口的四种工作方式如下:

SM0SM1工作方式说明波特率
00方式0同步移位串行方式SYSclk/12
01方式18位UART,波特率可变 2 S M O D / 32 ∗ 定时器 1 的溢出率 2^{SMOD}/32*定时器1的溢出率 2SMOD/32定时器1的溢出率
10方式29位UART ( 2 S M O D / 64 ) ∗ S Y S c l k (2^{SMOD}/64)*SYSclk (2SMOD/64)SYSclk
11方式39位UART,波特率可变 2 S M O D / 32 ∗ 定时器 1 的溢出率 2^{SMOD}/32*定时器1的溢出率 2SMOD/32定时器1的溢出率

配置总流程

  前面我们依次把定时器、PCON、SCON、SBUF全部讲过了,那么我们现在就将它们串起来,正式实现串口通信的功能。

配置定时器

  首先我们要确定定时器,在51单片机中,串口中断使用的定时器是Timer1,因此选择Timer1定时器,并将其进行初始化,为了方便,对定时器,我们选择模式2,即8位自动重装模式。选择完定时器和定时器模式后,由于串口通信属于异步通信,因此我们需要确定当前通信双方所使用的波特率,假定我们设置波特率为4800,那么现在就需要去设置定时器的重装初值,也就是前面我们计算的高八位的值,结果是0xFA,计算的具体过程参照前面,这里不再叙述。于是定时器1的初始化相关配置如下:

TMOD &= 0x0f; // 0000 1111
TMOD |= 0x20; // 0010 0000
TL1 = 0xFA;
TH1 = 0xFA;

  但是需要注意的是,和一般配置定时器不同的是,我们需要关闭定时器1的中断使能,即:

ET1=0

  因为我们只是需要用定时器1的溢出率而不需要定时器1的中断,因此定时器1的中断使能需要关闭,然后开始计数:

TR1=1;

配置串口

  定时器打开后,就要配置串口相关寄存器了,首先配置PCON寄存器,由于PCON寄存器不可位寻址,因此需整体赋值,将最高的两位设为00,其余位不变,PCON的设置如下:

PCON &=0x3f; // 0011 1111

  接着配置SCON寄存器,由于SCON寄存器可位寻址,因此,直接对需要操作的位进行赋值,将串口工作方式设为方式1,打开串口接收使能,发送中断和接收中断置0,具体如下:

SM0=1;
SMO1=0;
REN=1;
TI=0;
RI=0;

打开中断

  最后,配置结束之前,我们需要打开总中断、串口中断:

EA=1;
ES=1

  这样,全部的配置就都完成了。

串口发送数据

  那么配置好了之后,我们该怎么发送数据或者接收数据呢?其实很简单,还记得SBUF寄存器吗,还有TI发送中断标志位、RI接收中断标志位,发送与接收就是通过它们来实现的。
对应发送而言,十分简单,如果你想往串口发送数据,直接将数据写入到SBUF即可,代码如下:

void UART_SendByte(unsigned char sendByte)
{
	SBUF = sendByte;
	while(TI==0);
	TI=1; // 手动清零
}

  代码中的while循环用于等待数据发送完成,当数据发送完成后,TI标志位会自动置1,这样我们就知道数据发送完成了,此时我们需要手动将TI位置0。

串口接收数据

  对于接收数据,相对而言较为麻烦一点,还记得我们在最开始打开了接收使能吗,当REN=1时,串口就可以接收数据了,但是我们该怎么知道什么时候有数据来了呢?这个时候就要用到上一篇文章讲到的中断了,串口有它对应的中断,其中断号为4,这样,我们只需要编写一个中断函数,在该函数中接收数据就可以了,代码如下:

void UART_Rountine() interrupt 4
{
	if(RI==1)
	{
		RI=0; // 手动清零
		unsigned char recieveByte=SBUF;
	}
}

  当有接收到数据时,RI会被自动置为1,同时串口中断被触发,此时就会进入到中断函数UART_Routine()中去,而进去后,我们首先判断RI的值,如果为1说明有数据来了,然后我们同样需要将RI置为0,之后再做其他的操作。

整体代码

void UART_Init()
{
	// 设置串行口工作方式为 方式1
	SM0 = 0;
	SM1 = 1;
	REN = 1; // 打开接收使能
	TI = 0; // 发送中断标志位
	RI = 0; // 接收中断标注位

	PCON &=0x3f;
	// 设置定时器
	TMOD &= 0x0f;
	TMOD |= 0x20;
	
	TL1 = 0xfa;
	TH1 = 0xfa;
	ET1=0;
	// 打开中断
	EA = 1;
	ES = 1;
	// 开始计数
	TR1 =1;
}
void UART_SendByte(unsigned char Byte)
{
	SBUF = Byte;
	while(TI==0);
	TI=0;
}
void UART_Rountine() interrupt 4
{
	if(RI==1)
	{
		RI=0;
		P2 = SBUF;
	}
}
void main()
{
	UART_Init();
	while(1)
	{
		
	}
}

总结

  串口主要用到的寄存器有,TMOD(用于配置定时器工作模式)、TCON(TR,TF)、PCON(波特率和帧错误控制)、SCON(串口工作模式配置),如果有AUXR,可以使用AUXR对波特率进行配置,进行选择定时器1或2作为波特率发生器,以及波特率是否加倍。

好了,本篇文章到这里就结束了,希望你在阅读完本文后能够对串口通信的工作方式有一个更加深刻的了解!如果有什么疑问欢迎在评论区留言~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值