基于TI MSPM0的DS18B20单总线温度传感器驱动移植与精度解析最近在做一个环境监测的小项目需要用到温度传感器DS18B20这个老朋友自然成了首选。它精度不错价格便宜最关键的是只用一根数据线就能通信布线特别方便。不过这次我用的主控是TI新出的MSPM0系列和以前常用的STM32在库函数和配置上有些不同。网上关于MSPM0的DS18B20驱动资料不多我就把自己完整的移植过程、代码解析和精度计算的经验整理出来希望能帮到正在用MSPM0做电赛或者项目的朋友。这篇文章会手把手带你完成从理解单总线协议、配置MSPM0的GPIO引脚到编写驱动、读取并计算高精度温度值的全过程。即使你是嵌入式新手跟着步骤走也能搞定。1. 认识我们的主角DS18B20温度传感器在动手写代码之前咱们先花几分钟了解一下DS18B20到底是个啥它有什么特点这样后面理解通信时序和数据处理会轻松很多。DS18B20是一款数字温度传感器它最大的特点就是采用“单总线”通信。什么叫单总线简单说就是单片机只需要用一个GPIO引脚既能给它发命令又能从它那里读数据地线GND和电源线VCC除外。这极大地节省了单片机宝贵的IO资源。它的几个核心参数你需要知道工作电压3.0V ~ 5.5V和MSPM0的3.3V系统完美兼容。测量范围-55°C 到 125°C范围很广大部分场景都够用。测量精度典型精度为±0.5°C。分辨率可调支持9位到12位的温度分辨率分辨率越高能分辨的温度变化就越细微。默认是12位。提示分辨率指的是温度数据的位数。12位分辨率意味着温度值用12位二进制数表示对应的温度增量是0.0625°C。也就是说传感器读回来的数字每变化1实际温度就变化0.0625°C。这是实现高精度计算的关键。模块通常有三个引脚VCC电源正极3.3V/5VGND电源地DQ数据输入/输出引脚接单片机的任何一个GPIO。通信全靠这一根DQ线所以时序要求非常严格这也是驱动编写的核心和难点。2. 单总线通信协议读懂DS18B20的“语言”DS18B20和单片机“对话”有一套严格的规则就是单总线协议。你可以把它理解成两个人用摩尔斯电码通信发一个脉冲代表“点”发一个长脉冲代表“划”双方必须约定好节奏否则就乱套了。协议主要包括三种操作初始化复位、写一位数据、读一位数据。所有复杂的命令如启动转换、读取数据都是由这些基本操作组合而成的。2.1 初始化时序打招呼每次通信开始前主机单片机必须先发起一个“复位脉冲”然后等待从机DS18B20回应一个“存在脉冲”这相当于双方握手确认设备在线。具体步骤对照代码中的DS18B20_Check函数看单片机拉低DQ主机将数据线拉低至少480微秒µs。这是一个明确的复位信号。单片机释放总线主机将引脚切换为输入模式或输出高电平总线被上拉电阻拉高。DS18B20回应如果DS18B20存在它会在总线被释放后的15-60µs内主动将总线拉低60-240µs发出一个“存在脉冲”。DS18B20释放DS18B20释放总线总线恢复高电平。初始化成功我们才能进行后续的读写操作。2.2 写时序单片机发命令写一位数据0或1的时序是不同的。写“1”和写“0”的起始动作一样但拉低的时间长度不同。写“1”主机拉低DQ约2µs。然后迅速拉高DQ并保持高电平至少60µs。写“0”主机拉低DQ至少60µs一般保持60µs。然后拉高DQ。注意所有写时序的周期从开始拉低到下次写操作开始前不能少于60µs并且两次写操作之间要有至少1µs的恢复时间。2.3 读时序单片机读数据读一位数据时由主机发起读时隙DS18B20在时隙内将数据放到总线上。主机拉低DQ至少1µs启动一个读时隙。主机释放总线切换为输入模式并在拉低后的15µs内采样总线电平。如果DS18B20想发送“0”它会保持总线为低电平。如果DS18B20想发送“1”它会释放总线让上拉电阻把总线拉高。主机在启动读时隙后的60µs后才能开始下一个读时隙。3. 在TI MSPM0上配置DS18B20引脚理解了协议我们就要在MSPM0开发板上找一个GPIO引脚连接DS18B20的DQ线。TI为MSPM0系列提供了非常方便的图形化配置工具——SYSCONFIG。我们用这个工具来配置引脚比直接写寄存器代码要直观得多。操作步骤在你的CCS或IAR工程中找到empty.syscfg文件或其他类似的.syscfg文件双击打开。在打开的界面中点击Tools菜单然后选择SYSCONFIG工具。在SYSCONFIG界面中点击ADD按钮来添加一个新的外设或引脚配置。我们需要添加一个GPIO配置。根据你的开发板原理图选择一个空闲的GPIO引脚例如PA12。在配置中将这个引脚初始化为数字输出因为我们需要控制它输出高低电平但注意我们的驱动代码里会在输入和输出模式间动态切换。配置完成后点击保存。这时可能会弹出一个对话框询问是否生成代码一定要选择Yes to All。保存后点击编译工程。SYSCONFIG工具会自动根据你的配置在ti_msp_dl_config.h文件中生成相应的宏定义。这个文件通常已经被包含在board.h中所以我们后续只需要包含board.h即可使用这些定义。假设我们配置的引脚是PA12那么在board.h或相关文件中你应该能找到类似这样的定义#define DS18B20_PORT GPIOA #define DS18B20_DQ_PIN DL_GPIO_PIN_12 #define DS18B20_DQ_IOMUX ... // 具体的IOMUX配置由SYSCONFIG生成这些宏定义就是我们驱动代码里控制引脚的基础。4. 驱动代码移植与解析接下来就是核心部分——编写驱动代码。我们将代码分为头文件bsp_ds18b20.h和源文件bsp_ds18b20.c。4.1 头文件 (bsp_ds18b20.h)引脚控制宏与函数声明头文件主要做了两件事定义控制引脚的宏以及声明所有要用到的函数。#ifndef _BSP_DS18B20_H_ #define _BSP_DS18B20_H_ #include board.h // 设置DQ引脚为输出模式主机控制总线 #define DQ_OUT() {\ DL_GPIO_initDigitalOutput(DS18B20_DQ_IOMUX);\ DL_GPIO_setPins(DS18B20_PORT, DS18B20_DQ_PIN);\ DL_GPIO_enableOutput(DS18B20_PORT, DS18B20_DQ_PIN);\ DL_GPIO_setPins(DS18B20_PORT, DS18B20_DQ_PIN);\ } // 设置DQ引脚为输入模式主机释放总线读取DS18B20状态 #define DQ_IN() { DL_GPIO_initDigitalInput(DS18B20_DQ_IOMUX); } // 读取DQ引脚的电平 (1 或 0) #define DQ_GET() ( ( DL_GPIO_readPins( DS18B20_PORT, DS18B20_DQ_PIN ) DS18B20_DQ_PIN ) ? 1 : 0 ) // 控制DQ引脚输出高(1)或低(0) #define DQ(x) ( (x) ? (DL_GPIO_setPins(DS18B20_PORT,DS18B20_DQ_PIN)) : (DL_GPIO_clearPins(DS18B20_PORT,DS18B20_DQ_PIN)) ) // 函数声明 void DS18B20_Reset(void); uint8_t DS18B20_Check(void); char DS18B20_Init(void); void DS18B20_Start(void); float DS18B20_GetTemperture(void); #endif这几个宏是驱动的基础DQ_OUT()和DQ_IN()用于在读写时序中快速切换引脚方向DQ(x)用于输出电平DQ_GET()用于读取电平。4.2 核心函数解析 (bsp_ds18b20.c)我们挑几个最关键的函数来看看具体怎么实现。1. 设备检测函数DS18B20_Check这个函数实现了我们前面讲的初始化时序用来检测总线上是否有DS18B20设备。uint8_t DS18B20_Check(void) { uint8_t timeout 0; // 1. 主机拉低DQ至少480us (这里拉了750us留有余量) DQ_OUT(); DQ(0); delay_us(750); DQ(1); delay_us(15); // 释放后短暂延时 // 2. 切换为输入模式等待DS18B20的应答脉冲低电平 DQ_IN(); while ( DQ_GET() timeout 200 ) // 等待低电平出现 { timeout; delay_us(1); }; if(timeout 200) return 1; // 超时未等到低电平设备不存在 timeout 0; // 3. 等待DS18B20释放总线电平变高 while ( !DQ_GET() timeout 240 ) // 等待高电平出现 { timeout; delay_us(1); }; if(timeout 240) return 1; // 超时应答异常 return 0; // 检测成功 }2. 读写一个字节的函数读和写一个字节的函数就是严格按照前面讲的位时序循环8次完成一个字节的传输。写一个字节DS18B20_Write_Byte从最低位开始根据要写的位是1还是0调用不同的时序。读一个字节DS18B20_Read_Byte同样从最低位开始每次读时隙读取一位组合成一个字节。3. 温度读取与计算函数DS18B20_GetTemperture这是最上层的函数我们调用它就能直接得到温度值。它完成了完整的命令交互流程。float DS18B20_GetTemperture(void) { uint16_t temp; uint8_t dataL 0, dataH 0; float value; DS18B20_Start(); // 发送0xCC跳过ROM和0x44启动转换命令 // 等待温度转换完成对于12位分辨率最大需要750ms。简单做法可以延时750ms以上。 delay_ms(800); DS18B20_Check(); // 再次初始化总线 DS18B20_Write_Byte(0xcc); // 跳过ROM命令 DS18B20_Write_Byte(0xbe); // 发送读取暂存器命令 dataL DS18B20_Read_Byte(); // 读取温度低字节 dataH DS18B20_Read_Byte(); // 读取温度高字节 temp (dataH 8) dataL; // 合成16位温度数据 // 精度计算与处理 if(dataH 0X80) // 判断符号位最高位为1表示负数 { temp (~temp) 1; // 取反加1得到补码的正值 value temp * (-0.0625); // 乘以分辨率得到负温度 } else // 正数 { value temp * 0.0625; // 乘以分辨率得到正温度 } return value; }4.3 精度计算深度解析上面代码中的temp * 0.0625是理解DS18B20精度的关键。DS18B20默认输出12位分辨率的数据。这12位数据中低4位是小数部分高5位对于正数或高12位对于负数是整数部分还有1位符号位。分辨率0.0625°C是怎么来的温度每变化1°C对应的数字量变化是1 / 0.0625 16。也就是说数字量每增加16温度就增加1°C。所以我们把读出的16位数据实际上有效的是12位当作一个整数乘以0.0625就得到了实际的温度值浮点数。举个例子 假设读到的dataH 0x01,dataL 0x91。 合成16位数0x0191。 转换为十进制0x0191 401。 计算温度401 * 0.0625 25.0625 °C。对于负数传感器输出的是补码。所以代码中先判断符号位如果是负数则对数据取反加1得到原码的正值然后再乘以负的分辨率。5. 主程序验证最后我们在主函数里调用这些驱动读取并打印温度。#include board.h #include stdio.h #include bsp_ds18b20.h int main(void) { // 开发板初始化时钟、串口等 board_init(); // 初始化DS18B20 if(DS18B20_Init() 0) { printf(DS18B20 Init OK!\r\n); } else { printf(DS18B20 Init Failed!\r\n); while(1); } while(1) { // 读取温度DS18B20_GetTemperture返回的是浮点数如25.0625 float temp DS18B20_GetTemperture(); // 为了通过串口打印将其放大100倍转为整数处理避免直接打印浮点数格式问题 uint32_t temp_int (uint32_t)(temp * 100.0f); printf(Temperature %d.%02d C\r\n, temp_int / 100, temp_int % 100); delay_ms(1000); // 每秒读取一次 } }将代码编译下载到MSPM0开发板连接好DS18B20模块VCC接3.3V GND接地 DQ接你配置的GPIO引脚打开串口助手你应该就能看到每秒更新一次的温度数据了。用手握住传感器温度会逐渐上升放开后会慢慢下降这说明我们的驱动移植成功啦几个常见的坑点时序不准delay_us函数的精度至关重要。如果延时偏差太大通信会失败。确保你的系统时钟配置正确微秒延时函数是准确的。未等待转换完成发送启动转换命令0x44后DS18B20需要时间进行模数转换最多750ms。必须等待足够时间后再去读取否则读到的可能是旧数据。上拉电阻单总线需要一只4.7kΩ~10kΩ的上拉电阻连接到VCC确保总线在空闲时为高电平。有些模块已经集成如果没有需要自己外接。电源干扰如果使用“寄生电源”模式只接DQ和GND在温度转换期间电流较大可能导致电源电压跌落建议还是接上VCC线更稳定。希望这篇教程能让你在MSPM0上顺利驱动DS18B20。单总线协议是很多传感器的基础搞懂了它再玩类似像DHT11温湿度传感器就会觉得容易多了。