STM32 与传感器:DS18B20/DHT11 单总线读取实战
👁 2 阅读 · 2026-08-15 · STM32 传感器
单总线协议以极少的引脚实现传感器通信,DS18B20 和 DHT11 是其中最常用的两种。本文从物理层时序差异出发,讲解如何在 STM32 上通过 GPIO 模拟单总线时序,分别实现温度与湿度读取,并给出可直接移植的代码示例及常见坑点,帮助开发者快速上手。
## 一、单总线协议概要
单总线(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 能轻松驱动这两类传感器。掌握上述原理后,你可以灵活应用于温湿度监控、环境采集等项目。