☰
STC15+DS18B20温度采集串口通信仿真与实战详解
2026/9/25 18:31:52 网站建设 项目流程

简介:基于STC15W4K32S4单片机的DS18B20温度读取与串口发送完整工程包,面向单片机学习者与电子设计开发者。压缩包共37个文件,包含C语言源码(main.c、ds18b20.c、uart.c、delay.c)、头文件、Keil工程文件(.uvproj/.plg)及Proteus仿真工程(.pdsprj/.pdsbak),另含hex烧录文件与编译中间文件,可直接打开仿真和源码进行学习调试,包体仅233KB。资源演示了单总线协议读取DS18B20、串口1波特率配置与数据发送等关键技术点,已有3201人学习。通过Proteus仿真验证硬件逻辑,再结合Keil编写控制程序,适合想掌握STC15单片机外设驱动、单总线通信和UART串口应用的初学者快速上手。 STC15W4K32S4 读取 DS18B20 温度再通过串口发出去,这个组合我帮人调过好多次,不管是学生课设还是小型工装项目,都是很典型的一套单片机测控链路。Proteus 仿真把电路逻辑跑通,Keil 写固件,DS18B20 负责采集温度,STC15W4K32S4 负责处理数据,最后从串口把结果吐出来。整个过程能把 IO 控制、单总线时序、串口通信这些基本功全部串起来,非常适合刚入门 51 内核单片机、又不想一上来就碰硬件坑的朋友照着复现。这篇文章我会把完整流程拆开讲,包括 Proteus 仿真搭建、DS18B20 时序、Keil 工程配置、串口波特率计算,以及我实际调板时踩过的一些坑,争取看完就能跑起来。

1. 系统方案与器件选型解析

1.1 这套方案到底解决什么问题

说穿了就三件事:采集、处理、上报。DS18B20 把环境温度变成数字量,STC15W4K32S4 通过单总线协议把温度数据读回来,再通过 UART 串口发送给上位机或者串口调试助手。就这么一个闭环,在温度监控、设备预警、环境采集这类场景里非常常见。

和传统的方案相比,这套东西有个明显的好处:DS18B20 是数字传感器,温度数据直接在芯片内部完成模数转换,单片机只要按协议把数据“抠”出来就行,不需要再用 ADC 通道和模拟前端做一大堆校准。数据精度和一致性也比较好控制,不同板子之间差异很小。

1.2 为什么选 STC15W4K32S4 而不是 AT89C52

很多人一开始学 51 用的是 AT89C52,但真正做项目的时候我更推荐 STC15 系列。AT89C52 是 12T 架构,一个机器周期要吃 12 个时钟周期,跑 12MHz 晶振实际指令速度只有 1MIPS 左右。而 STC15W4K32S4 是 1T 架构,同频率下执行速度大约是传统 51 的 8 到 12 倍,跑同样的代码,实时响应能力完全不在一个层级。

STC15W4K32S4 内部资源也很充裕,4K 字节 SRAM、32K 字节 Flash,自带 4 个串口、5 个定时器、高速 ADC、PWM 等等,一颗片子基本能覆盖中低端工控和物联网节点的需求。最方便的是它支持 ISP 下载,不需要专门的编程器,用 USB 转 TTL 串口模块就能烧录程序,配合 STC-ISP 工具,一个 CH340 模块就能搞定调试和下载。

1.3 DS18B20 的优势与注意事项

DS18B20 用的人太多了,核心优势是单总线通信,只有一根数据线,加上 VCC 和 GND 一共 3 个引脚,硬件连接非常简单。测量范围 -55℃ 到 +125℃,12 位分辨率下精度可以达到 0.0625℃,对于大多数温度采集场景完全够用。

需要注意的一点是,DS18B20 虽然便宜好用,但它对时序要求比 I2C 和 SPI 要严格得多。因为单总线是半双工、主机主动控制的结构,所有读写操作都得在精确的时间窗口内完成,延时不够或者过长都会导致通信失败。这也是为什么很多人仿真能跑、真板子上却读不出温度的根本原因。后面我会详细拆时序。

2. 硬件电路与 Proteus 仿真工程搭建

2.1 DS18B20 应用电路的关键点

