单总线协议

单总线(1-wire)是美国DALLAS公司推出的外围串行扩展总线技术。与SPI、I²C串行数据通信方式不同,它采用单根信号线传输。 这种协议由一个总线主节点、或多个从节点组成系统,通过根信号线对从芯片进行数据的读取。每一个符合单总线协议的从芯片都有一个唯一的地址,包括48位的序列号、8位的家族代码和8位的CRC代码。主芯片根据64位寻址对各个芯片进行双向通信,因此其协议对时序的要求较严格,初始化、写bit或读bit都有严格的时序要求,但是位于位之间没有严格要求。
单总线的数据传输速率一般为16.3Kbit/s,最大可达142 Kbit/s,通常情况下采用100Kbit/s以下的速率传输数据。主设备I/O口可直接驱动200m范围内的从设备,经过扩展后可达1km范围。
这种传输方式信号线上既传输时钟又传输数据,而且数据传输是双向的,具有节省I/O口线、资源结构简单、成本低廉、便于总线扩展和维护等诸多优点。

单总线只有一根数据线,设备(主机或从机)通过一个漏极开路或三态端口,连接至该数据线,这样允许设备在不发送数据时释放数据总线,以便总线被其它设各所使用。单总线端口为漏极开路,其内部等效电路下图所示:
在这里插入图片描述
单总线电路外接一个约5K的上拉电阻,当单总线处于空闲状态时为高电平,如果总线保持低电平超过480us,总线上的所有器件将复位。另外,在寄生方式供电时,为了保证单总线器件在某些工作状态下(如温度转换期间、EEPROM写入等)具有足够的电源电流,必须在总线上提供强上拉。

单总线器件主要有数字温度传感器(如DS18B20)、DHT11、A/D转换器(如DS2450)、门标、身份识别器(如DS1990A)、单总线控制器(如DS1WM)等。

DHT11 简介

DHT11 是一款温湿度一体化的数字传感器。该传感器包括一个电容式测湿元件和一个 NTC测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个 I/O 口。传感器内部湿度和温度数据 40Bit 的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。DHT11功耗很低,5V电源电压下,工作平均最大电流0.5mA。
DHT11 的技术参数如下:
⚫ 工作电压范围:3.3V ~ 5.5V
⚫ 工作电流:平均 0.5mA
⚫ 输出:单总线数字信号
⚫ 测量范围:湿度 5 ~ 95%RH,温度-20 ~ 60℃
⚫ 精度:湿度±5%,温度±2℃
⚫ 分辨率:湿度 1%,温度 0.1℃

DHT11 工作时序

虽然 DHT11 与 DS18B20 类似,都是单总线访问,但是 DHT11 的访问,相对 DS18B20 来说简单很多。
DHT11 数字温湿度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由 5byte(40bit)组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先处。DHT11 的数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数部分+8bit 校验和。其中校验和数据为前面四个字节相加。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如图

在这里插入图片描述
由以上数据就可得到湿度和温度的值,计算方法:
湿度 = byte4 . byte3 = 45.0(%RH)
温度 = byte2 . byte1 = 28.0(℃)
校验 = byte4 + byte3 + byte2 + byte1 = 73 (= 湿度 + 温度) (校验正确)
可以看出,DHT11 的数据格式十分简单的,DHT11和 MCU的一次通信最大为 34ms 左右,建议主机连续读取时间间隔不要小于 2s。
在这里插入图片描述首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后拉高数据线 t2(10~35us)时间,然后读取 DHT11 的响应,正常的话,DHT11 会拉低数据线,保持 t3(78~88us)时间,作为响应信号,然后 DHT11 拉高数据线,保持 t4(80~92us)时间后,开始输出数据。

DHT11 输出数字‘0’时序
在这里插入图片描述
DHT11 输出数字‘1’的时序
在这里插入图片描述
DHT11输出数字‘0’和‘1’时序,一开始都是 DHT11拉低数据线 54us,后面拉高数据线保持的时间就不一样,数字‘0’就是 23~27us,而数字‘1’就是 68~74us。

