十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DS18B20在ESP32上的单总线时序实现与优化

DS18B20在ESP32上的单总线时序实现与优化 简介面向ESP32开发者的DS18B20数字温度传感器驱动源码包适用于智能家居、农业环境监测、工业过程控制等物联网测温场景。DS18B20支持高精度单总线通信仅需一根数据线即可与ESP32的GPIO连接电源与数据线供电方式灵活很适合快速搭建温度采集节点。压缩包共2个文件包含一个C源文件和一个头文件整体仅2KBC源文件实现单总线时序、温度转换和设备搜索逻辑头文件则负责声明对外接口方便直接集成。目前已有1253人学习/下载。源码整合了常用单总线库与温度传感器库的典型调用流程覆盖传感器初始化、总线设备识别、温度读取与浮点结果返回并可通过设备唯一识别码区分多个传感器同时预留了浮点转字符串、多设备管理上的扩展空间便于开发者借助ESP32的联网能力实现本地显示或云端上报从而灵活改造为多点温度监控方案。1. DS18B20 在 ESP32 上真正难的不是读温度而是时序DS18B20 是单总线数字温度传感器里最普及的一颗硬件上只需一根数据线外加一个上拉电阻12 位分辨率下精度可达 ±0.5°C在智能温控、农业环境监测和工业过程控制里随处可见。难点在于它的通信完全依赖微秒级时序复位要拉低 480μs写 0 时隙要持续 60μs读采样点在释放后 15μs 内完成。ESP32 跑着 FreeRTOSGPIO 要经过 IO MUX 和 GPIO 矩阵两级映射再加上 Wi-Fi 协议栈的中断抢占直接套用 Arduino 库的 delayMicroseconds 方案经常出现随机 CRC 错误。DS18B20.c 和 DS18B20.h 这对文件把初始化、ROM 搜索、CRC 校验、温度转换封装成 ESP-IDF 组件关键时序操作放入临界区保护实测在一条总线上挂多探头仍然稳定。适合从 Arduino 迁移到 ESP-IDF 的开发者也适合要把多个 DS18B20 接入生产环境的嵌入式工程师。2. DS18B20.c/.h 中单总线协议的时序骨架单总线协议由三类基本操作构成复位脉冲、写时隙、读时隙。DS18B20.c 的核心工作就是在 ESP32 的 GPIO 上精确复现这三类操作再把设备寻址、温度转换命令、数据校验封装成上层可直接调用的接口。2.1 复位时序与存在检测的实现逻辑总线空闲时外部上拉电阻把数据线维持在 3.3V 高电平。主机发起通信的第一步是把总线拉低至少 480μs 再释放DS18B20 检测到这个长低电平窗口后延迟 1560μs 把总线拉低 60240μs作为存在脉冲回应主机。这个握手过程决定了后续所有命令是否有效如果存在脉冲没抓到后面的读写时隙全部是空操作。ESP32 上实现复位关键是采样存在脉冲的窗口和对 GPIO 方向的切换。典型实现如下// ds18b20.c — 复位总线并检测设备存在 int ds18b20_reset(ds18b20_dev_t *dev) { gpio_set_direction(dev-gpio_num, GPIO_MODE_OUTPUT_OPEN_DRAIN); gpio_set_level(dev-gpio_num, 0); ets_delay_us(500); // 拉低 500us覆盖最小 480us 要求 gpio_set_level(dev-gpio_num, 1); gpio_set_direction(dev-gpio_num, GPIO_MODE_INPUT); ets_delay_us(70); // 等待 DS18B20 拉低总线 int presence gpio_get_level(dev-gpio_num); ets_delay_us(500); // 等待存在脉冲窗口结束 return presence 0 ? 0 : -1; }延时用ets_delay_us而非vTaskDelay是因为 FreeRTOS 的 tick 默认是 10ms 粒度无法表达微秒级等待。ets_delay_us是 ROM 层忙等函数期间不会让出 CPU。gpio_set_direction在每次操作前重复调用是为了确保引脚在开漏输出和输入两个状态之间正确切换漏掉这一步会导致引脚方向停留在输出模式存在脉冲被输出寄存器电平钳住而读不到。存在脉冲采样点选在释放总线后 70μs落在手册规定的 1560μs 起始窗口之后、240μs 结束窗口之前。如果此处采到高电平优先查三处传感器是否供电、外部上拉是否开路、GPIO 是否被配置成了推挽输出而不是开漏模式。2.2 读写时隙的时间参数与容差分析写时序分写 0 和写 1。主机都要先把总线拉低区别只在释放时机写 0 要持续拉低 60120μs写 1 只保持 115μs 就释放。DS18B20 从下降沿开始计时在 1560μs 窗口内读取总线电平所以写 0 必须低到采样窗口结束写 1 则要在采样前让总线恢复高电平。读时隙类似写 1 的启动主机拉低 16μs 后释放并在 15μs 内完成采样。传感器要返回 0 时从释放时刻起拉低总线返回 1 时保持高电平。读写单个 bit 的典型实现// 写一个 bit static void ds18b20_write_bit(ds18b20_dev_t *dev, int bit) { portENTER_CRITICAL(dev-spinlock); gpio_set_direction(dev-gpio_num, GPIO_MODE_OUTPUT_OPEN_DRAIN); gpio_set_level(dev-gpio_num, 0); if (bit) { ets_delay_us(6); // 写 1拉低 6us 后释放 gpio_set_level(dev-gpio_num, 1); ets_delay_us(60); } else { ets_delay_us(60); // 写 0持续拉低 60us gpio_set_level(dev-gpio_num, 1); ets_delay_us(6); } portEXIT_CRITICAL(dev-spinlock); } // 读一个 bit static int ds18b20_read_bit(ds18b20_dev_t *dev) { int level; portENTER_CRITICAL(dev-spinlock); gpio_set_direction(dev-gpio_num, GPIO_MODE_OUTPUT_OPEN_DRAIN); gpio_set_level(dev-gpio_num, 0); ets_delay_us(2); // 拉低 2us 启动读时隙 gpio_set_direction(dev-gpio_num, GPIO_MODE_INPUT); ets_delay_us(8); // 在 15us 窗口内完成采样 level gpio_get_level(dev-gpio_num); ets_delay_us(50); // 等待读时隙结束 portEXIT_CRITICAL(dev-spinlock); return level; }portENTER_CRITICAL进入当前 CPU 的临界区屏蔽中断防止 Wi-Fi 协议栈或定时器回调打断时序窗口。临界区内不能调用任何可能阻塞或触发调度的 APIvTaskDelay、printf、ESP_LOGI都不能出现否则触发断言或产生不可预期的总线状态。字节操作建立在 bit 操作之上DS18B20 的数据一律低位在前// 写一个字节低位先出 void ds18b20_write_byte(ds18b20_dev_t *dev, uint8_t byte) { for (int i 0; i 8; i) { ds18b20_write_bit(dev, byte 0x01); byte 1; } } // 读一个字节低位先出 uint8_t ds18b20_read_byte(ds18b20_dev_t *dev) { uint8_t byte 0; for (int i 0; i 8; i) { byte 1; if (ds18b20_read_bit(dev)) { byte | 0x80; } } return byte; }时隙参数没有取极端值而是卡在手册区间的中间写 1 拉低 6μs写 0 拉低 60μs读采样点选在释放后 8μs。这样给 PCB 走线电容、引脚容差和温漂留了余量。量产时如果个别的设备通信失败可以微调这几个延时但不要低于手册下限否则高温环境下会出现间歇性失败。2.3 CRC 校验与地址完整性的验证DS18B20 的 ROM 共 8 字节最后 1 字节是 CRC8。读暂存器命令0xBE会连续返回 9 字节最后 1 字节同样是 CRC8用于校验前 8 字节在传输中是否发生位翻转。总线挂多个设备或线缆较长时CRC 错误率会明显上升驱动层不做校验的话读到的是错误温度值而不自知。// CRC8 校验多项式 x^8 x^5 x^4 1 uint8_t ds18b20_crc8(const uint8_t *data, int len) { uint8_t crc 0; for (int i 0; i len; i) { uint8_t byte data[i]; for (int j 0; j 8; j) { uint8_t mix (crc ^ byte) 0x01; crc 1; if (mix) { crc ^ 0x8C; // 低位优先形式的 CRC 寄存器值 } byte 1; } } return crc; }0x8C对应X^8X^5X^41在 LSB First 算法下的归约值。把 9 字节传入后结果为 0 表示校验通过。如果频繁非零优先检查上拉电阻阻值范围以及数据线是否靠近开关电源或电机驱动线。3. 从 Arduino 迁移到 ESP-IDFGPIO 驱动与任务设计3.1 GPIO 矩阵映射与 IO MUX 的选型差异Arduino 环境的digitalWrite经过框架的引脚映射层包含多层条件判断和位运算。在 240MHz 主频下虽然只多几百纳秒但对微秒级协议来说是实质性延迟——写 1 时隙被拖慢超过 15μsDS18B20 就会把它误判成写 0。ESP-IDF 提供 IO MUX 和 GPIO 矩阵两条路径。IO MUX 是芯片内固定引脚到外设的直连延迟最低但功能固定GPIO 矩阵是任意 GPIO 到任意外设的交叉开关灵活但多一级路由。单总线协议完全软件模拟不经过任何外设信号路径直接用gpio_set_level和gpio_get_level操作寄存器即可。DS18B20.c 里的实现正是在开漏输出和输入之间切换 GPIO 方向绕开 Arduino 的引脚映射开销。选引脚时注意避开 Strapping 引脚。GPIO 12、GPIO 0、GPIO 2 在芯片复位时有特殊上电时序要求DS18B20 挂在 GPIO 12 且被外部拉低可能导致 ESP32 无法正常启动。常见做法是选 GPIO 16、GPIO 17、GPIO 4 这类普通 IO其中 GPIO 16 和 GPIO 17 还支持 RTC 域深度睡眠唤醒后能快速恢复 IO 状态。3.2 GPIO 初始化配置与上拉电阻选型数据线在 ESP-IDF 里的推荐配置是开漏输入输出双向模式gpio_config_t io_conf { .pin_bit_mask (1ULL dev-gpio_num), .mode GPIO_MODE_INPUT_OUTPUT_OD, // 开漏模式输入输出双向 .pull_up_en GPIO_PULLUP_ENABLE, // 内部弱上拉45k 级别 .pull_down_en GPIO_PULLDOWN_DISABLE, .intr_type GPIO_INTR_DISABLE, }; gpio_config(io_conf);开漏模式下输出低电平时主动拉低总线输出高电平时引脚呈高阻态由外部电阻把电平拉回高。GPIO_PULLUP_ENABLE启用的内部上拉约 45kΩ短距离实验够用线缆一长电平上升沿被 RC 延迟拖得很慢读时隙采到的电平还没恢复到高。量产建议在 ESP32 引脚附近外接一颗 2.2kΩ4.7kΩ 电阻到 3.3V。注意数据线上拉只能接 3.3V不能接 5V。如果 DS18B20 是外部 5V 供电高电平已是 5V 级别直接进 ESP32 的 3.3V IO 会超耐压需要加 MOS 管双向电平转换电路。数据线缆长度上拉电阻布局建议 15cm4.7kΩ板载直连无需屏蔽15cm ~ 3m3.3kΩ双绞线或屏蔽线远离电源线 3m2.2kΩ屏蔽线单端接地主机端上拉总线上挂多个 DS18B20 时上拉电阻只需一颗放在离 ESP32 最近的位置。每颗传感器各加上拉会让等效并联电阻降到几百欧低电平采样窗口被破坏还会白白增加功耗。提示数据线对地并联 100pF 电容可以滤掉一部分高频干扰但电容不能太大否则电平上升沿变缓反而拉长时序。线长超过 1 米时建议放弃电容滤波改用屏蔽线。3.3 FreeRTOS 任务划分与看门狗的影响温度采集是周期性任务时序操作必须在临界区内完成而临界区内不能做浮点运算、不能打印日志、不能发送队列消息。正确做法是临界区只包住读写时隙部分CRC 校验、温度换算、日志输出都放到临界区外void temp_collect_task(void *arg) { ds18b20_dev_t *dev (ds18b20_dev_t *)arg; float temp_c; while (1) { ds18b20_start_convert(dev); // 发送转换命令 vTaskDelay(pdMS_TO_TICKS(200)); // 等待转换完成 if (ds18b20_read_temp(dev, temp_c) 0) { ESP_LOGI(temp, %.2f°C, temp_c); } else { ESP_LOGW(temp, read failed / crc error); } vTaskDelay(pdMS_TO_TICKS(2000)); // 2 秒采样周期 } }任务栈建议给 4096 字节。ds18b20_read_temp内部涉及浮点格式化和 CRC 计算栈太小会溢出表现为任务运行一段时间后系统随机重启。任务优先级给 5 即可不要用最高优先级。读时序需要短时间屏蔽中断优先级太高会把 Wi-Fi 协议栈的低优先级任务饿死。温度采集任务只负责数据获取上报交给网络任务。常见做法是数据采集任务把 float 温度值通过xQueueSend发给 MQTT 或 HTTP 任务两边用队列解耦避免采集任务因 Wi-Fi 重连阻塞影响时序。队列深度 4 就够采集频率高于上报频率时直接丢旧报新。深度睡眠场景下DS18B20 掉电后暂存器内容不保留唤醒后必须先复位、再搜索 ROM不能直接用静态保存的地址跳过枚举。ESP32 的 RTC GPIO 在睡眠期间保持电平但 DS18B20 内部状态还是要重新初始化。4. 多设备挂载、ROM 搜索与电源模式实战4.1 单总线上多设备的搜索与寻址DS18B20 同一根总线可挂多颗传感器每颗有 64 位 ROM 唯一标识第 1 字节是家族代码0x28最后 1 字节是 CRC。主机上电后第一步是枚举总线上存在的设备通过搜索命令0xF0完成。搜索算法的核心是位冲突检测主机逐位输出地址位DS18B20 分两次回传该位的真实值和反码主机根据组合判断当前位是 0、1 还是两颗设备同时存在。// 搜索总线上所有设备rom_ids 由调用方分配 int ds18b20_search_all(ds18b20_dev_t *dev, uint64_t rom_ids[], int max_devs) { int dev_count 0; uint64_t rom 0; int last_branch -1; // 位冲突分支回溯位置 while (dev_count max_devs) { if (ds18b20_rom_search(dev, rom, last_branch) 0) { break; } rom_ids[dev_count] rom; } return dev_count; }last_branch是容易出错的地方。它记录上一次搜索遇到的冲突位下一次搜索从这个位置回溯到另一分支继续。每次调用都赋为 -1搜索只会返回第一个设备形成死循环。把这个字段封装进结构体长期保存比用函数参数传递可靠。单设备场景不必走搜索。发送0x33读 ROM 命令后直接读 8 字节即可但这条命令在多设备挂载时会因为总线冲突读到一堆0xFF。工程经验是先从设备数量上判断明确只有一颗时用0x33否则一律走0xF0搜索。4.2 匹配 ROM 与跳过 ROM 的使用边界拿到 ROM 后对指定传感器发送命令需要先发0x55匹配 ROM再跟 8 字节 ROM 地址。总线上其他设备收到不匹配地址后进入休眠只有地址匹配的那颗响应后续命令。完整读取流程// 读取指定 ROM 地址的设备温度 float ds18b20_read_temp_by_rom(ds18b20_dev_t *dev, uint64_t rom) { ds18b20_reset(dev); ds18b20_write_byte(dev, 0x55); // 匹配 ROM for (int i 0; i 8; i) { uint8_t byte (rom (8 * i)) 0xFF; ds18b20_write_byte(dev, byte); // 低位优先发送 } ds18b20_write_byte(dev, 0xBE); // 读暂存器 uint8_t data[9]; for (int i 0; i 9; i) { data[i] ds18b20_read_byte(dev); } if (ds18b20_crc8(data, 9) ! 0) { return NAN; } int16_t raw (int16_t)(data[1] 8 | data[0]); return raw * 0.0625f; // 12 位分辨率转换 }温度寄存器是 16 位二进制补码data[1] 8 | data[0]合并后是实际温度值的 16 倍乘以 0.0625 得到摄氏温度。返回 NAN 标记读取失败调用端要判断isnan。总线上只有单颗设备时直接发0xCC跳过 ROM省去 8 字节地址传输。但两条命令不能混用对多设备总线发0xCC0x44让所有设备同时开始温度转换这是预期行为如果对多设备总线发0xCC0xBE所有传感器会同时往总线上推数据波形直接冲突读回来是乱码。4.3 寄生供电与外部供电的选择依据寄生供电模式下VCC 引脚直接接地传感器通过数据线低电平期间的电流给内部电容充电高电平期间用储存的能量驱动采样电路。这个模式省一根走线但温度转换期间数据线必须被主机持续拉高 750ms否则转换中断。ESP32 上实现时发送0x44后要强制把 GPIO 置为高电平输出void ds18b20_start_convert_parasite(ds18b20_dev_t *dev) { ds18b20_send_command(dev, 0x44); // 转换命令 gpio_set_direction(dev-gpio_num, GPIO_MODE_OUTPUT_OPEN_DRAIN); gpio_set_level(dev-gpio_num, 1); // 总线保持高电平给传感器供电 }保持期间不能依赖弱上拉必须由主机主动驱动高电平否则线上电流不足以支持 ADC 采样。外部供电模式则简单得多VCC 接 3.3V 或 5V数据线只做通信。凡是数据线超过半米的场景都建议选外部供电寄生供电对线缆电容太敏感线长后充电不足导致转换结果漂移。4.4 批量转换与统一读取的时序优化多设备巡检场景逐个发转换命令再等待总周期等于设备数量乘以单次转换时间10 颗设备就是 7.5 秒。优化做法是先发一次0xCC0x44让所有设备同时转换等待 750ms 之后逐个匹配 ROM 读温度void collect_all_temps(ds18b20_dev_t *dev, uint64_t rom_ids[], int n) { ds18b20_reset(dev); ds18b20_write_byte(dev, 0xCC); // 跳过 ROM所有设备同时转换 ds18b20_write_byte(dev, 0x44); // 温度转换命令 vTaskDelay(pdMS_TO_TICKS(800)); for (int i 0; i n; i) { ds18b20_reset(dev); ds18b20_write_byte(dev, 0x55); for (int j 0; j 8; j) { ds18b20_write_byte(dev, (rom_ids[i] (j * 8)) 0xFF); } ds18b20_write_byte(dev, 0xBE); uint8_t data[9]; for (int j 0; j 9; j) { data[j] ds18b20_read_byte(dev); } if (ds18b20_crc8(data, 9) 0) { int16_t raw (int16_t)(data[1] 8 | data[0]); ESP_LOGI(temp, dev[%d]: %.2f°C, i, raw * 0.0625f); } } }批量转换后每读一颗都要重新复位并发一次0x55因为读取过程中其他设备保持休眠总线空闲后所有设备回到初始状态。时间预算批量转换 800ms之后每读一颗约 5ms挂 10 颗探头时一轮完整采集周期约 850ms比逐个转换的 7.5 秒压缩了近 9 倍适合冷链仓储这类高频巡检场景。5. 逻辑分析仪抓时序与 CRC 失败的快速定位排查 DS18B20 通信问题最直接的手段是接逻辑分析仪抓数据线波形。10MHz 采样率即可通道夹在数据线上GND 与 ESP32 共地触发方式设成下降沿。正常波形具备三段结构复位阶段是 480μs 以上的长低电平窗口随后有一个 60240μs 的负向存在脉冲写时隙连续出现 60μs 宽方波说明写 0短下拉后快速回高是写 1读时隙是一个约 6μs 的窄负脉冲后面跟着 15μs 内的采样窗口。三种典型异常波形对应不同根因。第一种波形完全消失且总线一直低电平多半是 GPIO 配置成纯输出模式持续拉低或者外部上拉电阻漏焊、内部上拉没打开。第二种存在脉冲正常但读写时隙电平恢复呈明显斜坡是数据线分布电容大、上拉电阻偏大的典型症状把 4.7kΩ 换成 2.2kΩ 波形后沿会变陡。第三种波形看起来正常但 CRC 频繁失败优先怀疑数据线紧挨 PWM 输出或电机驱动线高频干扰把采样点前的电平翻转了。定位具体板卡时在驱动里临时打开诊断模式CRC 失败打印原始数据if (ds18b20_crc8(data, 9) ! 0) { ESP_LOGE(ds18b20, crc fail rom%08x%08x, (uint32_t)(rom 32), (uint32_t)rom); ESP_LOG_BUFFER_HEX(ds18b20, data, 9); }ESP_LOG_BUFFER_HEX把收到的 9 字节按十六进制打印出来与逻辑分析仪逐位对照能立刻定位是哪一位翻转。再对比关闭 Wi-Fi 和蓝牙后的 CRC 错误率可以判断射频中断是否干扰了时序临界区。确认是干扰后把 DS18B20 任务绑定到空闲 CPU 核心运行并缩短临界区长度。长线场景的三项加固措施传感器端加 100nF 退耦电容数据线串联 100Ω 电阻抑制反射屏蔽层只在 ESP32 端单点接地。线缆超过 5 米时单总线的容性负载已经接近 DS18B20 手册上限这时与其加大上拉电阻不如把总线拆成两路分别用两个 GPIO 驱动每路挂的设备不超过 5 颗。本文还有配套的精品资源点击获取
返回列表