DS18B20 的典型应用电路看着很简单,但有一个细节不能忽略:数据线 DQ 必须要接一个 4.7kΩ 左右的上拉电阻到 VCC。原因很简单,DS18B20 的数据引脚是漏极开路结构,它只能主动拉低电平,不能主动输出高电平,不加上拉电阻的话,高电平状态根本没法建立,通信直接失败。

供电方式有两种:寄生供电和外部供电。寄生供电只用两根线,数据线兼做电源供给,但电流能力有限,转换温度的时候可能电压跌落导致初始化失败。我做项目都是用外部供电,也就是 VCC 接 3.3V 或 5V,GND 接地,DQ 接单片机引脚,稳定省心。

单片机这边,我一般把 DQ 接到 P1.0 口。STC15 的 IO 口默认是准双向口模式,和传统 51 兼容,直接接 DS18B20 没问题。如果想增强驱动能力,也可以用推挽模式,但准双向口在这个场景下已经够用了。

2.2 Proteus 中放置器件与参数配置

打开 Proteus,新建工程后在元件库里搜索以下元件:

  • STC15W4K32S4:单片机本体,Proteus 8.6 以上版本一般都能找到
  • DS18B20:温度传感器,元件库中有完整仿真模型
  • RES:下拉/上拉电阻,选 4.7kΩ
  • VTERM:虚拟终端,用来观察串口输出

放置元件后按电路连接:DS18B20 的 DQ 接 P1.0,VCC 和 GND 正确供电,DQ 到 VCC 之间接 4.7kΩ 上拉电阻。单片机的 TXD 引脚接虚拟终端的 RXD,波特率要在虚拟终端属性里设置成和代码一致,默认我用 9600。

双击单片机,把晶振频率设置为 11.0592MHz。这个值不是随便选的,后面串口波特率计算部分会解释它的好处。还有一点,STC15 系列默认是 1T 模式,仿真时如果代码里用了延时,晶振频率必须和 Keil 工程里假设的一致,否则时序就乱了。

2.3 STC15 在 Proteus 中的几个常见坑

我在帮别人解决 Proteus 仿真问题的时候,最常见的失败原因有三个。

第一是 Proteus 版本太旧,元件库里根本没有 STC15W4K32S4 这个模型。如果你搜不到,要么升级到较新的版本,要么用相近型号替代,但替代后引脚和外设可能会有差异,不建议硬凑。

第二是 STC15 的 xdata 在仿真器中支持不完整。STC15W4K32S4 有 4K 字节内部扩展 RAM,Proteus 对这部分空间的支持在某些版本里有问题。如果你在代码里把大数组定义到 xdata 空间并且超出了仿真器能模拟的范围,跑起来可能直接卡死或出错。解决方法是尽量让变量使用默认的 data/idata 空间,或者确认仿真器对 xdata 的支持情况再使用。

第三是 1T 和 12T 的时钟模式混淆。有些代码在传统 51 上写了固定延时的循环,移植到 STC15 后发现时序快了 8 到 12 倍,DS18B20 通信就崩溃了。这个在仿真环境里一样会出现,因为仿真器会严格按照设定的晶振频率和指令周期来执行代码。

3. 让 DS18B20 开口说话:单总线时序拆解

3.1 单总线协议的工作方式

DS18B20 用的单总线协议,说白了就是一根线上既传时钟又传数据,所有时序都由主机发起。主机拉低电平的时间长度不同,传感器就能区分出复位、读 0、读 1、写 0、写 1 这些操作。由于没有独立的时钟线,双方必须对时间窗口有非常一致的认知,这就是 DS18B20 时序代码必须精确的原因。

打个比方,单总线通信就像两个人用约定好的节奏打摩尔斯电码,敲一下长一下短各有含义,但前提是双方对“长”和“短”的定义保持一致。DS18B20 数据手册里给的时间参数就是那个“约定”,代码要严格按照它来执行。

3.2 初始化(复位)时序

每次和 DS18B20 通信之前,主机都要先发送一个复位脉冲,检测传感器是否在线上。具体做法是:主机先把总线拉低至少 480μs,然后释放总线,等待 15 到 60μs 后,DS18B20 会把总线拉低 60 到 240μs 作为存在脉冲响应。单片机读到这个低电平,就知道传感器在线,可以继续后续操作。

