尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F407驱动DS18B20温度传感器:单总线协议时序与HAL库实战解析

STM32F407驱动DS18B20温度传感器:单总线协议时序与HAL库实战解析 简介面向STM32开发者的F407与DS18B20温度采集显示工程包完整实现单总线通信、温度读取及TFT屏幕GUI显示适合嵌入式初学者及物联网温度监测场景。包内共248个文件约10.96MB涵盖55个H头文件、44个C源文件以及HAL库、DS18B20驱动、TFT图形库另有工程配置、可执行文件、map映射文件等编译产物目录按Library、Project、User分类便于定位。已有1005人学习下载。通过阅读和编译代码可深入理解DS18B20单总线时序、STM32F407 GPIO开漏配置以及如何在TFT上绘制温度仪表从库到源码的组织方式也为完整嵌入式项目开发提供实践模板。 很多玩STM32的朋友手里大概率都有一块F407的板子。这芯片性能没得说168MHz主频带FPU各种外设齐全跑个电机控制、界面交互、音频处理都绰绰有余。但要说拿它去读一个DS18B20温度传感器很多人第一反应是“杀鸡用牛刀”。可恰恰是这种组合在实际项目里出现频率极高尤其是做温控系统、环境监测、设备自检的时候F407强大的处理能力配合DS18B20极简的硬件接口往往能省掉一整套复杂电路。这个项目看着简单其实里面藏着不少门道。DS18B20用的是单总线协议时序要求微妙而F407主频高、GPIO翻转速度快稍不注意时序就容易“跑飞”。我见过太多人直接把网上51单片机的代码搬过来结果在F407上死活读不到温度最后卡在排查时序问题上。这篇文章我就把这套组合从硬件连接到底层时序再到代码实现全部拆开揉碎讲讲那些文档里不会明说、但实际调试中一定会碰到的坑。1. 整体设计与方案选型思路1.1 为什么用F407驱动DS18B20不算浪费这里得先说说F407和DS18B20各自的定位。DS18B20是个数字温度传感器测温范围-55℃到125℃精度正负0.5℃分辨率可配置最高12位。它最讨喜的地方是只需要一根数据线就能通信还能多点组网挂在同一条总线上。而STM32F407ZGT6是意法半导体的高性能MCU内置1MB Flash、192KB RAM各种通信接口齐全主频最高能到168MHz。把它俩放一起最实在的理由有三个。第一F407的GPIO翻转速度极快达到几十MHz级别这意味着在软件模拟单总线时序时延时的精度反而好控制只要代码优化得当时序稳定性远超低速单片机。第二F407的5V容忍引脚可以直接接DS18B20的数据线不需要电平转换芯片硬件设计极其简单。第三实际产品里F407通常还要干很多别的事比如驱动LCD屏幕、处理按键、跑Modbus协议栈顺便挂个DS18B20采集温度这属于“顺手牵羊”的性价比选择。1.2 单总线协议核心机制DS18B20用的单总线协议说到底是根半双工的双向数据线一个主机可以挂载多个从机。这套协议有几个关键机制必须理解到位。数据线默认状态是高电平所有通信都由主机发起。主机通过拉低总线来产生不同的时隙信号从机通过拉低总线来回复“存在脉冲”或者输出数据位。单总线上没有时钟线所以每个比特的宽度和采样点就是通信的生命线。DS18B20内部有一个64位激光ROM编码前8位是家族码0x28中间48位是唯一序列号最后8位是CRC校验。这套编码的意义在于多点组网时主机可以通过ROM命令精确寻址每一个从机。单点使用时我们可以直接发跳过ROM命令不用关心序列号。通信流程通常分四步初始化复位脉冲和存在脉冲、ROM命令跳过或匹配地址、功能命令启动温度转换或读暂存器、数据传输。温度转换命令发出后12位分辨率下最长需要750ms转换时间这个在写代码时必须有心理预期不能发了转换命令立刻去读数据读回来大概率是上次转换的旧值。2. 硬件电路设计与连接要点2.1 引脚选择与上拉电阻计算DS18B20的硬件连接可以用“极简”来形容VCC接电源3.3V到5V都行GND接地DQ数据线接一个GPIO然后在DQ和VCC之间接一个上拉电阻。这个上拉电阻非常关键因为单总线协议要求空闲时为高电平而DS18B20的输出结构是开漏的只能拉低不能主动拉高。上拉电阻的阻值选取有讲究。常见取值是4.7kΩ但我实际测试下来在3.3V供电且线长较短小于50cm的情况下4.7kΩ完全没问题。如果线比较长或者环境干扰大可以换成2.2kΩ甚至1kΩ拉电流能力更强抗干扰更好。但也要注意阻值太小会增大静态功耗对低功耗设计不友好。电阻的计算逻辑其实很简单。DS18B20在3.3V下输出低电平的最大压降是0.4V灌电流能力IOL最大4mA所以R (VCC - VOL) / IOL (3.3 - 0.4) / 0.004 ≈ 725Ω这是理论最小值。实际留足余量取4.7kΩ远小于这个约束拉高能力绰绰有余。2.2 硬件连接中的几个细节GPIO要选带FT5V容忍标识的引脚。F407很多引脚都支持5V容忍数据手册的引脚定义表里带FT标记的都可以直接用。我习惯用PA0、PB12这种随便一个普通IO没必要非要去挑特殊功能引脚。还有个很多人踩过的坑DS18B20的GND一定不能省而且如果走线长VCC和GND最好用双绞线或者靠近布线否则容易引入共模干扰导致温度读数跳变。如果你用的是寄生供电方式只接两根线数据线兼做电源那数据线上的上拉电阻必须很小还要在温度转换期间强制拉高总线给传感器充电。这种玩法省一根线但时序要求更苛刻新手不建议折腾老老实实三根线接法最稳。3. DS18B20时序解析与F407适配要点3.1 初始化时序复位与存在脉冲单总线通信的第一步永远是复位。主机将总线拉低保持480到960微秒然后释放总线。释放后上拉电阻会把总线拉回高电平。DS18B20检测到总线上升沿后等待15到60微秒主动将总线拉低60到240微秒这就是存在脉冲。在F407上写这段代码最关键的是延时精度。HAL库的HAL_Delay只能做到毫秒级480微秒用HAL_Delay完全做不到。必须用微秒级延时函数这个在后面代码分析部分专门讲。代码层面复位函数首先要确认总线被拉低足够久释放后能读到低电平从机应答如果读不到低电平说明总线上没设备或者接线有问题。注意有些从机上电慢第一次复位会失败最好重试几次。3.2 写时隙的时序细节写时序是单总线里最容易出错的环节。一个写时隙最少60微秒最长120微秒两个写时隙之间至少要有1微秒的恢复时间。写0时隙主机拉低总线保持60到120微秒然后释放。 写1时隙主机拉低总线但只需保持1到15微秒然后释放让上拉电阻把总线拉高保持40微秒以上。这里的关键在于DS18B20在每个写时隙开始后的15到60微秒窗口内采样总线状态。也就是说写0要保持低电平足够久超过采样窗口写1要尽快释放在采样窗口到来之前拉高。在168MHz主频下几条指令的耗时都是纳秒级所以时隙内的延时操作要精确。很多人写1时觉得1到15微秒很宽裕随手delay一下结果整个时隙超长或者释放太早通信不稳定。实操上我一般把写0的拉低时间控制在60微秒整写1的拉低时间控制在2到5微秒释放后补充到整个时隙至少70微秒。3.3 读时隙与采样时机读时序更微妙。主机拉低总线至少1微秒然后释放总线在释放后的15微秒内采样数据线电平。DS18B20看到主机拉低后会立刻决定是否拉低总线来表示数据“0”如果表示“1”则不做任何动作让上拉电阻维持高电平。F407因为主频高在拉低总线、释放后代码执行到读取引脚电平之间可能只过了几百纳秒这时候DS18B20还没来得及驱动总线。所以读时序里必须有一个延时从释放总线开始计算大约9到12微秒时读取电平最稳定。这个采样点太早会误读太晚可能进入下一个时隙。我测试过的经验值释放后延时10微秒再读实测成功率非常高。注意每次读时隙之间也要留足恢复时间至少保证整个读时隙在60微秒以上。3.4 温度数据格式与计算DS18B20的测温结果存于暂存器的第0和第1字节LSB和MSB。12位分辨率下温度寄存器的格式如下bit11是符号位Sbit10到bit4是整数部分2的幂次从64℃到0.5℃bit3到bit0是小数部分0.0625℃的倍数。举个例子读取到的值为0x0191二进制是0000 0001 1001 0001。符号位为0说明是正温度。去掉符号位数值为401乘以0.0625得到25.0625℃。如果是负温度比如读到0xFC90先取反加一得到0x0370也就是880乘以0.0625得55℃取负号就是-55℃。实际写代码时我的做法是判断原始值的bit11。如果为1就用0xFFFF - rawValue 1算出补码绝对值再取负。如果为0直接乘系数转换。这个逻辑虽然简单但漏掉符号判断的错误在论坛上屡见不鲜读出来的负温度变成一个大正数。4. 基于HAL库的工程实现与代码分析4.1 CubeMX基础配置用STM32CubeMX初始化工程很快GPIO配置也不需要特别复杂。把接DQ的引脚设为推挽输出模式初始电平设高速度设高GPIO_SPEED_FREQ_HIGH其他默认就行。不需要开上拉下拉因为外部已经有了4.7kΩ上拉电阻内部上拉反而会改变时序特性。有一点要注意在程序运行中同一个引脚要来回切换输入和输出模式。这没法在CubeMX里一次性配好必须在代码里动态切换。HAL库提供了HAL_GPIO_Init函数但每次调用都重新配置寄存器开销太大。更高效的做法是直接操作寄存器把引脚的方向寄存器位切换一下即可。我这里分享一个常用的宏定义#define DS18B20_GPIO_PORT GPIOA #define DS18B20_GPIO_PIN GPIO_PIN_0 #define DS18B20_OUT_MODE() do{ GPIOA-MODER ~(3UL (0 * 2)); GPIOA-MODER | (1UL (0 * 2)); }while(0) #define DS18B20_IN_MODE() do{ GPIOA-MODER ~(3UL (0 * 2)); }while(0) #define DS18B20_HIGH() HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET) #define DS18B20_LOW() HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET) #define DS18B20_READ() HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN)这里MODER寄存器的最后两位对应PA0的模式00是输入01是输出10是复用功能。一次清位再置位的写法保证切模式时不干扰其他引脚。4.2 微秒级延时的三种实现方案前面反复强调微秒延时的重要性这里给出三个方案。方案一使用DWTData Watchpoint and Trace单元。Cortex-M4内核自带DWT其中的CYCCNT计数器每个时钟周期加一精度极高。使能方法很简单static void DWT_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static void delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); }方案二利用定时器做延时。比如用TIM2的16位向上计数实现1MHz时基也能达到微秒级精度但占用一个定时器资源。方案三空循环校准法。简单粗暴编译后用示波器或逻辑分析仪慢慢调循环次数。不推荐因为优化等级一变、主频一变延时就全乱套了。我做项目首选DWT方案零资源占用精度又高而且不受中断影响只要不关DWT时钟。4.3 完整读取流程示例下面给出一份我调试通过的完整读取流程可以直接抄作业。这里用代码块展示附详细注释。uint8_t DS18B20_Reset(void) { uint8_t presence 0; DS18B20_OUT_MODE(); DS18B20_LOW(); delay_us(600); // 拉低至少480us600us留足余量 DS18B20_HIGH(); DS18B20_IN_MODE(); // 切换为输入读取存在脉冲 delay_us(70); // 等待从机拉低总线典型为15~60us后出现 presence DS18B20_READ(); // 读到0表示存在 delay_us(410); // 剩余时隙填满保证复位周期至少960us return presence; } void DS18B20_WriteBit(uint8_t bit) { DS18B20_OUT_MODE(); DS18B20_LOW(); delay_us(2); // 写时隙起始拉低2us足够 if (bit) { DS18B20_HIGH(); // 写1释放总线 delay_us(60); } else { delay_us(60); // 写0保持拉低 DS18B20_HIGH(); } } uint8_t DS18B20_ReadBit(void) { uint8_t data 0; DS18B20_OUT_MODE(); DS18B20_LOW(); delay_us(2); // 读时隙起始拉低 DS18B20_HIGH(); DS18B20_IN_MODE(); // 转输入 delay_us(10); // 等约10us从机驱动总线稳定后采样 data DS18B20_READ(); delay_us(50); // 填满整个读时隙 return data; } void DS18B20_WriteByte(uint8_t byte) { for (int i 0; i 8; i) { DS18B20_WriteBit(byte 0x01); byte 1; } } 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; } float DS18B20_GetTemperature(void) { uint8_t lsb 0, msb 0; int16_t raw 0; float temperature 0.0f; DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0x44); // 启动温度转换 HAL_Delay(750); // 12位分辨率转换时间最长750ms DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器从第0字节开始 lsb DS18B20_ReadByte(); msb DS18B20_ReadByte(); raw (msb 8) | lsb; if (raw 0x8000) { // 负温度补码转原码取绝对值后加负号 raw ~raw 1; temperature raw * 0.0625f; temperature -temperature; } else { temperature raw * 0.0625f; } return temperature; }这段代码的几个细节值得说明。HAL_Delay(750)在高优先级中断频繁触发时不够精确750毫秒是最长转换时间即便中断干扰了延时通常也足够DS18B20完成转换。如果你非要极限提速可以在等待期间先干别的活不阻塞式等待。读暂存器时字节内数据是LSB在前所以循环移位用右移读到第0位先放到数据最高位。这个顺序弄反了读出来的数据会完全对不上。4.4 关于F407 GPIO速度与时序稳定性的优化F407的GPIO翻转速度特别快这在模拟单总线时既是优势也是隐患。如果代码里频繁切换输入输出模式后立刻读数据GPIO端口可能还在“震荡”读到的电平不稳定。我的经验是在读取前加一个非常短的延时上文代码里10us就是这么来的让总线电平稳定后再采样。另外一个优化点是关闭编译器优化或者设置优化等级为-O2后重新测试。不同优化等级下DWT延时的计数循环可能被编译器调整导致延时偏短。实测-O2和-O0之间同样代码读出的温度值偶尔会偏差0.1℃左右。如果你发现不同工程配置下时序表现不一致先去查优化等级。5. 常见问题排查与工程化经验5.1 典型故障一览主流的单总线调试问题我整理成了一张表方便对照排查。现象可能原因排查方法复位检测不到存在脉冲接线错误、上拉电阻缺失、IO模式切换失败先量DQ引脚对地电压空闲应为高用万用表量DS18B20供电温度读数恒定85℃转换命令没发出读的是上电默认寄存器值检查是否执行了0x44命令并等待足够时间读出的温度跳变、偶尔出错时序采样点偏早或偏晚用逻辑分析仪抓取读时隙波形调整采样延时多设备组网时读数错乱ROM命令未正确使用、设备地址冲突先用0x33读ROM命令逐个识别设备确认序列号唯一数据线上电平只有0.8V左右上拉电阻太小导致灌电流过大换回4.7kΩ检查DS18B20方向性DQ、VCC接反也会这样5.2 逻辑分析仪是排查时序的利器我调试这类传感器时序时基本离不开逻辑分析仪。市面上几十块的24MHz采样率逻辑分析仪就够用配合PulseView或Saleae Logic软件把DQ引脚接上去采样能直接看到每个时隙的实际波形。通过波形可以直观判断三个关键点复位时拉低时间是否在480-960us窗口内写时序的释放点是否准确读时序中从机输出数据的窗口是否被正确覆盖。我在实际项目中曾经因为一个delay_us函数在-O2优化下执行时间缩短了近一半导致写时序全部偏短用逻辑分析仪一抓波形就水落石出省去了大量猜测。提示调试单总线时序时千万别只靠调试器断点。单总线是硬实时协议一旦进入断点时钟照走通信早就断了。要么用波形分析工具要么在代码里加状态标志位串口打印关键状态。5.3 长时间稳定运行的设计考究如果你的设备不是实验室跑着玩而是要7x24小时在线监控温度有几个设计层面的坑必须提前避开。第一DS18B20连续频率不宜过高。每次温度转换都要消耗几十毫安的浪涌电流主要是内部电荷泵给寄生电容充电如果每秒读一次长期运行会加速传感器老化。一般场景建议1到5秒采一次样就够了温控场景可能会用到100到200毫秒一次的频率但要做好滤波和平滑。第二软件上要做异常值过滤。传感器偶尔会给出明显不合理的读数比如-55℃以下或125℃以上。我的做法是保留上次有效值连续3次读到越界值才更新当前温度中间任一一次恢复有效就立刻更新。这个策略简单有效能过滤掉大部分总线上的毛刺干扰。第三如果DS18B20和电机、继电器等感性负载共板数据线要走远离大电流回路最好穿过磁珠或者加一个小电容比如0.1uF到地。我遇到过继电器吸合瞬间温度读数跳1-2℃的情况加了RC滤波后彻底解决。6. 这套方案还能怎么扩展读完上面的内容你已经能驱动单个DS18B20了。如果想让这套东西发挥更大价值有两条扩展路线值得琢磨。一条是多点组网。单总线的多设备挂载能力是它的一大特色。只要把多个DS18B20并联在同一条数据线上主机用0x55匹配ROM命令逐个读取即可。但要注意挂载数量越多总线电容越大上拉电阻可能要相应调小比如挂5个设备时用2.2kΩ否则波形沿变缓导致通信不稳定。还有一点每个DS18B20的序列号都要在出厂时读取并登记到程序里否则程序没法寻址。另一条是给设备加日志存储功能。既然已经用了F407完全可以把温度数据周期性地写入F407内部Flash或者外接的SD卡、EEPROM。网上关于“基于stm32f407的日志存储记录方法”的热词本质上就是这么个需求定期采集温度连同时间戳一起存储掉电不丢失。用F407的RTC获取实时时间再用Flash模拟EEPROM或者用FATFS挂SD卡都是成熟方案。我个人的建议是别急着把功能堆满。先把单总线时序这个最底层的东西吃透把读写稳定性做到位再往上扩展。底层不稳上层功能再多也是空中楼阁。我在实际做项目时F407 DS18B20这种组合已经用在了好几个产品上。最有意思的一个是某冷库的温度监控器24小时不间断记录温度挂了8个传感器分布在库房不同位置上位机通过RS485轮询。从那以后我对单总线的抗干扰能力有了新的认识——只要硬件设计规范时序代码写正确单总线在工业现场也完全能干活。做技术就是这样越是看起来简单的器件越要敬畏它背后的协议细节。希望这篇东西能帮你少走点弯路。本文还有配套的精品资源点击获取
返回列表