news 2026/6/10 17:45:42

DS18B20的STM32F103C8T6使用方法 HAL库全解(超详细时序拆解+源码逐行解析)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DS18B20的STM32F103C8T6使用方法 HAL库全解(超详细时序拆解+源码逐行解析)

DS18B20的STM32F103C8T6使用方法 HAL库全解(超详细时序拆解+源码逐行解析)

0. 原创前言

网上大部分DS18B20教程我都看过,基本都是丢一段源码、简单标几个注释,只告诉你怎么调用函数,根本不讲底层逻辑。很多同学照搬代码后,经常遇到各种奇葩问题:传感器初始化失败、温度数值卡死不动、负数温度数据错乱、偶尔读取超时跳数,出了问题完全不知道从哪里排查。

这篇文章是我自己用STM32F103C8T6 + HAL库实测调试、一步步踩坑改出来的完整教程。我把DS18B20的单总线时序、延时适配、正负温度补码计算、常见报错问题全部梳理透彻,所有代码都是工程实测稳定版本,适配72MHz主频,直接复制就能用,随便移植到F1系列项目里都没问题。

这里简单说下这篇教程的亮点,都是我实测总结的干货:

  • 亲手拆解全套时序:复位、应答、读位、写位、读写字节全部掰开揉碎讲,不用死记硬背时序参数
  • 适配HAL库精准延时:专门针对F103 72MHz主频校准微秒延时,解决大部分人时序不对、通信失败的问题
  • 通俗讲解正负温度计算:很多教程不讲的补码逻辑,我用大白话讲清楚,彻底搞定负数温度乱码问题
  • 汇总所有常见bug:把我踩过的坑全部整理出来,初始化失败、温度跳变、数据卡死等问题全部对应解决方案
  • 代码极简好移植:模块化分层写的代码,初始化、读写、测温完全分开,课程设计、毕业设计直接套用

适配硬件:STM32F103C8T6 + DS18B20数字温度传感器

开发框架:STM32 HAL库

通信方式:单总线One-Wire(单IO双向通信)

核心难点:单总线精准时序匹配 + 微秒级延时适配 + 温度补码数据解析

1. DS18B20传感器核心原理与特性

DS18B20 是一款常用的单总线数字温度传感器,广泛用于单片机测温项目、环境监测、温控设备、毕业设计等场景,相比模拟热敏电阻,最大优势是无需AD转换、单IO双向通信、精度高、抗干扰强

1.1 核心硬件特性

  • 测温范围:-55℃ ~ +125℃,覆盖绝大多数民用测温场景
  • 默认分辨率:0.0625℃,测温精度极高
  • 供电方式:3.3V/5V 宽电压供电,支持寄生电源模式
  • 通信方式:标准单总线协议,仅需1个IO口即可完成双向通信,极大节省单片机IO资源
  • 支持多设备挂载:同一总线可挂载多个DS18B20,通过ROM地址区分设备

1.2 单总线通信核心痛点(新手必看)

我实测下来发现,DS18B20最坑的地方就是对时序要求极其严格,这也是90%的人代码跑不通的核心原因!普通的粗略延时、毫秒级延时根本满足不了通信需求,稍微差几微秒就会时序错位,直接导致初始化无应答、温度数据错乱。

另外单总线是半双工双向通信,同一个PB12引脚,需要频繁切换推挽输出和输入模式。很多新手忽略IO模式切换,一直输出或者一直输入,传感器根本不会应答,这也是通信失败的高频原因。

2. 硬件接线说明(工程实测管脚)

我工程里固定用的是PB12作为数据引脚,大家如果需要改其他IO口也很方便,只需要改对应宏定义就行,不用动底层时序代码,适配性很高。

DS18B20引脚

STM32F103C8T6引脚

功能说明

VCC

3.3V / 5V

模块供电,兼容宽电压

GND

GND

共地,必须严格共地否则通信异常

DQ

PB12

单总线双向通信数据引脚

这里提醒一下大家:DQ数据线最好接一个4.7K上拉电阻,我实测不加电阻的话,总线容易浮空,偶尔会出现时序紊乱、读取数据异常。现在很多成品模块自带上拉电阻,如果是模块可以不用额外焊接。