主机拉低 → 等待 600μs → 释放总线 → 等待 60μs → 读取存在脉冲 → 等待 240μs

采样点放在释放总线后 60μs 左右比较稳妥,太早传感器还没开始应答,太晚应答脉冲可能已经结束,两种情况都会误判“无设备”。

3.3 写时序和读时序

写时序分写 0 和写 1。写 0 时,主机拉低总线并保持 60 到 120μs 后释放;写 1 时,主机拉低总线 1 到 15μs 后立即释放。也就是说,区别在于低电平持续的时间长度,传感器在主机释放后的采样窗口内读电平状态。

读时序的触发方式和写 1 类似:主机拉低总线 1 到 15μs 后释放,然后马上读取总线电平。传感器如果想回 1,就保持高电平;如果想回 0,就会主动把总线拉低。主机必须在释放后的 15μs 内完成采样,超过这个窗口就可能读到错误的电平。

实现的时候要注意,读写每一位之前总线默认应该是高电平状态,拉低动作要在严格的时间窗内完成。用软件延时实现时,建议用 NOP 指令或者精确的延时函数,不要在循环里塞太多无关操作,否则时间窗口容易被拖垮。

3.4 STC15 的 1T 模式对延时函数的影响

STC15 是 1T 架构,普通的for循环延时写法在不同编译器优化级别下差异可能很大。比如DelayUs(10)这个函数,在传统 51 上写一个循环可能恰好是 10μs,但在 STC15 上同样写法的执行时间可能只有 1μs 左右,DS18B20 初始化直接失败。

解决思路有两种:一是用 STC-ISP 软件自带的延时计算器,输入晶振频率和所需延时时间,它会直接生成精确的汇编循环代码;二是用定时器延时,在关键时序位置调用定时器查询等待。我一般用第一种,简单直接,代码也不用引入额外中断开销。

4. Keil 工程搭建与完整源码实现

4.1 Keil C51 工程初始化

先在 Keil 里新建工程,选择 STC15W4K32S4 这个芯片型号。如果下拉列表里没有,需要用 STC-ISP 软件里的“Keil 仿真设置”把 STC 型号库安装到 Keil C51 中。这一步很多人会漏掉,装完之后 Keil 才能识别 STC 全系列芯片。

工程选项里需要勾选生成 HEX 文件。Output 选项卡中把 "Create HEX File" 选上,编译之后才能得到 Proteus 加载固件需要的 .hex 文件。如果需要优化代码体积,可以把优化等级调到 9 级,但要注意优化等级太高可能改变延时函数的行为,建议先用默认等级跑通再调优化。

头文件方面,STC-ISP 安装的时候会把STC15.h或STC15W4K.H复制到 Keil 的 INC 目录下。代码里直接#include "STC15.h"就能获得寄存器定义和位定义,不需要手动再去翻数据手册逐一定义寄存器地址。

4.2 串口初始化与波特率计算

STC15W4K32S4 的串口 1 可以用定时器 1 或定时器 2 作为波特率发生器,我用的是定时器 2,因为它可以独立工作,不影响其他定时器使用。核心公式是:

波特率 = 时钟频率 / (4 × (65536 - RCAP))

其中 RCAP 是定时器 2 的重载值。反推 RCAP:

RCAP = 65536 - 时钟频率 / (4 × 波特率)

如果用 12MHz 晶振算 9600 波特率,结果是 65536 - 12000000 / 38400 = 65536 - 312.5,不是整数,会产生波特率误差,串口数据在长时间传输时容易出现乱码。但如果把晶振换成 11.0592MHz:

RCAP = 65536 - 11059200 / (4 × 9600) = 65536 - 288 = 65248 = 0xFEE0

正好是整数,这也是为什么大家都推荐用 11.0592MHz 晶振跑 9600 波特率的原因,误差为零,通信最稳。

串口初始化代码如下:

