前言
STC89C52RC系列单片机内部集成有一个功能很强大的全双工串口通信口。
与传统8051单片机的串口完全兼容;
有两个互相独立的的发送和接收缓冲器,可以同时收数据和发数据;
那么问题来了,什么是串行通信?什么是全双工?什么是发送缓冲器?什么是接收缓冲器?
还有一个比较重要的事,要想搞明白串口通讯的原理,必须先把单片机的定时器学明白,尤其定时器的8位自动重装工作模式。
什么是串行通讯?
就是把数据串成一串,然后一个一个BIT的发出去,
比如要发送数据0xAA,转换成二进制就是1010 1010,按照BIT7、BIT6、BIT5、BIT4、BIT3、BIT2、BIT1、BIT0的顺序发出去。
什么是全双工?
与全双工相对的是半双工,全双工指的是发送数据的同时也可以接收数据,接收数据的时候也可以发送数据。半双工指的是发送数据的时候不准接收数据,接收数据的时候不能发送数据。
打个比方,边吃饭边拉屎那就是全双工;吃饭的时候不能拉屎,拉屎的时候不能吃饭那就是半双工。
什么是发送缓冲器?
首先这个缓冲器是一个寄存器,它是起缓冲作用的。单片机把要发送出去的数据,先准备好,放在这个缓冲器里面,这个数据是一个字节大小的,也就是8个BIT。当数据准备就绪之后,就可以发送出去。这个缓冲器跟枪的枪膛有点像,子弹上膛的过程就跟数据放入缓冲器的过程相似,只要扣动扳机子弹就发射出去了,同理数据发送出去也有个类似的“扳机”。
什么是接收缓冲器?
接收外面发过来的数据,也是先存在接收缓冲器里面,然后CPU取走,这个接收缓冲器有点类似外卖柜,外卖员把外面送过来的时候,不是直接送你手上,而是放在外卖柜中,然后你去外面柜中取走外卖。
发送缓冲器和节数缓冲器在单片机里面的名字是同一个SBUF,地址也是同一个。
什么是波特率
串行通信有4种工作模式:又两种是固定的波特率,有两种是可变的波特率。那么问题由来了,先不说通讯模式的事,先告诉我什么是波特率?为什么又要引入波特率这个东西?
波特率 Baud Rate,一般简称Baud,意思就是每秒几个码元,码元这个字太拗口,位了解释一个名词又引入一个新的名词,这是很不对的,其实一般情况下可以把码元理解为BIT,波特率就可以理解为每秒发送多少个BIT。
我们要明白一个事情,数据发送,不是发送给空气的,肯定有一个接收数据的对象;同理数据接收,肯定有一个数据发送方。你说发过来的是0xAA,那数据发送的时候就是一串波形,我怎么去识别,那就要告诉对方每个BIT数据的宽度是多少?那个BIT数据的宽度就是波特率。比如波特率9600,就表示每秒能发9600给BIT。这样发送和接收的双方就知道每个BIT的宽度是多少了。
发送与接收引脚
STC89C51RC系列单片机的接收引脚P3.0/RxD
STC89C51RC系列单片机的发送引脚P3.1/TxD
这个P3.0/RxD就表示P3.0引脚有两个功能,普通GPIO功能和串口接收功能;
这个P3.1/TxD就表示P3.1引脚有两个功能,普通GPIO功能和串口发送功能;
怎么理解?拿起枪就是兵,放下枪就是民,人还是那个人;引脚还是那个引脚。
串口相关寄存器
串口控制寄存器器(SCON)
SCON == Serial Control 用于选择串行通讯的工作方式和某些控制功能,这个某些有点那个啥了
SM0/FE:这一看就有两个功能,SM0功能和FE功能,FE就是帧错误检测功能,平时用的不多,就不要花精力来解释了,越解释越糊涂;主要还是用它的SM0功能吧,既用于串行口工作模式的设定,要想用这个功能,PCON的Bit6,既SMOD0的值必须为0。
SM1:与SM0组合使用用来确定串口的工作方式
STC89C5RC系列单片机工作在12T模式,既1个机器周期等于12个时钟周期,定时器1工作在8位自动重装模式,所以定时器1的溢出率 = SYSCLK/12/(256-TH1)。
SM2:允许方式2或方式3多机通讯控制,多机通讯用得少,有机会再展开。
REN:串行接收控制位,由软件来控制,当REN=1时,允许串口接收数据,这是启用串行接收器RxD,就可以接收数据了,当REN=0,禁止串口接收数据。
TB8:在方式2和方式3,它就是要发送的第9位数据,由软件来写1或者写0。
RB8:在方式2或方式3,它就是要接受的第9位数据,如果工作在方式1,若SM2=0,RB8就是接收到的停止位。
TI:串口发送中断请求标志位,在方式0的时候,第8位数据发送接收之后,单片机自己就把TI位置1,即TI= 1,并向CPU请求中断,中断响应之后必须用软件来复位,就是在代码中让TI=0。在方式1/2/3,停止位开始发送时,单片机自己就把TI位置1,中断响应之后必须用软件来复位。
RI:串口接收中断请求标志位,在方式0,当串行接收到第8位结束时,由内部硬件自动置位RI=1,向主机请求中断,响应中断后必须软件复位。在方式1/2/3串行接收到停止位的中间时刻由内部硬件置位,即RI=1,响应中断后必须软甲复位。
SCON的所有位都可以通过复位变成0,每位都可以位寻址。
电源控制寄存器(PCON)
PCON == Power Control
SMOD:设置串口的波特率是否加倍,SMOD=1,串行通讯1/2/3的波特率加倍;SMOD=0,波特率不加倍。
SMOD0:设置为SMOD0=0,位了让SCON的SM0起作用。
其他的位跟串口没什么关系,就不说了。
中断使能寄存器(IE)
IE == Interrupt Enable
EA:中断总开关
ES:串口中断开关
中断优先级寄存器高(IPH)
中断优先寄存器低(IP)
从地址屏蔽(SADEN):多机通讯使用
从地址(SADDR):多机通讯使用
串口缓存寄存器(SBUF)
SBUF == Serial Buffer
STC89C51RC系列单片机的串行口缓冲器SBUF地址是99H,实际上由2个缓冲器,一个是发送缓冲器,也叫发送寄存器,只能写数据进去;一个是接收缓冲器,也叫接收寄存器,只能读里面的数据。
串行通道内有一个数据寄存器
串口方式1的功能和结构示意图
发送过程:数据由串行发送端TxD输出。
1、当CPU执行一条写“SBUF”的指令就启动串口的发送;
2、写“SBUF”信号的同时还把1装入发送移位寄存器的第9位,因为第9位就是停止位;
并通知TX控制单元开始发送。发送每个BIT的定时由16分频计数器同步;
3、移位寄存器将数据不断的右移,送到TxD端口发送,右移的过程中,左边的空位就来填入0作补充;
4、当数据的最高位移到输出位置的时候,紧跟其后的是第9位,也就是停止位,这个时候TX控制单元作最后一次移位输出,然后让发送允许信号失效,完成一帧数据的发送;
5、这个时候将中断请求标志位TI置1,TI=1,向主机请求中断处理
接收过程:
1、软件将接收允许位REN置1,REN=1,接收器便以设定的波特率去采样接收端口RxD;
2、当检测到RxD端口从1变为0的跳变时,就启动接收器准备接收数据;并立即复位16分频计数器,将1FFH的值写入移位寄存器。(复位16位分频计数器是为了与输入位的时间同步)
3、16分频计数器的16个状态将每个BIT的接收时间分成16份,在每个BIT时间的7、8、9份由检测器对RxD端口进行采样,所采样到的3个值,要么为0,要么为1,只要三个值有两个以上为1,那这个BIT就是1,如果三个值有两个以上是0,那这个BIT就是0;
4、接收数据从接收移位寄存器的右边移入,已装入1FFH向左边移出,当起始位0移到移位寄存器的最左边时,使Rx控制器作最后一次移位,完成一帧数据的接收,
5、如果RI=0,且SM2=0(或接收到的停止位位1),则接收到的数据是有效的,实现装载入SBUF,停止位进入RB8,置位RI,既RI= 1,向主机请求中断,如果RI=0,且SM2=0(或接收到的停止位位1)这两个条件有一个不满足,那就收到的数据就作废,重新开始接收
串口模式3是9位UART,比模式1多一位,多的这一位就是校验位,由TB8提供。
C语言实现
#include
#include "intrins.h"
#define FOSC 11059200L //单片机的时钟频率
#define BAUD 9600 //串口通讯的波特率
bit busy;
unsigned char Rx_Buffer[5] = {0};
//unsigned char Tx_Buffer[5] = {0};
unsigned char i;
unsigned char j;
void SendData(unsigned char dat);
void SendString(char *s);
unsigned char SendNByte(unsigned char *p, unsigned char n);
void Delay1000ms();
void main()
{
SCON = 0x50; //SCON==0101 0000, 模式1,8 bit UART,接收使能
TMOD = 0x20; //定时器1设置为8位自动重装模式
/*
TH1 = TL1 = -(FOSC/12/32/BAUD)
等同于TH1 = TL1 = 256 -(FOSC/12/32/BAUD);
*/
TH1 = TL1 = -(FOSC/12/32/BAUD); //设定自动重装值
TR1 = 1;//定时器1开始运行
ES = 1;//串口中断使能
ET1 = 0;//定时器中断关闭
EA = 1;//中断总开关使能
while(1)
{
if(Rx_Buffer[0] == 1) //检查接收到的第一个数据是不是1,
{
SendNByte(Rx_Buffer, 5); //把接收到的5个数据发出去
for(j=0;j<5;j++) //把Rx_Buffer清零
{
Rx_Buffer[j] = 0;
}
}
Delay1000ms();
}
}
void Uart_Isr() interrupt 4
{
if(RI)
{
RI = 0; //接收中断标志位清零
Rx_Buffer[i++] = SBUF;
if(i == 5)
{
i = 0;
}
}
if(TI)
{
TI = 0;
busy = 0;
}
}
void SendData(unsigned char dat)
{
while(busy);//等待前面的数据发送完成
busy = 1; //发送数据时,busy标志为1
SBUF = dat; //把数据发给SBUF
}
void SendString(char *s)
{
while(*s)
{
SendData(*s++);
}
}
unsigned char SendNByte(unsigned char *p, unsigned char n)
{
unsigned char i;
for(i=0;i