3. DS18B20 完整通信时序详解(必看,代码核心依据)

很多人写不好DS18B20驱动,根本原因就是没吃透整套单总线时序逻辑,只会照搬代码,稍微改一下主频、延时参数就彻底跑废。我这里用大白话把整套通信时序讲清楚,所有驱动代码都是严格按照下面这套流程写的,看懂时序就看懂了整个驱动。

DS18B20所有通信流程固定分为四步:复位应答 → 写指令 → 等待执行 → 读取数据,全程依靠微秒级精准时序,任何一步延时超标或不足,都会导致通信失败。

3.1 复位+应答时序(通信初始化前提)

这是每次通信的第一步,也是初始化成功的关键。主机(STM32)先抢占总线,将DQ引脚拉低,持续时长必须大于480us,我工程里用750us预留余量。随后释放总线,等待传感器应答。

正常工作的DS18B20,会在总线释放后的15~60us内主动拉低总线,生成60~240us的低电平应答信号。如果超时没拉低、或者拉低时长异常,直接判定传感器未接入、损坏或时序错乱。

3.2 写时序(主机发指令给传感器)

单总线写数据按位传输,分为写0和写1两种时序,时序差异非常大,不能统一延时:

写1时序:主机拉低总线 ≥1us 后立刻释放,随后保持总线高电平60us以上,完成1位数据写入;

写0时序:主机持续拉低总线 ≥60us,再释放总线,完成0位数据写入;

两次位写入之间需要预留短暂间隔,防止时序重叠紊乱,这也是我代码里长短延时区分的核心原因。我们常用的0xCC、0x44、0xBE指令,都是靠逐位写时序发送的。

3.3 读时序(主机获取传感器数据)

传感器不会主动上传数据,必须由主机发起读信号。每读取1bit数据,都需要主机先拉低总线≥1us,触发传感器输出数据,随后释放总线,在15us以内完成电平采样读取。

我工程里选择12us采样,刚好卡在最优时序窗口,既不会提前采样导致数据不准,也不会超时错过有效电平。读完1bit后补齐延时,再循环读取8次,拼接成1字节完整数据。

3.4 完整测温工作时序流程

正常读取一次温度,必须严格按照固定顺序执行,顺序错了永远读不到正确数据:

1、总线复位 + 设备应答检测,确认传感器在线;

2、发送0xCC跳过ROM指令(单设备通用);

3、发送0x44温度转换指令,启动传感器测温;

4、再次复位总线、检测应答;

5、发送0xBE读取暂存器指令;

6、连续读取低8位、高8位温度数据,拼接换算得到真实温度。

整套时序环环相扣,这也是为什么不能随便改延时、不能省略复位、不能不启动转换的原因。吃透这套时序,后续不管改IO口、改主频、移植工程,驱动都能正常跑通。

4. 工程配置与宏定义说明

我把所有硬件相关的配置全部用宏定义封装好了,这样后期改IO口特别方便,不用去底层函数里挨个改参数。大家直接在ds18b20.h头文件添加以下宏定义即可,当前适配PB12引脚:

c
// 根据实际硬件修改IO口
#define DS18B20_DQ_GPIO_PORT GPIOB
#define DS18B20_DQ_GPIO_PIN GPIO_PIN_12

// IO模式切换、电平控制宏定义
#define DS18B20_IO_OUT() HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &(GPIO_InitTypeDef){.Pin=DS18B20_DQ_GPIO_PIN,.Mode=GPIO_MODE_OUTPUT_PP,.Pull=GPIO_NOPULL,.Speed=GPIO_SPEED_FREQ_HIGH})
#define DS18B20_IO_IN() HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &(GPIO_InitTypeDef){.Pin=DS18B20_DQ_GPIO_PIN,.Mode=GPIO_MODE_INPUT,.Pull=GPIO_NOPULL})

#define DS18B20_DQ_LOW() HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT,DS18B20_DQ_GPIO_PIN,GPIO_PIN_RESET)
#define DS18B20_DQ_HIGH() HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT,DS18B20_DQ_GPIO_PIN,GPIO_PIN_SET)
#define DS18B20_DQ_READ() HAL_GPIO_ReadPin(DS18B20_DQ_GPIO_PORT,DS18B20_DQ_GPIO_PIN)

