****
本节来学习 51 单片机的串口通信。 开发板上集成了 1 个串口通信电路, 是 USB 转串口模块, 它既可下载程序也可实现串口通信功能。 本节要实现的功能是:51 单片机通过串口(UART) 实现与 PC 机对话,51 单片机的串口收到 PC 机发来的数据后原封不动的返回给 PC 机显示。
51 单片机不仅可以实现串口通信, 还可以通过 IO 口模拟实现多种其他通信, 比如 SPI、 IIC 等, 学习这些通信前, 我们很有必要了解下通信的基本概念。 通信的方式可以分为多种, 按照数据传送方式可分为串行通信和并行通信。 按照通信的数据同步方式, 可分为异步通信和同步通信。 按照数据的传输方向又可分为单工、 半双工和全双工通信。 下面我们就来简单介绍这几种通信方式。
(1)串行通信
串行通信是指使用一条数据线, 将数据一位一位地依次传输, 每一位数据占据一个固定的时间长度。 其只需要少数几条线就可以在系统间交换信息, 特别适用于计算机与计算机、 计算机与外设之间的远距离通信。 如下图所示:
串行通信的特点: 传输线少, 长距离传送时成本低,且可以利用电话网等现成的设备, 但数据的传送控制比并行通信复杂。
(2)并行通信
并行通信通常是将数据字节的各位用多条数据线同时进行传送, 通常是 8位、16 位、32 位等数据一起传输。 如下图所示:
并行通信的特点: 控制简单、 传输速度快; 由于传输线较多, 长距离传送时成本高且接收方的各位同时接收存在困难, 抗干扰能力差。
(1)异步通信
异步通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。 为使双方的收发协调, 要求发送和接收设备的时钟尽可能一致。
异步通信是以字符(构成的帧) 为单位进行传输, 字符与字符之间的间隙(时间间隔) 是任意的, 但每个字符中的各位是以固定的时间传送的, 即字符之间不一定有“位间隔” 的整数倍的关系, 但同一字符内的各位之间的距离均为“ 位间隔” 的整数倍。 如下图所示:
异步通信的特点: 不要求收发双方时钟的严格一致, 实现容易, 设备开销较小, 但每个字符要附加 2~3 位用于起止位, 各帧之间还有间隔, 因此传输效率不高。
(2)同步通信
同步通信时要建立发送方时钟对接收方时钟的直接控制, 使双方达到完全同步。 此时, 传输数据的位之间的距离均为“位间隔” 的整数倍, 同时传送的字符间不留间隙, 即保持位同步关系, 也保持字符同步关系。 发送方对接收方的同步可以通过两种方法实现。 如下图所示:
(1) 单工通信
单工是指数据传输仅能沿一个方向, 不能实现反向传输。 如下图所示:
(2) 半双工通信
半双工是指数据传输可以沿两个方向, 但需要分时复用一根数据线进行。 如下图所示:
(3) 全双工通信
全双工是指数据可以同时进行双向传输。 如下图所示:
衡量通信性能的一个非常重要的参数就是通信速率, 通常以比特率(Bitrate)来表示。 比特率是每秒钟传输二进制代码的位数, 单位是: 位/ 秒(bps) 。 如每秒钟传送 240 个字符, 而每个字符格式包含 10 位(1 个起始位、 1 个停止位、8 个数据位), 这时的比特率为:
10 位× 240 个/秒 = 2400 bps
在后面会遇到一个“波特率” 的概念, 它表示每秒钟传输了多少个码元。 而码元是通信信号调制的概念, 通信中常用时间间隔相同的符号来表示一个二进制数字, 这样的信号称为码元。 如常见的通信传输中, 用 0V 表示数字 0, 5V 表示数字 1, 那么一个码元可以表示两种状态 0 和 1, 所以一个码元等于一个二进制比特位, 此时波特率的大小与比特率一致; 如果在通信传输中, 有 0V、 2V、4V 以及 6V 分别表示二进制数 00、 01、 10、 11, 那么每个码元可以表示四种状态, 即两个二进制比特位, 所以码元数是二进制比特位数的一半, 这个时候的波特率为比特率的一半。 由于很多常见的通信中一个码元都是表示两种状态,所以我们常常直接以波特率来表示比特率。
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单可实现两个设备的互相通信。
前两种电平的传输距离超过十几米错误率就很高了,而RS485电平传输距离可以达到千米以上。
串口通信(Serial Communication), 是指外设和计算机间通过数据信号线、地线等按位进行传输数据的一种通信方式, 属于串行通信方式。 串口是一种接口标准, 它规定了接口的电气标准, 没有规定接口插件电缆以及使用的协议。
(1)接口标准
串口通信的接口标准有很多, 有 RS-232C
、 RS-232
、 RS-422A
、 RS-485
等。常用的是 RS-232
和 RS-485
。 RS-232
其实是 RS-232C
的改进, 原理是一样的。这里我们就以 RS-232C
接口进行讲解。RS-232C
是 EIA(美国电子工业协会) 1969 年修订 RS-232C
标准。 RS-232C
定义了数据终端设备(DTE) 与数据通信设备(DCE) 之间的物理接口标准。RS-232C
接口规定使用 25 针连接器, 简称 DB25
, 连接器的尺寸及每个插
针的排列位置都有明确的定义, 如下图所示:
RS-232C
还有一种 9 针的非标准连接器接口, 简称 DB9
。 串口通信使用的大多都是 DB9
接口。 DB25
和 DB9
接头有公头和母头之分, 其中带针状的接头是公头, 而带孔状的接头是母头。 9 针串口线的外观图如下图所示:
从上图中可以看到公头和母头的管脚定义顺序是不一样, 这一点需要特别注意。 这些管脚都有什么作用呢? 9 针串口和 25 针串口常用管脚的功能说明如下图所示:
在串口通信中, 通常我们只使用 2、 3、 5 三个管脚, 即 TXD(transmit exchange data)
、 RXD
、 SGND
,其他管脚功能大家看不明白也没关系。RS-232C
对逻辑电平也做了规定, 如下:
在 TXD 和 RXD 数据线上:
1.逻辑 1 为-3~-15V 的电压
2.逻辑 0 为 3~15V 的电压
在 RTS、 CTS、 DSR、 DTR 和 DCD 等控制线上:
1.信号有效( ON 状态) 为 3~15V 的电压
2.信号无效( OFF 状态) 为-3~-15V 的电压
由此可见, RS-232C
是用正负电压来表示逻辑状态, 与晶体管-晶体管逻辑集成电路(TTL) 以高低电平表示逻辑状态的规定正好相反。 而我们 51 单片机使用的就是 TTL
电平, 所以要实现 51 单片机与计算机的串口通信, 需要进行 TTL
与 RS-232C
电平转换, 通常使用的电平转换芯片是 MAX232。
在串口通信中通常 PC 机的 DB9 为公头, 单片机上使用的串口 DB9
为母头,通过一根直通串口线进行相连。 在 9 针串口线实物图即为直通型串口线, 串口线(COM) 母头连接计算机 DB9
的公头, 串口线公头连接单片机上使用的 DB9
母头, 这样就是将 2、 3、 5 管脚直接相连。 如果你要实现两台计算机串口通信,那么就需要一根交叉串口线, 将 2 对 3、 3 对 2、 5 对 5 连接, 交叉串口线一般两头都是母头。
串口通信中还需要注意的是, 串口数据收发线要交叉连接, 计算机的 TXD
要对应单片机的 RXD
, 计算机的 RXD
要对应单片机的 TXD
, 并且共 GND
, 如下图:
有的朋友就会问了, 在计算机与单片机进行串口通信时, 使用的不是直通线吗, 这时候怎么让 TXD 与 RXD 交叉连接? 前面我们说了单片机处理的是 TTL电平, 需要使用 RS232 电平转换芯片, 将 RS232 电平转换芯片串行数据输出管脚交叉连接在 DB9 母头上即可, 本章后面硬件设计部分会介绍。
(2)通信协议
RS232 的通信协议比较简单, 通常遵循 96-N-8-1 格式。“96” 表示的是通信波特率为 9600。 串口通信中通常使用的是异步串口通信, 即没有时钟线, 所以两个设备要通信, 必须要保持一致的波特率, 当然, 波特率常用值还有 4800、 115200 等。
“N” 表示的是无校验位, 由于串口通信相对更容易受到外部干扰导致传输数据出现偏差, 可以在传输过程加上校验位来解决这个问题。 校验方法有奇校验(odd)、 偶校验(even)、 0 校验(space)、 1 校验(mark)以及无校验(noparity)。具体的介绍, 大家可以百度串口通信了解。
8”表示的是数据位数为 8 位, 其数据格式在前面介绍异步通信中已讲过。当然数据位数还可以为 5、 6、 7 位长度。“1” 表示的是 1 位停止位, 串口通讯的一个数据包从起始信号开始, 直到停止信号结束。 数据包的起始信号由一个逻辑 0 的数据位表示, 而数据包的停止信号可由 0.5、 1、 1.5 或 2 个逻辑 1 的数据位表示, 只要双方约定一致即可。
(3) 串口内部结构
SBUF(serial data bufferr)∶串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器
上图中右边的 TXD
和 RXD
为单片机 IO 口, TXD
对应的是 P3.1 管脚, RXD
对应的是 P3.0 管脚。 其内部工作方式在后面小节会介绍。
(1) 串口控制寄存器 SCON
SM0 和 SM1 为工作方式选择位:
SM2: 多机通信控制位, 主要用于方式 2 和方式 3。 当 SM2=1 时可以利用收到的 RB8 来控制是否激活 RI(RB8=0 时不激活 RI, 收到的信息丢弃; RB8=1 时收到的数据进入 SBUF, 并激活 RI, 进而在中断服务中将数据从 SBUF 读走) 。 当SM2=0 时, 不论收到的 RB8 为 0 和 1, 均可以使收到的数据进入 SBUF, 并激活 RI(即此时 RB8 不具有控制 RI 激活的功能) 。 通过控制 SM2, 可以实现多机通信。
REN: 允许串行接收位。 由软件置 REN=1, 则启动串行口接收数据; 若软件置REN=0, 则禁止接收。
TB8: 在方式 2 或方式 3 中, 是发送数据的第 9 位, 可以用软件规定其作用。可以用作数据的奇偶校验位, 或在多机通信中, 作为地址帧/数据帧的标志位。在方式 0 和方式 1 中, 该位未用到。
RB8: 在方式 2 或方式 3 中, 是接收到数据的第 9 位, 作为奇偶校验位或地址帧/数据帧的标志位。 在方式 1 时, 若 SM2=0, 则 RB8 是接收到的停止位。
TI(transmit interrupt): 发送中断标志位。 在方式 0 时, 当串行发送第 8 位数据结束时, 或在其它方式, 串行发送停止位的开始时, 由内部硬件使 TI 置 1, 向 CPU 发中断申请。在中断服务程序中, 必须用软件将其清 0, 取消此中断申请。
RI(receive interrupt): 接收中断标志位。 在方式 0 时, 当串行接收第 8 位数据结束时, 或在其它方式, 串行接收停止位的同时, 由内部硬件使 RI 置 1, 向 CPU 发中断申请。也必须在中断服务程序中, 用软件将其清 0, 取消此中断申请。
(2) 电源控制寄存器 PCON
SMOD:波特率倍增位。 在串口方式 1、 方式 2、 方式 3 时, 波特率与 SMOD 有关, 当 SMOD=1 时, 波特率提高一倍。 复位时, SMOD=0。
前面在介绍寄存器时已经知道了 51 单片机串口的几种工作方式, 下面分别来对它介绍。
方式 0 时, 串行口为同步移位寄存器的输入输出方式。 主要用于扩展并行输入或输出口。 数据由 RXD
(P3.0) 引脚输入或输出, 同步移位脉冲由 TXD
(P3.1)引脚输出。 发送和接收均为 8 位数据, 低位在先, 高位在后。 波特率固定为fosc/12(“fosc” 表示系统的振荡频率,“波特率 = fosc/12” 意味着每秒传输的符号数等于振荡频率除以12。例如,如果系统的振荡频率为120,000 Hz(也就是12MHz),那么根据这个公式,波特率将为120,000 Hz / 12 = 10,000 Baud。这意味着在每秒内会传输10,000个符号。)。 对应的输入输出时序图如下所示:
方式 1 是 10 位数据的异步通信口。 TXD
为数据发送引脚, RXD
为数据接收引脚, 传送一帧数据的格式如下所示。 其中 1 位起始位, 8 位数据位, 1 位停止位。
①方式 1 输出
②方式 1 输入
用软件置 REN
为 1 时, 接收器以所选择波特率的 16 倍速率采样 RXD
引脚电平, 检测到 RXD
引脚输入电平发生负跳变时, 则说明起始位有效, 将其移入输入移位寄存器, 并开始接收这一帧信息的其余位。 接收过程中, 数据从输入移位寄存器右边移入, 起始位移至输入移位寄存器最左边时, 控制电路进行最后一次移位。 当 RI=0, 且 SM2=0(或接收到的停止位为 1) 时, 将接收到的 9 位数据的前8 位数据装入接收 SBUF, 第 9 位(停止位) 进入 RB8, 并置 RI=1, 向 CPU 请求中断。
方式 2 或方式 3 时为 11 位数据的异步通信口。 TXD
为数据发送引脚, RXD
为数据接收引脚。 其数据格式如下所示:
对应的输入输出时序图如下所示:
①方式 2、方式 3 输出
发送开始时, 先把起始位 0 输出到 TXD
引脚, 然后发送移位寄存器的输出位(D0)到 TXD
引脚。每一个移位脉冲都使输出移位寄存器的各位右移一位, 并由TXD
引脚输出。 第一次移位时, 停止位“1” 移入输出移位寄存器的第 9 位上,以后每次移位, 左边都移入 0。 当停止位移至输出位时, 左边其余位全为 0, 检测电路检测到这一条件时, 使控制电路进行最后一次移位, 并置 TI=1, 向 CPU请求中断。
接收时, 数据从右边移入输入移位寄存器, 在起始位 0 移到最左边时, 控制电路进行最后一次移位。 当 RI=0, 且 SM2=0(或接收到的第 9 位数据为 1) 时,接收到的数据装入接收缓冲器 SBUF 和 RB8(接收数据的第 9 位) , 置 RI=1, 向CPU 请求中断。 如果条件不满足, 则数据丢失, 且不置位 RI, 继续搜索 RXD 引脚的负跳变。
(1)如何计算波特率
在学习 51 单片机串口时, 非常重要的一点是学会如何计算波特率。 以下列出了几种方式下波特率的计算公式:
方式 0 的波特率 = fosc/12
方式 1 的波特率 =(2SMOD/32)×(T1 溢出率)
方式 2 的波特率 =(2SMOD/64)× fosc
方式 3 的波特率 =(2SMOD/32) ×(T1 溢出率)
其中 T1 溢出率 = fosc /{12× [256 -(TH1)]}, 当然还可以使用STC-ISP内附带的小工具自动生成波特率。
设TL1=0XF3
假设晶振频率为12MHZ,所以晶振周期为1/12M(单位是s)。定时器时钟为12T,也就是12个时钟周期(晶振周期),机器完成一条指令。
所以机器完成一条指令所需的时间为:1/12M×12=1/1M=1us
F3换算成2进制为243,需要13us才会溢出一次
所以溢出频率就是1/13us=0.07692307692307692307692307692308MHZ
SMOD置为1,则走上面,无需除以2;置于0,则走下面,需要除以2,相当于波特率减倍
最后波特率=0.07692307692307692307692307692308MHZ÷16=0.00480769MHZ=4807.69HZ
为什么最后不是4800hz呢?因为这就是设计不够精细有误差,用STC-ISP中的小工具波特率计算器计算也可知:
会有0.16%的误差,这多出来的7.69正好就是0.16%的误差
(2) 串口初始化步骤
如何使用串口, 大家可以按照以下几个步骤配置。
①确定 T1 的工作方式(TMOD 寄存器) ;
②确定串口工作方式(SCON 寄存器) ;
③计算 T1 的初值(设定波特率) , 装载 TH1、 TL1;
④启动 T1(TCON 中的 TR1 位) ;
⑤如果使用中断, 需开启串口中断控制位(IE 寄存器) 。
例如: 设置串口为工作方式 1、 波特率为 9600、 波特率加倍、 使用中断。 其配置程序如下:
/*******************************************************************************
* 函 数 名 : uart_init
* 函数功能 : 串口通信中断配置函数, 通过设置 TH 和 TL 即可确定定时时间
* 输 入 : baud: 波特率对应的 TH、 TL 装载值
* 输 出 : 无
*******************************************************************************/
void uart_init(u8 baud)
{
TMOD|=0X20; //设置计数器工作方式 2
SCON=0X50; //设置为工作方式 1
PCON=0X80; //波特率加倍
TH1=baud; //计数器初始值设置
TL1=baud;
ES=1; //打开接收中断
EA=1; //打开总中断
TR1=1; //打开计数器
}
在主函数中调用该函数并传入 0XFA 值即可, 如下:
uart_init(0XFA);//波特率为 9600
开发板上板载一个 USB 转串口模块。 其硬件电路如下所示:
本节所要实现的功能是:
#include "reg52.h"
void UartInit(void) //[email protected]
{
PCON |= 0x80; //使能波特率倍速位SMOD,也就是使波特率提高一倍
SCON = 0x40; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位,也就是高4位全部置为0
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(T1==0);
T1=0;
}
void main()
{
UartInit();
UART_SendByte(0x11);
while(1)
{
}
}
把程序下载进板子,设置好串口号和波特率后(校验位一般设置为无校验,停止位一般设置为1位),点击51单片机开发板上的复位键,上方的白框中就会显示11
#include "reg52.h"
typedef unsigned int u16;
unsigned char Sec=0;
void delay(u16 i)
{
while(i--);
}
void UartInit(void) //[email protected]
{
PCON |= 0x80; //使能波特率倍速位SMOD,也就是使波特率提高一倍
SCON = 0x40; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位,也就是高4位全部置为0
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(TI==0);
TI=0;
}
void main()
{
UartInit();
while(1)
{
UART_SendByte(Sec);
Sec++;
delay(1000000);
}
}
中断号为4的中断是串口中断,随意向51单片机发送一个数据,即可触发该中断,从而点亮对应的led灯
#include "reg52.h"
void UartInit(void) //[email protected]
{
PCON |= 0x80; //使能波特率倍速位SMOD,也就是使波特率提高一倍
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位,也就是高4位全部置为0
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA=1;
ES=1;
}
void main()
{
UartInit();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=SBUF;
RI=0;
}
}
串口在接收到数据之后,硬件会自动将RI=1。
只要RI>=1或者TI>=1就会触发串口中断,也就是发送和接收都会触发中断。前提是程序打开了总中断和串口中断。
为什么要加if(RI==1)判断?因为发送和接收都会触发中断,当接收了时,RI会被自动置为1。
这样根据P2被赋给的值即可点亮相应的led灯