基于STM32单片机对DS18B20温度传感器的驱动设计方案


原标题:基于STM32单片机对DS18B20温度传感器的驱动设计方案
基于STM32单片机对DS18B20温度传感器的驱动设计方案
在当今物联网与智能控制系统飞速发展的时代,温度数据采集作为环境感知的重要组成部分,其精度与稳定性直接影响到整个系统的性能。DS18B20作为一款经典的数字温度传感器,以其独特的单总线(One-Wire)通信方式、宽广的测量范围、高精度以及优秀的环境适应性,在工业控制、消费电子、医疗设备、智能家居等诸多领域得到了广泛应用。本设计方案将深入探讨如何基于功能强大的STM32系列微控制器,实现对DS18B20温度传感器的稳定、高效驱动,涵盖硬件选型、软件设计、通信协议解析、数据处理以及常见问题解决方案等多个方面。
1. 系统概述与设计目标
本系统旨在构建一个基于STM32微控制器驱动DS18B20温度传感器的高精度温度采集模块。其核心目标包括:
高精度温度采集: 利用DS18B20传感器本身的高精度(可配置9-12位分辨率)确保测量结果的准确性。
稳定可靠的通信: 实现STM32与DS18B20之间基于单总线协议的稳定数据传输,抵抗干扰。
灵活的硬件接口: 设计合理的硬件连接方案,便于集成与扩展。
高效的软件驱动: 编写模块化、可移植的软件代码,实现DS18B20的初始化、温度读取、配置等功能。
友好的数据输出: 将采集到的温度数据通过串口或其他接口输出,便于上位机显示或进一步处理。
低功耗设计(可选): 考虑在特定应用场景下,如何通过软件和硬件协同实现低功耗运行。
2. 核心元器件选型与功能分析
成功的嵌入式系统设计离不开对核心元器件的精确选择与深刻理解。本章节将详细阐述STM32微控制器与DS18B20温度传感器的选型依据、功能特性以及其在整个系统中的关键作用,并介绍其他必要的辅助元器件。
2.1 微控制器单元(MCU):STM32系列
2.1.1 选型推荐
对于DS18B20的驱动,由于其单总线协议对时序的严格要求,以及未来可能的功能扩展(如LCD显示、网络通信等),推荐选用STM32F103C8T6或STM32F401RCT6。
STM32F103C8T6: 这是一款基于ARM Cortex-M3内核的微控制器,属于STM32F1系列的主流产品。
CPU内核(Cortex-M3): 提供强大的处理能力,可以高效执行DS18B20的单总线时序操作和数据解析算法。
GPIO(通用输入输出): 用于与DS18B20的DQ线直接连接,并通过软件控制其输出高低电平,或配置为输入模式读取数据。F103C8T6的GPIO可以配置为推挽输出、开漏输出、浮空输入、上拉/下拉输入等多种模式,这对于单总线协议中DQ线的控制至关重要。
定时器(Timers): DS18B20的单总线协议对时序有严格要求,例如复位脉冲的持续时间、数据位的采样点等。STM32的定时器可以提供精确的微秒级延时,确保通信的准确性。
NVIC(嵌套向量中断控制器): 可以管理外部中断,虽然DS18B20通常不需要中断来驱动,但在多任务系统中,或需要对通信错误进行实时响应时,中断功能会非常有用。
Flash存储器和SRAM: 用于存储程序代码和运行时数据,包括DS18B20的驱动代码、温度数据、以及其他应用逻辑。
高性价比: F103系列是STM32家族中性价比较高的入门级芯片,非常适合学习和中小型项目开发。它的成本效益使其成为许多原型设计和量产应用的首选。
丰富的资源: 拥有72MHz的主频,64KB的Flash存储器和20KB的SRAM,以及多个通用GPIO、定时器、USART、SPI、I2C等外设。这些资源足以满足DS18B20的时序要求,并为未来扩展其他传感器、通信模块(如蓝牙、Wi-Fi)或显示设备预留了足够的硬件基础。
成熟的生态系统: ST公司为STM32F1系列提供了非常完善的开发工具链(如Keil MDK, STM32CubeIDE)和丰富的例程、库函数(如HAL库、LL库),社区支持广泛,方便开发者快速上手和调试。
管脚兼容性: TSSOP48封装,引脚数量适中,便于PCB设计和手工焊接。
选择理由:
功能:
STM32F401RCT6: 这是一款基于ARM Cortex-M4内核的微控制器,属于STM32F4系列的入门级产品。
FPU(浮点单元): 大幅提升浮点运算速度,对于需要精确温度计算和显示的应用非常有利。
更高效的DMA(直接存储器访问): 可以实现数据在内存和外设之间的高速传输,减轻CPU负担,提高系统整体效率。虽然DS18B20的通信量不大,但DMA在多任务系统中依然能发挥作用。
更宽的工作温度范围和更高可靠性: 部分F4系列芯片设计用于更严苛的工业环境,这可能是一个额外优势。
更高性能: F401系列拥有84MHz的主频,配备浮点单元(FPU),使其在进行复杂数学运算(如温度数据的浮点转换、滤波算法等)时效率更高。如果未来的应用需要更复杂的信号处理或图形显示,F401会提供更好的性能储备。
更大的存储空间: 256KB的Flash和64KB的SRAM,为更大型的应用程序和数据存储提供了充足的空间。
更多高级外设: 比如更多的定时器、DMA控制器、以及更快的ADC等。虽然DS18B20本身不直接使用ADC,但如果系统中还有其他模拟传感器,这些高级外设会很有用。
适用于更复杂的系统: 如果考虑到未来项目可能扩展到更复杂的系统,例如需要运行RTOS(实时操作系统)、进行大量数据处理或与更高速外设通信,F401提供了更强的处理能力和更丰富的外设接口。
选择理由:
功能: 除了F103的所有功能外,F401还具备:总结: 对于初学者和大多数基本温度采集应用,STM32F103C8T6是更具性价比和易用性的选择。如果项目对性能有更高要求,或者有未来扩展至更复杂系统的考虑,STM32F401RCT6将是更好的选择。
2.2 温度传感器:DS18B20
2.2.1 选型推荐
DS18B20是Maxim Integrated公司(现已被Analog Devices收购)生产的一款单总线数字温度传感器。
选择理由:
单总线接口: 这是DS18B20最显著的特点,只需一根数据线(DQ)即可与微控制器进行通信,大大简化了硬件连接。DQ线同时用于供电(寄生电源模式)和数据传输,减少了布线复杂性。
宽测量范围: -55°C 至 +125°C,覆盖了大多数日常及工业应用场景。
高精度: 在-10°C至+85°C范围内,精度可达±0.5°C。用户可配置9位、10位、11位或12位的测量分辨率,对应的转换时间分别为93.75ms、187.5ms、375ms、750ms,精度越高,转换时间越长。
数字输出: 直接输出数字温度值,避免了传统模拟温度传感器(如热敏电阻、PT100)需要进行AD转换的环节,简化了硬件设计,提高了抗干扰能力,且无需校准。
独特ID: 每个DS18B20都有一个独一无二的64位ROM序列码,允许多个DS18B20并联在同一根单总线上,并通过ROM匹配指令进行寻址,实现多点温度测量。
寄生电源模式: 在某些应用中,可以通过数据线(DQ)直接供电,无需额外电源线,进一步简化了布线。但通常建议使用外部电源供电,以保证通信的稳定性和转换的准确性,尤其是在长距离传输或多点测量时。
封装多样性: 提供TO-92、SOP8、防水探头(如不锈钢封装)等多种封装形式,满足不同应用环境的需求。TO-92封装适合板载或短距离测温,防水探头则适合液体或潮湿环境。
功能:
温度测量: 将测得的温度值转换为数字信号,存储在其内部的2字节温度寄存器中。
用户可编程分辨率: 用户可以通过写入配置寄存器来设置温度转换的分辨率。
报警功能: 具有可编程的高低温度报警触发点(TH和TL寄存器),当测得的温度超出设定范围时,DS18B20会设置一个报警标志,并通过报警搜索命令被识别出来。
暂存器(Scratchpad): 包含温度寄存器、高/低报警触发寄存器(TH/TL)、配置寄存器等。
EEPROM: 用于存储配置数据(TH/TL和分辨率设置),即使断电也能保存。
64位ROM序列码: 唯一的硬件地址,用于区分总线上的多个DS18B20。2.3 辅助元器件
2.3.1 上拉电阻
选型推荐: 4.7kΩ ~ 10kΩ 精密电阻。 通常选择4.7kΩ。
作用: DS18B20的单总线协议是一个开漏(Open-Drain)总线结构。这意味着DS18B20或微控制器只能将DQ线拉低(输出逻辑0),而不能主动拉高(输出逻辑1)。当DQ线空闲时,或者需要传输逻辑1时,必须依靠外部的上拉电阻将总线拉高到VCC电平。
选择理由:
协议要求: DS18B20数据手册明确指出需要一个上拉电阻。
保证逻辑高电平: 没有上拉电阻,DQ线将无法达到VCC电平,导致通信错误。
平衡上升/下降时间: 电阻值过大会导致上升时间过长,影响通信速度和可靠性;电阻值过小会增加功耗,并可能对DS18B20的开漏输出电流能力造成过大负担。4.7kΩ是经过验证的典型值,能在确保可靠通信的同时保持较低功耗。对于长距离传输或多点连接,可能需要适当减小电阻值或增加有源上拉电路。
功能:
当总线空闲时,通过电阻将DQ线拉高至逻辑高电平。
当DS18B20或STM32释放总线(停止拉低)时,迅速将DQ线拉高。
为DS18B20提供寄生电源模式下的充电电流。2.3.2 滤波电容
选型推荐: 100nF(0.1μF)陶瓷电容,并联在DS18B20的VCC和GND之间。对于更长的引线或噪声较大的环境,可以在电源入口增加一个10μF或47μF的电解电容。
作用: 滤波电容用于滤除电源线上的高频噪声,提供稳定的电源,以及在DS18B20进行内部操作(如温度转换)时提供瞬时电流。
选择理由:
去耦: 微控制器和传感器在工作时会产生瞬态电流需求,导致电源线上的电压波动。100nF陶瓷电容具有低ESR(等效串联电阻)和良好的高频特性,能有效地进行高频去耦,保证VCC的稳定性。
抗干扰: 减少外部电磁干扰(EMI)对DS18B20电源的冲击,提高测量的准确性和通信的稳定性。
寄生电源模式下的储能: 在寄生电源模式下,DS18B20在温度转换期间会消耗较大的电流。这个电容可以作为能量储存器,在DQ线被拉低时,为DS18B20提供所需的瞬时能量。
功能:
旁路高频噪声。
提供稳定的电源,确保DS18B20内部电路的正常工作。
在电流高峰时为DS18B20提供瞬时能量,防止电源跌落。2.3.3 排针/排母
选型推荐: 2.54mm间距的直插或弯角排针/排母。
作用: 提供便捷的硬件连接接口,便于DS18B20模块与STM32开发板之间的插拔连接,也方便进行调试和测试。
选择理由:
标准化: 2.54mm(0.1英寸)是电子元器件的常用间距,兼容性好。
易于连接: 方便使用杜邦线进行连接,或者直接插在面包板、洞洞板上进行原型开发。
灵活性: 方便更换DS18B20传感器或将模块集成到更大的系统中。
功能:
提供机械连接和电气连接。
方便调试和维护。
3. 硬件连接方案
DS18B20与STM32的硬件连接相对简单,主要涉及供电、地线和数据线(DQ)。
3.1 基本连接
VCC (电源): 连接到STM32开发板的3.3V或5V电源(DS18B20支持3.0V至5.5V供电)。为确保稳定,通常建议连接到5V,因为DS18B20手册规定在温度转换时需要较大的瞬间电流,5V电源可以提供更稳定的供电能力。
GND (地线): 连接到STM32开发板的GND。
DQ (数据线): 连接到STM32的任意一个通用GPIO口。例如,可以连接到PA1。
上拉电阻: 一个4.7kΩ的上拉电阻(Rpu)必须连接在DQ线和VCC之间。这是单总线协议的强制要求。
滤波电容: 一个100nF(0.1μF)的陶瓷电容并联在DS18B20的VCC和GND引脚之间,尽可能靠近DS18B20,用于电源去耦。3.2 连接示意图
+-----------------+ | STM32 | | | DS18B20 | | (TO-92/SOP8) | | ----------- | | | | | | | VCC----|------VCC (3.3V/5V) | | | | | GND----|------GND | | | | | DQ-----|------PA1 (GPIO) | | | | ----------- | | | | +-----------------+ | | Rpu (4.7kΩ) | --- VCC --- | --- 100nF --- 电容 | GND
说明:
图中DS18B20的VCC引脚接STM32的电源,GND接STM32的地。
DS18B20的DQ引脚通过一个4.7kΩ上拉电阻接到VCC,然后直接连接到STM32的某个GPIO引脚(例如PA1)。
100nF的滤波电容并联在DS18B20的VCC和GND引脚之间。3.3 多点连接
DS18B20的单总线特性允许在同一根DQ线上连接多个传感器。此时,每个DS18B20仍然需要自己的上拉电阻和滤波电容(虽然通常情况下,一个总线上的多个DS18B20可以共用一个主上拉电阻,但为每个传感器提供局部滤波电容仍然是好的实践)。STM32通过发送ROM指令(如跳过ROM、匹配ROM、搜索ROM)来与特定的DS18B20通信。
4. 单总线(One-Wire)通信协议解析
DS18B20的驱动核心在于理解并精确实现其单总线通信协议。该协议定义了一系列严格的时序要求,包括初始化、ROM指令和功能指令。所有通信都以最低有效位(LSB)优先传输。
4.1 单总线时序基础
单总线协议是主从协议,STM32作为主设备,DS18B20作为从设备。所有通信都由主设备发起。
4.1.1 协议信号概述
复位(Reset)和存在(Presence)脉冲: 这是每次通信开始的第一个时序。
主设备将DQ线拉低至少480微秒(us),然后释放(上拉电阻将其拉高)。
DS18B20检测到总线被拉低后,会等待15-60us,然后将DQ线拉低60-240us作为存在脉冲,表示其已准备好通信。
主设备在DS18B20释放总线后(即DQ线再次被上拉电阻拉高)15-60us内读取DQ线的状态。如果检测到低电平,则表示存在DS18B20。
写入时隙(Write Time Slot): 主设备向从设备发送数据。每个写入时隙至少需要60us,并在120us内完成。
写入逻辑1: 主设备将DQ线拉低1-15us,然后释放总线(拉高)。
写入逻辑0: 主设备将DQ线拉低60-120us,然后释放总线(拉高)。
在每次写入时隙结束后,总线必须保持空闲至少1us。
读取时隙(Read Time Slot): 主设备从从设备读取数据。每个读取时隙至少需要60us,并在120us内完成。
主设备将DQ线拉低1-15us,然后立即释放总线(拉高)。
主设备在拉低总线后15us内采样DQ线的状态。如果从设备要发送0,则DQ线将保持低电平;如果发送1,则DQ线将保持高电平。
在每次读取时隙结束后,总线必须保持空闲至少1us。4.2 ROM指令
ROM指令用于选择或识别总线上的DS18B20设备。
0x33 - READ ROM: (读取ROM码) 主机发出此命令后,DS18B20将自己的64位ROM码传输给主机。此命令只适用于总线上只有一个DS18B20的情况。
0xCC - SKIP ROM: (跳过ROM码) 主机发出此命令后,DS18B20直接执行后续的功能命令,不再等待ROM码匹配。适用于总线上只有一个DS18B20,或者你确定要与所有DS18B20通信。
0x55 - MATCH ROM: (匹配ROM码) 主机发出此命令后,需要接着发送一个64位的ROM码。总线上只有与该ROM码匹配的DS18B20才会响应后续的功能命令。用于多点测温时精确选择目标DS18B20。
0xF0 - SEARCH ROM: (搜索ROM码) 用于在多点测温系统中查找所有DS18B20的64位ROM码。这是一个复杂的过程,通常通过专门的算法实现。4.3 功能指令
功能指令用于控制DS18B20进行温度转换、读写暂存器等操作。在发送功能指令前,必须先发送ROM指令(通常是SKIP ROM或MATCH ROM)。
0x44 - CONVERT T: (启动温度转换) DS18B20开始进行温度转换。转换过程需要一定时间(取决于分辨率设置,最长750ms)。在转换期间,DQ线会保持低电平,转换完成后会拉高。
0xBE - READ SCRATCHPAD: (读取暂存器) 主机发出此命令后,DS18B20将其内部9字节的暂存器内容传输给主机。暂存器内容包括:
字节0和字节1:温度数据(LSB和MSB)。
字节2和字节3:TH(高温报警阈值)和TL(低温报警阈值)。
字节4:配置寄存器(分辨率设置)。
字节5、字节6、字节7:保留。
字节8:CRC校验码(对前8个字节的CRC校验)。
0x4E - WRITE SCRATCHPAD: (写入暂存器) 主机发出此命令后,需要接着发送TH、TL和配置寄存器三个字节的数据。
0x48 - COPY SCRATCHPAD: (复制暂存器) 将暂存器中的TH、TL和配置寄存器数据复制到EEPROM中,断电后仍然保存。
0xB8 - RECALL E2: (召回EEPROM数据) 将EEPROM中存储的TH、TL和配置寄存器数据召回到暂存器中。通常在DS18B20上电后自动执行,但也可以手动触发。
0xEC - READ POWER SUPPLY: (读取电源供电模式) DS18B20会返回其当前的供电模式(外部供电或寄生供电)。4.4 温度数据解析
DS18B20输出的温度数据是2字节(16位)补码形式。其中,低字节包含温度的低8位,高字节包含温度的高8位。
数据格式:
S Sign (符号位,1为负,0为正)
MSB (最高有效位)
LSB (最低有效位)例如,当分辨率为12位时,数据格式如下:
Bit 15Bit 14Bit 13Bit 12Bit 11Bit 10Bit 9Bit 8Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
SSSSSSSST7T6T5T4T3T2T1T0
导出到 Google 表格
其中,Bit 0 到 Bit 7 是温度的低8位,Bit 8 到 Bit 10 是温度的高3位(12位分辨率),Bit 11 到 Bit 15 是符号位。
转换为摄氏度:读取到16位数据后,需要进行符号扩展和除以对应的权重。
对于正温度:直接将16位数据乘以对应分辨率的权重。
12位分辨率:1/16=0.0625 (circtextC/count)
11位分辨率:1/8=0.125 (circtextC/count)
10位分辨率:1/4=0.25 (circtextC/count)
9位分辨率:1/2=0.5 (circtextC/count)
对于负温度:先取反加1(补码转换),然后乘以权重,结果为负值。示例(12位分辨率):假设读取到数据为0x0191 (二进制 0000 0001 1001 0001) 这是正数,直接换算:0x0191=401 (十进制) 温度 = 401times0.0625=25.0625 (circtextC)
假设读取到数据为0xFE6F (二进制 1111 1110 0110 1111) 这是一个负数(最高位为1)。 取反:0000 0001 1001 0000加1:0000 0001 1001 0001 (十进制 401) 温度 = −401times0.0625=−25.0625 (circtextC)
5. 软件驱动设计与实现
软件驱动是实现DS18B20功能的关键。本节将详细介绍基于STM32HAL库的软件驱动设计,包括GPIO配置、延时函数、单总线基本操作(复位、读写位/字节)以及DS18B20高级功能实现。
5.1 开发环境与库选择
开发环境: Keil MDK-ARM或STM32CubeIDE。推荐使用STM32CubeIDE,它集成了STM32CubeMX,方便进行图形化配置。
库选择: STM32HAL库。 HAL库是ST官方推荐的高级抽象层库,提供了简单易用的API,可以快速开发。虽然LL库(Low-Layer)能提供更精细的控制和更高的效率,但对于DS18B20这种对时序精度要求高,但数据量不大的应用,HAL库的性能也足够。5.2 GPIO配置
DS18B20的DQ线是双向的,既可以作为输出(主设备拉低DQ线),也可以作为输入(主设备读取DQ线)。因此,STM32的GPIO需要频繁地在输出和输入模式之间切换。
C
// 定义DS18B20连接的GPIO端口和引脚#define DS18B20_PORT GPIOA#define DS18B20_PIN GPIO_PIN_1// 设置DQ线为输出模式 (推挽输出)void DS18B20_SetPinOutput(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上拉/下拉,依靠外部4.7kΩ上拉电阻 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速模式 HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);}// 设置DQ线为输入模式 (浮空输入)void DS18B20_SetPinInput(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 浮空输入 GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上拉/下拉,外部已有上拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速模式 HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);}// 拉低DQ线#define DS18B20_DQ_LOW() HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET)// 拉高DQ线 (实际上是释放,依靠上拉电阻拉高)#define DS18B20_DQ_HIGH() HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET)// 读取DQ线状态#define DS18B20_DQ_READ() HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN)
GPIO模式选择的说明:
输出模式: 必须配置为推挽输出(GPIO_MODE_OUTPUT_PP)。虽然DS18B20是开漏设备,但STM32的GPIO在推挽模式下既可以输出高电平也可以输出低电平。当需要拉低总线时,输出低电平;当需要释放总线时,输出高电平,但实际上DQ线是通过外部上拉电阻拉高的,所以即使STM32的输出高电平能力与DS18B20的开漏特性不完全匹配,只要我们配合外部上拉电阻,并确保在需要DS18B20响应时STM32释放DQ线(即配置为输入或输出高电平),就能正常工作。
输入模式: 必须配置为浮空输入(GPIO_MODE_INPUT)。这是因为DQ线需要由DS18B20或外部上拉电阻来控制高低电平,STM32只需被动读取其状态。如果配置为上拉/下拉输入,可能会干扰总线上的电平。
速度: 配置为高速(GPIO_SPEED_FREQ_HIGH),以确保GPIO电平切换足够快,满足DS18B20的严格时序要求。5.3 精确延时函数
DS18B20通信对延时精度要求很高,HAL库的HAL_Delay()函数基于系统时钟节拍(毫秒级),不适合微秒级延时。需要编写一个基于CPU空转或DWT(Data Watchpoint and Trace Unit)的微秒级延时函数。
方法一:基于DWT_CYCCNT寄存器的微秒延时(推荐,精度高)
DWT_CYCCNT是ARM Cortex-M内核自带的一个32位周期计数器,每过一个CPU时钟周期就会加1,因此可以实现非常精确的微秒甚至纳秒级延时。
C
// 在core_cmInstr.h中定义了DWT相关寄存器// 需要在主函数或初始化函数中启用DWT// DWT_Delay_Init() 函数void DWT_Delay_Init(void){ CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能DWT DWT->LAR = 0xC5ACCE55; // 解锁DWT DWT->CYCCNT = 0; // 清零计数器 DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 使能计数器}// 微秒延时函数void DWT_Delay_us(uint32_t us){ uint32_t start_tick = DWT->CYCCNT; uint32_t delay_ticks = us * (SystemCoreClock / 1000000); // 1us对应的时钟周期数 while ((DWT->CYCCNT - start_tick) < delay_ticks);}
注意: SystemCoreClock 变量通常由STM32CubeMX生成,表示CPU主频。使用DWT延时需要在CubeMX中配置调试接口为Trace模式。
方法二:基于__nop()的软件延时(精度较差,不推荐用于精确时序)
这种方法通过执行空指令来消耗CPU周期,但精度受编译器优化、CPU缓存、中断等因素影响。
C
// 伪代码,实际延时需要根据CPU主频和指令周期数进行调整// 不推荐用于DS18B20这类需要精确时序的场景void delay_us(uint32_t us){ for (volatile uint32_t i = 0; i < us * (SystemCoreClock / 1000000 / NOP_PER_US); i++) { __nop(); // 执行空操作 }}
5.4 单总线基本操作实现
基于前面定义的GPIO操作和延时函数,可以实现单总线协议中的复位、写位和读位。
5.4.1 单总线初始化(复位与存在脉冲)
C
// DS18B20复位函数,返回1表示复位成功,0表示DS18B20不存在uint8_t DS18B20_Reset(void){ uint8_t presence = 0; DS18B20_SetPinOutput(); // 设置为输出模式 DS18B20_DQ_LOW(); // 拉低DQ线 DWT_Delay_us(480); // 保持低电平至少480us (复位脉冲) DS18B20_DQ_HIGH(); // 释放DQ线 (上拉电阻拉高) DWT_Delay_us(70); // 等待DS18B20响应 (15-60us等待,这里取70us确保DS18B20开始响应) DS18B20_SetPinInput(); // 设置为输入模式,读取存在脉冲 if (DS18B20_DQ_READ() == GPIO_PIN_RESET) // 检测到低电平,表示DS18B20存在 { presence = 1; } DWT_Delay_us(410); // 等待存在脉冲结束 (DS18B20将DQ拉低60-240us,然后释放,这里等待410us确保其完全释放) return presence;}
5.4.2 写入一个位(Write Bit)
C
// DS18B20写入一个位void DS18B20_WriteBit(uint8_t bit){ DS18B20_SetPinOutput(); // 设置为输出模式 if (bit == 1) // 写入逻辑1 { DS18B20_DQ_LOW(); // 拉低DQ线 DWT_Delay_us(5); // 保持低电平1-15us (这里取5us) DS18B20_DQ_HIGH(); // 释放DQ线 DWT_Delay_us(65); // 等待时隙结束 (至少60us,这里取65us) } else // 写入逻辑0 { DS18B20_DQ_LOW(); // 拉低DQ线 DWT_Delay_us(65); // 保持低电平60-120us (这里取65us) DS18B20_DQ_HIGH(); // 释放DQ线 DWT_Delay_us(5); // 等待时隙结束 (确保总线空闲) }}
5.4.3 读取一个位(Read Bit)
C
// DS18B20读取一个位,返回0或1uint8_t DS18B20_ReadBit(void){ uint8_t bit = 0; DS18B20_SetPinOutput(); // 设置为输出模式 DS18B20_DQ_LOW(); // 拉低DQ线 DWT_Delay_us(5); // 保持低电平1-15us (这里取5us) DS18B20_DQ_HIGH(); // 释放DQ线 (准备采样) DS18B20_SetPinInput(); // 设置为输入模式,读取DQ线 DWT_Delay_us(10); // 等待15us采样窗口的中间点 (5us拉低 + 10us等待 = 15us) if (DS18B20_DQ_READ() == GPIO_PIN_SET) // 采样DQ线 { bit = 1; } DWT_Delay_us(50); // 等待读取时隙结束 (总共60us,已经用了15us,再等50us) return bit;}
5.4.4 写入一个字节(Write Byte)
C
// DS18B20写入一个字节void DS18B20_WriteByte(uint8_t data){ for (int i = 0; i < 8; i++) { DS18B20_WriteBit(data & 0x01); // 从最低位开始写入 data >>= 1; }}
5.4.5 读取一个字节(Read Byte)
C
// DS18B20读取一个字节uint8_t DS18B20_ReadByte(void){ uint8_t data = 0; for (int i = 0; i < 8; i++) { data >>= 1; // 每次读取一位,数据左移,低位进来 if (DS18B20_ReadBit()) { data |= 0x80; // 将读取到的位放到最高位(因为每次右移,最高位会被挤掉,所以需要反向操作) } } return data;}
修正: DS18B20_ReadByte 的逻辑,由于DS18B20通信是LSB优先,读取时应该从最低位开始组装字节。
C
// DS18B20读取一个字节 (LSB优先)uint8_t DS18B20_ReadByte(void){ uint8_t data = 0; for (int i = 0; i < 8; i++) { // 每次读取一位,然后将该位放到字节的正确位置 if (DS18B20_ReadBit()) { data |= (0x01 << i); // 将读取到的位放到对应位置 } } return data;}
5.5 DS18B20高级功能实现
结合上述基本操作,可以实现DS18B20的温度读取等功能。
5.5.1 获取温度函数
C
// 读取DS18B20温度函数float DS18B20_GetTemp(void){ uint8_t temp_H, temp_L; int16_t raw_temp; float temp_C; if (DS18B20_Reset() == 0) // 复位失败,DS18B20可能不存在或通信异常 { return -200.0; // 返回一个特殊值表示错误 } DS18B20_WriteByte(0xCC); // 跳过ROM命令 (适用于单DS18B20) DS18B20_WriteByte(0x44); // 启动温度转换 // 等待温度转换完成 // 可以通过读取DQ线,等待其变为高电平(非寄生供电模式) // 或者直接延时750ms(12位分辨率最长转换时间) // 推荐使用DWT_Delay_us(750000); 等待 // 更稳妥的方式是轮询DQ线,直到它变为高电平 while(DS18B20_ReadBit() == 0) // 等待DQ线变为高电平 (转换完成) { // 可以加入超时机制防止死循环 // HAL_Delay(1); // 每次等待1ms // count++; // if(count > 1000) break; // 超时 } // 或者直接使用固定延时(简单但可能不准确) DWT_Delay_us(750000); // 12位分辨率最长转换时间 if (DS18B20_Reset() == 0) // 再次复位,准备读取数据 { return -200.0; } DS18B20_WriteByte(0xCC); // 跳过ROM命令 DS18B20_WriteByte(0xBE); // 读取暂存器命令 temp_L = DS18B20_ReadByte(); // 读取温度低字节 temp_H = DS18B20_ReadByte(); // 读取温度高字节 // 组合成16位原始温度值 raw_temp = (temp_H << 8) | temp_L; // 转换为摄氏度(假设12位分辨率) temp_C = (float)raw_temp * 0.0625f; // 12位分辨率,每位0.0625℃ // 这里可以进一步读取CRC校验字节,进行数据校验,提高可靠性 // uint8_t crc = DS18B20_ReadByte(); return temp_C;}
5.5.2 设置DS18B20分辨率(可选)
DS18B20允许设置9-12位的分辨率。
C
// 设置DS18B20分辨率函数// resolution: 9, 10, 11, 12uint8_t DS18B20_SetResolution(uint8_t resolution){ uint8_t config_reg = 0; // 配置寄存器值 if (DS18B20_Reset() == 0) { return 0; // 失败 } // 读取当前暂存器内容,保留TH和TL,只修改配置寄存器 DS18B20_WriteByte(0xCC); // SKIP ROM DS18B20_WriteByte(0xBE); // READ SCRATCHPAD // 丢弃前两个字节的温度数据 (这里我们不读取,因为我们只关心配置寄存器) DS18B20_ReadByte(); // temp_L DS18B20_ReadByte(); // temp_H uint8_t TH = DS18B20_ReadByte(); // TH uint8_t TL = DS18B20_ReadByte(); // TL config_reg = DS18B20_ReadByte(); // 配置寄存器 // 根据分辨率设置配置寄存器 switch (resolution) { case 9: config_reg &= 0x7F; // R1=0, R0=0 -> 9位 break; case 10: config_reg &= 0x9F; // R1=0, R0=1 -> 10位 config_reg |= 0x20; break; case 11: config_reg &= 0xBF; // R1=1, R0=0 -> 11位 config_reg |= 0x40; break; case 12: config_reg |= 0x60; // R1=1, R0=1 -> 12位 break; default: return 0; // 无效分辨率 } if (DS18B20_Reset() == 0) { return 0; // 失败 } DS18B20_WriteByte(0xCC); // SKIP ROM DS18B20_WriteByte(0x4E); // WRITE SCRATCHPAD DS18B20_WriteByte(TH); // 写入TH DS18B20_WriteByte(TL); // 写入TL DS18B20_WriteByte(config_reg); // 写入配置寄存器 if (DS18B20_Reset() == 0) // 复位,准备复制到EEPROM { return 0; // 失败 } DS18B20_WriteByte(0xCC); // SKIP ROM DS18B20_WriteByte(0x48); // COPY SCRATCHPAD (保存到EEPROM) HAL_Delay(10); // 等待复制完成,大约10ms return 1; // 成功}
5.6 CRC校验(提高数据可靠性)
DS18B20的暂存器读取包含一个8位的CRC校验码。在读取数据后计算CRC并与读取到的CRC校验码进行比对,可以有效检测通信过程中是否发生错误,提高数据可靠性。
实现CRC校验需要一个CRC8算法。DS18B20使用的是Dallas Semiconductor的CRC8算法。
5.7 多点DS18B20驱动(ROM搜索)
如果需要连接多个DS18B20,则需要实现ROM搜索算法。ROM搜索是一个迭代过程,通过发送0xF0 SEARCH ROM指令,DS18B20会返回其ROM码中的冲突位,主机根据冲突位来遍历所有可能的ROM码,最终找到总线上所有DS18B20的唯一ID。这个算法相对复杂,需要维护一个搜索状态变量和ROM地址数组。
大致步骤:
发送复位和存在脉冲。
发送SEARCH ROM (0xF0) 命令。
循环8次(每个字节)或64次(每个位): a. 读取两位(DS18B20返回的“0”和“1”的冲突信息)。 b. 根据冲突信息和上一次搜索路径,决定发送“0”或“1”以解决冲突。 c. 记录当前路径的ROM码。
重复直到所有ROM码被发现。由于篇幅限制,这里不提供完整的ROM搜索算法代码,但这是实现多点DS18B20测量的基础。
6. 软件流程图与主程序设计
一个典型的DS18B20温度采集主程序流程如下:
6.1 主程序流程图
代码段
graph TD A[系统初始化:时钟、GPIO、DWT等] --> B{DS18B20复位成功?} B -- 否 --> C[报错:传感器不存在或通信异常] B -- 是 --> D[发送SKIP ROM命令 (0xCC)] D --> E[发送CONVERT T命令 (0x44)] E --> F[等待温度转换完成 (约750ms或轮询DQ线)] F --> G{DS18B20再次复位成功?} G -- 否 --> C G -- 是 --> H[发送SKIP ROM命令 (0xCC)] H --> I[发送READ SCRATCHPAD命令 (0xBE)] I --> J[读取9字节暂存器数据 (包括温度高低字节和CRC)] J --> K[进行CRC校验] K -- 校验失败 --> C K -- 校验成功 --> L[解析温度数据] L --> M[将温度数据通过串口/LCD等方式显示/发送] M --> N[延时一段时间 (例如1秒) ] N --> D
6.2 主程序代码骨架
#include "main.h"
#include "stm32f1xx_hal.h" // 根据实际MCU系列选择
#include "ds18b20.h" // 包含DS18B20驱动头文件
#include <stdio.h> // 用于printf输出
// 定义串口句柄,用于调试输出
extern UART_HandleTypeDef huart1; // 假设使用UART1
void SystemClock_Config(void); // 系统时钟配置函数
static void MX_GPIO_Init(void); // GPIO初始化函数
static void MX_USART1_UART_Init(void); // 串口初始化函数
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
DWT_Delay_Init(); // 初始化DWT用于微秒延时
float temperature = 0.0f;
char temp_str[30];
// 可选:设置DS18B20分辨率为12位
// DS18B20_SetResolution(12); // 如果需要设置,请在循环外执行一次
while (1)
{
temperature = DS18B20_GetTemp_WithCRC(); // 获取温度 (带CRC校验)
if (temperature > -100.0f) // 假设-200.0或-201.0是错误码
{
sprintf(temp_str, "Temperature: %.2f C ", temperature);
}
else if (temperature == -200.0f)
{
sprintf(temp_str, "DS18B20 Not Found or Reset Failed! ");
}
else if (temperature == -201.0f)
{
sprintf(temp_str, "DS18B20 CRC Error! Data Corrupted! ");
}
HAL_UART_Transmit(&huart1, (uint8_t*)temp_str, strlen(temp_str), 100);
HAL_Delay(1000); // 1秒更新一次
}
}
// 示例:SystemClock_Config, MX_GPIO_Init, MX_USART1_UART_Init 由STM32CubeMX生成
//
7. 调试与注意事项
在DS18B20驱动设计与实现过程中,可能会遇到一些问题。理解并解决这些问题是确保系统稳定运行的关键。
7.1 常见问题与解决方案
DS18B20复位失败(无存在脉冲):
检查硬件连接: 确保VCC、GND、DQ线连接正确,特别是4.7kΩ上拉电阻是否正确连接且阻值合适。
检查供电: 确保DS18B20供电电压在3.0V-5.5V之间,且供电稳定。尝试使用独立供电而不是寄生电源模式。
检查GPIO配置: 确保STM32的GPIO设置为正确的输出/输入模式,并且在释放总线时能正确变为输入或高电平输出。
检查延时函数: DS18B20时序对延时精度要求很高。确保微秒延时函数(DWT_Delay_us)工作正常且准确。示波器是调试时序的利器。
DS18B20损坏: 少数情况下传感器本身可能损坏。尝试更换一个DS18B20。读取温度数据异常(始终为85℃或0℃):
检查READ SCRATCHPAD (0xBE) 命令是否正确发送。
检查DS18B20_ReadByte()函数,确保数据位能正确地被STM32读取。使用示波器观察DQ线的数据波形。
检查温度数据解析算法,特别是负数的补码转换和浮点数转换。检查CONVERT T (0x44) 命令是否正确发送。
确保在发送完CONVERT T命令后有足够的等待时间(12位分辨率需要750ms)让DS18B20完成转换。85℃: DS18B20在上电或复位后的默认温度值是85℃。如果一直读到这个值,说明温度转换没有成功启动或者数据读取有问题。
0℃: 可能是在读取数据时,接收到的都是0,或者数据解析错误。多点测温时无法识别所有传感器:
ROM搜索算法问题: ROM搜索算法比较复杂,任何一个细节错误都可能导致部分或全部传感器无法被发现。仔细对照DS18B20数据手册中的SEARCH ROM流程。
总线负载过大: 当连接的DS18B20数量过多或DQ线过长时,总线的容性负载会增加,导致信号上升沿变慢。此时可能需要减小上拉电阻的阻值(例如2.2kΩ),或者增加有源上拉电路(如使用一个MOSFET)。CRC校验失败:
通信不稳定: 检查电源稳定性,确保没有大的噪声干扰。
时序问题: 再次检查所有时序延时是否严格符合DS18B20数据手册要求。任何微小的时序偏差都可能导致数据传输错误。
CRC算法实现错误: 仔细核对CRC8算法是否严格按照Dallas Semiconductor的规范实现。7.2 调试工具
示波器: 这是调试单总线通信必不可少的工具。通过示波器可以直观地观察DQ线的波形,判断复位脉冲、存在脉冲、写入时隙和读取时隙的时序是否符合要求,以及数据位的电平是否正确。特别注意信号的上升沿和下降沿。
逻辑分析仪: 如果没有示波器,或者需要同时观察多路信号,逻辑分析仪也是一个很好的选择。它可以捕捉数字信号,并解析出协议内容,方便分析通信数据。
串口调试助手: 用于接收STM32发送的温度数据和调试信息,验证程序逻辑是否正确。
STM32在线调试器(ST-Link/J-Link): 用于程序的烧录、单步调试、变量观察,是定位软件逻辑错误的必备工具。7.3 功耗优化(可选)
在某些对功耗有严格要求的应用中,可以考虑以下优化:
降低DS18B20的采样频率: DS18B20在进行温度转换时功耗最高。减少温度读取的频率可以显著降低系统平均功耗。
STM32进入低功耗模式: 在等待DS18B20转换完成的长时间延时期间,可以将STM32进入停止模式(Stop Mode)或待机模式(Standby Mode),然后通过定时器或外部中断唤醒。
控制DS18B20的供电: 在极端低功耗场景下,可以通过一个GPIO控制MOSFET来切换DS18B20的VCC供电,只在需要测量时上电。
8. 总结与展望
本设计方案详细阐述了基于STM32微控制器驱动DS18B20温度传感器的全过程,从核心元器件选型、硬件连接、单总线通信协议解析到软件驱动的详细实现,并提供了关键代码片段。通过精确的GPIO控制、严格的时序延时以及完善的CRC校验,可以构建一个稳定、高精度的温度采集系统。
DS18B20因其独特的单总线特性和优秀的性能,在众多应用中仍占有一席之地。未来,在此基础上可以进一步扩展,例如:
多点温度网络: 实现复杂的ROM搜索算法,构建多达上百个DS18B20的温度传感器网络。
数据存储与显示: 将温度数据存储到EEPROM或SD卡中,并通过LCD、OLED显示屏进行实时显示。
无线传输: 集成Wi-Fi(ESP8266/ESP32)、蓝牙或LoRa模块,将温度数据上传至云平台或远程监控。
温度控制: 根据采集到的温度数据,通过PID算法或其他控制策略,控制风扇、加热器等执行机构,实现智能温度控制。
更高级的软件架构: 引入实时操作系统(RTOS),将DS18B20驱动封装为独立的任务,提高系统的并发性和可维护性。掌握DS18B20的驱动不仅是温度采集的基础,更是深入理解单总线通信协议和嵌入式系统时序控制的绝佳实践。希望本设计方案能为您的开发工作提供全面而有价值的参考。
责任编辑:David
【免责声明】
1、本文内容、数据、图表等来源于网络引用或其他公开资料,版权归属原作者、原发表出处。若版权所有方对本文的引用持有异议,请联系拍明芯城(marketing@iczoom.com),本方将及时处理。
2、本文的引用仅供读者交流学习使用,不涉及商业目的。
3、本文内容仅代表作者观点,拍明芯城不对内容的准确性、可靠性或完整性提供明示或暗示的保证。读者阅读本文后做出的决定或行为,是基于自主意愿和独立判断做出的,请读者明确相关结果。
4、如需转载本方拥有版权的文章,请联系拍明芯城(marketing@iczoom.com)注明“转载原因”。未经允许私自转载拍明芯城将保留追究其法律责任的权利。
拍明芯城拥有对此声明的最终解释权。