51单片机基础教程(四):串口通信-单片机与PC的对话艺术
一、引言:从单机到互联的跨越
在掌握了中断技术后,我们的单片机已经具备了"感知环境,及时响应"的能力。然而,真正的智能系统不仅需要感知,更需要沟通。本次课程将带您进入单片机与外部世界通信的新领域:
通过异步串行通信实现单片机与PC的数据交换
运用串口中断实现高效数据收发
构建双向控制系统:既接收指令又上传状态
"串口是单片机的'嘴巴'和'耳朵',让机器学会与人类和其他设备交流。"
二、串口通信:异步传输的精妙设计
1. 串行 vs. 并行:效率与成本的平衡
|
特性 |
并行通信 |
串行通信 |
|
数据线数量 |
8根或更多 |
2根(TXD/RXD) |
|
传输速率 |
高(短距离) |
适中(任意距离) |
|
抗干扰能力 |
弱(易受干扰) |
强(差分信号可选) |
|
成本 |
高(线材多) |
低(线材少) |
|
适用场景 |
内部模块短距通信 |
远距离设备间通信 |
并行通信示意图 串行通信的示意图


2. 异步串行通信协议解析
[起始位] [数据位(5-8)] [校验位(可选)] [停止位(1-2)]
- 波特率:通信速度(如9600bps表示每秒9600位)
- 数据格式:通常采用8N1(8数据位、无校验、1停止位)
- 电平标准:TTL电平(0-5V)或RS-232(±12V)


3. 串行通信的传输模式
串行通信按照按照数据传输的方向及时间关系可分为单工、半双工和全双工。
1.单工
数据传输仅能按一个固定方向传输,不能反向传输
2.半双工
数据传输可双向传输,但不能同时传输
3.全双工
数据传输可同时进行双向传输
三、硬件升级:添加串口通信电路
1. 串口通信硬件连接
[单片机TXD] --- [电平转换芯片] --- [PC RXD]
[单片机RXD] --- [电平转换芯片] --- [PC TXD]
[GND] --- [电平转换芯片] --- [PC GND]
2. Proteus中的串口组件
- VIRTUAL TERMINAL (虚拟终端,用于仿真调试)
- COMPIM (串口组件,连接实际硬件)
- MAX232 (电平转换芯片,TTL↔RS232)
3. 完整串口控制流水灯电路
为确保速率计算的准确性,我们将单片机主频设置为11.0592MHz,并将RX引脚连接至虚拟终端的TXD,TX引脚连接至RXD。

四、51单片机串口机制原理
1. 串口相关寄存器
SCON寄存器(串口控制):
|
位 |
符号 |
功能说明 |
|
7 |
SM0 |
工作模式选择位0 |
|
6 |
SM1 |
工作模式选择位1 |
|
5 |
SM2 |
多机通信控制位 |
|
4 |
REN |
接收使能位 |
|
3 |
TB8 |
发送第9数据位 |
|
2 |
RB8 |
接收第9数据位 |
|
1 |
TI |
发送中断标志 |
|
0 |
RI |
接收中断标志 |
PCON寄存器(电源控制):
- SMOD位:波特率加倍控制位
2. 串口工作模式
- 模式0:同步移位寄存器(常用于扩展I/O)
- 模式1:8位UART,可变波特率(最常用)
- 模式2:9位UART,固定波特率
- 模式3:9位UART,可变波特率
3. 波特率计算
定时器1作为波特率发生器时:
波特率 = (2^SMOD / 32) × (定时器1溢出率)
常用波特率设置(12MHz晶振):
// 9600bps @12MHz
TH1 = 0xFD; // 重装值
TL1 = 0xFD;
PCON &= 0x7F; // SMOD=0
用定时器T1产生的常用波特率

