一、项目概述
本项目以 STC89C52(51 内核)单片机为核心,基于自定义 Modbus 协议帧格式,实现了串口指令解析 、LED 控制 、数码管数字显示 、定时器频率调节(即蜂鸣器频率调节) 、DS18B20 温度采集等功能。系统通过 UART 串口接收上位机指令,解析后执行对应操作,并支持指令响应回传,适用于小型工业控制、教学实验等场景。
核心功能清单
表格
| 功能码 | 功能描述 |
|---|---|
| 0x01 | 控制指定 LED 点亮 |
| 0x02 | 触发数码管显示指定数字 |
| 0x03 | 配置定时器 0 频率(200/400/600/800/1000Hz),蜂鸣器频率调节 |
| 0x04 | 读取 DS18B20 温度值并通过串口回传 |
二、硬件设计
2.1 硬件架构框图(注意P2.1与蜂鸣器相连)


2.2 关键硬件连接(STC89C52 为例)
表格
| 外设 | 单片机引脚 | 说明 |
|---|---|---|
| UART 串口 | P3^0(RX)/P3^1(TX) | 2400bps 波特率,中断接收 |
| DS18B20 | P3^7(DQ) | 单总线通信 |
| LED 阵列 | P2 口 | 共阳 LED,低电平点亮 |
| 数码管 | P0 (段码)/P1 (位选) | 四位共阳数码管 |
| 定时器 0 | T0(P3^4) | 方式 1,中断控制 蜂鸣器 |
| 定时器 1 | T1(P3^5) | 方式 2,作为 UART 波特率发生器 |
2.3 硬件实物示意图(文字描述)
- 核心板:STC89C52 最小系统(含 11.0592MHz 晶振、复位电路);
- 外设模块:
- LED:8 个共阳 LED 接 P2.0~P2.7,串联 220Ω 限流电阻;
- 数码管:四位共阳数码管,P0 接 a~dp 段(串 220Ω 电阻),P1.0~P1.3 接位选;
- DS18B20:DQ 引脚接 P3.7,外接 4.7K 上拉电阻,VCC 接 5V,GND 接地;
- 串口:CH340 模块转 USB,TX/RX 分别接单片机 RX/TX。
三、软件设计
3.1 软件整体流程

