通信的三种基本类型
常用的通信从传输方向上可以分为单工通信、半双工通信、全双工通信三类。
单工通信就是指只允许一方向另外一方传送信息,而另一方不能回传信息。比如电视遥控器、收音机广播等,都是单工通信技术。
半双工通信是指数据可以在双方之间相互传播,但是同一时刻只能其中一方发给另外一方,比如我们的对讲机就是典型的半双工。
全双工通信就发送数据的同时也能够接收数据,两者同步进行,就如同我们的电话一样,我们说话的同时也可以听到对方的声音。
UART模块介绍
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)串口通信是一种用于设备之间的数据传输的技术。它是一种非常基础的串行通信协议,经常被用在各种电子设备中,比如计算机、传感器和微控制器等。
要理解UART,我们可以把它想象成两个人之间的对话。比如,如果两个人在没有任何外界干扰的情况下通过对讲机(walkie-talkie)进行对话,他们就可以轮流说话和听对方说话。UART串口通讯也是类似的过程,只不过这里的对话是通过电子信号进行的。
这里有几个关键点帮助理解UART:
- 异步通信:这意味着发送方和接收方不需要像对讲机那样通过按下按钮来轮流通话。它们不需要共享一个精确的时钟信号来同步他们的交流。相反,每个设备都有自己的时钟,用于定时它们发送或接收的信号。
- 串行数据传输:数据是一位一位地传输的,就像人们说话时一个字母接一个字母组成单词一样。这与并行传输相对,后者可以同时发送多位数据。
- 波特率:这是衡量数据传输速度的单位。如果把它比作说话的速度,波特率就像是每分钟可以说多少个字。在UART通信中,两个设备必须同意使用相同的波特率,以确保一方发送的速度与另一方接收的速度相匹配。
- 起始位和停止位:在UART通信中,每一块数据(通常是一个字节,即8位)的开始和结束都会有特殊的信号。起始位告诉接收器:“嘿,我要开始发送一块数据了”,而停止位则表示:“好的,我这块数据发送完了”。这就像说话时用特定的词汇来开始对话和结束对话。
- 奇偶校验位:这是一个可选的错误检测机制。发送方可以添加一个额外的位,以帮助接收方检查数据是否在传输过程中出现了错误。这就像是在说话结束时询问对方:“你听清楚我说的每个字了吗?”
UART串口通信因其简单性和可靠性而被广泛使用。它不需要很多的引脚(电线),通常只需要两个:一个用于发送数据,另一个用于接收数据。这使得UART成为许多微型电子项目和嵌入式系统中的理想选择。
51单片机的UART串口的结构由串行口控制寄存器SCON、发送和接收电路三部分组成。
串口控制寄存器SCON如下表所示。
对于串口的四种模式,模式1是最常用的,也就是1位起始位,8位数据位和1位停止位。下面就详细介绍模式1。
在硬件串口模块中,有一个专门的波特率发生器用来控制发送和接收数据的速度。对于STC89C52单片机来讲,这个波特率发生器只能由定时器T1和定时器T2产生,而不能由定时器T0产生。
如果使用定时器2,需要配置额外的寄存器,默认是使用定时器1的,我们这里只讨论定时器T1,方式1下的波特率发生器必须使用定时器T1的模式2,也就是自动重装载模式。
定时器的重载值计算公式为TH1 = TL1 = 256 - 晶振值 / 12 / 2 / 16 / 波特率,和波特率有关的还有一个寄存器,是一个电源管理寄存器 PCON,他的最高位可以把波特率提高一倍,也就是如果写 PCON |= 0x80 以后,计算公式就成了:TH1 = TL1 = 256 - 晶振值 / 12 / 16 / 波特率。
这里解释一下公式,256是8位定时器的溢出值,也就是TL1的溢出值,晶振值即板子上的晶振频率(普中51开发板即11059200),12是说1个机器周期等于12个时钟周期,这个16是一个分频值,记住就行。
串口通信的发送和接收电路在物理上有 2 个名字相同的 SBUF 寄存器,它们的地址也都是 0x99,但是一个用来做发送缓冲,一个用来做接收缓冲。意思就是说,有 2 个房间,两个房间的门牌号是一样的,其中一个只出人不进人,另外一个只进人不出人,这样的话,我们就可以实现 UART 的全双工通信,相互之间不会产生干扰。但是在逻辑上呢,我们每次只操作 SBUF,单片机会自动根据对它执行的是“读”还是“写”操作来选择是接收 SBUF 还是发送 SBUF,后边通过程序,我们就会彻底了解这个问题。
UART 串口程序
一般情况下,我们编写串口程序的基本步骤如下所示:
- 配置串口为模式1.
- 配置定时器T1为模式2,即自动重装载模式。
- 根据波特率计算TH1和TL1的初值,如果有需要可以使用PCON进行波特率加倍。
- 打开定时器控制寄存器TR1,让定时器跑起来。
当然,这个时候不能再使能T1中断了。
下面我们的程序实现的是由电脑发送一个字符给我们的普中51开发板,再由开发板+1后发送给电脑。
#include <REG52.H>
void configUART(unsigned int baud);
void main()
{
configUART(9600); // 配置波特率为9600
while (1)
{
while (!RI) // 等待接受完毕
;
RI = 0; // 软件清零接收中断标志位
SBUF = SBUF + 1; // 接收到的数据+1后发送回去
while (!TI) // 等待发送完毕
;
TI = 0; // 软件清零发送中断标志位
}
}
void configUART(unsigned int baud)
{
SCON = 0x50; // 配置串口为模式1,并且使能串行接收
TMOD &= 0x0F; // 清零T1的控制位
TMOD |= 0x20; // 配置T1为模式2
TH1 = 256 - 11059200 / 12 / 2 / 16 / baud; // 计算T1重载值
TL1 = TH1; // 初值等同于重载值
TR1 = 1; // 启动T1
}
当然,我们在实际工程中要使用串口中断来判断中断的类型,而不是在主程序中。
#include <REG52.H>
void configUART(unsigned int baud);
void main()
{
configUART(9600);
while (1)
;
}
void configUART(unsigned int baud)
{
EA = 1;
SCON = 0x50;
TMOD &= 0x0F;
TMOD |= 0x20;
TH1 = 256 - 11059200 / 12 / 2 / 16 / baud;
TL1 = TH1;
ES = 1; // 使能串口中断
TR1 = 1;
}
void interruptTimer1() interrupt 4
{
if (RI)
{
RI = 0;
SBUF = SBUF + 1;
}
if (TI)
{
TI = 0;
}
}
下面我们就做一个简单的例程,实现单片机串口调试助手发送的数据,在我们开发板上的数码管上显示出来。
#include <REG52.H>
#define LED P0
unsigned char code LedChar[] = {
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};
unsigned char ledBuf[8] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
unsigned char T0RH = 0;
unsigned char T0RL = 0;
unsigned char rxdByte = 0;
void configTimer0(unsigned int ms);
void configUART(unsigned int baud);
void main()
{
EA = 1;
configTimer0(1);
configUART(9600);
while (1)
{ // 将接收字节在数码管上以十六进制形式显示出来
ledBuf[0] = LedChar[rxdByte & 0x0F];
ledBuf[1] = LedChar[rxdByte >> 4];
}
}
/*配置并启动T0即T0中断,ms为T0定时时间*/
void configTimer0(unsigned int ms)
{
unsigned long temp = 0;
temp = 11059200 / 12;
temp = (temp * ms) / 1000;
temp = 65536 - temp;
temp = temp + 13; // 补偿中断响应延时造成的误差
T0RH = (unsigned char)(temp >> 8); // 定时器重载值拆分为高低字节
T0RL = (unsigned char)temp;
TMOD &= 0xF0;
TMOD |= 0x01;
TH0 = T0RH;
TL0 = T0RL;
ET0 = 1;
TR0 = 1;
}
/*串口配置函数,baud为通信波特率*/
void configUART(unsigned int baud)
{
SCON = 0x50;
TMOD &= 0x0F;
TMOD |= 0x20;
TH1 = 256 - 11059200 / 12 / 2 / 16 / baud;
TL1 = TH1;
ES = 1; // 使能串口中断
TR1 = 1;
}
void ledScan()
{
static unsigned char i = 0;
LED = 0x00;
P2 = (P2 & 0xE3) | (i << 2);
LED = ~ledBuf[i];
if (i <= 6)
{
i++;
}
else
{
i = 0;
}
}
void interruptTimer0() interrupt 1
{
TH0 = T0RH;
TL0 = T0RL;
ledScan();
}
void interruptTimer1() interrupt 4
{
if (RI)
{
RI = 0;
rxdByte = SBUF;
SBUF = rxdByte;
}
if (TI)
{
TI = 0;
}
}