dht11.c

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <driver/rmt_rx.h>
#include <driver/rmt_tx.h>
#include <soc/rmt_reg.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp32/rom/ets_sys.h"

#define TAG		"DHT11"

uint8_t DHT11_PIN = -1;

//rmt接收通道句柄
static rmt_channel_handle_t rx_chan_handle = NULL;

//数据接收队列
static QueueHandle_t rx_receive_queue = NULL;

// 将RMT读取到的脉冲数据处理为温度和湿度
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10);

//接收完成回调函数
static bool IRAM_ATTR example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
    BaseType_t high_task_wakeup = pdFALSE;
    QueueHandle_t rx_receive_queue = (QueueHandle_t)user_data;
    // send the received RMT symbols to the parser task
    xQueueSendFromISR(rx_receive_queue, edata, &high_task_wakeup);
    return high_task_wakeup == pdTRUE;
}

/** DHT11初始化
 * @param dht11_pin GPIO引脚
 * @return 无
*/
void DHT11_Init(uint8_t dht11_pin)
{
	DHT11_PIN = dht11_pin;
    
    rmt_rx_channel_config_t rx_chan_config = {
        .clk_src = RMT_CLK_SRC_APB,   // 选择时钟源
        .resolution_hz = 1000 * 1000, 	  // 1 MHz 滴答分辨率,即 1 滴答 = 1 µs
        .mem_block_symbols = 64,          // 内存块大小,即 64 * 4 = 256 字节
        .gpio_num = dht11_pin,            // GPIO 编号
        .flags.invert_in = false,         // 不反转输入信号
        .flags.with_dma = false,          // 不需要 DMA 后端(ESP32S3才有)
    };
	//创建rmt接收通道
	ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle));
 
	//新建接收数据队列
    rx_receive_queue = xQueueCreate(20, sizeof(rmt_rx_done_event_data_t));
    assert(rx_receive_queue);

	//注册接收完成回调函数
	 ESP_LOGI(TAG, "register RX done callback");
    rmt_rx_event_callbacks_t cbs = {
        .on_recv_done = example_rmt_rx_done_callback,
    };
    ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan_handle, &cbs, rx_receive_queue));

	//使能RMT接收通道
	ESP_ERROR_CHECK(rmt_enable(rx_chan_handle));
}

// 将RMT读取到的脉冲数据处理为温度和湿度(rmt_symbol_word_t称为RMT符号)
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10)
{
	int i = 0;
	unsigned int rh = 0, temp = 0, checksum = 0;
	if (item_num < 41){					// 检查是否有足够的脉冲数
		//ESP_LOGI(TAG, "item_num < 41  %d",item_num);
		return 0;
	}
	if(item_num > 41)
		item++;								// 跳过开始信号脉冲

	for (i = 0; i < 16; i++, item++)	// 提取湿度数据
	{
		uint16_t duration = 0;
		if(item->level0)
			duration = item->duration0;
		else
			duration = item->duration1;
		rh = (rh << 1) + (duration < 35 ? 0 : 1);
	}


	for (i = 0; i < 16; i++, item++)	// 提取温度数据
	{
		uint16_t duration = 0;
		if(item->level0)
			duration = item->duration0;
		else
			duration = item->duration1;
		temp = (temp << 1) + (duration < 35 ? 0 : 1);
	}
	
	for (i = 0; i < 8; i++, item++){	// 提取校验数据
	
		uint16_t duration = 0;
		if(item->level0)
			duration = item->duration0;
		else
			duration = item->duration1;
		checksum = (checksum << 1) + (duration < 35 ? 0 : 1);
	}
	// 检查校验
	if ((((temp >> 8) + temp + (rh >> 8) + rh) & 0xFF) != checksum){
		ESP_LOGI(TAG, "Checksum failure %4X %4X %2X\n", temp, rh, checksum);
		return 0;
	}
	// 返回数据
	
	rh = rh >> 8;
	temp = (temp >> 8) * 10 + (temp & 0xFF);

	//判断数据合法性
	if(rh <= 100)
		*humidity = rh;
	if(temp <= 600)
		*temp_x10 = temp;
	return 1;
}


