131 lines
4.7 KiB
C++
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;
|
|
}
|