目录
[5.1 DS18B20基本结构](#5.1 DS18B20基本结构)
[5.2 温度获取](#5.2 温度获取)
[5.3 供电模式](#5.3 供电模式)
[5.4 存储器结构](#5.4 存储器结构)
[5.4.1 配置寄存器](#5.4.1 配置寄存器)
[5.5 单总线系统](#5.5 单总线系统)
[5.5.1 单总线概念](#5.5.1 单总线概念)
[5.5.2 单总线协议时序](#5.5.2 单总线协议时序)
[5.5.2.1 复位脉冲与应答脉冲](#5.5.2.1 复位脉冲与应答脉冲)
[5.5.2.2 读写时隙](#5.5.2.2 读写时隙)
[5.5.2.3 DS18B20执行序列](#5.5.2.3 DS18B20执行序列)
[5.6 小结一下](#5.6 小结一下)
[6.1 单总线实现](#6.1 单总线实现)
[6.2 DS18B20温度采集](#6.2 DS18B20温度采集)
[6.3 主函数实现](#6.3 主函数实现)
引言
本次学习的外设模块是一款数字温度传感器模块------DS18B20,在学习该模块之前,我们需要具有一定的STM32基础,同时准备好DS18B20芯片手册,便于了解驱动DS18B20的方式。本次我们通过芯片手册来一步一步了解和学习驱动这款模块,后面我参考的DS18B20芯片手册来自拓电半导体的产品规格书。
注:后续DS18B20-xx为方便,均统称为DS18B20。
一、DS18B20概述
首先,我们先看看手册,从中了解,如下图所示。

DS18B20是一款数字温度传感器,也就是测量的数值以数字信号输出。其温度测量分辨率为9~12位,与MCU通讯的方式是单总线协议。其工作模式有两种,分别是外部供电和寄生供电模式,一般使用的是外部供电模式。通过单总线协议,我们可以实现一个MCU控制多个DS18B20实现大空间区域的温度检测,常常用于各类温度控制系统中。

二、常见封装
DS18B20常见封装有TO-92和MSOP6两种。
一是TO-92封装,其引出三根管脚,分别是GND(地线)、DQ(信号线)、VDD,(电源线);
二是MSOP8封装,其一个引出8根管脚,其中3个引脚即DQ、GND、VDD与TO-92封装中的功能相同,另外5个管脚悬空,不使用。
如下图所示

值得注意的是,【DQ】引脚,也就是信号线,即与微控制器(如STM32)通信的单总线接口,一般设置为开漏输出模式,同时在寄生供电模式下是供电端口。【VDD】电源线,其在寄生供电模式下必须接地。
三、DS18B20基本特征
手册中产品特征也需要关注一下,其中的一些电压电流说明决定了我们给其供电时提供的输入电压范围,如下图所示。

可见,DS18B20的工作电压应在2.7V~5.5V之间,那么MCU(如STM32)可输出3.3V或5V左右的电压足够支持DS18B20工作。测温范围、精度等图中描述很清楚,这里不再赘述。
需要注意的是,分辨率选择12位的时候,一次温度转换耗时小于750ms,所以一般为保证测量温度准确,一般每一次开启温度转换以后等待750ms后读取温度数据。
四、DS18B20单总线时序
由于DS18B20通过数字信号输出测量温度值,因此MCU与DS18B20需要同时约定规定好的单总线通讯协议才能保证DS18B20输出的温度值准确的给到MCU,便于解析获取正确的测得温度数值。所以这里理解一下单总线的通信时序,如下图所示。


五、DS18B20工作原理
5.1 DS18B20基本结构


由上图可知:
1、DS18B20通过单总线实现利用一个控制信号(DQ)实现总线通信,外部连接DQ的MCU(主设备)需要将相应端口设置为开漏输出模式。
2、DS18B20一般使用外部供电模式,本次也是以此为例。其还有另外一种工作模式------寄生供电模式,其特点是总线处于高电平时通过DQ供电,并给内部电容Cpp充电,当总线为低电平时即可通过电容Cpp为器件供电,也就是通过单总线获取电源的一种工作模式。
3、DS18B20内部还有一64位的ROM,用于存储DS18B20的唯一标识码(作为区分总线上挂载多设备时的区分码),只有单个设备时可跳过ROM,直接进行暂存器的读写。
4、DS18B20内部还有一个暂存器,包括温度寄存器(存储测量温度值)、高低温报警门限值、配置寄存器值(用于设置测量分辨率)以及8位的CRC校验产生器(用于温度值校验)。
5.2 温度获取
接着是温度获取的方式,如下图所示。

由图中可以了解到:
1、DS18B20测量温度的核心是有一个能直接进行数字输出的温度传感器;
2、温度分辨率可以通过用户编程设置为9、10、11、12位,对应的就是温度最小的精度即0.5、0.25、0.125、0.0625℃,其中DS18B20上电时默认分别率为12位,即不设置的话温度精度可达0.625℃;
3、需要进行温度转换时,主设备需要发送[44h]指令。也就是通过单总线方式向DS18B20发送一串0x44的数据,DS18B20收到后就知道我们需要进行温度转换了。温度转换完成后的结果会被DS18B20以两字节数据存储在前面提到的暂存器的温度寄存器中;
4、外部供电模式下,判断温度转换完成的方式:在发生温度转换指令后,再发出"读时隙"指令,若DS18B20发出应答响应为1则代表转换完成,反之正在进行。寄生供电模式下该方法不适用;
5、DS18B20输出的温度单位是℃,被存储在两个8位的温度寄存器,如下图所示。分别存储的高8位(MSB)和低8位数据(LSB)字节,其中16个位数据占12位即第0位~第11位,其中第11位是符号位,表示数据正负(S=0为正、=1为负),剩余4位用符号位补全。
若分辨率小于12位,则温度寄存器数值从第【12-分辨率】位开始表示测得温度值的第0位,如分别率为9位时,bit 3~bit11位温度数值有效位,温度/数据对照表如下。
值得注意的是,DS18B20上电初始化温度寄存器中的值为+85,即0x550。
5.3 供电模式
DS18B20的供电模式前面说过,有两种,不过一般选择外部供电模式即可,而且温度较高时外部供电模式也更占优势。

外部供电模式下,微控制器与DS18B20的连接方式如下图所示。

5.4 存储器结构
DS18B20中的存储器结构图如下

可见,DS18B20的存储器由一个暂存器和一个EEPROM构成。其中EEPROM包含了高低报警触发寄存器(TL、TH)以及配置寄存器,可与暂存器进行数据交换。
暂存器包括了8个字节的内容,我们本次主要关注BYTE0和BYTE1,也就是前两个字节。前两字节也就是前面所介绍的温度寄存器数值,后续读取数据就是从这里读取,且这两个字节只读不可写。
暂存器的BYTE2、3是访问TL、TH的,BYTE4包含了配置寄存器数据,也就是设置温度分辨率的。其他内容可自行研究手册了解,这里不过多赘述。

如上图所示,手册中提到,读暂存器时,DS18B20会向主设备从字节0开始发送数据,此时主设备就可以读取温度寄存器的数值了。
5.4.1 配置寄存器
如下图所示为配置寄存器的结构。

其中,第5位R1和第六位R0用于设置温度转换分辨率,即前面所说的温度测量精度。配置寄存器的最高位保留,不可写入。R0 R1的配置方式如下表所示。

注:DS18B20还有一些寄存器设置以及一个CRC发生器等,我们不过多赘述,本次完成一个基本的实现即可,也就是作比较核心的配置,实现基础功能。对于一下精度控制、校验数值等可自行研究完成。
5.5 单总线系统
5.5.1 单总线概念
DS18B20 的单总线是一种仅通过 1 根数据线(DQ 引脚) 实现微控制器(主机)与传感器(从机)间双向通信的简化接口技术,核心特点是 "一线多用", 单总线指依赖 DQ 引脚完成数据收发、设备寻址、指令传输。简化了硬件接线(需搭配一个约 4.7KΩ 外部上拉电阻,使总线空闲时保持高电平)无需额外时钟线或控制线。适配其低功耗、多点测温的应用场景。
5.5.2 单总线协议时序

5.5.2.1 复位脉冲与应答脉冲
这算是DS18B20与微控制器进行通信的初始化开始,由主设备发出的复位脉冲和从设备DS18B20响应的应答脉冲构成,时序图如下图所示。

按手册的话来讲即如下图所示。

整理一下初始化序列的过程即:
1、主机占用总线,拉低总线>480us --------- 发出复位脉冲;
2、拉高总线,主机释放总线 ------------------ 开始等待应答;
3、总线释放后,DS18B20等待15~60us;
4、DS18B20拉低总线60~240us ------------- 响应应答脉冲;
5、等待复位完成(释放总线到完成初始化要>480us)。
5.5.2.2 读写时隙

1、写时隙
写时隙的时序图如下图所示。

按照手册所说的来看即如下图所示。

整理一下写时隙的过程即:
1.1、主机拉低总线<15us ----- 准备写入数据;
2.1、释放总线,总线拉高>60us ----- DS18B20在15~60us内自动采样数据1。
1.0、主机拉低总线;
2.0、持续拉低总线>60us ----- DS18B20在15~60us内自动采样数据0 。
2、读时隙
读时隙的时序图如下

按照手册所述,如下图所示

整理一下读时隙过程即:
1、主机拉低总线1~2us,释放总线;
2、从机占用,发送数据,随后释放总线;
3、主机读取数据,15us内进行;
4、等待读时隙完成,维持>60us。
5.5.2.3 DS18B20执行序列

由图可知使用DS18B20的话,总体看需要机械能初始化、ROM操作指令以及功能指令三个过程。简单来说,初始化也就是主机发出复位脉冲,等待DS18B20响应并发出应答脉冲;ROM操作指令就是主机通过单总线发送ROM指令数据,DS18B20收到后进行相应动作;功能指令就是主机通过单总线发送功能指令数据,DS18B20收到后执行相应动作。
对于初始化而言。用手册上的话说即检查DS18B20是否准备好,如下图所示

对于ROM指令,也就是用于判断总线上从设备数量以及符合报警条件的从机等,同时在发起功能指令前必须先发出一条ROM指令。由于我们本次只使用一个从设备,所以其实直接发送跳过ROM指令继续后续步骤了。

相关的ROM指令可自行去手册查看,这里只贴本次使用的,如下图所示。

也就是跳过ROM指令[CCh],只要发送0xCC数据就可以直接在不发送任何ROM指令时发送功能指令。
比如发送忽略指令后发送温度转换指令,此时总线删的设备同时进行温度转换。同时总线上仅一个从机时,发送跳过ROM指令后可以发送读取暂存器指令[BEh]。
然后是功能指令。如下图所示

可知,主要是对暂存器进行读写的。
本次会用到的功能指令有启动温度转换指令、读暂存器指令,如下图所示。


功能指令使用的流程如下:

其中本次使用的流程如上图箭头所示。
5.6 小结一下
根据前面的分析,我们整理即可得出获取DS18B20采集的温度数据的步骤:
(1)启动DS18B20采集温度
1、初始化序列,即主机发送复位脉冲,从机发应答脉冲;
2、主机发送跳过ROM指令,准备温度转换;
3、主机发送温度转换功能指令,开始温度转换;
4、等待750ms,温度转换完成;
(2)获取DS18B20采集的温度
5、主机再次发送复位脉冲,从机发出应答脉冲;
6、主机发送跳过ROM指令,准备读取温度数据;
7、主机发送读暂存器指令,开始从机发送的读取温度数据;
(3)解析DS18B20中读出的温度数据
8、先读取低字节数据,再读取高字节数据并记录;
9、高位数据左移8位与低位数据进行合并
10、默认12位分辨率,对合并数据进行实际温度转换(温度数据对照表进行换算,如12位分辨率,即合并数据*0.0625就是实际温度数据)。
六、参考代码如下(STM32)
按照前面的分析,这里直接给出相应代码,该代码基于STM32编写,寄存器实现。
6.1 单总线实现
1、oneWire.h
cpp
/*
* @Descripttion: 单总线协议
* @Author: JaRyon
* @version: v1.0
* @Date: 2025-10-09 11:35:15
*/
#ifndef __ONE_WIRE_H
#define __ONE_WIRE_H
#include "stm32f10x.h"
#include "Delay.h"
// 信号线拉高拉低
#define OneWire_HIGH() {GPIOA->ODR |= GPIO_ODR_ODR5;}
#define OneWire_LOW() {GPIOA->ODR &= ~GPIO_ODR_ODR5;}
// 读取信号线电平
#define OneWire_READ (GPIOA->IDR & GPIO_IDR_IDR5)
// 应答与非应答
#define ACK 0
#define NACK 1
void OneWire_SetInputMode(void);
void OneWire_SetOutputMode(void);
void OneWire_Init(void);
int8_t OneWire_ResetPulse(void);
void OneWire_WriteBit(uint8_t data);
uint8_t OneWire_ReadBit(void);
void OneWire_WriteByte(uint8_t data);
uint8_t OneWire_ReadByte(void);
#endif // !__ONE_WIRE_H
2、oneWire.c
cpp
/*
* @Descripttion: 1-wire总线协议软件模拟
* @Author: JaRyon
* @version: v1.0
* @Date: 2025-10-09 11:35:06
*/
#include "onewire.h"
/***************************************************************
* @brief 设置单总线输入模式
* @param void
* @return void
* @note 无
* @Sample OneWire_SetInputMode();
**************************************************************/
void OneWire_SetInputMode(void)
{
// 接收外来信号 上拉 PA5 mode-00 cnf-10
GPIOA->CRL &= ~GPIO_CRL_MODE5;
GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
GPIOA->CRL |= GPIO_CRL_CNF5_1;
// 拉高
GPIOA->BSRR |= GPIO_BSRR_BR5;
}
/***************************************************************
* @brief 设置单总线输出模式
* @param void
* @return void
* @note 无
* @Sample OneWire_SetOutputMode();
**************************************************************/
void OneWire_SetOutputMode(void)
{
// 输出高低电平 开漏输出 默认拉高 PA5 mode-11 cnf-01
GPIOA->CRL |= GPIO_CRL_MODE5;
GPIOA->CRL &= ~GPIO_CRL_CNF5_1;
GPIOA->CRL |= GPIO_CRL_CNF5_0;
GPIOA->BSRR = GPIO_BSRR_BS5;
}
/***************************************************************
* @brief 单总线初始化配置
* @param void
* @return void
* @note 无
* @Sample OneWire_Init();
**************************************************************/
void OneWire_Init(void)
{
// 1. 开启时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 默认设置开漏输出
OneWire_SetOutputMode();
}
/***************************************************************
* @brief 发送复位脉冲
* @param void
* @return int8_t 应答脉冲 0-收到 1-未收到
* @note 无
* @Sample int8_t ack = OneWire_ResetPulse();
**************************************************************/
int8_t OneWire_ResetPulse(void)
{
int8_t ack = 1;
// 1. 确保GPIO为开漏输出
OneWire_SetOutputMode();
// 2. 拉低总线480µs(复位脉冲)
GPIOA->BSRR = GPIO_BSRR_BR5; // PA5置低
Delay_us(500);
// 3. 释放总线,电平拉高
OneWire_SetInputMode();
Delay_us(70); // 等待70µs,检测从设备响应
// 4. 读取总线状态,检测从设备发出低电平应答脉冲
ack = OneWire_READ ? NACK : ACK;
// 5. 采样完毕,等待复位完成
Delay_us(420);
// 6. 恢复为输出模式
OneWire_SetOutputMode();
return ack;
}
/***************************************************************
* @brief 向从设备写入一位数据
* @param uint8_t data 待写入的一位数据
* @return void
* @note 无
* @Sample OneWire_WriteBit(bit);
**************************************************************/
void OneWire_WriteBit(uint8_t data)
{
// 1. 输出模式,总线拉低,准备写入数据
OneWire_SetOutputMode();
OneWire_LOW();
// 2. 开始写入数据
if (data & 0x01) // 写入"1", 15us内释放总线
{
// 2.1 拉低总线,<15us
OneWire_LOW();
Delay_us(2);
// 2.2 写入数据,等待从设备进行数据采样
OneWire_HIGH();
Delay_us(65);
}
else
{
// 2.1 拉低总线,并写入数据0,保持至少60us
OneWire_LOW();
Delay_us(65);
// // 2.2 释放总线
// OneWire_HIGH();
// Delay_us(2);
}
// 3. 释放总线,电平拉高
OneWire_SetInputMode();
}
/***************************************************************
* @brief 向从设备读取一位数据
* @param void
* @return uint8_t 读取到的一位数据
* @note 无
* @Sample uint8_t bit = OneWire_ReadBit();
**************************************************************/
uint8_t OneWire_ReadBit(void)
{
uint8_t bit = 0;
// 1. 设置为输出模式并拉低总线开始读时隙
OneWire_SetOutputMode();
OneWire_LOW();
Delay_us(2); // 保持低电平1-2μs
// 2. 释放总线,等待从机发送数据
OneWire_SetInputMode();
Delay_us(10);
// 3. 数据采样,前15us内完成
bit = OneWire_READ ? 1 : 0;
// 4. 等待读时隙完成,一共>60us
Delay_us(50);
// 5. 回收总线控制权
OneWire_SetOutputMode();
return bit;
}
/***************************************************************
* @brief 向从设备写入一字节数据
* @param uint8_t data 待写入的一字节数据
* @return void
* @note 无
* @Sample OneWire_WriteByte(byte);
**************************************************************/
void OneWire_WriteByte(uint8_t data)
{
// 循环写入
for (uint8_t i = 0; i < 8; i++)
{
OneWire_WriteBit(data & 0x01);
data >>= 1;
}
}
/***************************************************************
* @brief 向从设备读取一字节数据
* @param void
* @return uint8_t 读取到的一字节数据
* @note 无
* @Sample uint8_t byte = OneWire_ReadByte();
**************************************************************/
uint8_t OneWire_ReadByte(void)
{
uint8_t byte = 0x00;
for (uint8_t i = 0; i < 8; i++)
{
uint8_t bit = OneWire_ReadBit();
byte |= (bit << i);
}
return byte;
}
6.2 DS18B20温度采集
1、ds18b20.h
cpp
/*
* @Descripttion: DS18B20驱动
* @Author: JaRyon
* @version: v1.0
* @Date: 2025-10-09 11:35:58
*/
#ifndef __DS18B20_H
#define __DS18B20_H
#include "stm32f10x.h"
#include "Delay.h"
#include "onewire.h"
#include "usart.h"
// ROM指令
#define SKIP_ROM 0XCC
// 操作指令
#define READ_SCRATCHPAD 0XBE
#define CONV_T 0x44
#define NO_DETECT 0
#define DETECTED 1
uint8_t DS18B20_StartConvT(float *T);
#endif
2、ds18b20.c
cpp
/*
* @Descripttion: 外接电源模式下的DS18B20驱动
* @Author: JaRyon
* @version:
* @Date: 2025-10-09 11:35:58
*/
#include "ds18b20.h"
/***************************************************************
* @brief 读取温度(外接电源模式)
* @param float *T 传址 获取的温度
* @return uint8_t DS18B20应答检测状态
* @note 无
* @Sample DS18B20_StartConvT(&T);
**************************************************************/
uint8_t DS18B20_StartConvT(float *T)
{
uint8_t TL, TH;
uint16_t raw;
// 1. DS18B20应答检测
OneWire_ResetPulse();
if (OneWire_ResetPulse() == NACK) { return NO_DETECT;}
// 2. 发送ROM操作指令
// 2.1 跳过ROM
OneWire_WriteByte(SKIP_ROM);
// 3. 启动温度转换,等待转换(<750ms)
OneWire_WriteByte(CONV_T);
Delay_ms(650);
// 4. 再次发生复位脉冲,等待应答
if (OneWire_ResetPulse() == NACK) { return NO_DETECT;}
// 5. 发送ROM操作指令
// 5.1 跳过ROM
OneWire_WriteByte(SKIP_ROM);
// 6. 发送功能指令
// 6.1 读暂存器
OneWire_WriteByte(READ_SCRATCHPAD);
// 7. 记录读出的两字节温度数据
TL = OneWire_ReadByte(); // LSB
TH = OneWire_ReadByte(); // MSB
// 7.1 合并高低位
raw = (TH << 8) | TL;
*T = raw * 0.0625f; // 转换温度值
return DETECTED;
}
6.3 主函数实现
cpp
/*
* @Descripttion: 串口输出DS18B20温度传感器数据(寄存器方式)
* @Author: JaRyon
* @version: v1.0
* @Date: 2025-10-09 11:33:28
*/
#include "ds18b20.h"
#include "usart.h"
int main(void)
{
float temperature;
USART_Init();
OneWire_Init();
while (1)
{
if (DS18B20_StartConvT(&temperature) == DETECTED)
printf("Temperature: %.2f C\n", temperature);
else
printf("No detect DS18B20 Device!\n");
Delay_ms(500);
}
}
最后,还有一部分代码没有展示,如延时函数、串口打印等,原因是在我之前的文章中均介绍过,所以这里不再赘述。
参考资料
DS18B20芯片手册_TDSEMIC/TDSEMIC-DS18B20-TD.pdf
以上便是本次文章的所有内容,欢迎各位朋友在评论区讨论,本人也是一名初学小白,愿大家共同努力,一起进步吧!
鉴于笔者能力有限,难免出现一些纰漏和不足,望大家在评论区批评指正,谢谢!