注意工程依赖:需要提前配置好HAL微秒延时函数、串口printf打印,系统时钟配置为72MHz,不然时序对不上,代码大概率跑不通。

5. 底层驱动源码逐行超详细解析

下面我把整个.c文件的代码逐函数拆解,结合我自己的实测经验,讲清楚每段代码的作用和为什么要这么写,让大家不光会复制,还能懂原理。

4.1 精准微秒延时函数(时序核心基础)

c
static void DS18B20_Delay_Us(uint32_t us)
{
uint32_t i;
if (us > 0) {
// 简单循环延时,根据系统时钟调整系数
// 这里系数9是在72MHz主频下实测接近1us的延时
for (i = 0; i < us * 9; i++);
HAL_Delay_us(1);
}
}

我的实测总结:

DS18B20对时序精度要求极高,毫秒延时完全不够用。这个延时函数是我在72MHz主频下反复调试校准出来的,循环系数9是实测最接近1微秒的参数,搭配HAL库微秒延时,能精准匹配单总线时序,彻底避免延时偏差导致的通信失败。

我把这个函数设置为static静态函数,只允许当前文件调用,不对外暴露,代码封装性更好,工程更规范。

4.2 DS18B20复位函数

c
void DS18B20_Rst(void)
{
DS18B20_IO_OUT(); // 配置DQ引脚为推挽输出
DS18B20_DQ_LOW(); // 拉低总线
DS18B20_Delay_Us(750); // 保持低电平750us(满足>480us要求)
DS18B20_DQ_HIGH(); // 释放总线
DS18B20_Delay_Us(15); // 等待15us,等待DS18B20发送应答
}

时序思路(我实测优化):

官方手册要求复位拉低时长大于480us,我这里设置750us,多留一点余量,适配不同批次的传感器,兼容性更好。拉低复位后释放总线,等待15us让传感器完成应答,完全贴合官方标准时序,这是初始化成功的关键。

4.3 设备存在检测函数

c
uint8_t DS18B20_Check(void)
{
uint8_t retry = 0;
DS18B20_IO_IN(); // 切换为输入模式,释放总线

// 等待DS18B20拉低总线(应答信号)
while (DS18B20_DQ_READ() && retry < 200)
{
retry++;
DS18B20_Delay_Us(1);
};
if (retry >= 200) return 1; // 超时,无设备响应

retry = 0;
// 等待DS18B20释放总线
while (!DS18B20_DQ_READ() && retry < 240)
{
retry++;
DS18B20_Delay_Us(1);
};
if (retry >= 240) return 1; // 应答信号异常

return 0; // 检测正常
}

应答逻辑说明:

我们主机发送复位信号之后,正常的DS18B20会在15~60us内拉低总线应答,持续60~240us。我这里加了超时计数判断,超时无应答、应答时长异常,直接判定设备故障,能有效排查接线错误、传感器损坏、时序错乱等问题。

超时无应答、应答时长异常,直接判定设备未接入或通信故障,有效过滤硬件接线错误、设备损坏、时序错乱等问题。

4.4 单bit数据读取函数

c
uint8_t DS18B20_Read_Bit(void)
{
uint8_t data;
DS18B20_IO_OUT(); // 配置为输出
DS18B20_DQ_LOW(); // 拉低总线 2us
DS18B20_Delay_Us(2);
DS18B20_DQ_HIGH(); // 释放总线
DS18B20_IO_IN(); // 切换为输入
DS18B20_Delay_Us(12); // 延时12us后采样

data = DS18B20_DQ_READ() ? 1 : 0; // 读取总线电平
DS18B20_Delay_Us(50); // 等待读时序结束
return data;
}

读位时序思路:

官方要求拉低总线至少1us触发数据输出,15us内必须采样完毕。我实测设置拉低2us、12us采样,刚好卡在有效时序窗口内,读取数据精准不出错。最后延时50us补齐周期,防止连续读位时序重叠紊乱。

4.5 单字节读取函数(低位先行)

c
uint8_t DS18B20_Read_Byte(void)
{
uint8_t i, j, dat = 0;
for (i = 1; i <= 8; i++)
{
j = DS18B20_Read_Bit();
dat = (j << 7) | (dat >> 1); // 拼接数据(低位先行)
}
return dat;
}

