【C语言实战(64)】51单片机串口通信:开启嵌入式开发的通信大门
目录
- 一、引言
- 二、串口通信基础
- 2.1 串口通信原理
- 2.2 51 单片机串口寄存器
- 2.3 通信流程
 
- 三、串口初始化与数据发送
- 3.1 串口初始化
- 3.2 单片机向电脑发送数据
- 3.2.1 实现 send_char 函数
- 3.2.2 实现 send_string 函数
- 3.2.3 测试
 
 
- 四、串口数据接收与实战
- 4.1 单片机接收电脑数据
- 4.1.1 中断方式接收
- 4.1.2 实现 recv_char 函数
 
- 4.2 实战项目:串口控制 LED
- 4.2.1 功能
- 4.2.2 代码实现
- 4.2.3 测试
 
 
- 五、总结
一、引言
在嵌入式开发的广阔领域中,串口通信作为一种基础且重要的数据传输方式,发挥着不可或缺的作用。它就像是嵌入式系统的 “桥梁”,连接着不同的设备,实现数据的交互与共享 ,在工业控制、智能家居、物联网等诸多场景中,串口通信都被广泛应用。无论是传感器数据的采集与传输,还是设备之间的控制指令交互,串口通信都以其简单易用、成本低廉等优势,成为开发者们的首选之一。
51 单片机作为经典的嵌入式微控制器,以其丰富的资源、成熟的技术和广泛的应用案例,在嵌入式领域占据着重要的地位。基于 51 单片机的串口通信实战,不仅能够帮助我们深入理解串口通信的原理和机制,更能掌握如何在实际项目中运用串口通信实现设备间的高效数据交互。
接下来,让我们一起深入探索基于 51 单片机的串口通信实战之旅,揭开串口通信的神秘面纱,掌握这一强大的数据传输技能。
二、串口通信基础
2.1 串口通信原理
串口通信,即串行通信,是指数据一位一位地顺序发送或接收。其主要方式分为异步通信和同步通信,这里我们重点关注异步通信。异步通信,就像两个人在不同步的时间下交谈,不需要额外的时钟线来协调节奏 ,每个设备都有自己的时钟信号,依靠起始位、停止位来实现数据的同步。当发送方发送数据时,会在数据前添加一个起始位(通常为低电平),表示数据传输的开始;在数据后添加一个停止位(通常为高电平),表示数据传输的结束 。接收方通过检测起始位和停止位,来确定数据的开始和结束,从而实现数据的正确接收。
波特率是串口通信中的一个重要概念,它表示串口每秒钟传输的位数,单位为 bps(bit per second)。打个比方,波特率就像是道路上车辆的行驶速度,它决定了数据传输的快慢。常见的波特率有 9600bps、115200bps 等。以 9600bps 为例,它意味着每秒钟可以传输 9600 位的数据。在实际应用中,通信双方的波特率必须保持一致,否则就会出现数据传输错误,就像两个人用不同的语速交谈,很难理解对方的意思。
数据帧格式是串口通信中数据的组织形式。在我们的案例中,采用的是 8 位数据位、1 位停止位、无校验的数据帧格式。8 位数据位可以表示一个字节的数据,这是最常见的数据表示方式;1 位停止位用于标志一帧数据的结束;无校验则表示在数据传输过程中,不进行数据校验,这样可以提高数据传输的效率,但同时也增加了数据传输错误的风险。如果需要更高的数据传输可靠性,可以选择添加校验位,如奇偶校验、CRC 校验等。
2.2 51 单片机串口寄存器
在 51 单片机中,串口通信的实现离不开一些关键的寄存器,其中 SCON 和 TH1/TL1 尤为重要。
SCON 是串口控制寄存器,地址为 98H,它就像是串口通信的 “指挥官”,负责配置和控制串口的工作方式、数据接收和发送的状态等。具体来说,它的每一位都有独特的功能:
- SM0 和 SM1:这两位组合起来决定串行口的工作方式。一共有四种工作方式,方式 0 是同步移位寄存器方式,常用于扩展 I/O 口,数据位是 8 位,波特率固定为晶振频率的 1/12;方式 1 是 8 位 UART(通用异步收发传输器),波特率可变,由定时器 1 的溢出率决定;方式 2 和 3 是 9 位 UART,方式 2 的波特率固定为晶振频率的 1/32 或 1/64 ,方式 3 的波特率可变,同样由定时器 1 的溢出率决定。
- SM2:多机通信控制位,主要用于方式 2 和 3 中,实现多机通信的允许控制。在多机通信中,通过设置 SM2 位,可以让单片机只接收特定地址的数据,避免接收无用的数据,提高通信的效率和可靠性。
- REN:允许接收位,当 REN = 1 时,允许串行接口接收数据;当 REN = 0 时,禁止接收。就像一个开关,控制着数据的接收通道。
- TB8:在方式 2 和 3 中,发送的第 9 位数据,可以用于奇偶校验或多机通信的地址 / 数据标识。通过设置 TB8 位,可以增加数据传输的可靠性和灵活性。
- RB8:在方式 2 和 3 中,接收的第 9 位数据,用于配合 SM2 位进行多机通信和数据校验。
- TI:发送中断标志位,当一帧数据发送完成时,由硬件置位,需要软件清零。它就像是一个 “信号灯”,告诉我们数据已经发送完成,可以进行下一步操作。
- RI:接收中断标志位,当一帧数据接收完成时,由硬件置位,同样需要软件清零。当 RI 置位时,说明有新的数据接收到了,我们可以从接收缓冲区中读取数据。
TH1 和 TL1 是定时器 1 的高 8 位和低 8 位寄存器,它们在串口通信中扮演着波特率发生器的重要角色。在 51 单片机中,波特率由定时器 1 决定,而定时器 1 的重载值则决定了波特率的具体数值。通过设置 TH1 和 TL1 的初值,可以控制定时器 1 的溢出率,从而实现不同波特率的设置。例如,当我们需要设置波特率为 9600bps 时,根据晶振频率和相关公式,可以计算出 TH1 和 TL1 的初值,并将其赋值给这两个寄存器,这样就可以实现 9600bps 的波特率通信。
2.3 通信流程
了解了串口通信的原理和相关寄存器后,我们来看看单片机与电脑之间的通信流程。
当单片机向电脑发送数据时,首先,我们需要将要发送的数据写入到单片机的串口数据缓冲寄存器 SBUF 中。SBUF 就像是一个 “数据中转站”,数据在这里等待发送。然后,单片机会自动将 SBUF 中的数据按照设定的波特率和数据帧格式,一位一位地通过串口发送出去。在发送过程中,发送中断标志位 TI 会被硬件置位,当我们检测到 TI 为 1 时,就知道数据已经发送完成,可以进行下一次数据发送或者其他操作了,最后,我们需要将 TI 清零,为下一次发送做好准备。
而电脑向单片机发送数据时,数据首先从电脑的串口发送出来,经过串口线传输到单片机的串口接收引脚。单片机的串口接收电路会检测到数据的到来,并将数据一位一位地接收下来,存储到 SBUF 中。同时,接收中断标志位 RI 会被硬件置位,当我们在单片机的程序中检测到 RI 为 1 时,就可以从 SBUF 中读取接收到的数据了。读取完数据后,记得要将 RI 清零,以便接收下一次的数据。
三、串口初始化与数据发送
3.1 串口初始化
在 51 单片机中,串口初始化是实现串口通信的关键步骤。首先,我们需要配置定时器 1 为模式 2,即 8 位自动重装模式。在这种模式下,定时器 1 被配置为 8 位计数器,其初值由 TH1(高字节寄存器)提供,并在每次计数溢出后自动恢复为该初值。这种方式非常适合用于波特率发生器,因为它可以提供稳定的定时,减少误差。
配置定时器 1 为模式 2 的代码如下:
TMOD &= 0x0F;  // 清零高4位,确保不影响定时器0的配置
TMOD |= 0x20;  // 设置定时器1为模式2,二进制表示为0010 0000,高四位0010表示模式2
接下来是设置波特率。以设置波特率为 9600bps 为例,我们需要根据晶振频率和相关公式来计算 TH1 和 TL1 的值。假设我们使用的是 11.0592MHz 的晶振,波特率的计算公式为:
波特率=2SMOD×晶振32×12×(256−TH1)波特率 = \frac{2^{SMOD} \times 晶振}{32 \times 12 \times (256 - TH1)}波特率=32×12×(256−TH1)2SMOD×晶振
其中,SMOD 是 PCON 寄存器的最高位,用于设置波特率是否加倍。当 SMOD = 0 时,波特率不加倍;当 SMOD = 1 时,波特率加倍 。在我们的例子中,假设 SMOD = 0(不加倍),将波特率 9600bps 和晶振频率 11.0592MHz 代入公式,可得:
9600=1×1105920032×12×(256−TH1)9600 = \frac{1 \times 11059200}{32 \times 12 \times (256 - TH1)}9600=32×12×(256−TH1)1×11059200
解这个方程,可计算出 TH1 的值:
256−TH1=1105920032×12×9600256 - TH1 = \frac{11059200}{32 \times 12 \times 9600}256−TH1=32×12×960011059200
 256−TH1=3256 - TH1 = 3256−TH1=3
 TH1=256−3=253TH1 = 256 - 3 = 253TH1=256−3=253
