简介
串口通信是指将数据一位一位地顺序发送,是一种应用十分广泛的通讯方式。
单片机的的串口使单片机能够与各种各样的模块、设备相互连接,极大地扩展了单片机的应用范围。
51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可以实现单片机的串口通信。
硬件电路
TX和RX
简单的双向串口通信需要两根线,TX、RX(Transmit exchange、Receive exchange)用于发送和接收数据,如果需要进行两台设备之间的相互通信,则需要交错连接TX和RX。
供电
除了传输数据用的TX和RX,串口通信设备常常还需要连接GND进行统一零电位,有时也需要连接VCC来进行供电。
此时就涉及到电平标准,必须注意所提供的VCC是否符合要求。如果不符合要求,轻则损害电源,重则损坏电脑。
51单片机所使用的电平标准为TTL,在此标准下,+5V表示1,0V表示0
UART
简介
UART是一种串行、异步、全双工的通信方式(下图来自江科大B站视频)
此处出现了异步这一个概念,在通信过程中,两个设备之间必须约定好传输速率,即波特率,每秒传输bit的位数。否则会出现传输错误的现象。
51单片机内部只设置了1个UART,具体位置可以参考原理图查找。同时设置了4种传输方式。
传输数据流程
此处以常用的模式1(8位UART,波特率可变)作为例子讲解UART传输数据的具体流程。
串行传输
(图片来自江科大B站视频)
串行传输模式下,包含起始位、数据位、校验位(该模式没有)、停止位。起始位和停止位用于间隔不同帧之间的数据;数据位则为实际需要传输的数据;校验位用于校验数据位在传输过程中是否出现错误,通常有奇校验、偶校验等校验方法。
读取到起始位后,开始读取数据,读取到停止位结束读取此帧数据。
UART整体框图
数据手册上的框图如下
江科大的图比手册上的更加简洁,便于理解
发送数据时,通过数据总线将数据传输到SBUF(serial data buffer,串行数据缓存器)内,通过调节定时器的溢出率来控制发送数据的时间间隔,即控制波特率;接收数据的区别仅仅在于数据从SBUF传输到数据总线。
定时器控制波特率的实现同样是通过中断来实现的,定时器溢出后,发送信号进入UART中断,此时配置中断允许控制寄存器、中心端优先级控制寄存器(ES = 1,EA = 1,PSH = 1,PS = 1),即可在中断函数内操作具体的串口通信内容。
实例
单片机向电脑发送数据
最终实现的功能为单片机向电脑间隔一秒发送一个递增的数
初始化UART
配置串口工作模式
控制单片机工作模式的寄存器为SCON,具体功能可以查找数据手册。
以使用串口的模式1为例,则应进行的操作为:
1.选择工作模式为模式1,SM0 = 0,SM1 = 1
2.因为在这个例子里面不需要接收数据,设置为禁止接收,REN = 0
3.初始化发送中断请示位为0,即TI = 0
4.其他的寄存器都与模式2、3或接收数据无关,都可以直接置0
最后应该写下代码:SCON = 0x40 ;
配置定时器
在串口通信时,因为通信要求精度更高,用代码重装会影响精度,所以单片机规定只能使用定时器1的模式2(8位自动重装模式)。
1.选择定时器1:TMOD &= 0x0F ;
2.设置定时器1:TMOD |= 0x20 ;
3.波特率加倍以减小误差(配置PCON):PCON |= 0x80
4.此处不需要开启定时器中断,这里定时器只起到溢出的波特率发生器的作用,所以禁止定时器1中断:ET1 = 0
5.打开定时器1:TR1 = 1
波特率计算
我们的目的是配置波特率为4800,也就是每秒发送4800个字节,回顾上面控制波特率的定时器部分电路。
因为我们配置了波特率加倍,定时器为12T模式,所以最后的波特率计算公式为:波特率 = 定时器溢出率/16。得到定时器溢出率 = 76800Hz = 0.0768MHz ;
而定时器溢出率又有计算公式:定时器溢出率 = 晶振频率 /(12×定时器重载值),得到定时器重载值应该设置为12
配置方式为:TL1 = 0xF4 ; TH1 = 0xF4 ;
发送数据
如果一切都配置好了,我们想要发送数据只需要将数据写入SBUF,就会自动发送,即:SBUF = Byte ;
除此以外需要注意TI,手册的说明如下:
所以我们需要检测TI的状态,只要检测到TI == 1,立即进行软件复位:TI = 0
最终代码
main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Second=0;
void main()
{
UART_Init();//初始化串口和定时器
while(1)
{
UART_SendByte(Second);
Second++;
Delay(1000);
}
}
UART.c
#include <REGX52.H>
/**
* @brief 串口初始化,4800bps@11.0592MHz
* @param 无
* @retval 无
*/
void UART_Init() //配置串口和定时器部分
{
SCON=0x40;//对串行控制寄存器初始化(选择工作方式)
PCON |= 0x80; //使能波特率倍速位SMOD
//SBUF不需要配置
TMOD &= 0x0F; //设置定时器模式(清零高四位(0000 1111))(使用计时器1)
TMOD |= 0x20; //设置定时器1为8位自动重装模式(0010 1111)
TL1 = 0xF4; //设置定时初始值
TH1 = 0xF4; //设置定时重载值
TR1 = 1; //定时器0开始计时(允许计时器0开始计时)
ET1 = 0; //禁止定时器1产生中断
}
/**
* @brief 串口发送一个字节数据
* @param Byte(要发送的一个字节数据)
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(TI==0);//写入数据前,TI=0;输出8位数据后,TI=1(跳出循环);
TI=0;//软件对TI置零
}
Delay.c
#include <INTRINS.H>
void Delay(unsigned int x)
{
int i;
for(i=0;i<x;i++)
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
单片机接收PC端发送的数据
最终实现功能为电脑向单片机发送8位二进制数,控制LED的状态
接收数据和发送数据的区别在于串口的配置,以及需要设置中断函数(因为不知道什么时候外部会发送数据)
串口配置
除了需要打开接收,其余没有区别,也就是:SCON = 0x50 ;
中断配置
查找手册,打开串口中断:ES = 1 ;EA = 1 ;PSH = 1 ;PS = 1 ;(因为通信的优先级高)
中断函数
首先需要判断是否是因为接收数据而产生中断,如果是因接收进入中断,将SBUF接收到的数据直接写入控制LED的寄存器,最后软件复位RI = 0 ;
最终代码
除了上面提到的部分,其他代码与第一个例子一样
main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4 //UART对应的中断号是4
{
if(RI==1)//只有接收中断才进行操作
{
P2=~SBUF; //取反是因为LED得到0才亮,要让LED可以指示发送的数据
UART_SendByte(SBUF);
RI=0;//必须由软件复位
}
}
UART.c
#include <REGX52.H>
/**
* @brief 串口初始化,4800bps@11.0592MHz
* @param 无
* @retval 无
*/
void UART_Init() //配置串口和定时器部分
{
SCON=0x50;//对串行控制寄存器初始化(选择工作方式)
PCON |= 0x80; //使能波特率倍速位SMOD
//SBUF不需要配置
TMOD &= 0x0F; //设置定时器模式(清零高四位(0000 1111))(使用计时器1)
TMOD |= 0x20; //设置定时器1为8位自动重装模式(0010 1111)
TL1 = 0xF4; //设置定时初始值
TH1 = 0xF4; //设置定时重载值
TR1 = 1; //定时器0开始计时(允许计时器0开始计时)
ET1 = 0; //关闭定时器1
EA=1; //打开CPU的总中断控制允许位(CPU开放中断)
ES=1; //打开串行口中断允许位(允许串行口中断)
}