Files
xInfo/main/Dht11Driver.cpp
T
2026-08-31 15:05:13 +08:00

131 lines
4.7 KiB
C++

#include "Dht11Driver.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_rom_sys.h"
static const char *TAG = "Dht11Driver";
Dht11Driver::Dht11Driver(gpio_num_t pin)
: pin_(pin), lastTemperature_(NAN), lastHumidity_(NAN), lastReadTimeUs_(0) {}
Dht11Driver::~Dht11Driver() {}
void Dht11Driver::init() {
ESP_LOGI(TAG, "初始化 DHT11 引脚: GPIO%d", pin_);
gpio_reset_pin(pin_);
gpio_set_pull_mode(pin_, GPIO_PULLUP_ONLY);
gpio_set_direction(pin_, GPIO_MODE_INPUT);
}
// 辅助函数:等待指定电平状态,超时返回 -1,成功返回耗时(us)
static inline int wait_for_level(gpio_num_t pin, int level, int timeout_us) {
int elapsed = 0;
while (gpio_get_level(pin) != level) {
if (elapsed >= timeout_us) return -1;
esp_rom_delay_us(1);
elapsed++;
}
return elapsed;
}
bool Dht11Driver::read(float& temperature, float& humidity) {
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
int err_phase = 0; // 0=OK, 1=PhaseA, 2=PhaseB, 3=PhaseC, 4=LowTimeout, 5=HighTimeout
int err_bit = 0;
int64_t now = esp_timer_get_time();
uint8_t data[5] = {0,0,0,0,0};
// 开机前 3 秒内,强制阻塞等待(而不是直接抛弃),确保开机第一次上报就有温湿度!
if (now < 3000000) {
int wait_ms = (3000000 - now) / 1000;
ESP_LOGI(TAG, "传感器刚上电,强制耐心等待 %d 毫秒...", wait_ms);
vTaskDelay(pdMS_TO_TICKS(wait_ms));
now = esp_timer_get_time();
}
if (now - lastReadTimeUs_ < 2200000 && lastReadTimeUs_ != 0) {
ESP_LOGI(TAG, "距离上次采集太近,直接返回缓存");
goto ret_cache;
}
lastReadTimeUs_ = now;
// 1. 发送启动信号:MCU 拉低至少 18ms
gpio_set_direction(pin_, GPIO_MODE_OUTPUT);
gpio_set_level(pin_, 0);
esp_rom_delay_us(20000); // 20ms
// 2. MCU 释放总线(拉高),等待 20-40us
gpio_set_level(pin_, 1);
esp_rom_delay_us(30);
gpio_set_direction(pin_, GPIO_MODE_INPUT);
// 3. 开始临界区,在这个 4~5 毫秒的时间内,我们将暂停 Wi-Fi 中断和操作系统任务调度
// 不然一旦 CPU 去处理网络包,几十微秒的时间窗口瞬间就会错过并导致“超时”
portENTER_CRITICAL(&mux); // 关闭中断保护时序
// 等待传感器拉低 (Phase A,传感器响应起始)
if (wait_for_level(pin_, 0, 80) < 0) { err_phase = 1; goto end_critical; }
// 等待传感器拉高 (Phase B,传感器准备发送数据)
if (wait_for_level(pin_, 1, 80) < 0) { err_phase = 2; goto end_critical; }
// 等待传感器再次拉低 (开始发送 40 bit 数据)
if (wait_for_level(pin_, 0, 80) < 0) { err_phase = 3; goto end_critical; }
// 读取 40 个 bit (5 bytes)
for (int i = 0; i < 40; i++) {
// 等待低电平结束 (50us)
if (wait_for_level(pin_, 1, 70) < 0) { err_phase = 4; err_bit = i; goto end_critical; }
// 高电平持续时间决定了这 bit 是 0 还是 1. (26-28us = 0, 70us = 1)
int high_time = wait_for_level(pin_, 0, 100);
if (high_time < 0) { err_phase = 5; err_bit = i; goto end_critical; }
data[i / 8] <<= 1;
if (high_time > 40) {
data[i / 8] |= 1;
}
}
end_critical:
portEXIT_CRITICAL(&mux); // 退出临界区,恢复系统调度和所有的硬件中断
if (err_phase != 0) {
if (err_phase == 1) ESP_LOGE(TAG, "读取失败: 传感器无响应 (Phase A timeout)");
else if (err_phase == 2) ESP_LOGE(TAG, "读取失败: 传感器握手失败 (Phase B timeout)");
else if (err_phase == 3) ESP_LOGE(TAG, "读取失败: 传感器未开始发数据 (Phase C timeout)");
else if (err_phase == 4) ESP_LOGE(TAG, "位读取失败: 低电平超时 bit %d", err_bit);
else if (err_phase == 5) ESP_LOGE(TAG, "位读取失败: 高电平超时 bit %d", err_bit);
goto ret_fail;
}
// 7. 校验和检验
if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
lastHumidity_ = data[0] + data[1] * 0.1f;
lastTemperature_ = data[2] + data[3] * 0.1f;
temperature = lastTemperature_;
humidity = lastHumidity_;
ESP_LOGI(TAG, "自写驱动读取成功: T=%.1f°C, H=%.1f%%", temperature, humidity);
return true;
} else {
ESP_LOGE(TAG, "Checksum failed: %02x %02x %02x %02x = %02x", data[0], data[1], data[2], data[3], data[4]);
goto ret_fail;
}
ret_cache:
temperature = lastTemperature_;
humidity = lastHumidity_;
return !std::isnan(lastTemperature_);
ret_fail:
lastTemperature_ = NAN;
lastHumidity_ = NAN;
temperature = NAN;
humidity = NAN;
return false;
}