80C51系列单片机的串口结构如下图,A是发送或接收的串行数据;有两个独立的缓冲器SBUF,上面的SBUF是发送缓冲器,下面的SBUF是接收缓冲器;由TH1和TL1可以计算出T1溢出率,SMOD确定波特率是否倍增,通过设置控制寄存器实现串口中断。
串口通信时除了串行口的控制寄存器SCON和控制波特率的位SMOD外,还需要定时器1确定溢出率。为了开启串口中断还需要IE.4的ES位打开串口中断允许。
SCON-串口控制寄存器
SCON是一个特殊功能寄存器,用来设定串行口的工作方式、接收/发送控制以及设置状态标志,各位结构如下:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
字节地址:98H | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
SCON | 工作方式选择位 | 多机通信控制位 | 允许串行接收位 | 方式2或方式3中是发送数据的第9位 | 方式2或方式3中是接收数据的第9位 | 发送中断标志位 | 接收中断标志位 |
SM0SM1是串口通信工作方式选择位,有4种工作方式:
SM0 | SM1 | 工作方式 | 说明 | 波特率 |
---|---|---|---|---|
0 | 0 | 0 | 移位寄存器 | f O S C / 12 f_{OSC}/12 fOSC?/12 |
0 | 1 | 1 | 10位异步收发器,含8位数据 | 可变 |
1 | 0 | 2 | 11位异步收发器,含9位数据 | f O S C / 64 f_{OSC}/64 fOSC?/64 或 f O S C / 32 f_{OSC}/32 fOSC?/32 |
1 | 1 | 3 | 11位异步收发器,含9位数据 | 可变 |
SM2是多机通信控制位,主要用于方式2和方式3。
当接收机的SM2=1时利用收到的RB8来控制是否激活RI,RB8=0时不激活RI,收到的信息丢弃;RB8=1时收到的数据进入SBUF,并激活RI,进而在中断服务中将数据从SBUF读走。
当SM2=0时,不论收到的RB8为0和1,均可以使收到的数据进入SBUF,并激活RI。
工作方式0时,SM2必须是0;工作方式1时,如果SM2=1,只有接收到有效停止位时,RI置1。
REN是允许串行接收位,软件置REN=1,则启动串行口接收数据;软件置REN=0,禁止接收数据。
TB8,方式2和方式3中,是发送数据的第9位,可以用软件规定作用,可以用作数据的奇偶校验位,或者在多机通信中,作为地址帧和数据帧的标志位。方式0和方式1中,该位未使用。
RB8,方式2和方式3中,是接收到数据的第9位,作为奇偶校验位或地址帧/数据帧的标志位。方式1中,如果SM2=0,RB8是接收到的停止位。
TI是发送中断标志位。方式0时,当串行发送第8位数据结束时,或在其它方式,串行发送停止位的开始时,由内部硬件使TI置1,向CPU发送中断申请。在中断处理程序中,用软件将其清0,取消此中断申请。
RI是接收中断标志位。方式0时,当串行接收第8位数据结束时,或在其它方式,串行接收停止位的中间时,由内部硬件使RI置1,向CPU发送中断申请。在中断处理程序中,用软件将其清0,取消此中断申请。
PCON.7-与波特率倍增有关的位
PCON中有一个位SMOD与串行口工作有关,PCON结构如下:
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
字节地址:97H | SMOD |
SMOD是波特率倍增位。在串行口方式1、方式2、方式3时,波特率与SMOD有关。当SMOD=1时,波特率提高一倍。复位时SMD=0。
串口通信时,收发双方对发送或接收数据的速率要有约定。串口编程方式有四种工作方式,其中方式0和方式2的波特率是固定的,方式1和方式2的波特率是固定的,方式1和方式3的波特率是可变的,由定时器T1的溢出率来决定。
串口通信的四种工作方式对应三种波特率。由于输入的时钟来源不同,所以,各种方式的波特率计算公式也不相同。
方式0的波特率=
f
O
S
C
/
12
f_{OSC} / 12
fOSC?/12。
方式2的波特率=
(
2
S
M
O
D
/
64
)
?
f
O
S
C
(2^{SMOD}/64) \cdot f_{OSC}
(2SMOD/64)?fOSC?。
方式1的波特率=
(
2
S
M
O
D
/
32
)
?
(
T
1
溢出率
)
(2^{SMOD} / 32) \cdot (T1溢出率)
(2SMOD/32)?(T1溢出率)。
方式3的波特率=
(
2
S
M
O
D
/
32
)
?
(
T
1
溢出率
)
(2^{SMOD} / 32) \cdot (T1溢出率)
(2SMOD/32)?(T1溢出率)。
T1作为波特率发生器时,最典型的用法是使T1工作在自动重装的8位定时器方式,即定时器的工作方式2,并置TCON的TR1=1,以启动定时器。此时溢出率取决于TH1中的计数值。
T1溢出率计算公式:
T 1 溢出率 = f O S C / { 12 × [ 256 ? T H 1 ] } T1溢出率 = f_{OSC} / \{ 12 \times [256 - TH1 ]\} T1溢出率=fOSC?/{12×[256?TH1]}
单片机应用中,常用的晶振频率是12MHz和11.0592MHz,选用的波特率也相对固定。可以使用软件计算初值。
51单片机波特率初值计算软件:
链接:https://pan.baidu.com/s/1x5R4GzN5RAJmXt0-Qner3A?pwd=8051
提取码:8051
打开软件,设置定时器方式、晶振频率、波特率、SMOD,然后点确定,在上方的黑色框中会显示定时器初值。
串口通信使用51单片机的RXD(P3.0引脚)和TXD(P3.1引脚),当RXD接收数据或TXD发送数据时,向CPU申请中断,CPU响应串口中断进入中断处理函数中接收或发送SBUF中的数据。
中断响应的三个条件:①中断源有中断请求;②此中断源的中断允许位为1;③CPU开中断。
第一个条件:中断源有中断请求。对于串口来说,在发送数据结束或接收数据结束时,硬件会置TI或RI为1,向CPU申请中断。要发送或接收数据,需要确定波特率,即确定定时器1的工作方式TMOD、计数初值THx和TLx;要使定时器1工作,需要启动计数器TR1;同时还要确定数据发送或接收的控制方式SCON。
第二个条件:此中断源的中断允许位为1,设置串口中断允许位ES=1。
第三个条件:CPU开中断,设置CPU总中断允许位EA=1。
串口通信在工作方式0时为同步移位寄存器方式,这种方式用于单片机外接移位寄存器,用来扩展I/O口。
方式0以8位数据为1帧,没有起始位和停止位,先发送或接收最低位。波特率固定,为 f O S C / 12 f_{OSC}/12 fOSC?/12。
方式0输出的工作原理:当单片机执行将写入发送缓冲器SBUF的指令时,产生一个正脉冲,串口开始把SBUF中的8位数据以 f O S C / 12 f_{OSC}/12 fOSC?/12的固定波特率从RXD引脚串行输出,低位在先,TXD引脚输出同步移位脉冲,当8位数据发送完,中断标志位TI置1。
方式0输出的典型应用是串口外接串行输入/并行输出的同步移位寄存器74LS164,实现并行输出端口的扩展。
Proteus中设计串口工作在方式0,通过74LS164芯片的输出控制8只外接LED发光二极管亮灭的接口电路。串口被设置在方式0输出时,串行数据由RXD端(P3.0)送出,移位脉冲由TXD(P3.1)端送出。在移位脉冲的作用下,串口发送缓冲器的数据逐位地从RXD端串行地移入74LS164芯片中。
proteus设计原理图如下:
74LS164芯片的8脚为同步脉冲输入端;9脚为控制端,当9脚为低电平时,允许串行数据由RXD端向74LS164芯片的串行数据输入端输入,此时74LS164芯片的8位并行输出端关闭,当9脚为高电平时,串行数据输入端关闭,允许74LS164芯片中的8位数据并行输出。
当串口将8位串行数据发送完毕后,申请中断,在中断处理函数中,单片机向串口输出下一个8位数据。
首先需要设置串口通信在方式0的中断响应条件,即串口通信初始化,代码如下:
// 串口中断初始化函数
void UsartInit()
{
// 1. 确定串口控制方式,工作在方式0,RI不受RB8控制,禁止接收数据
SCON=0x00;
// 2. 打开串并口中断允许位
ES=1;
// 3. 打开CPU总中断允许位
EA=1;
}
串口发送完一帧数据后,申请中断,在中断处理函数中,向串口输出下一帧数据,即串口中断处理函数代码如下:
// 串口中断处理函数
void Usart() interrupt 4
{
// 判断TI是否置1,一个字节串行发送完毕
if(TI)
{
CTRL=1; // 允许74LS164并行输出,流水点亮二极管
SBUF=sendByte; // 向SBUF写入数据,启动串行发送
delay(500);
CTRL=0; // 允许串行写入,关闭并行输出
sendByte=_crol_(sendByte, 1);
SBUF=sendByte; // 再次发送点亮数据
}
// 数据处理结束,置TI和RI为0
TI=0;
RI=0;
}
在主函数中,首先调用串口初始化函数,然后需要设置74LS164的控制引脚为低电平,允许串行数据输入,最后再向SBUF中发送数据,启动串行发送。代码如下:
void main()
{
UsartInit();
CTRL=0; // 串行口向74LS164发送数据
// CPU向SBUF中写入数据,启动串行发送
SBUF=sendByte;
while(1);
}
全局变量sendByte声明:u8 sendByte=0x01;
。
仿真结果:
方式0输入时,REN作为串行口允许接收控制位,REN=0时禁止接收,REN=1时允许接收。
当向SCON寄存器写入控制字(设置方式0、REN=1,RI=0)时,产生一个正脉冲,串行口开始接收数据。
RXD引脚是数据输入端,TXD为移位脉冲信号输出端,接收器以 f O S C / 12 f_{OSC}/12 fOSC?/12的固定波特率采样RXD引脚的数据信息,当接收完8位数据时,中断标志RI置1,表示一帧数据接收完毕,可以进行下一帧数据的接收。
方式0输入时序图如下:
如下原理图,单片机的串口外接一片8位并行输入、串行输出的同步移位寄存器74LS165,从而实现了扩展一个8位并行输入口。将接在74LS165的8各开关S0 ~ S7的状态通过串行口的方式0输入方式读入到单片机内。
74LS165芯片的1脚SH/LD为控制端,SH/LD=0时,允许74LS165芯片并行输入数据,关闭串行输出端;当SH/LD=1时,关闭并行输入端,可向单片机串行发送数据。
当P3.2引脚连接的开关k合上时,通过外部中断0控制开关S0 ~ S7的状态数字量并行是否允许读入,使用一个连接在P1.0引脚的LED灯显示是否可以读入的状态,LED灯亮可以读入,LED不亮则不允许读入。采用中断方式对S0 ~ S7状态进行读取,由单片机的P2口控制对应的开关按下的二极管发光点亮。
Proteus设计原理图如下:
软件设计,首先设置中断响应条件,这里需要CPU接收数据,所以应该置REN=1,串口中断初始化函数如下:
// 串口通信方式0输入初始化
void UsartInit()
{
// 1. 设置工作方式0,允许接收数据
SCON=0x10;
// 2. 打开串口中断允许位
ES=1;
// 3. 打开CPU总中断允许位
EA=1;
}
串口中断处理函数如下:
// 串口中断处理函数
void Usart() interrupt 4
{
if(!LED){
CTRL_74LS165=0; // 读入并行数据
delay(1000);
CTRL_74LS165=1; // 向CPU发送串行数据
RI=0;
receiveByte=SBUF;
GPIO_LED=receiveByte;
}
}
其中LED在外部中断0处理函数中进行取反操作。
主函数调用串口中断初始化函数UsartInit
和外部中断0初始化函数Int0Init
后进入while(1);
即可。
仿真结果如下,可以看到,当没有按下按键K,直接按S0~S7,连接到P2口的LED不会亮;当按下按键K后,再按S0 ~ S7,对应的LED会亮。
串口通信的工作方式1为双机通信方式,如下图所示。SM0SM1设置为01时,串口通信设置为方式1的双机串行通信,TXD脚和RXD脚分别用于发送和接收数据。
方式1收发一帧数据为10位,1位起始位(0)+8位数据位+1位停止位(1)。先发送或接收最低位。
方式1时串口波特率可变,波特率计算方式为: 2 S M O D 32 × T 1 溢出率 \frac{2^{SMOD}}{32} \times T1溢出率 322SMOD?×T1溢出率,其中SMOD是寄存器PCON的最高位的值。
串口以方式1输出时,数据位由TXD端输出,发送一帧信息。当CPU执行写数据到发送缓冲器SBUF的命令后,就启动发送。
如上图是方式1发送时序图。先发送起始位,由TXD引脚输出,然后接着输出8位数据位,数据位全部发送完毕后,串口发送中断标志位TI置1,向CPU申请中断。
串口以方式1(SM0SM1=01)接收时(REN=1),数据从RXD引脚输入。当检测到起始位的负跳变,开始接收。
接收时,定时控制信号有两种,一种是接收移位时钟,它的频率和传送的波特率相同,另一种是位检测器采样脉冲,它的频率是接收移位时钟的16倍。也就是在1位数据器件,有16个采用脉冲,以波特率的16倍速率采样RXD引脚状态。
当采样到RXD端从1到0负跳变时,就启动接收检测器。接收的值是3次连续采样(第7、8、9个脉冲时采样),取两次相同的值,以确认是否是真正的起始位的开始,这样能够比较好地消除干扰引起的影响,保证可靠无误地开始接收数据。
当确认起始位有效时,开始接收一帧信息。接收每一位数据时,也都进行3次连续采样,接收的值是3次采样中至少两次相同的值,以保证接收到的数据位的准确性。
当一帧数据接收完毕后,必须同时满足以下两个条件,这次接收才真正有效。
如果不同时满足这两个条件,收到的数据不能装入SBUF,这表示该帧数据将丢失。
方式1可用来实现双机通信。两个单片机利用串口进行串行通信:串行通信的波特率由按键设定,可选的波特率为1200bps、2400bps、4800bps、9600bps。在工作方式1下进行全双工串行通信。
本示例通过按键得到设定的波特率,载入到T1的计数器中。运行时将数据0xaa从主机传输到从机,并显示到从机的LED上,从而验证串口通信的实现。
在实际的硬件实现中,如果串口通信线路过长,考虑采用MAX232进行电平转换,以延长传输距离。
为减少波特率的计算误差,采用11.0592MHz的晶振。
Proteus中两个单片机之间的串口通信接口设计原理电路如下图所示:
软件设计,因为是双机通信,有两个单片机,所以需要编写两份代码。其中一个单片机发送消息,另一个单片机接收消息。这里把发送消息的称为主机,接收消息的称为从机。
首先两个单片机的P1.0连接到K1,P1.1连接到K2,P1.2连接到K3,P1.3连接到K4,两份代码的按键扫描函数是一样的,如下:
sbit K1=P1^0;
sbit K2=P1^1;
sbit K3=P1^2;
sbit K4=P1^3;
// 按键扫描函数,返回按下的按键序号,K1~K4序号为0~3,初始运行时如果没有按下按键,默认使用K2指示的波特率
uchar KeyScan()
{
uchar keyscan_num, temp;
P1=0xff;
temp=P1;
if(~(temp & 0xff)) // 当没有按键按下时,temp=0xff,temp&0xff=0xff,取反后为0不会进入条件语句
{
if(0==K1)
{
keyscan_num=0;
}
else if(0==K2)
{
keyscan_num=1;
}
else if(0==K3)
{
keyscan_num=2;
}
else if(0==K4)
{
keyscan_num=3;
}
else
{
keyscan_num=1;
}
}
return keyscan_num;
}
然后根据按下的按键设置对应的波特率,K1对应1200bps,K2对应2400bps,K3对应4800bps,K4对应9600bps。设置随影波特率的函数如下,在该函数中需要设置串口控制字、定时器T1工作方式、T1计数初值、打开中断允许等。
以1200bps波特率为例,另外的三个波特率只有计数初值不一样,其它都是一样的
// 串口通信初始化,工作方式1,波特率为1200bps
void Init1200bps()
{
// 1. 确定串口工作方式;确定定时器工作方式及计数初值
SCON=0x50; // 方式1 8位异步收发,允许接收数据
PCON=0x00; // 波特率不加倍
TI=0;
TMOD=0x20; // 方式2,8位自动装载模式
TH1=0xE8;
TL1=0xE8;
// 2. 打开串口中断允许位
ES=1;
// 3. 打开CPU总中断允许位
EA=1;
// 4. 启动定时
TR1=1;
}
各个波特率计数初值如下:
最后是主函数,master.c中的主函数中,循环获取哪个按键按下,根据按下的按键设置对应的波特率,然后发送数据。代码如下:
/*
实现功能:串口通信,方式1实现双机通信。
主机代码:扫描按下的按键,根据按键设置波特率,发送数据
[2024-01-12]
*/
#include <reg52.h>
#include "KeyFuncs.h"
#include "BAUDs.h"
typedef unsigned char u8;
typedef unsigned int u16;
u8 sendByte=0xaa;
void main()
{
u8 keyPress; // 获取哪个按键按下,使用对应的波特率
while(1)
{
keyPress=KeyScan();
switch(keyPress)
{
case 0: Init1200bps(); break;
case 1: Init2400bps(); break;
case 2: Init4800bps(); break;
case 3: Init9600bps(); break;
}
SBUF=sendByte; // 发送数据
while(TI==0); // 等待直到发送完成
TI=0; // TI软件清0
}
}
slaver.c的主函数中,循环扫描按下的按键,根据按键设置对应的波特率,通过串口中断处理函数接收数据,并在LED上显示。代码如下:
/*
实现功能:串口方式1实现双机通信。
从机代码:扫描按下的按键,根据按键设置波特率,在串口中断处理程序中接收数据。
[2024-01-12]
*/
#include <reg52.h>
#include "KeyFuncs.h"
#include "BAUDs.h"
typedef unsigned char u8;
typedef unsigned int u16;
u8 reveiveByte;
void main()
{
u8 keyPress; // 获取哪个按键按下,使用对应的波特率
while(1)
{
keyPress=KeyScan();
switch(keyPress)
{
case 0: Init1200bps(); break;
case 1: Init2400bps(); break;
case 2: Init4800bps(); break;
case 3: Init9600bps(); break;
}
while(RI==0); // 等待直到接收数据
}
}
// 接收到数据进入串口中断处理程序
void ReceiveDat() interrupt 4
{
P2=SBUF; // 将接收到的数据作用到P2口,P2口连接了8个LED
RI=0; // RI软件清0
}
仿真结果:
串口通信工作于方式2和方式3时,被定义为9位的异步通信接口。每帧数据均为11位,1位起始位+8位数据位(先低位)+1位可编程位+1位停止位。
方式2的波特率计算公式为: 2 S M O D 64 × f O S C \frac{2^{SMOD}}{64} \times f_{OSC} 642SMOD?×fOSC?
方式2在发送前,先根据通信协议由软件设置TB8(如双击通信时的奇偶校验位或多机通信时的地址/数据的标志位),然后将要发送的数据写入SBUF,即可启动发送过程。
串口能够自动把寄存器SCON中的TB8取出,并装入到第9位数据位的位置,再逐一发送出去。发送完毕,使TI置1。
当串行口的SCON寄存器的SM0SM1=10B,且REN=1时。允许串行口以方式2接收数据。
接收时,数据由RXD端输入,接收11位信息。当位检测逻辑采样到RXD引脚从1到0的负跳变,并判断起始位有效后,便开始接收一帧信息。
在接收完第9位数据后,需要满足以下两个条件,才能将接收到的数据送入接收缓冲器SBUF。
当满足上述两个条件时,接收到的前8位数据送入SBUF,第9位数据送入RB8,且RI置1.如果不满足这两个条件,接收到的信息将被丢弃。
SM0SM1=11时,串口被定义在方式3,方式3为波特率可变的9位异步通信方式,除了波特率外,方式3和方式2相同。
方式3的波特率计算: 2 S M O D 32 × T 1 溢出率 \frac{2^{SMOD}}{32} \times T1溢出率 322SMOD?×T1溢出率。