
天空星HC32F4A0开发板驱动DS18B20单总线温度传感器移植与精度解析最近在做一个环境监测的小项目需要用到温度传感器。考虑到布线和成本我选择了经典的DS18B20数字温度传感器它只需要一根数据线就能通信非常方便。正好手头有天空星的HC32F4A0PITB开发板今天我就把整个移植过程、代码编写和精度验证的实战经验分享给大家手把手教你搞定这个单总线传感器。DS18B20大家应该不陌生它最大的特点就是“单总线”——只用一根线加上地线就能完成数据和命令的传输。这对于需要多点测温或者布线空间有限的场景特别友好。咱们这次的目标就是在HC32F4A0上通过一个普通的GPIO口模拟出这个单总线协议稳定地读取到温度数据。1. 先来认识一下DS18B20在动手写代码之前咱们得先搞清楚要驱动的对象是个啥。DS18B20是一个数字输出的温度传感器精度很高。核心特性通信方式单总线1-Wire。这是最特别的一点硬件接线极其简单。测量范围-55°C 到 125°C覆盖了绝大多数应用场景。测量精度出厂默认是12位分辨率温度增量最小可达0.0625°C也就是精度在±0.5°C以内非常够用。供电灵活可以用3-5.5V的外部电源供电也支持“寄生电源”模式直接从数据线上“偷电”工作进一步简化线路。全球唯一ID每个DS18B20都有一个独一无二的64位序列号这意味着你可以在同一根总线上挂很多个传感器通过ID来分别访问它们。硬件连接模块通常引出三个引脚VCC电源、DQ数据线、GND地。咱们的接线非常简单VCC 接 开发板的3.3V或5V。GND 接 开发板的GND。DQ 接 开发板的任意一个GPIO引脚我后面用的是PB0。注意如果使用寄生电源模式VCC需要接地同时DQ线上需要一个强上拉电阻比如4.7kΩ来保证供电。咱们这次用外部供电更稳定。2. 单总线通信的“规矩”时序是关键DS18B20的所有通信都建立在严格的时序之上。你可以把时序理解成一种双方约定好的“说话节奏”。主机我们的MCU必须按照这个节奏来发起对话、发送命令和读取数据DS18B20才会正确响应。整个通信过程由几种基本时序构成复位脉冲初始化、写时序、读时序。下面我结合代码把每个时序的要点给你讲透。2.1 初始化时序打招呼每次通信开始前主机必须先发一个复位脉冲拉低总线至少480us然后释放总线转为输入模式由上拉电阻拉高。如果总线上存在DS18B20它会在15-60us后拉低总线约60-240us作为应答然后释放总线。/** * brief 检测DS18B20是否存在复位与应答检测 * param 无 * retval 0: 存在 1: 不存在 */ uint8_t DS18B20_Check(void) { uint8_t timeout 0; // 1. 主机拉低总线至少480us这里拉了750us留足余量 DQ_OUT(); // 设置GPIO为输出模式 DQ(0); // 输出低电平 delay_us(750); // 2. 主机释放总线转为输入由上拉电阻拉高等待15-60us DQ(1); delay_us(15); // 3. 主机切换到输入模式检测DS18B20的应答信号低电平 DQ_IN(); while (DQ_GET() timeout 200) // 等待低电平出现 { timeout; delay_us(1); }; if(timeout 200) return 1; // 超时未检测到低电平说明设备无应答 timeout 0; // 4. 等待DS18B20释放总线电平变高 while (!DQ_GET() timeout 240) // 等待低电平结束 { timeout; delay_us(1); }; if(timeout 240) return 1; // 低电平持续时间异常 return 0; // 初始化成功设备存在 }提示这里的delay_us函数需要你自己根据HC32的系统时钟频率来实现一个精确的微秒级延时。时序是DS18B20驱动的核心延时不准会导致通信失败。2.2 写时序主机发命令写一个比特bit的时序如下主机先将总线拉低至少1us然后在15us内决定要写‘1’还是‘0’。如果要写‘0’就继续保持低电平约60us如果要写‘1’则在拉低1-2us后迅速释放总线拉高并维持高电平约60us。整个写时隙至少需要60us且两个时隙间要有至少1us的恢复时间。/** * brief 向DS18B20写入一个字节 * param dat: 要写入的字节数据 * retval 无 */ void DS18B20_Write_Byte(uint8_t dat) { uint8_t i; DQ_OUT(); // 设置为输出模式 for (i0; i8; i) // 一个字节8位从最低位(LSB)开始发送 { if (dat 0x01) // 判断当前要发送的位是1还是0 { // 写‘1’的时序 DQ(0); // 拉低至少1us delay_us(2); DQ(1); // 迅速拉高释放总线 delay_us(60); // 保持高电平 } else { // 写‘0’的时序 DQ(0); // 拉低 delay_us(60); // 保持低电平约60us DQ(1); // 释放总线 delay_us(2); } dat 1; // 准备发送下一位 } }2.3 读时序主机收数据读一个比特的时序主机先将总线拉低至少1us然后释放总线转为输入模式。DS18B20会在拉低后的15us内将数据位送到总线上。主机需要在拉低后的15us左右采样总线电平高电平为‘1’低电平为‘0’。整个读时隙也至少需要60us。/** * brief 从DS18B20读取一个字节 * param 无 * retval 读取到的字节数据 */ uint8_t DS18B20_Read_Byte(void) { uint8_t i0, dat0; for (i0; i8; i) // 读取8位组成一个字节 { dat 1; // 先右移为接收新位腾出最低位 DQ_OUT(); // 主机拉低总线启动读时隙 DQ(0); delay_us(2); // 拉低至少1us DQ(1); // 主机释放总线 DQ_IN(); // 切换为输入模式准备采样 delay_us(20); // 等待约15us后DS18B20的数据稳定了 // 在恰当的时机这里约20us后采样总线电平 if(DQ_GET()) // 如果总线为高电平 { dat | 0x80; // 说明读到的是‘1’将其放到最高位因为后面会右移 } delay_us(60); // 等待读时隙结束 } return dat; }3. 在天空星HC32F4A0上移植驱动理解了时序我们就可以开始动手写代码了。这里的关键是我们要用HC32的GPIO来精确模拟出上面那些时序。3.1 硬件引脚与宏定义首先在头文件bsp_ds18b20.h里我们把用到的GPIO引脚和操作宏定义好。这样以后换引脚改起来也方便。// 端口移植定义 #define PORT_DQ GPIO_PORT_B // 使用B端口 #define GPIO_DQ GPIO_PIN_00 // 使用第0号引脚即PB0 // 将DQ引脚设置为输入模式的宏 #define DQ_IN() { \ stc_gpio_init_t stcGpioInit; \ GPIO_StructInit(stcGpioInit); \ stcGpioInit.u16PinState PIN_STAT_RST; \ stcGpioInit.u16PinDir PIN_DIR_IN; // 方向输入 \ stcGpioInit.u16PullUp PIN_PU_OFF; // 关闭内部上拉外部已有上拉 \ GPIO_Init(PORT_DQ, GPIO_DQ, stcGpioInit); \ } // 将DQ引脚设置为输出模式的宏 #define DQ_OUT() { \ stc_gpio_init_t stcGpioInit; \ GPIO_StructInit(stcGpioInit); \ stcGpioInit.u16PinState PIN_STAT_RST; \ stcGpioInit.u16PinDir PIN_DIR_OUT; // 方向输出 \ stcGpioInit.u16PullUp PIN_PU_OFF; \ GPIO_Init(PORT_DQ, GPIO_DQ, stcGpioInit); \ } // 读取DQ引脚电平 #define DQ_GET() GPIO_ReadInputPins(PORT_DQ, GPIO_DQ) // 设置DQ引脚输出电平 (x: 1-高电平 0-低电平) #define DQ(x) ( (x) ? GPIO_SetPins(PORT_DQ, GPIO_DQ) : GPIO_ResetPins(PORT_DQ, GPIO_DQ) )3.2 GPIO初始化与主流程函数有了底层操作宏我们就可以封装几个核心函数了。初始化函数配置好GPIO并检测传感器是否存在。char DS18B20_GPIO_Init(void) { unsigned char ret 255; stc_gpio_init_t stcGpioInit; LL_PERIPH_WE(LL_PERIPH_ALL); // 解锁GPIO寄存器写保护HC32特有操作 (void)GPIO_StructInit(stcGpioInit); stcGpioInit.u16PinState PIN_STAT_SET; // 初始状态设为高电平 stcGpioInit.u16PinDir PIN_DIR_OUT; // 先初始化为输出 stcGpioInit.u16PullUp PIN_PU_OFF; (void)GPIO_Init(PORT_DQ, GPIO_DQ, stcGpioInit); ret DS18B20_Check(); // 调用我们前面写的检测函数 return ret; // 返回0表示初始化成功并检测到设备 }启动温度转换函数发送命令让DS18B20开始一次温度测量。void DS18B20_Start(void) { /* 复位并检测设备 */ DQ_OUT(); DQ(0); delay_us(700); DQ(1); delay_us(10); DS18B20_Check(); // 这个检测过程本身也包含了复位 /* 发送命令 */ DS18B20_Write_Byte(0xCC); // 跳过ROM指令对总线上所有设备操作假设只有一个 DS18B20_Write_Byte(0x44); // 启动温度转换命令 }3.3 核心读取并换算温度值这是最核心的函数它完成了读取温度寄存器原始数据并将其转换为摄氏度值的全过程。float DS18B20_GetTemperture(void) { uint16_t temp; uint8_t dataL 0, dataH 0; float value; // 1. 启动一次温度转换 DS18B20_Start(); // 2. 等待转换完成。对于12位分辨率最大转换时间约为750ms。 // 这里简单延时实际项目建议用查询或中断方式。 delay_ms(800); // 3. 再次复位总线准备读取数据 DS18B20_Check(); // 4. 发送读取命令 DS18B20_Write_Byte(0xCC); // 跳过ROM DS18B20_Write_Byte(0xBE); // 读取暂存器命令包含温度值 // 5. 读取温度值的低字节和高字节 dataL DS18B20_Read_Byte(); // LSB (低8位) dataH DS18B20_Read_Byte(); // MSB (高8位) // 6. 将两个字节合并成一个16位整数 temp (dataH 8) dataL; // 7. 温度换算12位分辨率精度0.0625°C if(dataH 0xF8) // 或者 if(dataH 0x80) 判断符号位高5位为符号 { // 负温度数据以补码形式存储需先取反加1得到原码 temp (~temp) 1; value temp * (-0.0625f); // 乘以负的精度值 } else { // 正温度直接乘以精度值 value temp * 0.0625f; } return value; }温度换算原理详解DS18B12位分辨率下温度数据用一个16位有符号整数表示。这个数乘以0.0625就得到实际温度单位°C。举个例子如果寄存器读出的高8位是0000 0001低8位是1001 0001。合并成16位0000 0001 1001 0001(二进制) 0x0191(十六进制) 401(十进制)。实际温度 401 * 0.0625 25.0625°C。对于负温度比如-10.125°C计算过程-10.125 / 0.0625 -162。-162的16位补码是0xFF5E高8位0xFF低8位0x5E。传感器存储的就是0xFF5E。我们读取后判断符号位为1说明是负数。先对0xFF5E取反加1得到162再乘以-0.0625就得到-10.125。4. 在工程中测试与验证驱动写好了最后一步就是把它用起来。在主函数里初始化串口用于打印初始化DS18B20然后循环读取温度即可。#include board.h #include bsp_uart.h #include stdio.h #include bsp_ds18b20.h int32_t main(void) { board_init(); // 开发板基础初始化时钟、系统等 uart1_init(115200U); // 初始化串口1波特率115200用于打印 // 初始化DS18B20并检测是否连接成功 uint8_t ret DS18B20_GPIO_Init(); if(ret 1) { printf(DS18B20_Check Error!!\r\n); while(1); // 初始化失败停机 } else { printf(DS18B20 Init OK!\r\n); } while(1) { // 读取并打印温度值保留两位小数 float temp DS18B20_GetTemperture(); printf(温度 %.2f°C\r\n, temp); delay_ms(1000); // 每秒读取一次 } }把代码编译下载到天空星开发板连接好DS18B20模块VCC-3.3V GND-GND DQ-PB0记得在DQ和VCC之间接一个4.7kΩ的上拉电阻打开串口助手你就能看到每秒更新一次的温度数据了。用手捏住传感器能看到温度缓缓上升说明移植成功调试中可能遇到的坑读不到数据或数据全是0xFF/0x00首先检查硬件连接和上拉电阻。然后重点检查延时函数delay_us的精度时序是DS18B20通信的命门。可以用逻辑分析仪或者示波器抓一下DQ线上的波形对照数据手册的时序图看是否吻合。温度值跳变或不准确保电源稳定远离大功率或高频干扰源。单总线对干扰比较敏感。也可以尝试在读取温度转换状态命令0xBE后多读取几个字节如CRC来确保数据完整性。HC32的GPIO配置注意HC32的GPIO库函数可能和其他MCU略有不同确保GPIO_Init等函数调用正确特别是输入输出模式的切换要迅速。好了整个移植过程就是这样。代码我已经在实际项目里跑过稳定可靠。希望这篇教程能帮你顺利在天空星HC32F4A0上玩转DS18B20。