五、串口通信基础实现
1. 串口初始化配置
串行口初始化设置具体步骤如下:
- 设置波特率:
- ¨a.设置T1工作在计数方式2
- ¨b.设置T1计数初值
- ¨c.开启T1计数(不能开启T1中断)
- 设置串口工作方式SCON:工作方式1
- 开启串口中断(或查询方式)
- 等待中断,或发送后等待中断(或查询)
- 处理中断,接收或发送数据
void UART_Init(void)
{
SCON = 0x50; // 模式1,8位UART,允许接收
TMOD &= 0x0F; // 清除T1控制位
TMOD |= 0x20; // T1模式2:8位自动重装
TH1 = 0xFD; // 9600bps @12MHz
TL1 = 0xFD;
PCON &= 0x7F; // SMOD=0
ES = 1; // 允许串口中断
EA = 1; // 全局中断允许
TR1 = 1; // 启动定时器1
}
2. 查询方式发送数据
void UART_SendByte(unsigned char dat)
{
SBUF = dat; // 数据写入发送缓冲区
while(!TI); // 等待发送完成
TI = 0; // 清除发送中断标志
}
void UART_S极String(unsigned char *str)
{
while(*str != '\0') // 遍历字符串
{
UART_SendByte(*str++); // 发送每个字符
}
}
3. 中断方式接收数据
unsigned char uart_buf[32]; // 接收缓冲区
unsigned char uart_index = 0; // 缓冲区索引
void UART_ISR(void) interrupt 4
{
if(RI) // 接收中断
{
RI = 0; // 清除接收中断标志
uart_buf[uart_index] = SBUF; // 保存接收数据
if(uart_buf[uart_index] == '\n' || uart_index >= 31)
{
uart_buf[uart_index] = '\0'; // 字符串终止
uart_index = 0;
// 设置数据接收完成标志
}
else
{
uart_index++;
}
}
if(TI) // 发送中断
{
TI = 0; // 清除发送中断标志
// 可在此添加发送完成处理
}
}
六、PC与单片机双向通信系统
1. 通信协议设计
设计简单的指令协议:
'L':查询LED状态'S':设置流水灯速度(后跟数字)'D':设置流水灯方向(0左移/1右移)'R':复位系统
2. 完整串口控制程序
#include <reg51.h>
#include <stdio.h>
/* 全局变量声明 */
volatile unsigned int time_count = 0;
volatile bit key_pressed = 0;
volatile bit uart_cmd_ready = 0;
unsigned char led = 0x01; // 初始LED状态:最低位亮
bit direction = 0; // 0: 左移, 1: 右移
unsigned int speed = 100; // LED移动速度(单位:ms)
unsigned char uart_buffer[32]; // 串口接收缓冲区
/* 函数声明 */
void Timer0_Init(void);
void ExtInt_Init(void);
void UART_Init(void);
void Process_UART_Command(void);
/******************************
* 主函数
******************************/
void main()
{
static unsigned int last_update = 0;
// 初始化外设
Timer0_Init();
ExtInt_Init();
UART_Init();
// 启动提示
printf("System Ready!\r\n");
printf("Commands: L-Status, S-Speed, D-Direction, R-Reset\r\n");
while (1)
{
// 检测按键中断(外部中断0)
if (key_pressed)
{
direction = !direction; // 按键切换方向
key_pressed = 0;
printf("Direction changed to: %s\r\n", direction ? "Right" : "Left");
}
// 处理串口命令
if (uart_cmd_ready)
{
Process_UART_Command();
uart_cmd_ready = 0;
}
// 定时更新LED状态
if ((time_count - last_update) >= speed)
{
if (direction)
{
// 向右移动(从高位到低位)
led = (led == 0x01) ? 0x80 : (led >> 1);
}
else
{
// 向左移动(从低位到高位)
led = (led == 0x80) ? 0x01 : (led << 1);
}
P1 = ~led; // P1接LED,低电平点亮,故取反
last_update = time_count;
}
}
}
/******************************
* 串口命令处理函数
******************************/
void Process_UART_Command(void)
{
switch (uart_buffer[0])
{
case 'L': // 查询当前状态
printf("LED: 0x%02X, Direction: %s, Speed: %dms\r\n",
led, direction ? "Right" : "Left", speed);
break;
case 'S': // 设置速度,例如:S200
sscanf(uart_buffer + 1, "%d", &speed);
printf("Speed set to: %dms\r\n", speed);
break;
case 'D': // 设置方向:D1=右移,D0=左移
direction = (uart_buffer[1] == '1');
printf("Direction set to: %s\r\n", direction ? "Right" : "Left");
break;
case 'R': // 复位系统
led = 0x01;
direction = 0;
speed = 100;
P1 = ~led;
printf("System reset\r\n");
break;
default:
printf("Unknown command: %s\r\n", uart_buffer);
break;
}
}
/******************************
* 串口中断服务函数(接收中断)
******************************/
void UART_ISR(void) interrupt 4
{
static unsigned char index = 0;
unsigned char received;
if (RI) // 接收中断
{
RI = 0;
received = SBUF; // 读取接收到的数据
// 判断是否为结束符(回车或换行)或缓冲区满
if (received == '\r' || received == '\n' || index >= 31)
{
if (index > 0)
{
uart_buffer[index] = '\0'; // 添加字符串结束符
uart_cmd_ready = 1; // 标记命令就绪
index = 0; // 重置索引
}
}
else
{
uart_buffer[index++] = received; // 存入缓冲区
}
}
if (TI) // 发送中断(一般用于发送完成标志,此处仅清标志)
{
TI = 0;
}
}
/******************************
* Timer0 初始化(用于定时更新LED)
* 晶振:11.0592MHz,定时1ms
******************************/
void Timer0_Init(void)
{
TMOD &= 0xF0; // 清除T0模式位
TMOD |= 0x01; // T0工作在模式1(16位定时器)
TH0 = 0xFC; // 1ms定时初值(11.0592MHz)
TL0 = 0x18;
ET0 = 1; // 使能T0中断
EA = 1; // 开总中断
TR0 = 1; // 启动T0
}
/******************************
* 外部中断0 初始化(按键切换方向)
******************************/
void ExtInt_Init(void)
{
IT0 = 1; // 下降沿触发
EX0 = 1; // 使能外部中断0
EA = 1;
}
/******************************
* 串口初始化(工作在模式1,8位UART,可变波特率)
* 波特率:9600bps(11.0592MHz晶振)
******************************/
void UART_Init(void)
{
SCON = 0x50; // 模式1,8位UART,允许接收
TMOD &= 0x0F; // 清除T1模式位
TMOD |= 0x20; // T1工作在模式2(8位自动重载)
TH1 = 0xFD; // 9600bps 波特率(11.0592MHz)
TL1 = 0xFD;
PCON &= 0x7F; // 波特率不倍增
ES = 1; // 使能串口中断
EA = 1;
TR1 = 1; // 启动T1
}
七、调试技巧与故障排除
1. 串口调试常见问题
|
问题现象 |
可能原因 |
解决方案 |
|
乱码 |
波特率不匹配 |
检查双方波特率设置 |
|
数据丢失 |
缓冲区溢出 |
增加接收缓冲区大小 |
|
无法通信 |
电平不匹配 |
使用MAX232进行电平转换 |
|
接收不完整 |
未处理帧错误 |
检查硬件连接和波特率 |
2. 使用虚拟终端调试
在Proteus中使用VIRTUAL TERMINAL组件:
- 设置与程序相同的波特率
- 可实时显示发送接收数据
- 支持发送指令测试
3. 实际硬件调试要点
- 确保共地连接(GND连接)
- 使用示波器检查TXD/RXD信号
- 通过LED指示灯显示通信状态
八、结语:开启物联的大门
通过本次串口通信实践,我们实现了单片机与外部世界的关键连接:
双向通信能力:既能接收指令又能上传状态
实时交互系统:PC端可实时监控和控制单片机
协议设计基础:为更复杂的通信协议打下基础
"串口通信是物联网的起点,从这里开始,单片机不再是信息孤岛,而是智能网络中的节点。"
学习拓展建议
- 实现数据日志功能:将系统状态保存并通过串口上传
- 添加图形界面:在PC端开发GUI控制程序
- 探索其他通信方式:I2C、SPI、CAN等总线协议
- 尝试无线通信:蓝牙、Wi-Fi、LoRa等无线模块
下篇预告
《LCD显示技术:从字符到菜单的视觉呈现》
探索内容:
- 字符型LCD1602驱动原理
- 自定义字符设计技巧
- 多级菜单界面实现
更多推荐

所有评论(0)