/** 获取DHT11数据
 * @param temp_x10 温度值
 * @return 无
*/
int DHT11_StartGet(int *temp_x10, int *humidity)
{
	//发送20ms开始信号脉冲启动DHT11单总线
	gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT);
	gpio_set_level(DHT11_PIN, 1);
	ets_delay_us(1000);
	gpio_set_level(DHT11_PIN, 0);
	ets_delay_us(20000);
	//拉高20us
	gpio_set_level(DHT11_PIN, 1);
	ets_delay_us(20);
	//信号线设置为输入准备接收数据
	gpio_set_direction(DHT11_PIN, GPIO_MODE_INPUT);
	gpio_set_pull_mode(DHT11_PIN,GPIO_PULLUP_ONLY);

	//启动RMT接收器以获取数据
	rmt_receive_config_t receive_config = {
        .signal_range_min_ns = 100,     //最小脉冲宽度(0.1us),信号长度小于这个值,视为干扰
        .signal_range_max_ns = 1000*1000, 	//最大脉冲宽度(1000us),信号长度大于这个值,视为结束信号
    };
	
	static rmt_symbol_word_t raw_symbols[128];	//接收缓存
    static rmt_rx_done_event_data_t rx_data;	//实际接收到的数据
	ESP_ERROR_CHECK(rmt_receive(rx_chan_handle, raw_symbols, sizeof(raw_symbols), &receive_config));

	// wait for RX done signal
	if (xQueueReceive(rx_receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) {
		// parse the receive symbols and print the result
		return parse_items(rx_data.received_symbols, rx_data.num_symbols,humidity, temp_x10);
	}
    return 0;
}

dht11.h

#ifndef _DHT11_H_
#define _DHT11_H_
#include <stdint.h>

/** DHT11初始化
 * @param dht11_pin GPIO引脚
 * @return 无
*/
void DHT11_Init(uint8_t dht11_pin);

/** 获取DHT11数据
 * @param temp_x10 温度值X10
 * @param humidity 湿度值
 * @return 无
*/
int DHT11_StartGet(int *temp_x10, int *humidity);

#endif

main.c

#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <driver/rmt_rx.h>
#include <driver/rmt_tx.h>
#include <soc/rmt_reg.h>
#include "driver/gpio.h" 
#include <esp_log.h>
#include <freertos/queue.h>
#include "esp32/rom/ets_sys.h"
#include "dht11.h"

#define DHT11_GPIO	25		// DHT11引脚定义
const static char *TAG = "DHT11_Demo";

// 温度 湿度变量
int temp = 0,hum = 0;

// 主函数
void app_main(void)
{
	ESP_ERROR_CHECK(nvs_flash_init());
	vTaskDelay(100 / portTICK_PERIOD_MS);

	ESP_LOGI(TAG, "[APP] APP Is Start!~\r\n");
	ESP_LOGI(TAG, "[APP] IDF Version is %d.%d.%d",ESP_IDF_VERSION_MAJOR,ESP_IDF_VERSION_MINOR,ESP_IDF_VERSION_PATCH);
	ESP_LOGI(TAG, "[APP] Free memory: %lu bytes", esp_get_free_heap_size());
	ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
	
	DHT11_Init(DHT11_GPIO);
	while (1){
		if (DHT11_StartGet(&temp, &hum)){
			ESP_LOGI(TAG, "temp->%i.%i C     hum->%i%%", temp / 10, temp % 10, hum);
		}
		vTaskDelay(1000 / portTICK_PERIOD_MS);
	}
}

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