3.2 核心模块代码解析
(1)UART 驱动模块(uart.c)
负责串口初始化、中断接收、数据发送,是系统与上位机通信的核心。
c
运行
#include <reg51.h>
#include "uart.h"
#include "modbus.h"
#include "led.h"
xdata char recv_buffer[32]; // 接收缓冲区(扩展RAM)
unsigned int pos = 0; // 缓冲区指针
// UART接收中断服务函数(中断号4)
void uart_handle(void) interrupt 4
{
if((SCON & (1 << 0)) == 1) // 检测接收完成标志RI
{
if(pos < 32) // 防止缓冲区溢出
{
recv_buffer[pos++] = SBUF; // 读取接收数据
recv_buffer[pos] = 0; // 字符串结束符
}
SCON &= ~(1 << 0); // 清RI标志
flag_digiter = 0; // 复位数码管标志
led_all_off(); // LED全灭
IE &= ~(1 << 1); // 关闭Timer0中断(可选)
}
}
// UART初始化:波特率38400、8位数据、1位停止位
void uart_init(void)
{
// SCON配置:8位数据,可变波特率(SM0=0, SM1=1);REN=1(允许接收)
SCON &= ~(3 << 6);
SCON |= 1 << 6;
SCON &= ~(1 << 7);
SCON |= 1 << 4;
// PCON配置:SMOD=1(波特率加倍)
PCON &= ~(3 << 6);
PCON |= 1 << 7;
PCON &= ~(1 << 6);
// Timer1配置:方式2(8位自动重装)
TMOD &= ~(0xF0 << 0);
TMOD |= 1 << 5;
TMOD &= ~(1 << 4);
// Timer1重装值:11.0592MHz晶振,2400波特率
TL1 = 232;
TH1 = 232;
TCON |= 1 << 6; // 启动Timer1(TR1=1)
IE |= 1 << 7; // 开总中断(EA=1)
IE |= 1 << 4; // 开UART中断(ES=1)
}
// 发送单个字符
void send_char(unsigned char ch)
{
SBUF = ch;
while((SCON & (1 << 1)) == 0); // 等待发送完成(TI=1)
SCON &= ~(1 << 1); // 清TI标志
}
// 发送字符串
void send_str(const char *pstr)
{
while(*pstr != '\0')
{
send_char(*pstr++);
}
}
// 发送指定长度缓冲区
void send_buff(const char *pbuff, int len)
{
while(len--)
{
send_char(*pbuff++);
}
}
关键说明:
-
波特率定时器初值: 2^8-2^smod * focs / 32 / bps / 12
其中smod表示PCON的B7,根据实际情况带入,不是0就是1;
focs晶振频率,我们这是11.0592M;
bps目标波特率我们这是2400(最终得到TH1 = TL1 = 232, 8位自动重装载)
-
接收中断采用缓冲区 + 指针的方式,避免数据丢失;
-
发送函数通过轮询 TI 标志确保数据发送完成。
(2)DS18B20 温度传感器驱动(ds18b20.c)
实现单总线通信的复位、读写、温度转换与解析:
c
运行
#include <reg51.h>
#include "uart.h"
#include "delay.h"
#include "ds18b20.h"
#include <intrins.h>
#define DQ_PIN_HIGH (P3 |= (1 << 7) ) // DQ引脚置高
#define DQ_PIN_LOW (P3 &= ~(1 << 7) ) // DQ引脚置低
#define DQ_PIN_CHECK ((P3 & (1 << 7)) != 0) // 检测DQ电平
// DS18B20复位(单总线核心步骤)
int ds18b20_reset(void)
{
int time = 0;
DQ_PIN_LOW;
Delay10us(70); // 拉低至少480us(70*10us=700us)
DQ_PIN_HIGH;
Delay10us(5); // 释放总线,等待传感器响应(15~60us)
// 等待传感器拉低总线
while(DQ_PIN_CHECK && time < 30)
{
Delay10us(1);
time++;
}
if(time >= 30)
{
send_str("wait ds18b20 low fail\r\n");
return -1;
}
// 等待传感器释放总线
time = 0;
while(!DQ_PIN_CHECK && time < 30)
{
Delay10us(1);
time++;
}
if(time >= 30)
{
send_str("wait ds18b20 high fail\r\n");
return -1;
}
return 0;
}
// 向DS18B20写入1字节数据
void write_ds18b20(unsigned char dat)
{
int i = 0;
for(i = 0; i < 8; i++)
{
if(dat & 1) // 写1:拉低<15us,释放总线
{
DQ_PIN_LOW;
_nop_();
_nop_();
DQ_PIN_HIGH;
Delay10us(5);
}
else // 写0:拉低≥60us,释放总线
{
DQ_PIN_LOW;
Delay10us(6);
DQ_PIN_HIGH;
}
dat >>= 1; // 处理下一位
}
}
// 从DS18B20读取1字节数据
unsigned char read_ds18b20(void)
{
unsigned char dat = 0;
int i = 0;
for(i = 0; i < 8; i++)
{
DQ_PIN_LOW;
_nop_();
_nop_();
DQ_PIN_HIGH; // 拉低<15us后释放
_nop_();
_nop_();
_nop_();
_nop_();
if(DQ_PIN_CHECK) // 读取当前电平
{
dat |= (1 << i);
}
Delay10us(6); // 等待时隙结束
}
return dat;
}
// 读取温度值(转换为浮点型)
float get_tmp(void)
{
short tmp = 0;
unsigned char tmp_low = 0;
unsigned char tmp_high = 0;
ds18b20_reset();
write_ds18b20(0xCC); // 跳过ROM指令(单传感器)
write_ds18b20(0x44); // 启动温度转换
Delay1ms(1000); // 等待转换完成(最大750ms)
ds18b20_reset();
write_ds18b20(0xCC); // 跳过ROM指令
write_ds18b20(0xBE); // 读取暂存器指令
tmp_low = read_ds18b20(); // 读取低字节
tmp_high = read_ds18b20(); // 读取高字节
tmp = tmp_high << 8;
tmp |= tmp_low;
return tmp * 0.0625; // DS18B20分辨率0.0625℃/LSB
}
关键说明:
- 单总线复位是通信前提,需严格遵守时序(拉低→释放→等待响应);
- 温度值为 16 位有符号数,高字节为符号位,低字节为小数位,乘以 0.0625 得到实际温度。
(3)Modbus 协议解析(modbus.c)
自定义 Modbus 帧格式(适配 51 单片机资源),实现指令解析与功能执行:
c
运行
#include <stdio.h>
#include <string.h>
#include "modbus.h"
#include "uart.h"
#include "led.h"
#include "timer.h"
#include "digiter.h"
#include "ds18b20.h"
#define DEV_ADDRESS 0x01 // 设备地址
unsigned int flag_digiter = 0; // 数码管显示标志
// 解析Modbus功能码(帧格式:0xAA + 地址 + 功能码 + 数据 + 校验和 + 0xBB)
int PraseFunCode(void)
{
int i = 0;
int ret = 0;
unsigned char sum = 0;
// 帧头帧尾校验
if((unsigned char)recv_buffer[0] == 0xAA && (unsigned char)recv_buffer[6] == 0xBB)
{
// 设备地址校验
if((unsigned char)recv_buffer[1] == 0x01)
{
// 计算前5字节校验和
for(i = 0; i < 5; i++)
{
sum += recv_buffer[i];
}
// 校验和匹配则返回功能码
if(sum == recv_buffer[5])
{
ret = recv_buffer[2];
}
}
}
return ret;
}
// 响应帧回传(功能码最高位置1表示响应)
void call_back(void)
{
int i = 0;
unsigned char sum = 0;
xdata char tmpbuff[10] ={0};
memcpy(tmpbuff, recv_buffer, 7);
tmpbuff[2] |= 1 << 7; // 功能码最高位标记响应
if((unsigned char)tmpbuff[0] == 0xAA && (unsigned char)tmpbuff[6] == 0xBB)
{
if((unsigned char)tmpbuff[1] == 0x01)
{
for(i = 0; i < 5; i++)
{
sum += tmpbuff[i];
}
tmpbuff[5] = sum; // 重新计算校验和
send_buff(tmpbuff, 7); // 发送响应帧
}
}
}
// 功能码执行逻辑
void do_handle(int funcode)
{
float tmp = 0;
xdata char tmpbuff[32] = {0};
switch (funcode)
{
case 1: // 控制LED点亮
led_on(recv_buffer[3]);
break;
case 2: // 触发数码管显示
flag_digiter = 1;
break;
case 3: // 配置定时器频率
timer0_init();
switch ((unsigned char)recv_buffer[3])
{
case 0x01: frequency = HZ_200; break;
case 0x02: frequency = HZ_400; break;
case 0x03: frequency = HZ_600; break;
case 0x04: frequency = HZ_800; break;
case 0x05: frequency = HZ_1000; break;
}
break;
case 4: // 读取温度并回传
tmp = get_tmp();
sprintf(tmpbuff, "tmp:%.2f\r\n", tmp);
send_str(tmpbuff);
break;
}
}
关键说明:
- 自定义帧格式:
0xAA(帧头) + 0x01(地址) + 功能码 + 数据 + 校验和 + 0xBB(帧尾); - 校验和为前 5 字节累加和,简化 51 单片机的计算开销;
- 响应帧通过功能码最高位标记,便于上位机区分指令 / 响应。
(4)主函数(main.c)
系统入口,实现循环检测与指令处理:
c
运行
#include <reg51.h>
#include "uart.h"
#include "led.h"
#include "timer.h"
#include "delay.h"
#include "modbus.h"
#include "digiter.h"
void main(void)
{
int ret = 0;
uart_init(); // 初始化UART
while(1) // 主循环
{
if(pos != 0) // 接收缓冲区有数据
{
delay(0x4FFFF); // 消抖/等待帧接收完成
ret = PraseFunCode(); // 解析功能码
if(ret != 0)
{
do_handle(ret); // 执行功能
}
if(ret != 0)
{
call_back(); // 回传响应
}
pos = 0; // 清空缓冲区指针
}
if(flag_digiter == 1) // 数码管显示标志置位
{
while(flag_digiter)
{
num_show((unsigned char)recv_buffer[3]); // 显示指定数字
}
}
}
}
四、功能测试与验证
4.1 测试环境
- 硬件:STC89C52 核心板、DS18B20 模块、LED / 数码管模块、CH340 串口模块;
- 软件:串口调试助手(波特率 38400、8N1、无校验)。
4.2 测试用例
表格
| 测试功能 | 发送指令帧(16 进制) | 预期结果 |
|---|---|---|
| LED1 点亮 | AA 01 01 01 00 AA BB | P2.0 对应的 LED 点亮 |
| 数码管显示数字 5 | AA 01 02 05 00 AD BB | 数码管循环显示数字 5 |
| 定时器 200Hz | AA 01 03 01 00 AB BB | 蜂鸣器以200Hz发声 |
| 读取温度 | AA 01 04 00 00 AC BB | 串口回显 "tmp:XX.XX\r\n" |
4.3 常见问题排查
- DS18B20 复位失败:检查 4.7K 上拉电阻、DQ 引脚接线、时序函数(Delay10us)精度;
- 串口接收乱码:核对波特率(11.0592MHz 晶振)、SMOD 位配置、TX/RX 接线;
- 数码管显示异常:段码表是否匹配共阳 / 共阴、位选 / 段选引脚接线。
五、项目总结与优化
5.1 项目亮点
- 模块化设计:将 UART、DS18B20、LED、Modbus 等功能拆分为独立模块,便于维护和扩展;
- 中断驱动:UART 接收采用中断方式,避免主循环阻塞,提升实时性;
- 轻量化协议:自定义 Modbus 帧格式适配 51 单片机资源,简化校验逻辑。
5.2 优化方向
- 协议增强:替换为标准 Modbus RTU 协议,增加 CRC16 校验,提升可靠性;
- 多传感器支持:扩展 DS18B20 的 ROM 指令,支持多传感器组网;
- 低功耗优化:增加睡眠模式,仅在指令交互 / 温度采集时唤醒;
- 错误处理:增加指令帧长度校验、功能码合法性校验,提升鲁棒性;
六、附录:关键头文件(示例)
以uart.h为例,定义核心函数声明:
c
运行
#ifndef __UART_H__
#define __UART_H__
void uart_init(void);
void send_char(unsigned char ch);
void send_str(const char *pstr);
void send_buff(const char *pbuff, int len);
#endif
本项目完整实现了 51 单片机的多外设联动与串口指令解析,既适合 51 单片机入门学习,也可作为小型工业控制场景的基础框架。通过对代码的模块化解析和硬件逻辑的梳理,能够清晰理解嵌入式系统中 "外设驱动 + 协议解析 + 主循环调度" 的核心设计思路。