依赖51 单片机的 Modbus 协议温度采集与外设控制系统的实现
一、项目概述本项目以 STC89C5251 内核单片机为核心基于自定义 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 波特率中断接收DS18B20P3^7(DQ)单总线通信LED 阵列P2 口共阳 LED低电平点亮数码管P0 (段码)/P1 (位选)四位共阳数码管定时器 0T0(P3^4)方式 1中断控制 蜂鸣器定时器 1T1(P3^5)方式 2作为 UART 波特率发生器2.3 硬件实物示意图文字描述核心板STC89C52 最小系统含 11.0592MHz 晶振、复位电路外设模块LED8 个共阳 LED 接 P2.0~P2.7串联 220Ω 限流电阻数码管四位共阳数码管P0 接 a~dp 段串 220Ω 电阻P1.0~P1.3 接位选DS18B20DQ 引脚接 P3.7外接 4.7K 上拉电阻VCC 接 5VGND 接地串口CH340 模块转 USBTX/RX 分别接单片机 RX/TX。三、软件设计3.1 软件整体流程3.2 核心模块代码解析1UART 驱动模块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位数据可变波特率SM00, SM11REN1允许接收 SCON ~(3 6); SCON | 1 6; SCON ~(1 7); SCON | 1 4; // PCON配置SMOD1波特率加倍 PCON ~(3 6); PCON | 1 7; PCON ~(1 6); // Timer1配置方式28位自动重装 TMOD ~(0xF0 0); TMOD | 1 5; TMOD ~(1 4); // Timer1重装值11.0592MHz晶振2400波特率 TL1 232; TH1 232; TCON | 1 6; // 启动Timer1TR11 IE | 1 7; // 开总中断EA1 IE | 1 4; // 开UART中断ES1 } // 发送单个字符 void send_char(unsigned char ch) { SBUF ch; while((SCON (1 1)) 0); // 等待发送完成TI1 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就是1focs晶振频率我们这是11.0592M;bps目标波特率我们这是2400(最终得到TH1 TL1 232 8位自动重装载)接收中断采用缓冲区 指针的方式避免数据丢失发送函数通过轮询 TI 标志确保数据发送完成。2DS18B20 温度传感器驱动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); // 拉低至少480us70*10us700us 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 得到实际温度。3Modbus 协议解析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 BBP2.0 对应的 LED 点亮数码管显示数字 5AA 01 02 05 00 AD BB数码管循环显示数字 5定时器 200HzAA 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 单片机入门学习也可作为小型工业控制场景的基础框架。通过对代码的模块化解析和硬件逻辑的梳理能够清晰理解嵌入式系统中 “外设驱动 协议解析 主循环调度” 的核心设计思路。