简单说下原理:

DS18B20固定是低位先行传输数据,先传最低位、再传最高位。所以我们循环读8次单bit数据,通过移位运算逐位拼接成一个完整字节,这是读取温度数据的基础逻辑。

4.6 单字节写入函数

c
void DS18B20_Write_Byte(uint8_t dat)
{
uint8_t j;
uint8_t testb;
DS18B20_IO_OUT(); // 配置为输出

for (j = 1; j <= 8; j++)
{
testb = dat & 0x01; // 取最低位
dat = dat >> 1; // 右移准备下一位

if (testb) // 写 1
{
DS18B20_DQ_LOW();
DS18B20_Delay_Us(2);
DS18B20_DQ_HIGH();
DS18B20_Delay_Us(60);
}
else // 写 0
{
DS18B20_DQ_LOW();
DS18B20_Delay_Us(60);
DS18B20_DQ_HIGH();
DS18B20_Delay_Us(2);
}
}
}

写位时序区分:

写1:短暂拉低总线,快速释放,保持高电平;

写0:长时间拉低总线,再释放;

代码里逐位判断、分开延时,严格贴合官方协议,保证每一条指令都能被传感器正常识别。

4.7 温度转换启动函数

c
void DS18B20_Start(void)
{
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xCC); // 跳过ROM(单设备时使用)
DS18B20_Write_Byte(0x44); // 启动温度转换
}

指令简单解释:

0xCC是跳过ROM指令,我们单传感器项目直接用这个指令就行,不用匹配设备地址,简化通信流程;

0x44是启动温度转换指令,发送之后传感器才会开始采集温度数据。

这里重点提醒:每次测温前必须启动转换,不然读出来的一直是上一次的旧数据,温度永远不更新,这是新手最常踩的坑!

4.8 设备初始化函数

c
uint8_t DS18B20_Init(void)
{
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟

DS18B20_IO_OUT(); // 配置为推挽输出

DS18B20_Rst();
return DS18B20_Check(); // 返回检测结果
}

初始化流程很简单:

先开启GPIOB时钟,配置PB12为输出模式,发送复位信号,最后检测设备是否在线。返回0代表初始化成功,1代表失败,方便我们在代码里做异常判断。

4.9 温度读取与正负值解析(核心重点)

c
int16_t DS18B20_Get_Temp(void)
{
uint8_t TL, TH; // 温度低8位、高8位
int16_t tem; // 温度值

DS18B20_Start(); // 启动温度转换
DS18B20_Rst(); // 复位
DS18B20_Check(); // 检测设备
DS18B20_Write_Byte(0xCC); // 跳过ROM
DS18B20_Write_Byte(0xBE); // 读暂存器

TL = DS18B20_Read_Byte(); // 读低8位
TH = DS18B20_Read_Byte(); // 读高8位

// 处理正负温度
if (TH > 7) // 负温度(符号位为1)
{
TH = ~TH; // 取反
TL = ~TL;
tem = -((TH << 8) | TL); // 计算负值
}
else // 正温度
{
tem = (TH << 8) | TL; // 组合高低字节
}

tem = tem * 0.625; // 转换为实际温度(0.0625℃/LSB)
return tem;
}

大白话讲解温度解析逻辑:

DS18B20的温度数据存在两个8位寄存器里,TH是高8位、TL是低8位,拼起来是16位原始数据。高字节的最高位是符号位,用来区分正负温度:

1、正温度:符号位为0,直接拼接高低字节就能得到原始数值;

2、负温度:符号位为1,数据是以补码形式存储的,必须全部按位取反再计算负值,不然负数温度完全不准;

传感器固定分辨率是0.0625℃每刻度,我这里乘以0.625把数据放大10倍返回,保留一位小数,后续打印和屏幕显示都更方便,不用自己再二次换算。

6. 主函数调用示例(直接复制可用)

c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
UL_PRINTF_Init(); // 初始化串口打印

if(DS18B20_Init() == 0)
{
printf("DS18B20 Init Success!\r\n");
}
else
{
printf("DS18B20 Init Fail!\r\n");
}

while (1)
{
int16_t temp = DS18B20_Get_Temp();
printf("Current Temp: %.1f ℃\r\n", temp / 10.0f);
HAL_Delay(500);
}
}

