最近在做一个环境监测的小项目,需要在单片机上读取温湿度数据。翻了一圈传感器,最后又用回了DHT11。说实话,这颗芯片在工程师圈子里口碑两极分化很严重,有人觉得它精度一般、时序敏感,有人觉得它便宜好用、单根线就能通信。我的实际体验是:只要把它的协议真正吃透,DHT11在低成本、低复杂度的场景里依然是非常能打的选择。这篇博文就从器件原理、单总线协议细节到代码实现,完整拆一遍我在实战中总结出来的方案,希望能帮你少踩几个坑。
先说清楚这篇文章适合谁:手头有STM32或者类似单片机,想用DHT11但读出来的数据偶尔是0或者干脆没反应的人;以及刚接触单总线通信,想理解“一根线怎么既发命令又收数据”的初学者。如果你只是调包侠,拿着现成库一顿复制粘贴,那可以关掉了,因为这篇文章不讲“怎么抄”,讲的是“为什么这么做”。
1. 内容整体设计与思路拆解
1.1 为什么选DHT11而不是DHT22或SHT30
很多人在选型时会纠结,同价位附近有DHT22、SHT30这些看起来参数更漂亮的传感器。我的看法是,选型首先要看场景。
DHT11的核心优势是:单总线通信,只需要一根数据线,主控IO资源极度节省;价格极其便宜,批量采购一两块钱一颗;软件模拟协议成熟,不需要额外的硬件I2C或SPI外设。缺点是精度一般,湿度误差±5%RH,温度误差±2℃,测量范围也窄(0到50℃,20%到90%RH),更新速率只有1Hz。
如果你的项目是智能家居里的大棚环境监测、机房温湿度记录这种对精度要求不高的场景,DHT11完全够用。但如果你做的是医疗设备、精密仪器校准,那直接上SHT30或者BME280,别在DHT11上浪费时间。这就像买车,DHT11是代步小车,SHT30是性能车,先想清楚用途再掏钱。
1.2 单总线通信的核心思路:一根线如何完成双向通信
单总线(1-Wire)协议的名字很直白,就是一根数据线完成主机和从机之间的双向通信。DHT11用的就是这种通信方式,和DS18B20属于同一个家族思路。
它的工作方式可以类比成两个人用一根绳子传纸条:某个人先拉一下绳子(主机拉低总线),告诉对方“我要开始说话了”,然后松开绳子(拉高释放总线),轮到对方回应。DHT11接收到主机的起始信号后,会发回一串脉冲,主机通过测量每个脉冲的宽度来判断数据是0还是1。
一根线双向通信的关键是“时分复用”和“开漏输出”。DHT11的数据引脚属于开漏结构,外部需要接一个上拉电阻(通常是4.7kΩ到10kΩ),默认状态下总线被上拉到高电平。主机想发送信号时,把引脚配置为输出模式,拉低总线;想接收数据时,把引脚配置为输入模式,释放总线,让上拉电阻把电平拉高,然后检测从机拉低总线的时刻。
这个设计的好处是节省引脚,坏处是时序要求严格,稍有偏差就通信失败。下面第二章节详细讲协议层的每个时间参数。
2. 单总线协议深度拆解:从时序图读懂DHT11
2.1 DHT11的数据帧结构和校验机制
先看通信数据格式。DHT11一次完整传输40位数据,顺序是:
- 湿度整数部分(8位)
- 湿度小数部分(8位)
- 温度整数部分(8位)
- 温度小数部分(8位)
- 校验和(8位)
需要特别说明的是,DHT11的“小数部分”在实际应用中基本是0,因为它的分辨率就是1%RH和1℃。但协议里依然保留了小数位,读取的时候要当成有效数据来处理,校验和的计算是把前四个字节加起来,取低8位。如果算出来和校验字节不一致,这帧数据就得丢弃。
举个例子,如果读到湿度整数0x32(50%RH)、湿度小数0x00、温度整数0x1A(26℃)、温度小数0x00、校验和0x4C,那校验和就等于0x32+0x00+0x1A+0x00=0x4C,说明数据有效。
2.2 主机起始信号和DHT11响应时序
通信的第一步是主机发起起始信号。具体操作是:
- 主机把数据引脚拉低,持续时间至少18ms。我实测下来18ms是下限,用20ms更稳妥,因为有些DHT11批次对18ms响应不够干脆。
- 主机释放总线(拉高),然后等待20到40us。
- 此时DHT11会主动拉低总线80us,作为响应信号,然后再拉高80us,准备发送数据。
从主机的视角看,这个响应信号是“一个低脉冲接一个高脉冲”。如果主机发送起始信号后,等了好久都没有看到这串脉冲,大概率是接线问题或者传感器坏了。
时序参数在这个阶段有一个很容易被忽略的细节:主机拉低的时间不能太长也不能太短。太长会让DHT11误判为持续低电平故障,太短则DHT11根本没反应过来。我之前有一次用延时函数不精准,实际拉低了30多ms,DHT11偶尔能响应偶尔不能,折腾了很久才发现是延时过长的问题。
2.3 数据位“0”和“1”的区分方式
起始信号之后,DHT11开始发送40位数据。每一位数据的结构都一样:先拉低总线50us,然后拉高总线,高电平持续的时间决定了这一位是0还是1。
关键的时间标准在这里:
- 高电平持续26到28us,表示“0”
- 高电平持续70us左右,表示“1”
读取数据的代码思路就很清晰了:等DHT11拉低总线(每一位开始的50us低电平),然后等总线拉高,再测量从拉高到再次拉低之间的时间。如果高电平时间小于50us,判为0;如果大于50us,判为1。
这里要注意的是,不同厂家的DHT11时序参数会有微小差异,判0/1的阈值不要卡得太死。从26us到70us之间差距很大,取一个50us的中间值作为阈值,容错性最好。有些教程里用40us作为阈值,也能用,但我觉得50us更稳。
2.4 通信结束与总线释放
40位数据全部发送完毕后,DHT11会释放总线,由上拉电阻把电平拉高。此时一次完整的通信过程结束,总线回到空闲状态。
空闲状态的高电平很重要。很多人在写完读取代码后,发现第二次读取失败,就是因为上一次通信结束后总线没有恢复到正确状态。DHT11的采样周期是1秒,也就是两次起始信号之间至少间隔1秒,否则传感器可能还在处理上一次的数据,不会正常响应。实际使用中,我习惯把读取周期设在1.5秒到2秒,留出足够余量。
3. 实战代码全拆解:STM32 HAL库下的DHT11驱动
3.1 硬件准备和最小电路
写代码之前先把硬件接对。DHT11通常有3个引脚(有的模块是4脚,其中一个悬空):
- VCC接3.3V或5V都可以,DHT11的工作电压范围是3.3V到5.5V
- GND接地
- DATA数据线,接单片机的一个普通GPIO,同时通过一个4.7kΩ上拉电阻接到VCC
如果你用的是现成的DHT11模块,大部分模块上已经集成了上拉电阻和滤波电容,直接接杜邦线就行。如果是裸的传感器,别偷懒,上拉电阻一定要加,否则总线浮空,通信必然失败。
在主控选择上,我以STM32F103系列配合HAL库为例。为什么选这个组合?因为STM32F1是市面上保有量最大的MCU之一,HAL库也是现在CubeMX默认生成的代码框架,通用性最强。你用其他型号或者标准库,逻辑是完全一样的。
3.2 微秒级延时函数的实现
DHT11时序操作对延时精度要求很高,20us、50us、70us这些时间参数,普通单片机自带的延时函数很难做到精确。所以第一步要搞定微秒级延时。
在STM32上,我推荐两种方案。第一种是用定时器:配置一个1us递增的定时器,延时时候读取计数器值做忙等。第二种是用DWT(Data Watchpoint and Trace)模块,这是Cortex-M3/M4内核自带的周期计数器,精度高且不占用额外的定时器资源。
HAL库下用DWT实现延时的代码很简洁:
#include "stm32f1xx_hal.h" static volatile uint32_t *DWT_CYCCNT = (uint32_t *)0xE0001004; static volatile uint32_t *DWT_CONTROL = (uint32_t *)0xE0001000; static volatile uint32_t *SCB_DEMCR = (uint32_t *)0xE000EDFC; void DWT_Delay_Init(void) { *SCB_DEMCR |= 0x01000000; // 使能DWT访问 *DWT_CYCCNT = 0; // 清零周期计数器 *DWT_CONTROL |= 1; // 使能周期计数器 } void DWT_Delay_us(uint32_t us) { uint32_t start = *DWT_CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((*DWT_CYCCNT - start) < ticks); }这段代码的核心思路是:利用CPU的机器周期计数,1us对应的周期数等于SystemCoreClock除以1000000。如果你的系统时钟是72MHz,那1us就是72个周期。注意减法的无符号运算特性,即使计数器回绕也能正确计算差值,所以不用担心溢出问题。
3.3 GPIO模式的动态切换
DHT11通信过程中,主机的GPIO要不断在输出模式和输入模式之间切换:发送起始信号时是输出模式,读取响应和数据时是输入模式。
HAL库下将GPIO从输出切换到输入,最简单的办法是用HAL_GPIO_WritePin配合GPIO_MODE_OUTPUT,同时修改GPIO初始化结构体。但频繁调用HAL_GPIO_Init会带来不小的开销,时序上容易出问题。更推荐直接用寄存器操作,修改CRL或CRH寄存器。
STM32F103的PA0到PA7对应CRL寄存器,PA8到PA15对应CRH寄存器。每个引脚占用4位,其中MODEx位控制模式,CNFy位控制配置类型。下面这个函数可以把指定引脚切换为推挽输出或浮空输入:
void DHT11_Pin_Output(void) { GPIOA->CRL &= ~(GPIO_CRL_CNF0_Msk); // 清CNF位 GPIOA->CRL |= (0 << 2); // CNF=00,推挽输出 GPIOA->CRL &= ~(GPIO_CRL_MODE0_Msk); // 清MODE位 GPIOA->CRL |= (3 << 0); // MODE=11,50MHz输出 } void DHT11_Pin_Input(void) { GPIOA->CRL &= ~(GPIO_CRL_CNF0_Msk); // 清CNF位 GPIOA->CRL |= (1 << 2); // CNF=01,浮空输入 GPIOA->CRL &= ~(GPIO_CRL_MODE0_Msk); // MODE=00,输入模式 }这里以PA0为例,如果你的引脚不是PA0,按同样的思路修改对应的位偏移即可。用寄存器操作的好处是切换速度极快,几个机器周期就搞定了,不会破坏时序。
3.4 完整读取时序的代码实现
在DWT延时和GPIO动态切换都准备好之后,就可以开始写读取函数了。整体流程分四步:
- 主机拉低总线20ms,发送起始信号
- 释放总线,切换为输入模式,等待DHT11响应
- 检测80us低电平响应信号和80us高电平就绪信号
- 依次读取40位数据,最后做校验
直接上代码:
uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] = {0, 0, 0, 0, 0}; uint8_t i, j; // 主机发送起始信号 DHT11_Pin_Output(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); DWT_Delay_us(20000); // 拉低20ms HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); DWT_Delay_us(30); // 释放总线,等待30us // 切换输入模式,等待响应 DHT11_Pin_Input(); // 等待DHT11拉低总线(响应信号开始) uint16_t timeout = 0; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { if (++timeout > 1000) return 0; // 超时退出 } // 等待DHT11拉高总线(80us低电平结束) timeout = 0; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { if (++timeout > 1000) return 0; } // 等待DHT11再次拉低总线(80us高电平结束,即将发送数据) timeout = 0; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { if (++timeout > 1000) return 0; } // 读取40位数据 for (j = 0; j < 5; j++) { for (i = 0; i < 8; i++) { // 等待50us低电平结束 timeout = 0; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { if (++timeout > 1000) return 0; } // 延时30us,避开0和1高电平的交界区 DWT_Delay_us(30); // 如果30us后仍然是高电平,说明是1;否则是0 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { data[j] |= (0x80 >> i); } // 等待高电平结束,进入下一位的50us低电平 timeout = 0; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) { if (++timeout > 1000) return 0; } } } // 校验 if ((uint8_t)(data[0] + data[1] + data[2] + data[3]) != data[4]) { return 0; } *humidity = data[0]; *temperature = data[2]; return 1; }这套代码的读位思路是“延时判电平”:低电平结束(即50us低电平的最后时刻)后,延时30us,如果此时总线仍然是高电平,就判定为数据位1。因为数据位0的高电平只有26到28us,30us之后已经变低;数据位1的高电平有70us,30us之后依然维持高电平。
这个技巧比“等待高电平结束再测量宽度”要快得多,也避免了测量高电平宽度时的额外开销。不过要注意,30us这个延时值有讲究,不同主频下需要微调。72MHz的STM32F103上,我实测30us是最佳值,偏低或偏高都会导致误判。
3.5 容易忽略的中断和优先级问题
还有一个特别容易踩的坑是中断。如果在读取DHT11的过程中,来一个UART中断或者定时器中断,中断处理函数执行时间超过10us,那整个时序就乱了。数据位0的高电平只有26us左右,一个高优先级中断就能把这个窗口直接吃掉,导致误判。
解决办法有几种:
- 如果项目里中断比较多,在读取DHT11之前用
__disable_irq()关闭全局中断,读完再恢复。但这样会影响系统实时性,不适合中断密集的应用。 - 把DHT11的读取放到一个高优先级的定时器中断里执行,并且中断服务函数里只做读取操作,不做数据处理。
- 使用RTOS时,把DHT11读取放在一个紧急任务里,并临时挂起其他任务。
我的建议是,除非你的系统特别简单,否则不要关全局中断,尽量预留一个专用定时器中断来处理DHT11的读取。这个思路在后续做多传感器采集时尤其重要,因为每个传感器都会占用一段不可被打断的时间窗口。
4. 常见问题与排查技巧实录
4.1 读取数据一直为0或校验失败
这是我被问得最多的问题。数据一直为0,通常不是传感器坏了,而是主机压根没等到响应信号。排查顺序按照从易到难:
- 先量电压。确认DHT11的VCC引脚电压在3.3V到5.5V之间,GND确实接地。
- 检查上拉电阻。数据线上拉电阻是否焊接好,阻值是不是4.7kΩ到10kΩ之间。阻值太小会增大功耗,阻值太大会让电平变化变慢,影响时序。
- 用示波器或者逻辑分析仪抓数据引脚波形。主机起始信号有没有发出去,DHT11有没有响应脉冲。没有示波器的话,可以写一段代码让DHT11引脚翻转点灯,观察是否正常。
校验失败说明数据传回来了,但内容不对,通常是延时精度不够。检查你的微秒延时函数在72MHz下是否准确,用示波器实测一下引脚高低电平时长,对比协议标准。另一个常见原因是供电电压太低,有些主板3.3V电源纹波大,DHT11工作不稳定,换成5V供电试试。
4.2 DHT11数据偶尔跳变或长时间无响应
如果数据大部分时候正常,偶尔跳变,优先怀疑干扰。DHT11的数据线如果和电源线、电机驱动线平行走线,电磁干扰会直接影响时序判断。解决办法是让数据线尽量短,远离大电流线路,必要时在传感器的VCC和GND之间加一个100nF的去耦电容。
如果出现“长时间无响应”的问题,时间间隔超过10秒,大概率是传感器进入了错误状态。这种状态下即使重新发送起始信号也没用,需要给传感器断电重启。代码层面可以做一个自动恢复机制:连续读取失败N次后,主动切断DHT11供电几十毫秒再上电,强制复位。这是个很实用的招,我在户外环境监测的项目里靠这个办法把故障率降了一个量级。
4.3 常见问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 读取超时,无响应 | 接线错误或上拉电阻缺失 | 检查VCC/GND/DATA接线,补焊4.7kΩ上拉电阻 |
| 数据为0但偶有成功 | 起始信号延时不足 | 拉低时间从18ms增加到20ms以上 |
| 校验总是失败 | 微秒延时不准或中断干扰 | 用DWT或定时器实现延时,读取期间关中断或提高优先级 |
| 读取值明显偏高/偏低 | 测量环境有热源或湿度干扰 | 检查传感器周围是否有发热元件,避免紧贴PCB铜皮 |
| 连续多次读取失败后稳定失败 | 传感器进入错误状态 | 断电几十毫秒强制复位,或降低读取频率 |
| 每次读取周期必须大于1秒 | 采样周期限制 | 两次读取之间间隔1.5秒以上 |
4.4 实战中使用频率控制的经验
最后一个建议,DHT11不适合像读取内部寄存器那样高频轮询。它的物理响应时间决定了1Hz是极限,超过这个频率只会让总线上堆满无效请求,还会增加传感器进入异常状态的概率。
我一般采取的策略是:主循环里每2秒读取一次DHT11,每次读取完成后立即处理数据、刷新LCD或上报上位机,并且在两次读取之间让MCU进入低功耗模式。这个策略既保证了数据的实时性,又不会让传感器过载。如果项目里需要更高频率的温湿度数据,那就不是DHT11的活儿了,换个I2C接口的数字传感器,读取速度能快到几百赫兹。
5. 基于HAL库的完整工程整合思路
5.1 工程文件的组织方式
我习惯把DHT11的驱动单独做成两个文件:dht11.c和dht11.h。模块化之后,主程序文件保持干净,业务逻辑和传感器驱动分离,后续要换传感器或者移植到其他平台也方便。
在dht11.h中对外提供以下接口:
#ifndef __DHT11_H #define __DHT11_H #include "main.h" #define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 uint8_t DHT11_Init(void); uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature); void DHT11_Delay_Init(void); #endifdht11.c文件里再补充引脚切换和读取实现,这部分代码在上面已经给出了完整逻辑。唯一要注意的是,DHT11_Init函数只需要调用一次DWT_Delay_Init,不要每次读取都重新初始化延时模块。
5.2 主循环中的调用示例
主程序里的调用逻辑很简单,但有一件事千万别做错:读取DHT11前不要用阻塞式的延时等待传感器稳定,DHT11不像I2C设备那样上电后立即可以通信,它需要1秒左右完成上电自检。上电后立刻读取大概率失败,建议系统上电延时500ms以上再发起第一次读取。
下面是一个完整的调用示例:
#include "dht11.h" #include "stdio.h" char uart_buf[64]; uint8_t hum, temp; int main(void) { HAL_Init(); SystemClock_Config(); DHT11_Delay_Init(); DHT11_Init(); HAL_Delay(500); while (1) { if (DHT11_ReadData(&hum, &temp)) { sprintf(uart_buf, "Humidity: %d.%d%% Temperature: %d.%dC\r\n", hum >> 4, hum & 0x0F, temp >> 4, temp & 0x0F); HAL_UART_Transmit(&huart1, (uint8_t *)uart_buf, strlen(uart_buf), 1000); } else { HAL_UART_Transmit(&huart1, (uint8_t *)"DHT11 read failed\r\n", 20, 1000); } HAL_Delay(1500); } }温度的小数部分处理值得单独说一下。DHT11返回的温度低位数据通常在小数部分,但实际分辨率是1℃,所以小数部分大多数情况是0。用temp >> 4取高位作为整数部分,temp & 0x0F取低位作为小数部分,打印出来格式比较直观。不过要注意,某些批次的DHT11在湿度上会返回0x80这种低位值,表示0.8%,这时候直接忽略小数部分或者按协议解析都可以。我建议在主控里保留小数位的解析逻辑,毕竟数据都已经读回来了。
5.3 移植到其他单片机的要点
DHT11驱动移植到ESP32、GD32、AT32等其他平台时,核心逻辑不用改,需要调整的只有两处:GPIO输出/输入切换的方式和微秒级延时函数。ESP32用Arduino框架的话,可以用pinMode切换GPIO模式,delayMicroseconds做微秒延时,速度也够。GD32本身兼容STM32的寄存器操作,代码几乎可以无缝移植。AT32同理,内核同样是Cortex-M系列,DWT延时函数也能直接用。
有一点要提醒,不同MCU的GPIO翻转速度不一样,同样的延时参数跑在不同主频上,结果可能截然不同。换平台之后务必用示波器检查一下起始信号和数据位的实际波形,再做微调。不要想当然认为代码一样就能直接跑,时序这个东西,和硬件强相关。
6. 从DHT11到单总线协议的通用能力迁移
6.1 单总线协议家族器件的共性
学会了DHT11,再去看DS18B20或者其他单总线器件,会发现协议骨架非常相似:主机先发起复位脉冲,从机回响应脉冲,然后按位传输数据。区别主要在每个器件的数据长度、位定义和命令序列不同。DS18B20比DHT11复杂一些,多了ROM命令、存储器操作等环节,但底层读“0”和读“1”的方式是一样的,都是测量高电平宽度。
所以DHT11很适合作为理解单总线协议的敲门砖。把这个协议读透了,后续接触任何单总线器件都会轻松很多。这也是我在这篇文章里花大量篇幅讲协议时序而不是直接丢代码的原因。代码终究是表象,协议才是灵魂。
6.2 多传感器挂载时的总线仲裁思路
DHT11的单总线协议本身不支持多设备挂载,因为它在数据发送阶段不会做总线仲裁。但是实践中可以利用GPIO模拟的方式,把多个DHT11分别接在不同的引脚上,轮询读取。这个方案在引脚充足时完全可行。
如果引脚紧张,可以考虑用一个模拟多路开关(比如CD4051)来切换多个DHT11的数据线。这时候要注意一个问题:CD4051的导通电阻会影响信号边沿,导致时序变化,所以切换之后要加一个短暂延时让总线稳定,再发起始信号。读取频率也要相应调整,两个传感器之间的间隔至少500ms,不然传感器可能响应不过来。这类方案虽然看起来原始,但在低成本、多节点的场景下,比上I2C总线的传感器要省不少钱。
6.3 结合现有项目的实际扩展思路
如果你已经在做一个嵌入式项目,DHT11读取功能可以作为一个独立的任务模块,通过消息队列把温湿度数据发给其他任务。比如用FreeRTOS的环境里,可以创建一个DHT11_Task,优先级设为中等,每2秒读取一次,读取结果通过队列发送给显示任务和上报任务。这样做的好处是DHT11的时序敏感问题被隔离在单一任务里,其他任务不会干扰它,显示任务也不会因为等待DHT11而卡死。
如果你用的是乐鑫ESP8266或者ESP32做物联网项目,DHT11的数据读取方式也一样,只是GPIO操作和延时函数换成对应SDK的接口。我自己试过在ESP8266上用软件模拟的单总线读取DHT11,刷新率1Hz,连续跑了一周多没有出现卡死,稳定性还是很可靠的。
7. 写在最后的实战心得
DHT11这个传感器在社区里被吐槽最多的就是“时序太烂”,但我实际用下来发现,大部分人的问题不是出在传感器本身,而是出在对协议的理解不够透彻。你清楚每个时序参数的含义和容差范围之后,写驱动就像是照着图纸拼乐高,每一步都很确定,根本不需要瞎试。
如果让我给刚上手的人一个建议,那就是先拿逻辑分析仪或者示波器把波形抓出来看一遍。不用看太久,一次正常的通信过程就够了,你会看到起始信号、响应脉冲、40位数据的完整波形,那一刻对协议的理解会超过看十篇文章。没有示波器的话,退而求其次用LED翻转法,通过延时调整观察读取结果的变化,也能定位大部分问题。
我自己的项目里,DHT11的驱动代码从第一次写到现在已经用了快四年,换过STM32F103、GD32F303、ESP32三种平台,核心逻辑几乎没有变过。这一方面说明协议本身很稳定,另一方面也说明把原理吃透比背代码重要得多。
最后分享一个小技巧:如果你的产品需要在低温环境下用DHT11,比如低于0℃的冷库,DHT11的温度测量精度会明显下降,而且容易导致内部晶振频率偏移,影响通信时序。这种场景下我建议在硬件上留一个加热电阻的焊盘位置,低温时给传感器轻微加热,保证它在规格书标称的工作范围内工作。这个设计一开始可能用不上,但等到现场温度跌破0℃的时候,就知道这个预留方案省了多少麻烦了。