void UART1_Init(void) { P_SW1 &= 0x3F; // 串口1引脚选择 P3.0/P3.1 S1CON = 0x50; // 8位可变波特率,允许接收 T2L = 0xE0; // 11.0592MHz 下 9600 波特率重载值低字节 T2H = 0xFE; // 重载值高字节 AUXR |= 0x14; // T2R=1 启动定时器2,S1BRT=1 选择T2做波特率 EA = 1; }

注意 STC15 的串口引脚是可以重映射的。默认是 P3.0(RXD)和 P3.1(TXD),如果板子上这两个引脚被别的外设占用,可以切换到 P3.6/P3.7 或 P1.6/P1.7,但代码里的P_SW1配置要同步修改。

4.3 温度读取与数据处理

读取 DS18B20 温度的标准流程是:复位 → 跳过 ROM 匹配(0xCC)→ 启动温度转换(0x44)→ 等待转换完成 → 复位 → 跳过 ROM → 读暂存器(0xBE)→ 连续读 9 个字节。实际只需要前两个字节,分别是温度值的低字节和高字节。

温度转换的时间在 12 位分辨率下最长需要 750ms,所以启动转换之后要等待足够的时间再读暂存器。如果等得太短,读回来的数据可能是上一次的旧值,现象就是温度一直不变或者跳动异常。

温度值的计算规则是:将读回的高字节和低字节拼成一个 16 位有符号数,乘以 0.0625 就得到摄氏温度。例如读回 0x0191,十进制是 401,401 × 0.0625 = 25.0625℃。负数温度用补码表示,判断最高位为 1 时,需要先取反加一得到绝对值,再在前面加负号。

我提供一段精简的读取函数:

unsigned int ReadTemperature(void) { unsigned char L, H; unsigned int temp; DS18B20_Reset(); // 复位 DS18B20_WriteByte(0xCC); // 跳过ROM匹配 DS18B20_WriteByte(0x44); // 启动温度转换 DelayMs(750); // 等待转换完成 DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器 L = DS18B20_ReadByte(); H = DS18B20_ReadByte(); temp = (H << 8) | L; return temp; }

4.4 主流程与串口输出格式化

主函数就是不断重复“读温度 → 格式化 → 串口发送”这个循环。格式化输出我建议加上单位,方便调试时候直接看,比如输出T=25.06 C。如果不想引入sprintf这种大函数,可以手动拆数字,把整数和小数部分分开发送。

这里还需要一个串口发送函数。STC15 的 UART 发送是写S1BUF寄存器,然后查询发送完成标志位 TI。TI 需要软件清零,很多新手忘了清标志位,导致第一次发送正常、后面全是乱码。

void UART1_SendChar(unsigned char dat) { S1BUF = dat; while(!(S1CON & 0x02)); // 等待TI置位 S1CON &= ~0x02; // 软件清TI } void UART1_SendString(char *s) { while(*s) { UART1_SendChar(*s++); } }

完整主函数逻辑:

void main(void) { float t; unsigned char buf[32]; UART1_Init(); while(1) { t = (float)ReadTemperature() * 0.0625f; sprintf((char *)buf, "T=%.2f C\r\n", t); UART1_SendString((char *)buf); DelayMs(1000); } }

5. 仿真联调与常见问题排查

5.1 用虚拟终端验证串口输出

Proteus 里查看串口输出最简单的方法是用 Virtual Terminal,也就是虚拟终端。把它从元件库拖出来,把单片机的 TXD 引脚连接到虚拟终端的 RXD,然后在虚拟终端属性里把波特率设置为 9600,数据位 8、停止位 1、无校验,和代码配置保持一致。

运行仿真后,虚拟终端窗口会滚动显示温度数据。如果显示乱码,先检查波特率是否一致,再检查晶振频率是否和代码匹配。如果虚拟终端没有任何输出,优先检查单片机的 TXD 是否真的接到了虚拟终端的 RXD,以及有没有在工程里生成并加载 HEX 文件。

关于虚拟终端还有一个容易踩的坑:Proteus 的虚拟终端有时会受流控信号影响,RTS 和 CTS 引脚悬空状态在部分版本里会导致显示冻结。遇到这种情况,把虚拟终端的属性里的流控选项关闭,或者把 RTS/CTS 引脚做相应处理,大多数情况就能正常显示了。

5.2 典型问题与解决速查表

现象可能原因排查方向
虚拟终端完全没数据HEX 未加载或 TXD 接错检查工程 HEX 路径和引脚连接
串口输出乱码波特率不一致或晶振频率误差统一 9600 和 11.0592MHz
温度恒为 85℃复位后未启动转换或初始化失败检查复位时序和上拉电阻
温度读回 0xFFFF传感器无应答或 DQ 引脚接错检查存在脉冲和线路连接
仿真中 STC15 不运行Proteus 版本不支持该型号升级版本或用兼容型号
程序在真板上下载不了串口模块驱动或冷启动时序问题安装 CH340 驱动,P3.0/P3.1 不要占用
温度值跳动过大未加软件滤波或转换未完成就读取延时 750ms 后再读,多采几次取平均

5.3 仿真和真实硬件之间不可忽视的差异

Proteus 仿真能验证逻辑正确性,但永远不能完全替代真实硬件测试。DS18B20 在仿真器里的时序模型比较理想,延时稍微偏长偏短都能容忍,而真实传感器对时序窗口更敏感。我在真板上遇到过仿真完美、但硬件死活读不出温度的情况,最后定位到是延时函数在优化后执行时间变了,导致复位脉冲过短。

真实硬件上还有一个常见问题是供电和接线。DS18B20 的数据线如果走线过长,或者和电源线靠太近,寄生电容和噪声会影响信号完整性。条件允许的话,DQ 线上可以并一个小电容滤波,但不能太大,否则信号边沿变缓会影响时序。另外,STC15 的供电电压范围是 2.5V 到 5.5V,DS18B20 在 3.0V 到 5.5V 范围内都能工作,但两者电压要匹配,如果单片机用 3.3V 供电,DS18B20 也接 3.3V,别一个 5V 一个 3.3V 混着接。

6. 踩坑记录与后续扩展思路

6.1 我实际调这块板子时遇到过的几个坑

第一次画板的时候,我把 DS18B20 的 DQ 直接连到了单片机引脚,忘了放上拉电阻,结果仿真能过,真板子上稳定输出 85℃。当时花了好一会儿才排查出来,因为 DS18B20 的数据手册里说了需要上拉,但 Proteus 仿真模型在某些版本里内部已经做了简化处理,不加上拉也能跑。

还有一次是串口输出一直乱码,查了半天发现是代码里使用了 12MHz 晶振算出来的波特率重载值不是整数,误差在长字符串传输时被放大了。后来统一改成 11.0592MHz 晶振,乱码问题彻底消失。建议在做任何用到串口的项目时,优先选择 11.0592MHz 这个频率点,省去很多麻烦。

下载程序时也踩过坑。STC15 的 ISP 下载需要冷启动,也就是先点下载按钮,再给单片机上电。很多人第一次用的时候顺序搞反,软件一直提示“正在检测目标单片机”。解决办法就是先把串口模块接好,点击下载,然后给板子断电重新上电,保证 P3.0 和 P3.1 引脚在下载期间没有被其他外设占用。

6.2 这套系统可以继续扩展的方向

当前方案只是完成了温度采集和串口上报,在这个基础上扩展空间还很大。比如可以接一个 OLED 显示屏,把温度和系统状态直接显示出来,让设备更完整; 也可以把读取频率提高,对多组数据做平均值滤波,提高数据的稳定性;甚至可以在单片机端加一个简单的协议帧,把温度、采样时间、设备号一起打包上传,为后续接入上位机监控系统做准备。

软件层面也可以继续优化。STC15W4K32S4 的定时器资源比较多,可以把 DS18B20 的 750ms 等待时间用定时器中断来做,主循环做其他任务,避免阻塞式延时浪费 CPU 资源。对于实时性要求更高的场景,这是很必要的改造。

我个人觉得,这类项目最值得反复琢磨的地方不是代码能不能跑通,而是能不能把每一段延时、每一个电平变化都理解透。真正搞懂 DS18B20 的时序之后,再去看其他单总线传感器,比如 DHT11、DHT22,思路几乎是通用的。如果你正在做类似的东西,建议先用逻辑分析仪或者示波器抓一下 DQ 引脚的波形,对照数据手册的时间参数看一遍,很多疑惑会一下子清晰起来。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询