7. 实测踩坑总结与解决方案

  • 初始化一直失败:优先检查PB12时钟是否开启、模块是否共地、上拉电阻是否正常,大概率是时序延时不匹配72MHz主频
  • 温度数值固定不变:循环内没有重复启动温度转换,一直在读取旧的缓存数据
  • 负数温度数据错乱:缺少补码取反处理,这是很多开源代码都存在的小bug
  • 温度轻微跳变:真实测温正常现象,可以加个简单均值滤波,多次采样取平均就能平滑数据
  • 偶尔读取超时:微秒延时参数偏差,重新适配当前主频的延时系数即可

8. 原创总结

这篇文章是我自己全程实测、一步步调试总结出来的DS18B20 HAL库完整教程。从最基础的单总线原理、时序适配、IO模式切换,到指令解析、正负温度补码计算,最后到工程调用和bug排查,全部梳理到位。

我没有单纯堆砌代码,而是把新手容易疑惑的点全部用大白话讲透:为什么要精准延时、为什么要切换IO模式、负数温度为什么要取反、为什么每次都要启动转换。整套代码实测稳定性很强,不管是课程设计、毕业设计还是小型测温项目,都可以直接拿来用。

我在哔哩哔哩录制了视频,对应大家可以查看,并且有源码链接,哔哩哔哩视频地址为:https://www.bilibili.com/video/BV1uznpzmEUp/

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 17:44:16

【Qt】信号和槽(三) (断开连接和lambda函数)

信号与槽断开连接 使用disconnect函数&#xff0c;与connect的用法类似。 示例&#xff1a; 按过按钮二后&#xff1a; 实际开发中disconnect的使用较少&#xff0c;大部分情况是把信号和槽连接好后就不再关心。这里用disconnect的场景是主动断开当前信号所连接的槽函数&a…

作者头像 李华
网站建设 2026/6/10 17:41:20

认识自动化测试

在我开展自动化测试之前&#xff0c;其实该项目以前的测试人员也已经写了很多的接口测试用例&#xff0c;但是大多数用例处于“半瘫痪”状态&#xff0c;在CI上无人维护&#xff08;听说起初是有人维护的&#xff0c;但是后来用例多了&#xff0c;维护的人每次花很长时间去定位…

作者头像 李华
网站建设 2026/6/10 17:38:19

鸿蒙新特性:Menu 下拉菜单深度解析 —— 工具栏与操作面板

引言 在桌面级应用和生产力工具中&#xff0c;下拉菜单是最基础且使用频率最高的交互模式之一。文件菜单、编辑菜单、视图菜单——这些经典的菜单栏设计承载着大量操作入口。HarmonyOS NEXT 的 bindMenu 属性为 ArkUI 组件提供了下拉菜单绑定能力&#xff0c;让开发者能够轻松地…

作者头像 李华
网站建设 2026/6/10 17:38:17

从单机到分布式:用 Go + Eino + DeepSeek V4 构建生产级 Code Review Agent

从单机到分布式:用 Go + Eino + DeepSeek V4 构建生产级 Code Review Agent 不是把大模型接到 GitHub Webhook 上,就叫生产级 Code Review Agent。真正决定系统上限的,是任务编排、规则前置、上下文治理、并发隔离与可观测性。 引言:为什么团队越来越需要“生产级” Code R…

作者头像 李华
网站建设 2026/6/10 17:37:10

Python3 JSON

Python3 JSON 概述 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Python3 提供了内置的 json 模块,使得处理 JSON 数据变得非常简单。本文将详细介绍 Python3 中 JSON 的使用方法,包括基本操作、数据序列化…

作者头像 李华
网站建设 2026/6/10 17:33:05

【JVM】垃圾回收GC全套深度详解(大厂高频八股)

大家好&#xff0c;我是程序员二叉。简介 本文一次性讲透对象存活判定、GC Roots、三大GC回收算法、分代回收设计逻辑、对象晋升规则、Minor/Major/Full GC区别、STW、主流垃圾收集器、三色标记法等全套核心考点。欢迎点赞收藏关注。一、如何判断对象是否存活&#xff1f;引用计…

作者头像 李华