UART串口通信的基本应用
通信的三种基本类型:
单工通信:值允许一方向另一方传送星系,另一方不能回传信息。例:电视遥控器、收音机广播
半双工通信:数据可在双方之间相互传播,但同一时刻只能一方传给另一方。例:对讲机
全双工通信:发送数据的同时也可接收数据,两者同步进行。例:电话
UART模块介绍
通常情况下,我们关心的是通信的结果而非过程。51单片机内部存在UART模块,可自动接收数据,接收完毕,会发出通知信号。要使用这个模块,需要配置对应的具有特殊功能的寄存器。
51单片机的UART串口结构由串行口控制寄存器SCON、发送和接收电路三部分构成。
先来了解串口控制寄存器SCON
SCON——串行控制寄存器的位分配(地址0X98、可位寻址)
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
符号 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
复位值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
SCON——串行控制寄存器的位描述
位 | 符号 | 描述 |
---|---|---|
7 | SM0 | SM0和SM1两位共同决定了串口通信的模式0~模式3共4种模式。最常用的是模式1,即SM0=0,SM1=1。下面重点讲模式1,其他模式从略。 |
6 | SM1 | SM0和SM1两位共同决定了串口通信的模式0~模式3共4种模式。最常用的是模式1,即SM0=0,SM1=1。下面重点讲模式1,其他模式从略。 |
5 | SM2 | 多机通信控制位(极少用),模式1直接清零。 |
4 | REN | 使能串口接收。由软件置位使能接收,软件清零则禁止接收。 |
3 | TB8 | 模式2和3中要发送的第9位数据(很少用)。 |
2 | RB8 | 模式2和3中要接收的第9位数据(很少用),模式1用来接收停止位。 |
1 | TI | 发送中断标志位,当发送电路发送到停止位的中间位置时,TI由硬件置1,必须通过软件清零。 |
0 | RI | 接收中断标志位,当接收电路接收到停止位的中间位置时,RI由硬件置1,必须通过软件清零。 |
对于串口的四种模式,模式1是最常用的,就是前面提到的1位起始位,8位数据位和1位停止位。 下面讲详细介绍模式1的工作细节和使用方法(其他3种模式与模式1大同小异,要用的时候自行查阅资料既可)。
在硬件模块中,有一个专门的波特率发生器用来控制发送和接收数据的速度。对于STC89C52单片机来说,这个波特率只能由定时器T1或定时器T2产生。这与模拟的通信概念完全不同。默认使用定时器T1(定时器T2需要额外配置的寄存器)。
下面主要就使用T1作为波特率发生器来讲解,方式1下的波特率发生器必须使用T1的模式2,也就是自动重装载模式。
定时器的重载值计算公式为:TH1 = TL1 = 256-晶振值/12/2/16/波特率
这里介绍一个与波特率有关的另一个计算器:电源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果写PCON |= 0X80以后,计算公式就成了:TH1 = TL1 = 256-晶振值/12/16/波特率
公式中数字的含义解释如下:
256——8位定时器的溢出值,1TL1的溢出值。
晶振值——在开发板上是11059200
12——1个机器周期等于12个时钟周期
16——将一位信号采集16次(IO口模拟串口通信接收数据时采集的是这位数据的中间位置,而实际串口模块比模拟的要复杂和精确一些。它采取的方式是把一位信号采集16次,其中7,8,9次取出来,如果这三次中有两次是高电平,就认为这个数据是1,如果有两次是低电平,就认为这个数据是0,这样即使是受到干扰导致数据读错,仍可保证最终数据的正确性)
了解了串口采集模式,这里可以思考一个问题。“晶振值/12/2/16/波特率”计算出的值有没有可能是小数?把这里理解了也就解释了晶振为何使用11059200。 因为11059200可以被常见的波特率整除。
串口通信的发送和接收电路在物理上有两个名字相同的SBUF寄存器,它们的地址相同都是0X99。 这两个寄存器一个用来发送缓冲,一个用来接收缓冲,实现了UART的全双工通信,互相不干扰。操作SBUF,单片机会自动判断并决定选择接收SBUF还是发送SBUF。
UART串口程序
一般情况下,串口通信程序的编写步骤如下:
1)配置串口为模式1
2)配置定时器T1为模式2(自动重装模式)
3)根据波特率计算TH1和TL1的初值,如果有需要可以使用PCON进行波特率加倍。
4)打开定时器控制寄存器TR1,让定时器跑起来。
注意:使用T1时,即在使用T1做波特率发生器,千万不能再使能T1中断。
以下是一段演示代码
#include <reg52.h>
void ConfigUART(unsigned int baud);//串口配置函数,baud——通信波特率
void main()
{
ConfigUART(9600); //配置波特率为9600
while(1)
{
while(!RI); //等待接收完成
RI = 0; //清零接收中断标志
SBUF = SBUF + 1;//接收到的数据+1后,发送回去
while(!TI); //等待发送完成
TI = 0; //清零发送中断标志位
}
}
void ConfigUART(unsigned int baud)
{
SCON = 0X50; //配置串口为模式1
TMOD &= 0X0F; //清零T1的控制位
TMOD |= 0X20; //配置T1为模式2
TH1 = 256 - (11059200/12/32)/baud; //计算T1重载值
TL1 = TH1; //初值等于重载值
ES = 0; //禁止T1中断
TR1 = 1; //启动T1
}
注意:实际工程开发中,并不会这样编写这段代码,因为这段代码在主循环里等待接收中断标志位和发送中断标志位,占据了大量程序内存。实际使用串口时常用到串口中断。
以下是串口中断的演示代码
#include <reg52.h>
void ConfigUART(unsigned int baud);//串口配置函数,baud——通信波特率
void main()
{
EA = 1; //使能总中断
ConfigUART(9600); //配置波特率为9600
while(1);
}
void ConfigUART(unsigned int baud)
{
SCON = 0X50; //配置串口为模式1
TMOD &= 0X0F; //清零T1的控制位
TMOD |= 0X20; //配置T1为模式2
TH1 = 256 - (11059200/12/32)/baud; //计算T1重载值
TL1 = TH1; //初值等于重载值
ET1 = 1; //禁止T1中断
ES = 1; //使能串口中断
TR1 = 1; //启动T1
}
//UART中断服务函数
void InterruptUART() interrupt 4
{
if(RI) //接收到字节
{
RI = 0; //手动清零接收中断标志
SBUF = SBUF + 1; //接收到的数据+1后发回,左边是发送SBUF,右边是接收SBUF
}
if(TI) //字节发送完毕
{
TI = 0; //手动清零发送中断标志位
}
}