基于51单片机的串口通信与LED控制系统
项目概述
本项目实现了一个基于51单片机的串口通信系统,支持通过串口命令控制LED显示,并具备完整的数据校验和应答机制。系统采用自定义通信协议,确保数据传输的可靠性。
目录结构
project/
├── main.c // 主程序文件
├── uart.c // 串口驱动程序
├── uart.h // 串口头文件
├── led.c // LED控制程序
├── led.h // LED控制头文件
├── delay.c // 延时函数
└── delay.h // 延时函数头文件
一、核心数据结构
1. 串口接收缓冲区
// 定义在uart.c中
xdata unsigned char recv_buffer[32]; // 外部RAM中的接收缓冲区
unsigned int pos = 0; // 接收数据位置指针
说明:
-
使用
xdata关键字将缓冲区分配到外部RAM(64KB空间) -
缓冲区大小32字节,足够存储多组命令
-
pos变量记录当前接收位置,用于防止缓冲区溢出
二、串口驱动模块
1. 串口初始化函数
// uart.c - 串口初始化
void uart_init(void)
{
// 串口控制寄存器配置
SCON &= ~(3 << 6); // 清SM0、SM1位(bit6,7)
SCON |= (1 << 6); // SM1=1,选择工作模式1(8位UART)
SCON |= (1 << 4); // REN=1,允许串口接收数据
// 电源控制寄存器配置
PCON &= ~(1 << 6); // 清SMOD0位
PCON |= (1 << 7); // SMOD=1,波特率加倍
// 定时器模式寄存器配置
TMOD &= ~(0x0F << 4); // 清定时器1的高4位
TMOD |= (1 << 5); // 定时器1工作在模式2(8位自动重装)
// 波特率设置(12MHz晶振,2400波特率,SMOD=1)
// 计算公式:TH1 = 256 - (2^SMOD × fosc) / (384 × 波特率)
// TH1 = 256 - (2 × 12000000) / (384 × 2400) ≈ 256 - 26.04 ≈ 230
TL1 = 230;
TH1 = 230;
// 启动定时器1
TCON |= (1 << 6); // TR1=1,启动定时器1
// 中断使能
IE |= (1 << 7); // EA=1,开启总中断
IE |= (1 << 4); // ES=1,开启串口中断
}
关键配置参数:
-
工作模式:模式1(8位UART,波特率可变)
-
波特率:2400 bps
-
晶振频率:12MHz
-
SMOD:1(波特率加倍)
-
定时器:定时器1,模式2(8位自动重装)
2. 串口中断服务程序
// uart.c - 串口接收中断函数
void uart_RecvHandler(void) interrupt 4
{
if ((SCON & (1 << 0)) == 1) // 判断RI位是否为1(接收到数据)
{
if (pos < 32) // 防止缓冲区溢出
{
recv_buffer[pos++] = SBUF; // 读取接收到的数据
recv_buffer[pos] = 0; // 添加字符串结束符(可选)
}
SCON &= ~(1 << 0); // RI=0,清除接收中断标志
}
}
中断机制说明:
-
中断号:4(8051串口中断固定编号)
-
触发条件:接收到一字节数据(RI自动置1)
-
数据处理:存入缓冲区,指针后移
-
标志清除:必须软件清除RI标志
3. 数据发送函数
// uart.c - 发送单个字符
void uart_sendchar(unsigned char ch)
{
SBUF = ch; // 将数据写入发送缓冲区
while ((SCON & (1 << 1)) == 0); // 等待TI=1(发送完成)
SCON &= ~(1 << 1); // TI=0,清除发送完成标志
}
// uart.c - 发送字符串
void uart_sendstr(const char *p)
{
while (*p) // 遍历字符串直到结束符
{
uart_sendchar(*p++); // 发送当前字符并指针后移
}
}
// uart.c - 发送缓冲区数据
void uart_sendbuffer(const char *p, int len)
{
while (len--) // 发送指定长度的数据
{
uart_sendchar(*p++); // 发送当前字节并指针后移
}
}
发送流程:
-
数据写入SBUF寄存器,自动启动发送
-
查询TI标志位,等待发送完成
-
清除TI标志,准备下一次发送
三、通信协议设计
1. 命令帧格式
| 字节 | 名称 | 值 | 说明 |
|------|----------|---------|------------------------|
| 0 | 帧头 | 0xAA | 数据帧开始标志 |
| 1 | 地址码 | 0x01 | 设备地址(本机地址) |
| 2 | 功能码 | 0x01-0x04 | 功能编号 |
| 3 | 保留 | 0x00 | 预留字节 |
| 4 | 数据 | 0x00-0xFF | 控制数据 |
| 5 | 校验和 | SUM | 前5字节累加和校验 |
| 6 | 帧尾 | 0xBB | 数据帧结束标志 |
示例命令:
控制LED显示:AA 01 01 00 42 EE BB
└── LED显示数据0x42(二进制01000010)
2. 应答帧格式
| 字节 | 名称 | 值 | 说明 |
|------|----------|---------|------------------------|
| 0 | 帧头 | 0xAA | 应答帧开始标志 |
| 1 | 地址码 | 0x01 | 设备地址 |
| 2 | 功能码 | 0x81 | 原功能码最高位置1 |
| 3 | 保留 | 0x00 | 预留字节 |
| 4 | 数据 | 0x42 | 原数据 |
| 5 | 校验和 | 0x6E | 重新计算的校验和 |
| 6 | 帧尾 | 0xBB | 应答帧结束标志 |
四、命令解析与处理模块
1. 协议解析函数
// main.c - 解析接收到的数据
int Parse(void)
{
int ret = 0;
int i = 0;
unsigned char sum = 0;
// 判断数据帧完整性
if (recv_buffer[0] == 0xAA && recv_buffer[6] == 0xBB)
{
// 地址码校验
if (recv_buffer[1] == DEV_ADDRESS) // DEV_ADDRESS定义为0x01
{
// 计算校验和(前5字节累加)
for (i = 0; i < 5; i++)
{
sum += recv_buffer[i];
}
// 校验和验证
if (sum == recv_buffer[5])
{
ret = recv_buffer[2]; // 返回功能码
}
}
}
return ret; // 返回0表示无效命令,非0为有效功能码
}
校验流程:
-
帧头帧尾检查:确保数据帧完整
-
地址匹配:验证是否为发送给本机的命令
-
校验和验证:防止数据传输错误
2. 命令处理函数
// main.c - 根据功能码执行相应操作
void do_handler(unsigned int n)
{
switch (n)
{
case 1: // LED控制
led_show(recv_buffer[4]); // 显示指定模式
break;
case 2: // 数码管显示
// 数码管控制代码
break;
case 3: // 蜂鸣器控制
// 蜂鸣器控制代码
break;
case 4: // 温度采集
// 温度传感器读取代码
break;
default:
break;
}
}
3. 应答函数
// main.c - 向主机发送应答
void callback(void)
{
unsigned char send_buffer[10];
unsigned char sum = 0;
int i = 0;
// 复制接收缓冲区数据到发送缓冲区
memcpy(send_buffer, recv_buffer, 7);
// 设置应答标志(功能码最高位置1)
send_buffer[2] |= (1 << 7);
// 重新计算校验和
for (i = 0; i < 5; i++)
{
sum += send_buffer[i];
}
send_buffer[5] = sum; // 更新校验和
// 发送应答数据
uart_sendbuffer(send_buffer, 7);
}
应答机制:
-
复制原命令帧
-
功能码最高位置1(0x01 → 0x81)
-
重新计算校验和
-
发送7字节应答帧
五、LED控制模块
1. LED初始化与基本控制
// led.c - LED控制函数
#include <reg51.h>
#include "led.h"
// LED初始化(P2口控制LED,高电平熄灭)
void led_init(void)
{
P2 = 0xFF; // 全部熄灭
}
// LED全亮
void led_all_on(void)
{
P2 = 0; // 全部点亮
}
// LED全灭
void led_all_off(void)
{
P2 = 0xFF; // 全部熄灭
}
// LED状态翻转
void led_nor(void)
{
P2 = P2 ^ 0xFF; // 异或操作翻转所有位
}
// 指定LED显示模式
void led_show(unsigned int n)
{
P2 = ~n; // 取反操作(0亮1灭)
}
LED连接方式:
-
P2口连接8个LED
-
输出0:LED点亮
-
输出1:LED熄灭
-
led_show(0x42):显示二进制01000010(第2、7位点亮)
六、主程序流程
// main.c - 主函数
int main(void)
{
int ret = 0;
// 外设初始化
uart_init(); // 初始化串口(开启中断)
led_init(); // 初始化LED
// 主循环
while (1)
{
// 检查是否有数据接收
if (pos != 0) // 接收缓冲区不为空
{
delay(0xAFFF); // 延时等待数据接收完整
// 解析命令
ret = Parse();
// 执行命令
if (ret != 0)
{
do_handler(ret); // 执行功能操作
}
// 发送应答
if (ret != 0)
{
callback(); // 回复主机
}
// 重置接收指针
pos = 0;
}
}
return 0;
}
主程序流程:
-
初始化串口和LED
-
循环检查接收缓冲区
-
收到数据后延时确保完整接收
-
解析并验证命令
-
执行相应功能
-
发送应答帧
-
重置接收状态
七、延时函数模块
// delay.c - 简单延时函数
void delay(unsigned int n)
{
while (n--); // n次空循环
}
延时说明:
-
参数n决定延时时间
-
12MHz时钟下大致延时:n × 2μs
-
用于数据接收稳定和操作间隔
八、系统配置与优化建议
1. 波特率精确计算
对于12MHz晶振,2400波特率:
公式:
TH1=256−2SMOD×𝑓osc384×波特率TH1=256−384×波特率2SMOD×fosc
计算:
-
SMOD=1:TH1 = 256 - (2 × 12,000,000) / (384 × 2400) ≈ 230
-
实际值可能需要微调:230或232
2. 缓冲区管理优化
// 建议优化:环形缓冲区
#define BUFFER_SIZE 32
xdata unsigned char recv_buffer[BUFFER_SIZE];
unsigned int read_pos = 0;
unsigned int write_pos = 0;
// 中断服务程序修改
void uart_RecvHandler(void) interrupt 4
{
if (RI)
{
recv_buffer[write_pos] = SBUF;
write_pos = (write_pos + 1) % BUFFER_SIZE;
RI = 0;
}
}
3. 错误处理增强
// 添加错误码定义
#define ERROR_NONE 0
#define ERROR_FRAME 1
#define ERROR_ADDRESS 2
#define ERROR_CHECKSUM 3
// 增强的解析函数
int ParseWithError(unsigned char *error)
{
*error = ERROR_NONE;
// 帧头帧尾检查
if (recv_buffer[0] != 0xAA || recv_buffer[6] != 0xBB)
{
*error = ERROR_FRAME;
return 0;
}
// ... 其他检查
}
九、常见问题与调试
1. 串口无响应
-
检查晶振频率:确认是否为12MHz
-
检查波特率设置:确保TH1值正确
-
验证硬件连接:TX/RX线序正确
-
查看中断配置:EA和ES必须置1
2. 数据接收不全
-
增加延时:确保一帧数据完全接收
-
检查缓冲区大小:确保足够存储完整帧
-
验证协议格式:帧头帧尾匹配
3. LED显示异常
-
检查硬件连接:LED共阳/共阴配置
-
验证控制逻辑:0亮1灭或相反
-
测试IO口:直接操作P2口验证
十、项目扩展方向
-
添加更多外设控制:数码管、蜂鸣器、继电器等
-
实现双向通信:主动上报传感器数据
-
增加协议安全性:加密校验、命令授权
-
优化性能:中断优先级、DMA传输
-
添加配置文件:支持参数动态调整
总结
本项目展示了完整的8051串口通信系统设计,具备以下特点:
-
模块化设计:各功能模块分离,便于维护和扩展
-
可靠通信协议:帧头帧尾、地址校验、累加和校验三重保障
-
中断驱动:高效的数据接收处理机制
-
完整应答机制:确保命令执行状态反馈
-
易扩展性:支持添加多种外设控制功能