Jan 31, 2026
5 min read
ESP32,
Rust,
C,
Embedded Systems,

Using Rust for Embedded Development: Comparative Analysis of C and Rust Implementations for DHT11 Temperature and Humidity Sensor on ESP32S3

In-depth comparison of C and Rust implementations for DHT11 temperature and humidity sensor on ESP32S3 development board, detailing slow response issues encountered with the C version and the impact of CPU task scheduling, providing complete code examples and debugging experience sharing.

DHT11 is a sensor that can simultaneously detect temperature and humidity. It looks like this:

dht11

The four pins are VDD, DATA, NC, and GND. NC is not connected and can be ignored. That is to say, the DATA pin of the DHT11 transmits data, and I connect it to the free GPIO7 here.

The circuit schematic is as follows:

dht11-schematic

For C language, use this library:

idf.py add-dependency "esp-idf-lib/dht^1.2.0"

C implementation is as follows:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "dht.h"

#define DHT_GPIO_NUM 7  // GPIO pin, modify according to actual situation
#define DHT_TYPE DHT_TYPE_DHT11  // Select according to actual sensor model

static const char* TAG = "DHT";

void app_main(void)
{
    int16_t temperature;
    int16_t humidity;
    float f_temperature;
    float f_humidity;

    // Configure GPIO - pull-up resistor must be enabled
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << DHT_GPIO_NUM),
        .mode = GPIO_MODE_OUTPUT_OD,  // Open-drain output
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE
    };

    ESP_ERROR_CHECK(gpio_config(&io_conf));
    gpio_set_level(DHT_GPIO_NUM, 1);  // Set to high level idle state


    ESP_LOGI(TAG, "Starting DHT sensor test...");
    ESP_LOGI(TAG, "Sensor: DHT11, GPIO: %d", DHT_GPIO_NUM);

    while(1) {
         vTaskDelay(pdMS_TO_TICKS(2000));  // DHT sensor requires at least 2-second intervals
        // Use floating-point version to read data
        esp_err_t  result = dht_read_float_data(DHT_TYPE, DHT_GPIO_NUM, &f_humidity, &f_temperature);
        if (result == ESP_OK) {
            ESP_LOGI(TAG, "Float Readings: Humidity: %.1f%% Temp: %.1f°C",
                     f_humidity, f_temperature);
        } else {
            ESP_LOGE(TAG, "Could not read float data from sensor, error: %d", result);
        }

    }
}

Output is as follows:

I (240) sleep_gpio: Configure to isolate all GPIO pins in sleep state
I (247) sleep_gpio: Enable automatic switching of GPIO sleep configuration
I (253) main_task: Started on CPU0
I (263) main_task: Calling app_main()
I (263) DHT: Starting DHT sensor test...
I (263) DHT: Sensor: DHT11, GPIO: 7
I (301243) DHT: Float Readings: Humidity: 52.0% Temp: 20.0°C
I (396183) DHT: Float Readings: Humidity: 49.0% Temp: 20.0°C
I (725443) DHT: Float Readings: Humidity: 49.0% Temp: 20.0°C
I (1345583) DHT: Float Readings: Humidity: 50.0% Temp: 20.0°C
I (2525263) DHT: Float Readings: Humidity: 48.0% Temp: 20.0°C

Issues

Currently, with this circuit connection, the sensor readings from the C implementation are extremely slow (shouldn’t call it slow, but rather abnormal - most of the time no data is returned). It takes several minutes for the data to return, which significantly deviates from the sensor’s specifications.

The initial suspicion was voltage issues. Since the supply voltage of my ESP32S3 is 3.3V while the sensor’s operating voltage range is 3.3V - 5V, it’s possible that the sensor isn’t reaching its required working voltage, causing abnormal operation.

However, interestingly, some people online have encountered the same issue, while others have successfully implemented working solutions.

More interestingly, the Rust code below works perfectly fine with no problems!

Rust Implementation

Rust doesn’t need to implement the DHT11 protocol from scratch since there’s a library available for direct use.

embedded-dht-rs = { version = "0.5.0", features = ["dht11"] }

Similar to C, Rust also requires GPIO initialization, setting it to open-drain output and enabling the pull-up resistor.

    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    let mut dht11_pin = Flex::new(peripherals.GPIO15);
    dht11_pin.apply_output_config(
        &OutputConfig::default()
            .with_drive_mode(DriveMode::OpenDrain)
            .with_pull(Pull::None),
    );
    dht11_pin.set_output_enable(true);
    dht11_pin.set_input_enable(true);
    dht11_pin.set_high();

Rust implementation output is as follows:

I (107) esp_image: segment 3: paddr=000129d8 vaddr=00000000 size=0d640h ( 54848)
I (126) esp_image: segment 4: paddr=00020020 vaddr=42010020 size=02c68h ( 11368) map
I (130) boot: Loaded app from partition at offset 0x10000
I (130) boot: Disabling RNG early entropy source...
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %
DHT11 - Temperature: 21 °C, humidity: 47 %

Complete implementation:

#![no_std]
#![no_main]
#![deny(
    clippy::mem_forget,
    reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
    holding buffers for the duration of a data transfer."
)]
#![deny(clippy::large_stack_frames)]

use embedded_dht_rs::dht11::Dht11;
use esp_hal::main;
use esp_hal::{
    clock::CpuClock,
    delay::Delay,
    gpio::{DriveMode, Flex, OutputConfig, Pull},
};

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

extern crate alloc;

esp_bootloader_esp_idf::esp_app_desc!();

#[allow(
    clippy::large_stack_frames,
    reason = "it's not unusual to allocate larger buffers etc. in main"
)]
#[main]
fn main() -> ! {
    // generator version: 1.2.0

    let delay = Delay::new();

    rtt_target::rtt_init_print!();

    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    let mut dht11_pin = Flex::new(peripherals.GPIO15);
    dht11_pin.apply_output_config(
        &OutputConfig::default()
            .with_drive_mode(DriveMode::OpenDrain)
            .with_pull(Pull::None),
    );
    dht11_pin.set_output_enable(true);
    dht11_pin.set_input_enable(true);
    dht11_pin.set_high();

    let mut dht11 = Dht11::new(dht11_pin, Delay::new());

    esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744);

    loop {
        delay.delay_millis(2000);

        match dht11.read() {
            Ok(sensor_reading) => {
                esp_println::println!(
                    "DHT11 - Temperature: {} °C, humidity: {} %",
                    sensor_reading.temperature,
                    sensor_reading.humidity
                );
            }
            Err(error) => {
                esp_println::dbg!("Failed to read DHT11 sensor: {:?}", error);
            }
        }
    }
}

In theory, connecting a small screen would create a temperature and humidity meter.

Why?

Initially, the reason why the C code didn’t work properly while the Rust code did was unclear. I even reimplemented the DHT11 protocol again in C, but the result remained the same.

Since the DHT11 protocol transmits data through high and low-level signals on a single data line, I suspected that the C code didn’t handle timing delays properly during implementation, causing the sensor to fail to operate normally.

Until one time I bound this function to CPU1 of the ESP32S3, and it finally worked properly!

 xTaskCreatePinnedToCore(dht_task, "dht_task",4096, NULL, 3, NULL, 1); //assign to CPU1

This indicates that on CPU0, there are other higher priority tasks competing for time slices, preventing the DHT sensor from functioning properly.

Checking sdkconfig, I noticed that WiFi functionality was enabled in the configuration, though I’m unsure if this causes conflicts with the DHT.

Another point is that DHT11 requires microsecond-level timers, so time-consuming operations should not be performed during communication, otherwise communication will fail. For example, using ESP_LOGI for logging…