前言
串口是一种应用十分广泛的通讯接口,是串行通信最纯粹的表现形式,即数据结构是由一串一串的数据流构成的,通常是8位一串地进行传递,每一位都有先后顺序,并不是同时到达目的地址的,因此串口的成本低,容易使用,线路简单,但速率较慢,可以实现两个设备的简单通信。与之对应的是并口,以8位为例,并行通信是8位数据同时发出,同步达到,理论上速率是穿行的8倍,但成本较高,应用场景较少。
单片机的可以时单片机与单片机,单片机与电脑,单片机与各种各样的设备进行互相通信,极大地提升了单片机裸机的扩展性,增强了其硬件实力。
一、UART
51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。
STC89C52系列单片机内部集成有一个功能很强的全双工串行通信口,与传统8051单片机的串口完全兼容。设有2个互相独立的接收、发送缓冲器,可以同时发送和接收数据。发送缓冲器只能写入而不能读出,接收缓冲器只能读出而不能写入,,因而两个缓冲器可以共用一个地址码(99H)。两个缓冲器统称串行通信特殊功能寄存器 SBUF 。
电平协议:
值得一提的是,串行通信分为很多种电平协议,例如TTL,RS232,RS485等,不做任何处理的话它们相互之间是无法正常通信的。电平标准就是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系。单片机一般都是TTL协议,电脑端大多都使用的是USB接口,TTL转USB协议就需要一颗ch340芯片,不过51的开发板已经装好了,所以我们只需要插上USB线就可以实现单片机和电脑端的串口通信了。
串口常用的电平标准有如下三种:
TTL电平:+5V表示1,0V表示0
RS232电平:-3~-15V表示1,+3~+15V表示0
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
引脚定义 :
- TXD :(Transmit Exchange Data) 数据发送
- RXD:(Receive Exchange Data) 数据接收
严格意义上来讲串口通信只有这两根线组成,通信原理也非常简单,就是一发一收,发送和接收分别独占一根数据线,可以兼顾发送和接收的工作,所以串口为全双工。由于没有第三根时钟线来统一传输的时间基准,所以串口为异步通信方式,双方必须约定好波特率,否则数据就会出现不可预知的错误。
工作模式:
STC89C52只有一个UART,总共有四种工作模式:
模式0:同步移位寄存器
模式1:8位UART,波特率可变(常用)
模式2:9位UART,波特率固定
模式3:9位UART,波特率可变
常用的只有模式1,其他3种模式小编到现在还没遇到过。
二、UART参数、时序、及其工作原理
STC89C52系列单片机串行口对应的硬件部分对应的管脚是P3.0/RxD和P3.1/TxD。
参数:
波特率:串口通信的速率(发送和接收各数据位的间隔时间)
校验位:用于数据验证(奇校验/偶校验)
停止位:用于数据帧间隔
时序:
一般情况我们都会选则模式1,此模式为8位UART格式,一帧数据为10位,1位起始位,8位数据位,1位停止位,波特率可变,即可根据需要进行设置。
8位数据格式
模式2和模式3为9位数据格式,一帧数据总共11位,在停止位后紧接一位校验位,用以检验这帧数据的可靠性。
9位数据格式
这两种方法均采用低位先行的原则,即从一帧数据的右端开始逐一发送,比如1010 1010,就是从最低位 “0” 开始发,接着发第二位的 “1” 接着 “0” 、“1”、“0”......经过一些硬件电路的处理后,接收端收到的数据仍然是“1010 1010”。
下面是手册里给的时序图:
工作原理:
我们可以看到,串口是挂载在总线上的,除此之外总线上还挂载了许多设备交互工作。那么UART内部到底是如何完成数据的发送和接收的呢?将其放大后就是下面这张图:
这就是UART串口模式1的功能结构示意图,上下两处标记的INTERNAL BUS就是内部总线,与上面单片机内部结构图相关联。接下来详细些说说我对这张图的理解,可能有不对的地方也请大家指正哈!
波特率发生器:
首先就是控制波特率这边,stc89c52是利用了定时器1的溢出率,通过16分频或2分频后十六分频来控制收发器的采样时间,这里需要配置SMOD 这个寄存器来控制它的收发速率,来形成一个类似的波特率发生器,图中可以看到发送和接受是公用波特率电路的,所以发送和接收的波特率也是相同的,且与之进行通信的设备波特率也必须是相同的,否则传输的数据就会出错。
发送数据:
写入的数据会存放在SBUF(Serial Buffer)这个缓存区中,同时触发start这个标志位,使发送控制器开始工作,数据就会一位一位的发送到TX引脚端。这里出现了一个触发器,我忘了这是个什么触发器了QAQ(原谅小编数电没学好),不过不影响对这部分框图的理解。
接收数据:
数据从RX引脚端进来,接收控制器控制移位寄存器一位一位地放入到SBUF缓存区,再通过总线发送给CPU,我们想要数据的时候把这个缓存读出来就好了。注意,这个SBUF和发送端的SBUF在物理上是两个独立的寄存器,但占用了相同的地址(0x99),写操作时写入的是发送寄存器,读操作时读出的是接收寄存器。那么在程序中是如何区分的呢?当SBUF作为左值时(在“=”左边)就是写入缓存,当SBUF作为右值时(在“=”右边)就是读出缓存。
串口中断:
发送控制器和接收控制器在发送和接收时分别会置"TI"和"RI"标志位,这两个标志位经过一个或门可以申请串口中断,需要使用串口中断时需先配置好中断参数,包括使能,选用通道,和优先级等等,TX和RX占用同一个中断通道,需要对其进行判断。在中断服务程序中可以进行相应的处理,比如当有数据传入时触发接收中断,这时就可以及时地对接收到的数据进行处理。
OK那,这就是UART原理图部分的简单介绍了。
三、寄存器配置
书写程序需要对照上面的框图和寄存器表格来配置参数:
STC89C52系列单片机的穿行口有两个控制寄存器:串行控制寄存器SCON和波特率选择特殊功能寄存器PCON。
串行控制寄存器SCON用于选择串行通信的工作方式和某些控制功能。其格式如下:
SCON: 串行控制寄存器 (可位寻址)
其中SMO、SM1按下列组合确定串行口的工作方式:
我们这里选择方式1,即SMO、SM1分别给0、1。
SM2:该位是控制是否允许方式2、方式3进行多机通信,我们使用的是方式1,这一位可以随便给,就置0了。
RXE:该位为是否允许接收控制位,置1时为使能接收,置0时为失能接收通道,我们需要串口接收功能,这位给1。
TB8和RB8两位分别是方式2、方式3第九位的发送校验位和接收校验位,不用管它。不过在方式1时,如果SM2为0,则RB8接收到的是停止位,也就是说在当前模式,这位数会在接收到一帧数据时变为停止位,我们不对停止位进行处理,还是不用管。
TI:发送中断请求标志位。在方式0,当串行发送数据第8位结束时,由内部硬件自动置位,即TI=1,向主机请求中断,响应中断后必须用软件复位,即TI=0。在其他方式中,则在停止位开始发送时由内部硬件置位,必须用软件复位。
RI:接收中断请求标志位。在方式0,当串行接收到第8位结束时由内部硬件自动置位RI=1,向主机请求中断,响应中断后必须用软件复位,即RI=0。在其他方式中,串行接收到停止位的中间时刻由内部硬件置位,即RI=1(例外情况见SM2说明) ,必须由软件复位,即RI=0。
后面这几位都是标志位所以都不需要配,它们会在特定情况改变,我们只需要读出它即可,比如串口中断时,初始化配置都给0就可以。那么这个8位寄存器配置的值就是 “0101 0000”
PCON: 电源控制寄存器(不可位寻址)
SMOD:这个SMOD就是上面的波特率图中的那个开关控制,波特率选择位,当用软件置位SMOD,即SMOD=1,则使串行通信方式1、2、3的波特率加倍:SMOD=0,则波特率不加倍。复位时SMOD=0。
SMOD0:帧错误检测有效控制位。当SMODO=1,SCON寄存器中的SMO/FE位用于FE(帧错误检测)功能:当SMOD0=0,SCON寄存器中的SMO/FE位用于SMO功能,和SM1一起指定串行口的工作方式。复位时SMODO=0
剩余的其他位都是标志位不需要处理,配置为波特率加倍即可,即“1000 0000”,“0x80”。
不是很明白为什么要把波特率控制放在电源控制寄存器中,这个寄存器暂时就配这两位就可以,接下来看一看是如何用定时器来产生波特率的:
波特率是定时器产生的,前面博文介绍过关于定时器的工作原理和配置,这里就不做过多介绍了,需要注意的是串口波特率用的是定时器1,而且是不能更改的,要配置为双8位自动重装载模式,并且是根据溢出率来计算的,计数值每到2^8=255就会自动重装。至于这个波特率的计算方法还是比较麻烦的,涉及到晶振和时钟等复杂的计算,建议直接用ISP下载助手这个软件的波特率计算功能直接生成C代码。
四、 代码
终于结束了枯燥的讲解部分,来看看代码怎么写,这段代码主要实现一收一发的简单功能,详细解释都写在了注释里面:
#include <REGX52.H>
void Delay(unsigned int xms) //延时函数
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void Uart_Init(void) //4800bps@11.0592MHz
{
SCON = 0x50; //8位数据,可变波特率,接收使能
PCON |= 0x80; //波特率加倍
TMOD &= 0x0F; //设定定时器1为双8位自动重装方式
TMOD |= 0x20; //设定定时器1为双8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
ES=1; //开串口中断
EA=1; //开总中断
}
void UART_SendByte(unsigned char Byte) //串口发送一字节函数
{
SBUF=Byte; //这里SBUF做为左值,所以是写操作,也就是发送
while(TI==0); //判断发送标志位是否置位,置1发送完成,否则等待
TI=0; //软件复位
}
void main()
{
Uart_Init(); //串口初始化
}
void UART_Interrupt() interrupt 4
{
static unsigned char Temp; //创建一个8位变量传递数据
if(RI==1)
{
RI=0; //标志位置位
Temp=SBUF; //这里SBUF做为右值,所以是读操作,也就是接收
UART_SendByte(Temp); //将接收到的数据原封不动地发回去
}
}
总结
感谢观看!