本文主要参考资料:
马淑华、高 军、蔡凌.单片机原理与接口技术(第三版).北京:北京邮电大学出版社,2018.6
一、串行通信的基本知识
1.串行通信的基本方式
①全双工通信
通信双方可同时进行数据收发的工作方式。51单片机的串行口是全双工传输方式。
②半双工通信
通信双方交替进行双向数据传输,但两个方向的传输不能同时进行。
③单工通信
数据只能单方向传输。
2.串行通信相关寄存器
①串行数据缓冲器——SBUF
SBUF物理地址为99H,但实际是对应两个缓冲器(一收一发)
- 发送内容:CPU写入SBUF
- 接收内容:CPU读取SBUF
②串口控制寄存器——SCON(98H)
SM0和SM1:工作方式选择位,模式选择如下表
SM2:多机通信选择。当SM2=0时,单机对单机通信;当SM2=1时,多机对多机通信。
REN:串行接收允许位,REN=1 允许接收数据,手动控制;
RB8 :在模式 2、3 时为第 9 个接收位;
TB8:在模式 2、3 时为第 9 个发送位;
RI:串口接收完成中断标志位(即RI=1),需要手动清0;
TI:串口发送完成中断标志位(即TI=1),需要手动清0;
③波特率与电源控制寄存器——PCON(不可位寻址,87H)
SMOD:波特率加倍控制位。在模式1、2、3下,当SMOD=0时,波特率不加倍;当SMOD=1时,波特率加倍。
其他位暂不做详解
④与串行口通信相关的中断寄存器——IE(中断允许寄存器,可位寻址,A8H)
与串行中断相关的两个位:
EA:总中断控制位,EA=1时允许中断,EA=0时禁止中断
ES:串行口中断允许位,ES=1时允许串行口中断,ES=0时禁止串行口中断。
3、串行通信工作模式详解
波特率的计算方法:
1 .模式 0 的波特率波特率固定:每个机器周期产生一个移位时钟,可发送或接收一位数据。且不受SMOD位的影响。即
模式0的波特率=
/12
2 .模式 2 的波特率由系统的振荡频率fosc和SMOD共同确定,即
模式2的波特率=
·
/64
3 .模式 1 和模式 3 的波特率模式1和模式3的移位时钟由定时器T1的溢出率决定,故波特率由T1的溢出率与SMOD值共同决定,即
模式1和模式3的波特率=
·T1的溢出率/32
4. 波特率设计
当定时器T1做波特率发生器使用时,最典型的用法是使T1工作在模式2(初值自动加载),若计数初值为X,则每经过“256-X”个机器周期。定时器T1就会产生一次溢出,溢出周期为:
溢出率为溢出周期的倒数,所以波特率为:
从上式解出定时器的初值X:
①模式0:同步移位寄存器
(1)设置方式
当SM0=0,SM1=0,SM2=0时使用模式0
(2)模式特点
- 波特率固定,波特率等于
/12(即晶振频率的十二分之一),波特率不受SMOD的影响。
- 数据 由RXD端发送或接收
- 同步脉冲 由TXD端输出
- 发送接收的是8位数据,低位在先
(3) 数据传输过程
发送过程:
将数据写入SBUF时启动发送,串行口即将8位数据以/ 12的波特率从RxD输出(从低位到高位),发送完中断标志TI置"1”,TxD管脚输出同步移位脉冲。
在再次发送数据前,必须用软件将TI清0。.
接收过程:
接收时,将接收中断标志RI=0, 设置REN=1允许接收,启动串接收过程。启动接收过程后,RxD为串行输入端,TxD为同步脉冲输出端。串行接收的波特率为/12。
当接收完成数据(8位)后,控制信号复位,中断标志RI被置”1”,呈中断申请状态。
当再次接收时,必须通过软件将RI清0。
②模式1:8位UART,波特率可变(最常用)
(1)设置方式
当SM0=0,SM1=1时,使用模式1
(2)模式特点
- 波特率可变,由定时器的溢出率和SMOD决定。
- 数据通过TXD发送,RXD接收。
- 发送10位数据,1位起始位(0),8位数据位,1位停止位。
(3)数据传输过程
发送过程:
数据由串行发送端TXD输出。将数据写入SBUF时启动发送,写“SBUF”信号还把“1”装入发送移位寄存器的第9位,并通知TX控制单元开始发送。
发送完成后,硬件置位中断请求位Tl,即TI=1,向主机请求中断处理。
再次发送需要将TI置0。
接收过程
当允许接收位REN=1时,启动接收。
接收完成之后,置位中断请求位RI,即RI=1,向主机请求中断处理。
再次接收需要手动将RI置0。
③模式2和模式3
模式2: 9位UART,波特率固定
模式3: 9位UART,波特率可变
(1)设置方式
SM0=1,SM1=0设置为模式2;
SM0=1,SM1=1设置为模式3;
(2)模式特点
- 方式2和方式3都为11位数据的异步通信口,他们唯一的区别是传输速率不同。
- 数据通过TXD发送,RXD接收。
- 发送11位数据,1位起始位(0),9位数据位,1位停止位。
- 第9位的数据位发送时在SCON的TB8,接收时在RB8。
(3)数据传输过程
发送过程:
将数据写入SBUF后立即启动发送。
发送完成后,硬件置位中断请求位Tl,即TI=1,向主机请求中断处理。
再次发送需要手动将TI置0。
接收过程:
当允许接收位REN=1时,启动接收。
接收完成之后,置位中断请求位RI,即RI=1,向主机请求中断处理。
再次接收需要手动将RI置0。
二、串行通信的应用(实例分析)
模式1是串口最常用的方式,所以重点讲模式1的应用。
实例所使用的单片机是 亚博智能mini51开发板,编程开发环境为keil5
1.模式1的应用
①通过串口发送一个字符给计算机
步骤:
- 初始化串口(设置工作模式、波特率等)
- 编写发送字符的函数
1、初始化串口
设置工作模式:
由于我们使用的是模式1,所以设置SM0=0,SM1=0,直接赋值SCON=0x40;
配置波特率:
配置波特率要设置定时器的工作模式及计算定时器的初值。这里选择定时器T1模式2,8位自动重装方式,然后计算初值。波特率选择不加倍。
初值X的计算公式已经给出。
为了省事,可以直接使用下表经过计算后的值:
查表可知,9600波特率的TH1初值X为0XFD,波特率不加倍。
代码如下:
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x40; //0100 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
2、编写发送字符的函数
当寄存器SBUF被赋值时,启动发送。在这个发送字符的函数中,只需要写出被赋值语句即可。
void send_char(unsigned char m)
{
SBUF=m;
}
完整代码:
#include <REGX52.H>
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x40; //0100 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
//字符发送函数
void send_char(unsigned char m)
{
SBUF=m;
}
//延时约1s的程序
void delay()
{
unsigned char i, j, k;
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
//主函数
void main()
{
UartInit();//调用串口初始化函数
while(1)
{
send_char('p');//调用发送一个字符的函数
delay();//调用延时1S的函数
}
}
将程序烧录到单片机后,打开串口助手即可查看串口收到的单片机信息。
(串口调试助手操作)
(串口接受到了字符)
②通过串口发送字符串给计算机
类似于发送单个字符的步骤
步骤:
- 初始化串口(设置工作模式、波特率等)
- 编写发送字符串的函数
第一步已经介绍过,下面介绍编写发送字符串的函数。
编写发送字符串的函数:
void send_string(unsigned char str[])
{
unsigned char i=0;
while(str[i]!='\0')//判断是否到字符串尾
{
SBUF = str[i];
while(TI==0);//等待发送完成
TI=0;//下次发送前,要手动将TI置0
i++;//下次发送
}
}
完整代码:
#include <REGX52.H>
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x40; //0100 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
void send_string(unsigned char str[])
{
unsigned char i=0;
while(str[i]!='\0')//判断是否到字符串尾
{
SBUF = str[i];
while(TI==0);//等待发送完成
TI=0;//下次发送前,要手动将TI置0
i++;//下次发送
}
}
//延时1sd函数
void delay()
{
unsigned char i, j, k;i = 8;j = 1;k = 243;
do
{do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
UartInit();
while(1)
{
send_string("Hello World!\n\r");
delay();
}
}
打开串口助手即可看到效果(每隔1s发送“Hello World!”):
③单片机接收一个字符
当SCON寄存器接收位REN=1时,启动接收。
步骤:
- 编写串口初始化函数(配置寄存器模式)
- 编写接收函数
根据串口接收的原理:
当串口接收到数据时,RI自动硬件置1,所以我们判断RI是否是1即可判断串口是否接受到数据,再将SBUF的内容读出即可获得数据。
为了验证串口收到的数据是否是我们所发送的数据,可以对单片机定义一些操作来进行确认。
比如我们把串口接受到的数据在数码管/LCD等显示模块显示出来进行验证,当然也可以采用比较简单的方法,比如点灯验证法。
点灯验证法:
比如我们要发送一个字符‘O’,规定字符‘O’可以点亮LED1。我们只需观察发送后LED1是否点亮即可判断我们串口接收到了正确的字符。
同理,我们也可以发送一个字符'C',规定字符C可以熄灭LED1,我们只需观察发送后LED是否熄灭即可判断我们串口接收到了正确的字符。
完整代码(扫描判断法):
#include <REGX52.H>
sbit led=P1^0;
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x50; //0101 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
void main()
{
char receive;//用于接收串口数据的变量
led=1;
UartInit();
while(1)
{
if(RI==1)//RI=1说明串口接收到了数据
{
RI=0;//RI置0保证下次接收
receive=SBUF;//将从串口接收到的数据报存到变量中
//判断接收的数据,作出相应的操作
if(receive=='O')
led=0;
if(receive=='C')
led=1;
}
}
}
中断判断法:
#include <REGX52.H>
sbit led=P1^0;
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x50; //0101 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
ES=1;//打开串行通信中断
EA=1;//打开总中断
}
//串行中断函数
void Uart_receive() interrupt 4
{
if(RI==1)//RI=1说明串口接收到了数据
{ char receive;
RI=0;//RI置0保证下次接收
receive=SBUF;//将从串口接收到的数据报存到变量中
//判断接收的数据,作出相应的操作
if(receive=='O')
led=0;
if(receive=='C')
led=1;
}
}
//主函数
void main()
{
led=1;
UartInit();
while(1)
{
//do any other things what you want;
}
}
打开串口助手,按照步骤操作给单片机发送数据: