C51的UART 串口通信

1.串口通信简介

UART(Universal Asynchronous Receiver/Transmitter,即通用异步收发器)串行通信是单片机最常用的一种通信技术,通常用于单片机和电脑之间以及单片机和单片机之间的通信。串行通信,就如同一条车道,一次只能发送一个字节。
STC89C52 有两个引脚是专门用来做 UART 串行通信的,一个是 P3.0 一个是 P3.1,它们还分别有另外的名字叫做 RXD 和 TXD,由它们组成的通信接口就叫做串行接口,简称串口。
在这里插入图片描述
在 UART 通信过程中,是低位先发,高位后发的原则。一个高低电平的持续时间由波特率(比特率)表示,数据位的切换时间就是波特率分之一秒。波特率就是发送二进制数据位的速率,必须保持一致,收发双方才能正常实现通信。本来要发送一个字节的 8 位数据,而实际上我们一共发送了 10 位,多出来的两位是起始位与停止位。

2.串口调试助手

在这里插入图片描述
串口调试助手的实质就是利用电脑上的 UART 通信接口,发送数据给我们的单片机,也
可以把我们的单片机发送的数据接收到这个调试助手界面上。
配置波特率的时候,我们用的是定时器 T0 的模式 2,这样我们就可以把想要的定时器初值提前存在 TH0 中。

3.串口通信的实现

波特率设置好以后,打开中断,然后等待接收串口调试助手下发的数据。接收数据的时
候,首先要进行低电平检测 while (PIN_RXD),若没有低电平则说明没有数据,一旦检测到低电平,就进入启动接收函数 StartRXD()。
一旦读到了起始信号,我们就把当前状态设定成接收状态,打开定时器中断,第一次是半个周期进入中断后,对起始位进行二次判断,确认起始位是低电平,而不是干扰信号。以后每经过 1/9600 秒进入一次中断,并且把这个引脚的状态读到 RxdBuf 里边。等待接收完毕之后,我们再把这个 RxdBuf 加 1,再通过 TXD 引脚发送出去,同样需要先发一位起始位,然后发 8 个数据位,再发结束位,发送完毕后,程序运行到 while (PIN_RXD),等待第二轮信号接收的开始。

#include <reg52.h>
sbit PIN_RXD = P3^0; //接收引脚定义
sbit PIN_TXD = P3^1; //发送引脚定义
bit RxdOrTxd = 0; //指示当前状态为接收还是发送
bit RxdEnd = 0; //接收结束标志
bit TxdEnd = 0; //发送结束标志
unsigned char RxdBuf = 0; //接收缓冲器
unsigned char TxdBuf = 0; //发送缓冲器
void ConfigUART(unsigned int baud);
void StartTXD(unsigned char dat);
void StartRXD();
void main()
{
 EA = 1; //开总中断
 ConfigUART(9600); //配置波特率为 9600
 
 while (1)
 {
 while (PIN_RXD); //等待接收引脚出现低电平,即起始位
 StartRXD(); //启动接收
 while (!RxdEnd); //等待接收完成
 StartTXD(RxdBuf+1); //接收到的数据+1 后,发送回去
 while (!TxdEnd); //等待发送完成
 } }
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
 TMOD &= 0xF0; //清零 T0 的控制位
 TMOD |= 0x02; //配置 T0 为模式 2
 TH0 = 256 - (11059200/12)/baud; //计算 T0 重载值
}
/* 启动串行接收 */
void StartRXD()
{
 TL0 = 256 - ((256-TH0)>>1); //接收启动时的 T0 定时为半个波特率周期
 ET0 = 1; //使能 T0 中断
 TR0 = 1; //启动 T0
 RxdEnd = 0; //清零接收结束标志
 RxdOrTxd = 0; //设置当前状态为接收
}
/* 启动串行发送,dat-待发送字节数据 */
void StartTXD(unsigned char dat)
{
 TxdBuf = dat; //待发送数据保存到发送缓冲器
 TL0 = TH0; //T0 计数初值为重载值
 ET0 = 1; //使能 T0 中断
 TR0 = 1; //启动 T0
 PIN_TXD = 0; //发送起始位
 TxdEnd = 0; //清零发送结束标志
 RxdOrTxd = 1; //设置当前状态为发送
}
/* T0 中断服务函数,处理串行发送和接收 */
void InterruptTimer0() interrupt 1
{
 static unsigned char cnt = 0; //位接收或发送计数
 if (RxdOrTxd) //串行发送处理
 {
 cnt++;
 if (cnt <= 8) //低位在先依次发送 8bit 数据位
 {
 PIN_TXD = TxdBuf & 0x01;
 TxdBuf >>= 1;
 }
 else if (cnt == 9) //发送停止位
 {
 PIN_TXD = 1;
 }
 else //发送结束
 {
 cnt = 0; //复位 bit 计数器
 TR0 = 0; //关闭 T0
 TxdEnd = 1; //置发送结束标志
 }
 }
 else //串行接收处理
  if (cnt == 0) //处理起始位
 {
 if (!PIN_RXD) //起始位为 0 时,清零接收缓冲器,准备接收数据位
 {
 RxdBuf = 0;
 cnt++;
 }
 else //起始位不为 0 时,中止接收
 {
 TR0 = 0; //关闭 T0
 }
 }
 else if (cnt <= 8) //处理 8 位数据位
 {
 RxdBuf >>= 1; //低位在先,所以将之前接收的位向右移
 if (PIN_RXD) //接收脚为 1 时,缓冲器最高位置 1,
 { //而为 0 时不处理即仍保持移位后的 0
 RxdBuf |= 0x80;
 }
 cnt++;
 }
 else //停止位处理
 {
 cnt = 0; //复位 bit 计数器
 TR0 = 0; //关闭 T0
 if (PIN_RXD) //停止位为 1 时,方能认为数据有效
 {
 RxdEnd = 1; //置接收结束标志
 }
 }
 } }

一旦读到了起始信号,我们就把当前状态设定成接收状态,并且打开定时器中断,第一
次是半个周期进入中断后,对起始位进行二次判断一下,确认一下起始位是低电平,而不是一个干扰信号。以后每经过 1/9600 秒进入一次中断,并且把这个引脚的状态读到RxdBuf 里边。等待接收完毕之后,我们再把这个 RxdBuf 加 1,再通过 TXD 引脚发送出去,同样需要先发一位起始位,然后发 8 个数据位,再发结束位,发送完毕后,程序运行到 while (PIN_RXD),等待第二轮信号接收的开始。

4.UART模块

可以在单片机内部做一个硬件模块,让它自动接收数据,接收完了,通知我们就可以了。51 单片机内部就存在这样一个 UART 模块,要正确使用它,先把对应的特殊功能寄存器(串行控制寄存器)配置好。
51单片机的UART 串口的结构由串行口控制寄存器SCON、发送和接收电路三部分构成。
在这里插入图片描述
对于串口的四种模式,模式 1 是最常用的,就是我们前边提到的 1 位起始位,8 位数据位和 1 位停止位。对于STC89C52 单片机来讲,波特率发生器只能由定时器 T1 或定时器 T2 产生。

5.总结

一般情况下,我们编写串口通信程序的基本步骤如下所示:
1、配置串口为模式 1。 2、配置定时器 T1 为模式 2,即自动重装模式。
3、根据波特率计算 TH1 和 TL1 的初值,如果有需要可以使用 PCON 进行波特率加倍。
4、打开定时器控制寄存器 TR1,让定时器跑起来。
这里还要特别注意一下,就是在使用 T1 做波特率发生器的时候,千万不要再使能 T1 的
中断了。

#include <reg52.h>
void ConfigUART(unsigned int baud);
void main()
{
 EA = 1; //使能总中断
 ConfigUART(9600); //配置波特率为 9600
 while (1);
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
 SCON = 0x50; //配置串口为模式 1
 TMOD &= 0x0F; //清零 T1 的控制位
 TMOD |= 0x20; //配置 T1 为模式 2
  TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
 TL1 = TH1; //初值等于重载值
 ET1 = 0; //禁止 T1 中断
 ES = 1; //使能串口中断
 TR1 = 1; //启动 T1
}
/* UART 中断服务函数 */
void InterruptUART() interrupt 4
{
 if (RI) //接收到字节
 {
 RI = 0; //手动清零接收中断标志位
 SBUF = SBUF + 1; //接收的数据+1 后发回,左边是发送 SBUF,右边是接收 SBUF
 }
 if (TI) //字节发送完毕
 {
 TI = 0; //手动清零发送中断标志位
 } }

因为接收和发送触发的是同一个串口中断,所以在串口中断函数中就必须先判断是哪种中断,然后再作出相应的处理。

6.实例

单片机串口调试助手发送的数据,开发板上的数码管上显示出来

#include <reg52.h>
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //数码管显示字符转换表
 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = { //数码管+独立 LED 显示缓冲区
 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char T0RH = 0; //T0 重载值的高字节
unsigned char T0RL = 0; //T0 重载值的低字节
unsigned char RxdByte = 0; //串口接收到的字节
void ConfigTimer0(unsigned int ms);
void ConfigUART(unsigned int baud);
void main()
{
 EA = 1; //使能总中断
 ENLED = 0; //选择数码管和独立 LED
 ADDR3 = 1;
 ConfigTimer0(1); //配置 T0 定时 1ms
 ConfigUART(9600); //配置波特率为 9600
 
 while (1)
 { //将接收字节在数码管上以十六进制形式显示出来
 LedBuff[0] = LedChar[RxdByte & 0x0F];
 LedBuff[1] = LedChar[RxdByte >> 4];
 } }
/* 配置并启动 T0,ms-T0 定时时间 */
void ConfigTimer0(unsigned int ms)
{
 unsigned long tmp; //临时变量
 
 tmp = 11059200 / 12; //定时器计数频率
 tmp = (tmp * ms) / 1000; //计算所需的计数值
 tmp = 65536 - tmp; //计算定时器重载值
 tmp = tmp + 13; //补偿中断响应延时造成的误差
 T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
 T0RL = (unsigned char)tmp;
 TMOD &= 0xF0; //清零 T0 的控制位
 TMOD |= 0x01; //配置 T0 为模式 1
 TH0 = T0RH; //加载 T0 重载值
 TL0 = T0RL;
 ET0 = 1; //使能 T0 中断
 TR0 = 1; //启动 T0
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
 SCON = 0x50; //配置串口为模式 1
 TMOD &= 0x0F; //清零 T1 的控制位
 TMOD |= 0x20; //配置 T1 为模式 2
 TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
 TL1 = TH1; //初值等于重载值
 ET1 = 0; //禁止 T1 中断
 ES = 1; //使能串口中断
  TR1 = 1; //启动 T1
}
/* LED 动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
 static unsigned char i = 0; //动态扫描索引
 
 P0 = 0xFF; //关闭所有段选位,显示消隐
 P1 = (P1 & 0xF8) | i; //位选索引值赋值到 P1 口低 3 位
 P0 = LedBuff[i]; //缓冲区中索引位置的数据送到 P0 口
 if (i < 6) //索引递增循环,遍历整个缓冲区
 i++;
 else
 i = 0;
}
/* T0 中断服务函数,完成 LED 扫描 */
void InterruptTimer0() interrupt 1
{
 TH0 = T0RH; //重新加载重载值
 TL0 = T0RL;
 LedScan(); //LED 扫描显示
}
/* UART 中断服务函数 */
void InterruptUART() interrupt 4
{
 if (RI) //接收到字节
 {
 RI = 0; //手动清零接收中断标志位
 RxdByte = SBUF; //接收到的数据保存到接收字节变量中
 SBUF = RxdByte; //接收到的数据又直接发回,叫作-"echo",
 //用以提示用户输入的信息是否已正确接收
 }
 if (TI) //字节发送完毕
 {
 TI = 0; //手动清零发送中断标志位
 } }

在这里插入图片描述

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值