1、89C52串口通信简介
STC89C52系列单片机内部集成有一个功能很强的全双工串口通信,与传统8051单片机完全兼容。设有两个互相独立的接受、发送缓冲器,可以同时发送和接收数据。发送缓冲器只能写入而不能读出,接收缓冲器只能读出而不能写入。因而两个缓冲器可以共用一个地址码(99H)。两个缓冲器统称为串行通信特殊功能寄存器SBUF。 串口通信设置有四种工作方式,其中两种方式波特率可变,另外两种是固定的,以供不同应用场合使用。 STC89C52波特率由内部定时器/计数器产生,主机可通过查询或者中断方式接收/发送进行程序处理,使用十分灵活。 STC89C52系列单片机串口对应硬件部分管脚是P3.0/RXD和P3.1/TXD。 STC89C52系列单片机串行接口结构及功能如下图
2、串口通信需要配置的寄存器
2-1、串行数据缓冲器SBUF
查看芯片手册SBUF 可以得知SBUF是STC89C52系列单片机串行缓冲寄存器(SBUF)的地址是99H。 此外,在接收寄存器之前还有移位寄存器,从而构成了串行接收电脑双缓冲结构,这样可以避免在数据接过程中出现帧重叠错误。 发送数据时,由于CPU是主动的,不会发送帧重叠错误,因此发送电路不需要双重叠缓冲结构。 逻辑上,SBUF只有一个,它即表示发送寄存器,又表示接收寄存器,具有同一个单元地地址99H。但在物理结构上,则是两个完全独立的SBUF,一个是发送缓冲寄存器SBUF,另一个是接收缓冲寄存器SBUF。以便实现全双工通信方式。如果CPU写SBUF,数据就会被送入发送寄存器准备发送;如果CPU读SBUF,则读入的数据一定来自接收缓冲器。即CPU对SBUF的读写,实际上是分别访问上述两个不同的寄存器。
2-2、串行控制寄存器SCON
串行控制寄存器SCON用于设置串口工作方式、监视串行口的工作转态、控制发送与接收的状态。它是一个既可以字节寻址又可以位寻址的8位特殊功能寄存器。
(1)SM0 SM1
串口工作方式选择位对应下表
(2)SM2
多机通信控制位
- 在方式0中,SM2必须设成0
- 在方式1中,当处于接收状态时,SM2=1则只有接收到有效的停止位“1”时,RI才能被激活成“1”(产生中断请求)。
- 在方式2和方式3中,SM2=0,串口以单机发送或接收工工作方式工作,TI和RI以正常方式被激活并产生中断请求;若SM2=1,RB8=1时,RI被激活并产生中断请求。
(3)REN
串行接收允许位,该位由软件置位或复位,当REN=1,允许接收;当REN=0,禁止接收。
(4)TB8
方式2和方式3中要发送的第9位数据。该位由 软件置位或复位。在方式2和方式3时,TB8是发 送的第9位数据。在多机通信中,以TB8位的状态 表示主机发送的是地址还是数据:TB8=1表示地 址,TB8=0表示数据。TB8还可用作奇偶校验位。
(5)RB8
接收数据第9位。在方式2和方式3时,RB8存放 接收到的第9位数据。RB8也可用作奇偶校验位。在 方式1中,若SM2=0,则RB8是接收到的停止位。在 方式0中,该位未用。
(6)TI
发送中断标志位。TI=1,表示已结束一帧数据发 送, 可由软件查询TI位标志,也可以向CPU申请中断。 注意:TI在任何工作方式下必须由软件清0
(7)RI
接收中断标志位。 RI=1,表示一帧数据接收结束,可由软件查询RI位标志,也可以向CPU申请中断。 注意:RI在任何工作方式下也都必须由软件清零。
串口中断需要注意
在89C25系列单片机,串行发送中断TI和接收中断RI的中断入口地址同是0033H,因此在中断程序中必须由软件查询TI和RI的状态才能确定究竟是接收还是发送中断,进而做出相应处理。 单片机复位时,SCON所有位均清零。
2-3、电源控制寄存器PCON
SMOD: 串行波特率倍增位 在工作方式1-工作方式3时,如果SMOD=1则串口波特率增加一倍。如果SMOD=0,波特率不加倍,SMOD=0。
串口通信主要用到SMOD,其他寄存器位请查询芯片手册!
3、串行口工作方式
工作方式有四种,见下表,由SCON中SM0和SM1决定
1、工作方式0
在方式0下,串行口作为同步移位寄存器使用,此时SM2、PB8,TB8均设置为0。 (1)发送:TI=1时,启动发送,8位数据由低位到高位RXD引脚送出,TXD发送同步脉冲,发送完成 后,由硬件置位TI。
(2)接收:RI=0,RWEN=1时启动接收,数据从RCD输入,TXD输出同步脉冲,8位数据接收玩,由硬件置位RI。
2、工作方式1(重点,最常用的通信方式)
方式1是一帧10位的异步串行通信方式,包括1个起始位(值为0),8个数据位和1个停止位(值为1),其中格式如下: 因为是异步通信,帧与帧之间通信可以有空闲,也可以无空闲。 (1)数据发送:当TI=0时,开始发送,由硬件自动加入起始位和停止位,构成一帧数据,然后由TXD端串行输出。发送完成后,TXD输出线维持在“1”状态下,并将SCOM中的TI置1.表示一帧数据发送完毕。
(2)数据接收:RI=0,REN=1时,接收电路以波特率的16倍速度采样RXD引脚,如出现“1”变“0”跳变,认为有数据正在发送。 在接收到第9位数据(即停止位)时,必须同时满足以下两个条件:RI=0和SM2=0或接收到停止位位“1”,才能把接收到的数据存入SBUF中,停止位送RB8,同时置位RI。如果上述条件不满足,接收到的数据不装入SBUF被舍弃,在方式1下,SM2应设定为0。
(3)波特率计算
3、工作方式2和工作方式3
略,具体查看相关书籍!
4、波特率说明
对波特率需要说明的是,当串行工作方式在1或者3时,且要求波特率获取1200、2400、4800、9600…,如果采用12MHZ和6MHZ,按照上述公式算出的T1定时初值将不是一个整数,因此会参数波特率误差影响串行通信的同步性能。解决方法只能调整单片机的晶振频率,为此有一种11.0592MHZ的晶振,这样可以使计算出T1初始值为整数。
6、程序例程
要操作串口,必须要对单片机中一些串口相关的特殊寄存器进行初始化设置,主要是设置产生波特率的定时器(T1)、串行控制和中断相关寄存器。 具体步骤: 1、确定定时器T1工作方式(配置TMOD寄存器) 2、计算定时器T1的初值,装入TH1、TL1 3、启动定时器T1(配置TCON的TR1位) 4、确定串行口工作方式(配置SCON寄存器) 5、串行工作在中断方式时,要进行中断设置(配置IE、IP寄存器)
我们在熟悉了串口,可以使用单片机精灵自动生成串口初始化配置。
6-1、例程1:向串口发送一个0x01
#include <reg51.h>
void SendOneByte(unsigned char c)
{
SBUF = c;
while(!TI);
TI = 0;
}
void InitUART(void)
{
TMOD = 0x20;
SCON = 0x40;
TH1 = 0xFD;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
}
void main(void)
{
InitUART();
SendOneByte(0x01);
while(1);
}
void UARTInterrupt(void) interrupt 4
{
if(RI)
{
RI = 0;
}
else
TI = 0;
}
6-2、例程2:串口接收的数据反馈到P1端口
#include <reg51.h>
unsigned char data_Temporary;
void InitUART(void)
{
TMOD = 0x20;
SCON = 0x50;
TH1 = 0xFD;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
}
void main(void)
{
InitUART();
while(1);
}
void UARTInterrupt(void) interrupt 4
{
if(RI)
{
RI = 0;
data_Temporary = SBUF;
P1 = data_Temporary;
}
else
TI = 0;
}
6-3、例程3:在上位机用串口助手发送一个字符X,单片机收到字符后,回复“I get”
#include <reg51.h>
unsigned char data_Temporary;
char str[] = "I get";
void prints(char *s)
{
while(*s != '\0')
{
SBUF = *s++;
while(!TI);
TI = 0;
}
}
void InitUART(void)
{
TMOD = 0x20;
SCON = 0x50;
TH1 = 0xFD;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
}
void main(void)
{
InitUART();
while(1);
}
void UARTInterrupt(void) interrupt 4
{
if(RI)
{
RI = 0;
data_Temporary = SBUF;
if(data_Temporary == 'X')
{
prints(str);
}
}
else
TI = 0;
}
6-4、例程4:串口接收字符串在LCD1602显示
#include <reg51.h>
#include <intrins.h>
typedef unsigned char uchar;
typedef unsigned int uint;
uchar LCD_Buffer[16],com_dat;
#define LCD1602_DB P0
sbit LCD1602_RS = P3^5;
sbit LCD1602_RW = P3^6;
sbit LCD1602_EN = P3^4;
void InitUART(void)
{
TMOD = 0x20;
SCON = 0x50;
TH1 = 0xFD;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
}
void Delay1ms()
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay_ms(unsigned int time)
{
unsigned int i;
for(i=0; i<time; i++)
{
Delay1ms();
}
}
void Read_Busy()
{
uchar busy;
LCD1602_DB = 0xff;
LCD1602_RS = 0;
LCD1602_RW = 1;
do
{
LCD1602_EN = 1;
busy = LCD1602_DB;
LCD1602_EN = 0;
}while(busy & 0x80);
}
void LCD1602_Write_Cmd(uchar cmd)
{
Read_Busy();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_EN = 1;
LCD1602_EN = 0;
}
void LCD1602_Write_Dat(uchar dat)
{
Read_Busy();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_EN = 1;
LCD1602_EN = 0;
}
void LCD1602_Dis_Str(uchar x, uchar y, uchar *str)
{
if(y) x |= 0x40;
x |= 0x80;
LCD1602_Write_Cmd(x);
while(*str != '\0')
{
LCD1602_Write_Dat(*str++);
}
}
void Init_LCD1602()
{
LCD1602_Write_Cmd(0x38);
LCD1602_Write_Cmd(0x0c);
LCD1602_Write_Cmd(0x06);
LCD1602_Write_Cmd(0x01);
}
void main(void)
{
uchar TestStr[] = {"receive data:"};
com_dat = 0;
InitUART();
Init_LCD1602();
LCD1602_Dis_Str(0, 0, &TestStr[0]);
while(1)
{
LCD1602_Dis_Str(0, 1, LCD_Buffer);
com_dat = 0;
Delay_ms(100);
}
}
void UARTInterrupt(void) interrupt 4
{
if(RI)
{
LCD_Buffer[com_dat] = SBUF;
RI = 0;
com_dat++;
if(com_dat == 16) com_dat = 0;
}
else
TI = 0;
}
|