## 一、单总线协议概要 单总线(1-Wire)由 Dallas 提出,数据线既做供电又做通信。STM32 上通常用开漏输出 + 上拉电阻模拟。**DS18B20 与 DHT11 虽然都叫单总线,但时序完全不能互通,必须各自独立实现时序,更不能同时挂在同一根总线上。** 两者的差异点: - DS18B20:严格的 60~240μs 时隙,需要 ROM 命令和暂存器操作。 - DHT11:40bit 数据一次性输出,无设备寻址,起始时序要求更宽松。 由于标准库和 HAL 库在微秒延时上存在差异,本文基于 **HAL 库 + 微秒级延时函数**(如 `DWT->CYCCNT`)编写示例。 ## 二、DS18B20 读取原理与实现 ### 1. 初始化时序 主机拉低总线 480~960μs,然后释放。DS18B20 在 15~60μs 后拉低总线 60~240μs,表示存在脉冲。 ```c // 复位并检测存在,返回1表示存在 uint8_t DS18B20_Reset(void) { uint8_t presence = 0; DS18B20_MODE_OUT(); // GPIO 配置为开漏输出 DS18B20_WRITE_LOW(); delay_us(600); // 拉低 600μs DS18B20_WRITE_HIGH(); delay_us(30); DS18B20_MODE_IN(); // 切换输入 presence = DS18B20_READ(); // 读取存在脉冲 delay_us(500); return (presence == 0) ? 1 : 0; } ``` ### 2. 读写时隙 写 1/0:主机拉低总线,写 0 保持 60~120μs;写 1 等待 15μs 后释放。读时隙:主机拉低 1~15μs 后释放,并在 15μs 内采样。 ```c void DS18B20_WriteBit(uint8_t bit) { DS18B20_MODE_OUT(); DS18B20_WRITE_LOW(); if (bit) { delay_us(5); DS18B20_WRITE_HIGH(); delay_us(60); } else { delay_us(60); DS18B20_WRITE_HIGH(); delay_us(5); } } uint8_t DS18B20_ReadBit(void) { uint8_t bit = 0; DS18B20_MODE_OUT(); DS18B20_WRITE_LOW(); delay_us(3); DS18B20_WRITE_HIGH(); DS18B20_MODE_IN(); delay_us(7); bit = DS18B20_READ(); delay_us(50); return bit; } ``` ### 3. 温度读取完整流程 ```c typedef struct { int8_t temp_int; uint16_t temp_frac; // 精确到0.0625℃ } DS18B20_Temp; DS18B20_Temp DS18B20_GetTemp(void) { uint8_t data[2]; if (!DS18B20_Reset()) return (DS18B20_Temp){0, 0}; DS18B20_WriteByte(0xCC); // 跳过 ROM DS18B20_WriteByte(0x44); // 启动转换 while (DS18B20_ReadBit() == 0); // 等待转换完成 DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器 data[0] = DS18B20_ReadByte(); data[1] = DS18B20_ReadByte(); DS18B20_Reset(); // 释放总线 int16_t raw = (data[1] << 8) | data[0]; return (DS18B20_Temp){ raw >> 4, (raw & 0x0F) * 625 }; } ``` ## 三、DHT11 读取原理与实现 ### 1. 起始信号 主机拉低总线 ≥18ms,然后释放并延迟 20~40μs。DHT11 随后拉低 80μs,再拉高 80μs,表示准备发送数据。 ```c uint8_t DHT11_Start(void) { DHT11_MODE_OUT(); DHT11_WRITE_LOW(); delay_ms(20); // 拉低 20ms DHT11_WRITE_HIGH(); delay_us(30); DHT11_MODE_IN(); // 等待 DHT11 响应 if (DHT11_READ()) return 0; // 总线应被拉低 while (!DHT11_READ()); // 等待拉高 while (DHT11_READ()); // 等待数据开始 return 1; } ``` ### 2. 数据位解码 每 bit 开始于 50μs 低电平,随后高电平长短区分 0/1。通常高电平 26~28μs 为 0,70μs 左右为 1。采样点放在高电平中间偏后(约 40μs)。 ```c uint8_t DHT11_ReadBit(void) { while (!DHT11_READ()); // 等待低电平结束 delay_us(40); // 跳过前 40μs uint8_t bit = DHT11_READ(); // 读取电平状态 while (DHT11_READ()); // 等待高电平结束,准备下一位 return bit ? 1 : 0; } ``` ### 3. 完整读取函数 ```c typedef struct { uint8_t humidity_int; uint8_t humidity_dec; uint8_t temp_int; uint8_t temp_dec; } DHT11_Data; uint8_t DHT11_ReadData(DHT11_Data *data) { if (!DHT11_Start()) return 0; uint8_t buf[5] = {0}; for (int i = 0; i < 5; i++) for (int j = 7; j >= 0; j--) buf[i] |= DHT11_ReadBit() << j; // 校验:前 4 字节和等于第 5 字节 if (buf[4] != (buf[0] + buf[1] + buf[2] + buf[3])) return 0; data->humidity_int = buf[0]; data->humidity_dec = buf[1]; data->temp_int = buf[2]; data->temp_dec = buf[3]; return 1; } ``` ## 四、STM32 工程要点 ### 1. GPIO 配置 建议使用**开漏输出**,并外接 4.7kΩ 上拉电阻。若用内部上拉,阻值偏大,不适合长线,可能导致时序不稳。 ```c void DS18B20_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏 GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } ``` ### 2. 微秒延时实现 HAL 的 `HAL_Delay` 只能精确到毫秒。可基于 DWT 实现微秒延时: ```c void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (HAL_RCC_GetHCLKFreq() / 1000000); while ((DWT->CYCCNT - start) < ticks); } ``` ## 五、注意事项 - **中断抢占**:读取过程中若发生中断,会导致时序超时,建议在读取函数入口关闭中断,读取结束后再开启。 - **再次读取间隔**:DS18B20 转换时间最少 100ms,DHT11 读取间隔建议 ≥2s,否则易读失败。 - **总线争夺**:两传感器协议不兼容,严禁将两者直接并联。若需共用引脚,必须用跳线或模拟开关分时切换,并重新初始化时序。 - **上拉电阻**:开漏模式必须有上拉电阻,否则总线无法拉高。 - **信号完整性**:连接线尽量短(<50cm),若使用延长线,改用屏蔽双绞线并降低通信频率(拉长时间参数)。 - **电源去耦**:在传感器 VCC 与 GND 之间加 100nF 电容,避免供电波动影响时序。 ## 总结 DS18B20 与 DHT11 的单总线读取核心在于精确控制时长。DS18B20 需要严格的时隙和 ROM 命令;DHT11 则更简单,但依赖于对高电平时长的判断。通过 GPIO 模拟时序,STM32 能轻松驱动这两类传感器。掌握上述原理后,你可以灵活应用于温湿度监控、环境采集等项目。