即 TH1 = 0xFD 。在模式 2 下,TL1 会自动重载 TH1 的值,所以 TL1 也设置为 0xFD 。设置 TH1 和 TL1 的代码如下:
TH1 = 0xFD;  // 装载定时器初值
TL1 = 0xFD;  // 自动重装值
同时,我们还需要设置 PCON 寄存器,确保 SMOD = 0(波特率不倍增):
PCON &= 0x7F;  // 将PCON的最高位SMOD清零,确保波特率不倍增
最后,使能串口接收。通过设置 SCON 寄存器的 REN 位(允许接收位)为 1,来开启串口接收功能 。代码如下:
SCON = 0x50;  // 串口模式1,允许接收,二进制表示为0101 0000,SM0 = 0, SM1 = 1表示模式1,REN = 1允许接收
3.2 单片机向电脑发送数据
3.2.1 实现 send_char 函数
实现 send_char 函数的目的是将一个字符写入 SBUF 寄存器,并等待发送完成。代码如下:
void send_char(char c) {SBUF = c;  // 将字符c写入SBUF寄存器,启动数据发送while(TI == 0);  // 等待发送完成,TI为发送中断标志位,当TI = 1时,表示数据发送完成TI = 0;  // 软件清零TI标志位,为下一次发送做准备
}
在这段代码中,首先将字符 c 写入 SBUF 寄存器,此时单片机就会开始将数据按照设定的波特率和数据帧格式发送出去。然后,通过 while 循环检查 TI 标志位,当 TI 为 0 时,表示数据还未发送完成,程序会一直停留在这个循环中;当 TI 为 1 时,表示数据发送完成,跳出循环。最后,将 TI 清零,这是因为 TI 标志位在数据发送完成后由硬件置位,但不会自动清零,需要我们手动清零,以便下一次发送数据时能够正确检测 TI 标志位。
3.2.2 实现 send_string 函数
send_string 函数用于发送一个字符串,它通过循环调用 send_char 函数,将字符串中的每个字符依次发送出去。代码如下:
void send_string(const char *str) {while(*str != '\0') {  // 遍历字符串,直到遇到字符串结束符'\0'send_char(*str++);  // 调用send_char函数发送当前字符,并将指针指向下一个字符}
}
在这个函数中,首先通过 while 循环判断当前字符是否为字符串结束符 ‘\0’,如果不是,则调用 send_char 函数发送当前字符,然后将指针 str 向后移动一位,指向下一个字符,继续循环发送,直到发送完整个字符串。
3.2.3 测试
为了测试单片机向电脑发送数据的功能,我们可以使用串口助手,这里以 SecureCRT 为例。使用 SecureCRT 的基本步骤如下:
-  打开 SecureCRT 软件,点击 “文件” -> “快速连接”。 
-  在弹出的 “快速连接” 对话框中,配置串口参数: - 协议:选择 “Serial”,表示使用串口通信。
- 端口:选择单片机连接到电脑的串口号,这个串口号可以在电脑的设备管理器中查看。
- 波特率:设置为与单片机程序中设置的波特率一致,即 9600。
- 数据位:设置为 8 位,与我们在数据帧格式中设置的数据位一致。
- 停止位:设置为 1 位,与数据帧格式中的停止位一致。
- 校验:设置为 “无”,因为我们的数据帧格式中没有校验位。
 
-  配置完成后,点击 “连接” 按钮,即可建立与单片机的串口连接。 
-  在单片机程序中,调用 send_string 函数发送字符串,例如: 
send_string("Hello PC!");
在 SecureCRT 的界面中,就可以看到接收到的字符串 “Hello PC!”,从而验证数据发送的正确性。如果没有接收到正确的数据,可以检查串口连接是否正常、波特率设置是否一致、单片机程序是否正确等。
四、串口数据接收与实战
4.1 单片机接收电脑数据
4.1.1 中断方式接收
在 51 单片机中,使用中断方式接收电脑数据可以提高系统的实时性和效率。要实现中断方式接收,需要对相关寄存器进行设置,使能串口接收中断。
首先,将 SCON 寄存器中的 REN 位置 1,即REN = 1,这表示允许串行接口接收数据,就像是打开了数据接收的大门。然后,将 IE 寄存器中的 EA 位置 1,即EA = 1,这是使能总中断,允许单片机响应各种中断请求,相当于给整个中断系统 “通电” 。最后,将 IE 寄存器中的 ES 位置 1,即ES = 1,这是使能串口中断,让单片机能够对串口接收数据的事件做出响应,就像是专门为串口接收数据的中断请求开辟了一条 “绿色通道”。
当有数据从电脑通过串口发送到单片机时,单片机的串口接收电路会检测到数据,并将数据存储到 SBUF 寄存器中。同时,硬件会自动将 SCON 寄存器中的 RI 位置 1,表示有新的数据接收到了,这就像是给单片机发送了一个 “数据接收完成” 的信号。
在中断服务函数中,我们需要读取 SBUF 寄存器中的数据。首先,要判断 RI 标志位是否为 1,以确认是否有数据接收到。如果RI == 1,则表示有数据接收到了,此时可以从 SBUF 寄存器中读取数据。读取数据的操作很简单,直接读取 SBUF 寄存器的值即可,例如char received_char = SBUF; 。读取完数据后,一定要记得将 RI 标志位清零,即RI = 0,这是因为 RI 标志位不会自动清零,需要我们手动清零,以便下一次接收数据时能够正确检测 RI 标志位,为下一次数据接收做好准备。
在中断服务函数中,除了读取数据和清零 RI 标志位,还可以根据实际需求进行一些其他的操作,比如对接收到的数据进行处理、存储,或者根据接收到的数据执行相应的控制逻辑等。
4.1.2 实现 recv_char 函数
recv_char 函数用于接收单个字符,其实现原理基于等待接收完成(RI 标志位置 1),然后读取 SBUF 数据。代码如下:
char recv_char() {while(RI == 0);  // 等待接收完成,RI为接收中断标志位,当RI = 1时,表示数据接收完成char c = SBUF;   // 读取SBUF寄存器中的数据RI = 0;          // 软件清零RI标志位,为下一次接收做准备return c;        // 返回接收到的字符
}
在这段代码中,首先通过 while 循环检查 RI 标志位,当 RI 为 0 时,表示数据还未接收完成,程序会一直停留在这个循环中等待。当 RI 为 1 时,表示数据接收完成,跳出循环。然后,从 SBUF 寄存器中读取接收到的数据,并将其存储在变量 c 中。接着,将 RI 标志位清零,这是为了确保下一次接收数据时能够正确检测 RI 标志位。最后,返回接收到的字符 c,以便在其他函数中使用接收到的数据 。
4.2 实战项目:串口控制 LED
4.2.1 功能
本实战项目的功能是通过串口实现电脑对单片机上 LED 的控制。当电脑通过串口发送 “ON” 指令时,单片机接收到该指令后,会控制对应的 LED 点亮;当发送 “OFF” 指令时,单片机接收到后会控制 LED 熄灭。这就像是我们通过遥控器控制家里的电灯开关一样,只不过这里的 “遥控器” 是电脑,“电灯” 是单片机上的 LED ,而 “控制信号” 则是通过串口传输的指令。
4.2.2 代码实现
实现串口控制 LED 的代码逻辑如下:
#include <reg51.h>sbit LED = P1^0;  // 假设LED连接在P1.0引脚void Serial_Init() {SCON = 0x50;  // 串口模式1,允许接收TMOD |= 0x20; // 定时器1模式2TH1 = 0xFD;   // 波特率9600bpsTL1 = 0xFD;PCON &= 0x7F; // 波特率不倍增TR1 = 1;      // 启动定时器1EA = 1;       // 使能总中断ES = 1;       // 使能串口中断
}void Serial_ISR() interrupt 4 {if (RI) {RI = 0;static char buffer[3];  // 用于存储接收到的字符static unsigned char index = 0;char received_char = SBUF;buffer[index++] = received_char;if (index == 3) {buffer[index] = '\0';  // 添加字符串结束符if (strcmp(buffer, "ON") == 0) {LED = 0;  // 点亮LED} else if (strcmp(buffer, "OFF") == 0) {LED = 1;  // 熄灭LED}index = 0;  // 重置索引,准备下一次接收}}
}void main() {Serial_Init();while(1) {// 主循环可以进行其他操作}
}
在这段代码中,首先定义了一个 sbit 类型的变量 LED,用于表示连接在 P1.0 引脚的 LED。然后,在Serial_Init函数中进行串口初始化,包括设置串口模式、波特率、定时器 1 等,并使能总中断和串口中断。
在串口中断服务函数Serial_ISR中,当检测到 RI 标志位为 1 时,表示有数据接收到。首先将 RI 标志位清零,然后将接收到的字符存储到 buffer 数组中。当接收到 3 个字符(因为 “ON” 和 “OFF” 都是 3 个字符)时,在数组末尾添加字符串结束符’\0’,将其构成一个完整的字符串。接着,使用strcmp函数比较接收到的字符串与 “ON” 和 “OFF” ,如果是 “ON”,则将 LED 引脚置低电平,点亮 LED;如果是 “OFF”,则将 LED 引脚置高电平,熄灭 LED 。最后,重置索引 index,准备下一次接收数据。
在main函数中,调用Serial_Init函数进行串口初始化,然后进入一个无限循环,在这个循环中可以进行其他的操作,因为 LED 的控制是通过串口中断来实现的,不需要在主循环中进行轮询。
4.2.3 测试
使用串口助手发送指令来测试串口控制 LED 的功能。以常见的串口助手软件为例,测试步骤如下:
-  打开串口助手软件,配置串口参数: - 串口号:选择与单片机连接的电脑串口号,这个串口号可以在电脑的设备管理器中查看。
- 波特率:设置为 9600,与单片机程序中设置的波特率一致。
- 数据位:设置为 8 位,与程序中的数据帧格式一致。
- 停止位:设置为 1 位,与数据帧格式一致。
- 校验:设置为 “无”,因为程序中没有使用校验位。
 
-  配置完成后,点击 “打开串口” 按钮,建立与单片机的串口连接。 
-  在串口助手的发送框中输入 “ON”,然后点击 “发送” 按钮,观察单片机上 LED 的状态。如果 LED 点亮,说明 “ON” 指令发送成功,单片机正确接收到指令并执行了点亮 LED 的操作。 
-  在发送框中输入 “OFF”,点击 “发送” 按钮,再次观察 LED 的状态。如果 LED 熄灭,说明 “OFF” 指令发送成功,单片机正确执行了熄灭 LED 的操作。 
-  可以多次发送 “ON” 和 “OFF” 指令,反复验证 LED 的控制效果,确保功能的稳定性和正确性。如果 LED 没有按照预期的状态变化,可以检查串口连接是否正常、波特率设置是否一致、单片机程序是否烧录正确以及硬件连接是否有问题等。 
五、总结
通过本文的学习,我们深入了解了基于 51 单片机的串口通信技术,这是嵌入式开发领域中极为重要的一项技能。从串口通信的基础原理出发,我们掌握了异步通信的工作机制、波特率的概念及其对数据传输速率的影响,以及数据帧格式在数据传输中的关键作用 。这些理论知识是理解和实现串口通信的基石,就像建造高楼大厦的基石一样,只有基础牢固,才能构建出稳定高效的通信系统。
在 51 单片机的串口寄存器方面,我们认识到 SCON 寄存器对串口工作方式和状态的控制起着决定性作用,它就像是串口通信的 “大脑”,指挥着串口的各种操作;而 TH1/TL1 寄存器作为波特率发生器的核心,通过精准的配置,能够实现不同波特率的设置,满足各种应用场景的需求,如同调节发动机的转速,使串口通信以合适的 “速度” 运行。
在实际操作中,串口初始化是实现通信的关键步骤,它涉及到定时器 1 的配置、波特率的精确设置以及串口接收功能的使能 ,每一个环节都需要我们谨慎对待,确保配置的正确性。而单片机向电脑发送数据和接收电脑数据的实现过程,让我们进一步熟悉了串口通信的具体流程和代码实现方法,通过 send_char、send_string、recv_char 等函数的编写,我们能够灵活地控制数据的发送和接收,就像熟练掌握了一门语言的语法和词汇,能够自由地表达和交流。
最后的串口控制 LED 实战项目,将理论知识与实际应用紧密结合,通过电脑发送指令控制单片机上 LED 的亮灭,让我们真切地感受到了串口通信在实际项目中的应用价值 。这不仅是对前面所学知识的一次综合检验,更是为我们打开了一扇通往更多实际应用的大门,激励我们在嵌入式开发的道路上不断探索和创新。
串口通信在 51 单片机嵌入式开发中具有举足轻重的地位,它为我们提供了一种简单、可靠且成本低廉的数据传输方式,在工业控制、智能家居、物联网等众多领域都有着广泛的应用前景。希望读者能够在本文的基础上,进一步深入探索串口通信在实际项目中的更多应用,不断积累经验,提升自己的嵌入式开发能力,在这个充满挑战和机遇的领域中创造出更多有价值的成果。
