Skip to content

About

A Linux kernel driver for reading temperature and humidity data from DHT11, DHT22, and AM2302 sensors connected to Raspberry Pi GPIO pins (bcm2835), Pi 4 (bcm2711), Pi 5 (bcm2712)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

Repository files navigation

DHT11/DHT22/AM2302 Temperature and Humidity Sensor Driver

Version: 2.0
Author: (c) 2026, Chapvic
License: GNU General Public License v3

A Linux kernel module for reading temperature and humidity data from DHT11, DHT22, and AM2302 sensors connected to Raspberry Pi GPIO pins. The driver creates a procfs interface under /proc/sensors/dht/ for managing sensor registration, configuration, and data retrieval.


Table of Contents

  1. Overview
  2. DHT Protocol Architecture
  3. Supported Hardware
  4. Wiring
  5. Quick Start
  6. Build & Install
  7. DKMS Installation
  8. Kernel Compatibility
  9. Module Loading
  10. Configuration File
  11. Procfs Interface
  12. Sensor Registration
  13. Reading Data
  14. Auto-Polling
  15. Manual Measurement
  16. Sensor Type Detection
  17. systemd Service
  18. Bash Examples
  19. Python Examples
  20. Error Codes
  21. Configuration Parameters
  22. Debug Mode
  23. Architecture
  24. Known Limitations
  25. Troubleshooting
  26. Files in This Repository
  27. License

Overview

The DHT driver provides a procfs-based interface for reading temperature and humidity from DHT11, DHT22, and AM2302 sensors connected to Raspberry Pi GPIO pins.

Key features:

  • Dynamic sensor registration via procfs (export/unexport)
  • Per-sensor proc entries for temperature, humidity, status, and configuration
  • Background polling thread with configurable interval (per-sensor or global)
  • Manual measurement trigger via procfs
  • Nanosecond-precision pulse timing for reliable reads across all Pi models
  • GPIO chip base caching for fast multi-sensor registration on Pi 3/4/5
  • Rate limiting for all measurements (manual and auto-poll)
  • Safe module unload with module reference counting (kref)
  • Shared /proc/sensors directory: coexists with other sensor drivers
  • Configuration file: optional /etc/default/dht for auto-registration at load
  • Kernel compatibility: 5.0 through 6.18+ (with compile-time API shims)
  • DKMS support: automatic rebuild on kernel updates
  • Configurable GPIO pin range: safe mode (0-27) or extended mode (0-53) via unsafe_pins parameter

DHT Protocol Architecture

The DHT11/DHT22/AM2302 sensors use a proprietary single-wire bidirectional protocol. The MCU (host) initiates communication, and the sensor responds with 40 bits of data (5 bytes).

Communication Sequence

  1. Host start signal: MCU pulls the data line low for at least 18 ms (driver uses 20 ms)
  2. Host release: MCU switches the line to input; pull-up resistor brings it high
  3. Sensor response (20-40 us after release):
    • Low pulse for ~80 us
    • High pulse for ~80 us
  4. Data transmission (40 bits):
    • Each bit: ~50 us low pulse, then a high pulse whose duration encodes the value
    • 0 bit: high pulse ~26 us
    • 1 bit: high pulse ~70 us
  5. End of frame: line returns to idle high state

Bit Encoding

Bit value Low pulse High pulse
0 ~50 us ~26 us
1 ~50 us ~70 us

The driver uses a 40 us threshold (BIT_THRESHOLD = 40000 ns) to distinguish 0 from 1.

40-bit Data Frame

Byte Field DHT11 format DHT22 format
0 Humidity integer 0-100 (integer) RH high byte
1 Humidity decimal 0 (always zero) RH low byte
2 Temperature integer 0-50 (integer) T high byte (bit 7 = sign)
3 Temperature decimal 0 (always zero) T low byte
4 Checksum (byte0+byte1+byte2+byte3) & 0xFF Same

DHT11 sends integer values only (decimal bytes are always 0). Values are scaled x10 in the driver for consistent units.

DHT22/AM2302 sends 16-bit values scaled x10: humidity as (byte0 << 8) | byte1, temperature as ((byte2 & 0x7F) << 8) | byte3. Bit 7 of byte 2 is the sign bit for negative temperatures.

Timing Parameters

Parameter Value Description
Start signal low 20 ms Host pulls line low
Sensor response delay 20-40 us After host releases line
Bit low pulse ~50 us Fixed low before each bit
Bit high pulse (0) ~26 us Below BIT_THRESHOLD (40 us)
Bit high pulse (1) ~70 us Above BIT_THRESHOLD (40 us)
Pulse timeout 300 us PULSE_TIMEOUT_NS -- abort if exceeded
Total frame duration ~4 ms 40 bits + handshake
Min time between reads 2 s MEAS_MIN_GAP -- sensor recovery time

The driver disables interrupts (local_irq_save/local_irq_restore) during the bit-bang read to prevent timing corruption from IRQ handlers and context switches. On Raspberry Pi 3, USB/SDIO/timer interrupt handlers can take 100-300 us, exceeding PULSE_TIMEOUT_NS and corrupting the read. This follows the pattern used by w1-gpio and i2c-gpio bit-bang drivers in the mainline kernel.

At sensor registration, the driver logs a notice in dmesg informing that each measurement blocks IRQ for up to ~4 ms (5 retries max).


Supported Hardware

Sensors

Sensor Temperature range Humidity range Accuracy (T) Accuracy (H) Type
DHT11 0-50 C 20-90% RH +/-2 C +/-5% RH Integer
DHT22 -40 to 80 C 0-100% RH +/-0.5 C +/-2% RH Decimal
AM2302 -40 to 80 C 0-100% RH +/-0.5 C +/-2% RH Decimal

AM2302 is a wired version of DHT22 with the same protocol.

Raspberry Pi Models

Model GPIO chip label GPIO base Safe pins Extended pins Notes
Pi 5 pinctrl-rp1 512+ 0-27 28-53 RP1 south bridge, large offset
Pi 4 pinctrl-bcm2711 0 0-27 28-53 * Direct BCM = global GPIO
Pi 3/Zero pinctrl-bcm2835 0 0-27 28-53 * Direct BCM = global GPIO
Pi 1/2 pinctrl-bcm2835 0 0-27 28-53 * Direct BCM = global GPIO

* Extended pins (28-53) on Pi models other than Pi 5 may conflict with system functions (PCM, SPI, UART). Use the unsafe_pins module parameter to enable these pins at your own risk.

Kernel Requirements

  • Minimum kernel version: 5.0
  • Required config options: CONFIG_GPIOLIB, CONFIG_PROC_FS, CONFIG_MODULES
  • Tested on: 5.0 through 6.18+ (Raspberry Pi OS, Ubuntu, Debian)

Wiring

Raspberry Pi GPIO Header

Connect the sensor's data pin to any available GPIO pin (BCM numbering). By default, the driver accepts BCM pin numbers 0-27. Use the unsafe_pins module parameter to extend the range to 0-53 (see Module Loading).

DHT Sensor       Raspberry Pi
---------        ------------
VCC (pin 1)  ->   3.3V (pin 1 or 17)
DATA         ->   GPIO pin of your choice (e.g., GPIO4 = pin 7)
GND (pin 4)  ->   GND (pin 6 or 9)

Pull-up Resistor

Most DHT sensor modules include an on-board pull-up resistor (4.7k-10k). If using a bare sensor:

  • Connect a 4.7k-10k resistor between DATA and VCC (3.3V)
  • Without the pull-up, readings will be unreliable or fail entirely

Pin Selection

  • BCM GPIO pins 0-27 are supported by default (safe mode)
  • Pins 28-53 can be enabled with the unsafe_pins module parameter (for Pi 5)
  • On Pi models other than Pi 5, pins 28-53 may conflict with system functions -- use with caution
  • Avoid pins with special functions (e.g., GPIO14/15 = UART, GPIO3 = I2C SDA) if those interfaces are in use
  • GPIO4 is a common choice for DHT sensors on Raspberry Pi

Quick Start

# 1. Clone or copy the driver files to a directory
cd dht-driver

# 2. Build
make

# 3. Load the module
sudo insmod dht.ko

# 4. Register a sensor on GPIO4
echo 4 | sudo tee /proc/sensors/dht/export

# 5. Read temperature and humidity
cat /proc/sensors/dht/gpio4/value

# 6. Enable auto-polling every 5 seconds
echo 5 | sudo tee /proc/sensors/dht/gpio4/interval

# 7. Or enable global auto-polling for all sensors
echo 10 | sudo tee /proc/sensors/dht/auto_interval

# 8. Check driver version
cat /proc/sensors/dht/version

# 9. Unload when done
sudo rmmod dht

Build & Install

Prerequisites

# Raspberry Pi OS / Debian / Ubuntu
sudo apt install build-essential linux-headers-$(uname -r)

Native Build

make            # runs pre-build checks, then builds dht.ko
make check      # run pre-build checks only
make modules    # build without checks
make clean      # remove build artifacts

Install (Native)

make install    # builds, installs to /lib/modules/$(uname -r)/, runs depmod
sudo modprobe dht

The module is installed to /lib/modules/$(uname -r)/updates/ by Kbuild. This is the standard location for out-of-tree modules and takes priority over /kernel/ in modprobe lookup.

Uninstall

make uninstall   # searches all module directories for dht.ko and removes it
sudo modprobe -r dht

Cross-Compilation

# Example: build on x86 for Raspberry Pi 5 (arm64)
make ARCH=arm64 \
     CROSS_COMPILE=aarch64-linux-gnu- \
     KDIR=/path/to/rpi-kernel/build

Make Targets

Target Description
make Run pre-build checks, then build the module
make check Pre-build checks only (headers, version)
make modules Build dht.ko without checks
make clean Remove build artifacts
make install Build, install to /lib/modules/..., run depmod
make uninstall Search and remove dht.ko from all directories
make help Show available targets and variables

DKMS Installation

DKMS (Dynamic Kernel Module Support) automatically rebuilds the module when the kernel is updated.

Prerequisites

sudo apt install dkms

Installation

sudo mkdir -p /usr/src/dht-2.0
sudo cp dht.c Makefile dkms.conf /usr/src/dht-2.0/
sudo dkms add dht/2.0
sudo dkms install dht/2.0
sudo modprobe dht

Verification

sudo dkms status
lsmod | grep dht
cat /proc/sensors/dht/version

dkms.conf

PACKAGE_NAME="dht"
PACKAGE_VERSION="2.0"
BUILT_MODULE_NAME[0]="dht"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"

Kernel Compatibility

The driver supports Linux kernels from 5.0 through 6.18+. API differences are handled at compile time using preprocessor macros.

API Compatibility Shims

proc_ops / file_operations (kernel 5.6)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
  #define DHT_PROC_OPS     struct proc_ops
  #define DHT_PROC_READ    .proc_read
#else
  #define DHT_PROC_OPS     struct file_operations
  #define DHT_PROC_READ    .read
#endif

pde_data / PDE_DATA (kernel 5.17)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
  #define DHT_PDE_DATA(inode)  pde_data(inode)
#else
  #define DHT_PDE_DATA(inode)  PDE_DATA(inode)
#endif

API Stability Table

API Used for Stable since Notes
proc_ops / file_operations procfs operations 5.6 / 5.0 Shim via DHT_PROC_OPS
pde_data() / PDE_DATA() Per-sensor data 5.17 / 5.0 Shim via DHT_PDE_DATA
gpio_to_desc() BCM pin lookup 3.x Stable, not removed
gpiod_to_chip() GPIO chip detection 3.x Stable API
gpiod_direction_output/input DHT protocol 3.x Stable
gpiod_get_value() Bit-bang read 3.x Stable
gpiod_cansleep() Sleep check 3.x Stable
ktime_get_ns() Pulse timing 4.x Stable
kref / kref_put Reference counting 2.6.x Stable
atomic_cmpxchg Measuring flag 2.6.x Stable
local_irq_save/restore Bit-bang protection 2.6.x Stable
filp_open / kernel_read Config file reading 2.6.x Stable

Module Loading

insmod (direct)

sudo insmod dht.ko
sudo insmod dht.ko dht_debug=1
sudo insmod dht.ko unsafe_pins=1

modprobe (after install)

sudo modprobe dht
sudo modprobe dht dht_debug=1
sudo modprobe dht unsafe_pins=1

Verify in dmesg

dmesg | grep DHT
# Expected (safe mode):
# [DHT]: DHT Driver (c) 2026, Chapvic (v2.0)
# [DHT]: driver loaded - /proc/sensors/dht/ (max 16 sensors)

# Expected (unsafe_pins mode):
# [DHT]: DHT Driver (c) 2026, Chapvic (v2.0)
# [DHT]: warning: unsafe pin mode enabled -- pins above 27 are allowed but may be unsafe for your board model
# [DHT]: driver loaded - /proc/sensors/dht/ (max 16 sensors)

Unload

sudo rmmod dht

Module Parameters

Parameter Type Default Description
dht_debug int 0 Debug logging (0=off, 1=on)
strict_config bool 0 Abort on config error (0=warn and continue, 1=abort)
unsafe_pins bool 0 Allow GPIO pins above 27 (0=safe mode, 1=enable 28-53)

unsafe_pins Mode

By default, the driver restricts GPIO pin numbers to 0-27 (MAX_PIN_NUM = 27). This is safe for all Raspberry Pi models and prevents accidental use of system pins.

When unsafe_pins=1 is specified, the driver extends the valid range to 0-53 (MAX_PIN_NUM_UNSAFE = 53), enabling access to additional GPIO pins on Raspberry Pi 5 (BCM2712). A warning is logged in dmesg at load time:

[DHT]: warning: unsafe pin mode enabled -- pins above 27 are allowed but may be unsafe for your board model

When a sensor is registered on a pin above 27, an additional warning is logged:

[dht_gpio_42]: warning: pin 42 is above 27, this may be unsafe for your board model

Caution: On Pi models other than Pi 5, pins 28-53 may be used for system functions (PCM, SPI, UART). Registering a sensor on such a pin may cause system instability. Always verify the pin is available on your specific board model.


Configuration File

The driver reads an optional configuration file at /etc/default/dht during module load. If the file is missing, the driver loads with defaults.

Format

Lines starting with # and empty lines are ignored. Unknown options and invalid values produce warnings in dmesg.

Options

Global options:

Option Description
DEBUG Enable debug logging (same as DEBUG=1)
`DEBUG=0 1`
AUTO_INTERVAL=n Global auto-poll interval in seconds (2-60)

Sensor registration:

Option Description
SENSOR=<pin> Register a sensor on the given BCM pin
SENSOR=<pin>,<n> Register with per-sensor auto-poll interval (2-60s)

Example

# /etc/default/dht
DEBUG=1
AUTO_INTERVAL=10
SENSOR=4              # DHT22 on GPIO4, uses global interval (10s)
SENSOR=17,5           # DHT11 on GPIO17, polls every 5 seconds
SENSOR=22             # Sensor on GPIO22, no auto-poll

Processing Order

  1. DEBUG is applied first
  2. AUTO_INTERVAL is applied next
  3. SENSOR= entries are processed in order

Config Parser Protection

The config parser enforces MAX_SENSORS during parsing -- if the limit is reached, remaining SENSOR= lines are skipped with a warning in dmesg. This prevents unbounded memory allocation from malformed or excessively long config files.

Pin registration errors are differentiated:

  • -EBUSY: the GPIO pin is already in use by another driver
  • -EINVAL: the pin number is invalid or out of range
  • -ENODEV: the GPIO descriptor could not be resolved

Procfs Interface

Global Entries (/proc/sensors/dht/)

File Perm Format Description
debug rw 0 or 1 Debug logging on/off
version r 2.0 Driver version
export w write <pin> Register a new sensor
unexport w write <pin> Unregister a sensor
auto_interval rw <n> (2-60) or -1 Global auto-poll interval

Per-Sensor Entries (/proc/sensors/dht/gpio<pin>/)

File Perm Format Description
pin r <pin> BCM GPIO pin number
interval rw <n> (2-60) or -1 Per-sensor auto-poll interval
measure w write 1 Trigger manual measurement
status_code r 0-5 Error code (0 = success)
status_text r SUCCESS / error text Human-readable status
value r H=<humidity>\nT=<temperature>\n Last reading (scaled x10)
info r Sensor type: DHT11\nRegister time: <ISO> Sensor type + registration time
timestamp r <unix_timestamp> Timestamp of last successful reading

File Format Details

value: H=<h>.<d>\nT=<t>.<d>\n where values are scaled x10 (e.g., H=45.2 means 45.2% RH). Negative temperatures show as T=-23.1.

info: Sensor type: DHT11\nRegister time: 2026-01-15T14:30:00Z\n or Sensor type: DHT22\n.... Empty output (EOF) if sensor type is not yet determined (no successful measurement).

timestamp: Unix timestamp (seconds since epoch) of the last successful measurement. 0 if no measurement has succeeded.

status_code: 0 = success, 1 = invalid pin, 2 = GPIO error, 3 = read failed, 4 = auto mode active, 5 = too soon.


Sensor Registration

Register a Sensor

echo 4 | sudo tee /proc/sensors/dht/export

This:

  1. Validates the pin number (0-27 in safe mode, 0-53 with unsafe_pins)
  2. Checks for duplicate registration
  3. Resolves the GPIO descriptor (with chip base caching)
  4. Requests the GPIO line
  5. Creates procfs entries under /proc/sensors/dht/gpio4/
  6. Performs an initial measurement
  7. Logs IRQ blocking latency notice in dmesg

Unregister a Sensor

echo 4 | sudo tee /proc/sensors/dht/unexport

Multiple Sensors

echo 4  | sudo tee /proc/sensors/dht/export
echo 17 | sudo tee /proc/sensors/dht/export
echo 22 | sudo tee /proc/sensors/dht/export

Maximum: 16 sensors (MAX_SENSORS).


Reading Data

Cached Values

The value, status_code, status_text, and timestamp entries return cached data from the last measurement. They do not trigger a new read. The driver performs an initial measurement during registration.

Reading in Shell

cat /proc/sensors/dht/gpio4/value
# Output: H=45.2
#         T=23.1

cat /proc/sensors/dht/gpio4/status_code
# Output: 0

Stale Data

If the last measurement failed, value still returns the last successful reading. Check status_code to verify data freshness.


Auto-Polling

Per-Sensor Auto-Poll

echo 5 | sudo tee /proc/sensors/dht/gpio4/interval
echo -1 | sudo tee /proc/sensors/dht/gpio4/interval

Global Auto-Poll

echo 10 | sudo tee /proc/sensors/dht/auto_interval
echo -1 | sudo tee /proc/sensors/dht/auto_interval

How It Works

  • Each sensor with an active interval gets its own kernel thread (dht_poll_<pin>)
  • Global interval takes priority over per-sensor interval
  • Rate limiting (2s minimum) applies to all measurements
  • If the poll thread fails to start (e.g., out of memory), the error is propagated to userspace

Manual Measurement

echo 1 | sudo tee /proc/sensors/dht/gpio4/measure
cat /proc/sensors/dht/gpio4/value
cat /proc/sensors/dht/gpio4/status_code
  • Manual measurement is rejected if auto-poll is active (status_code = 4)
  • Rate limited: minimum 2 seconds between measurements (status_code = 5)
  • Cannot run concurrently with another measurement on the same sensor (atomic flag)

Sensor Type Detection

The driver automatically detects the sensor type (DHT11 or DHT22) from the data format on the first successful measurement.

Detection Heuristic

  1. If the combined 16-bit humidity value > 1000: DHT11 (integer format, byte 0 > 100)
  2. If decimal bytes are 0 and values are within DHT11 ranges (H <= 100%, T <= 50C): DHT11
  3. Otherwise: DHT22/AM2302

Once detected, the type is locked and does not change between measurements.

cat /proc/sensors/dht/gpio4/info
# Output:
# Sensor type: DHT22
# Register time: 2026-01-15T14:30:00Z

systemd Service

Method 1: /etc/modules (simplest)

echo "dht" | sudo tee -a /etc/modules
echo "dht" | sudo tee -a /etc/modules-load.d/dht.conf

Method 2: Module with parameters via systemd

Create /etc/modprobe.d/dht.conf:

options dht dht_debug=0 strict_config=0 unsafe_pins=0

Create /etc/systemd/system/dht-driver.service:

[Unit]
Description=DHT sensor driver
After=systemd-modules-load.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/modprobe dht
ExecStop=/sbin/rmmod dht

[Install]
WantedBy=multi-user.target

Method 3: Full service with config file

[Unit]
Description=DHT Temperature and Humidity Sensor Driver
After=systemd-modules-load.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/modprobe dht
ExecStart=/bin/sh -c 'for pin in 4 17 22; do echo $$pin > /proc/sensors/dht/export; done'
ExecStart=/bin/sh -c 'echo 10 > /proc/sensors/dht/auto_interval'
ExecStop=/bin/sh -c 'for pin in 4 17 22; do echo $$pin > /proc/sensors/dht/unexport; done'
ExecStop=/sbin/rmmod dht

[Install]
WantedBy=multi-user.target

Bash Examples

1. Basic Reading

#!/bin/bash
PIN=4
data=$(cat /proc/sensors/dht/gpio$PIN/value)
humidity=$(echo "$data"  | grep '^H=' | cut -d= -f2)
temperature=$(echo "$data" | grep '^T=' | cut -d= -f2)
echo "GPIO$PIN: H=${humidity}% T=${temperature}C"

2. Manual Measurement

#!/bin/bash
PIN=4
echo 1 | sudo tee /proc/sensors/dht/gpio$PIN/measure > /dev/null
sleep 1
status=$(cat /proc/sensors/dht/gpio$PIN/status_code)
if [ "$status" = "0" ]; then
    cat /proc/sensors/dht/gpio$PIN/value
else
    echo "Measurement failed: $(cat /proc/sensors/dht/gpio$PIN/status_text)"
fi

3. CSV Logging

#!/bin/bash
PIN=4
INTERVAL=10
LOGFILE=/tmp/dht_log.csv
echo "timestamp,humidity,temperature" > "$LOGFILE"
while true; do
    data=$(cat /proc/sensors/dht/gpio$PIN/value)
    ts=$(cat /proc/sensors/dht/gpio$PIN/timestamp)
    h=$(echo "$data" | grep '^H=' | cut -d= -f2)
    t=$(echo "$data" | grep '^T=' | cut -d= -f2)
    echo "$ts,$h,$t" >> "$LOGFILE"
    sleep "$INTERVAL"
done

4. Read All Sensors

#!/bin/bash
for dir in /proc/sensors/dht/gpio*/; do
    [ -d "$dir" ] || continue
    pin=$(cat "${dir}pin")
    data=$(cat "${dir}value")
    status=$(cat "${dir}status_code")
    echo "GPIO$pin (status=$status): $data"
done

5. Threshold Monitoring

#!/bin/bash
PIN=4
TEMP_MAX=30.0
HUM_MAX=70.0
while true; do
    data=$(cat /proc/sensors/dht/gpio$PIN/value)
    h=$(echo "$data" | grep '^H=' | cut -d= -f2)
    t=$(echo "$data" | grep '^T=' | cut -d= -f2)
    if awk "BEGIN{exit !($t > $TEMP_MAX)}"; then
        echo "WARNING: Temperature $t C exceeds threshold $TEMP_MAX C"
    fi
    if awk "BEGIN{exit !($h > $HUM_MAX)}"; then
        echo "WARNING: Humidity $h% exceeds threshold $HUM_MAX%"
    fi
    sleep 5
done

Python Examples

1. Basic Reading

#!/usr/bin/env python3
PIN = 4
with open(f"/proc/sensors/dht/gpio{PIN}/value") as f:
    data = f.read().strip()
values = dict(line.split("=") for line in data.splitlines())
print(f"GPIO{PIN}: H={values['H']}%  T={values['T']}C")

2. Continuous Monitoring with CSV

#!/usr/bin/env python3
import time, csv, sys
PIN = 4
INTERVAL = 10
writer = csv.writer(sys.stdout)
writer.writerow(["timestamp", "humidity", "temperature", "status"])
try:
    while True:
        with open(f"/proc/sensors/dht/gpio{PIN}/value") as f:
            data = dict(l.split("=") for l in f.read().strip().splitlines())
        with open(f"/proc/sensors/dht/gpio{PIN}/status_code") as f:
            status = f.read().strip()
        with open(f"/proc/sensors/dht/gpio{PIN}/timestamp") as f:
            ts = f.read().strip()
        writer.writerow([ts, data["H"], data["T"], status])
        sys.stdout.flush()
        time.sleep(INTERVAL)
except KeyboardInterrupt:
    pass

3. Read All Sensors

#!/usr/bin/env python3
import os, glob
for d in sorted(glob.glob("/proc/sensors/dht/gpio*/")):
    pin = open(os.path.join(d, "pin")).read().strip()
    value = open(os.path.join(d, "value")).read().strip()
    status = open(os.path.join(d, "status_code")).read().strip()
    print(f"GPIO{pin} (status={status}): {value}")

Error Codes

Status Codes

Code Constant Description
0 ERR_SUCCESS Operation completed successfully
1 ERR_PIN_INVALID GPIO pin out of range (0-27, or 0-53 with unsafe_pins)
2 ERR_GPIO_REQUEST Failed to request or find GPIO descriptor
3 ERR_READ_FAILED Sensor data read failed (checksum/timeout)
4 ERR_AUTO_MODE Manual measure while auto-poll active
5 ERR_TOO_SOON Rate limited (minimum 2s between reads)

errno Values

errno Meaning When
-EINVAL Invalid argument Bad pin number, bad interval value
-ENODEV No such device Driver unloading, sensor not found
-EFAULT Bad address copy_from_user/copy_to_user failed
-EBUSY Device busy Pin already registered or in use by another driver
-ENOMEM Out of memory kzalloc for sensor struct failed, or max sensors reached

Configuration Parameters

Module Parameters

Parameter Type Default Range Description
dht_debug int 0 0-1 Debug logging via insmod/procfs
strict_config bool 0 0-1 Abort on config file error
unsafe_pins bool 0 0-1 Allow GPIO pins 28-53 (extended range for Pi 5)

Compile-Time Constants

Constant Value Description
MAX_SENSORS 16 Maximum simultaneously registered sensors
MAX_PIN_NUM 27 Highest valid BCM GPIO pin (safe mode)
MAX_PIN_NUM_UNSAFE 53 Highest valid BCM GPIO pin (unsafe_pins mode)
MIN_INTERVAL 2 Minimum auto-poll interval (seconds)
MAX_INTERVAL 60 Maximum auto-poll interval (seconds)
MEAS_MIN_GAP 2 Minimum seconds between measurements
MAX_RETRIES 5 Read attempts before giving up
RETRY_DELAY_MS 100 Delay between retries (milliseconds)
BIT_THRESHOLD 40000 Nanosecond 0/1 threshold (40 us)
PULSE_TIMEOUT_NS 300000 Max nanoseconds per pulse (300 us)
CONFIG_PATH /etc/default/dht Configuration file path

Debug Mode

Enabling Debug

Method 1: Module parameter

sudo insmod dht.ko dht_debug=1

Method 2: procfs at runtime

echo 1 | sudo tee /proc/sensors/dht/debug

Method 3: Configuration file

# /etc/default/dht
DEBUG=1

Example Debug Output

[DHT]: debug enabled
[dht_gpio_4]: poll thread started
[dht_gpio_4]: measurement OK - H=45.2% T=23.1 C
[dht_gpio_4]: read attempt 1 failed - j=38, data=[45,0,23,0,68]
[dht_gpio_4]: measurement OK - H=45.2% T=23.1 C
[DHT]: debug disabled

Note: Debug logging in the hot path (bit-bang read loop) is minimized to avoid timing jitter. When dht_debug=1, logging occurs before and after the measurement, not during the critical timing loop.


Architecture

Reference Counting (kref)

Each sensor struct has a kref refcount. The initial reference is created during registration. Additional references are taken in dht_proc_open() for each open procfs file. This ensures the sensor struct is not freed while a user has a procfs file open, even if the sensor is unexported.

Interrupt and Preemption Control

During the bit-bang read loop (~4 ms worst case), interrupts are disabled using local_irq_save/local_irq_restore to prevent timing corruption from IRQ handlers and context switches. This also disables preemption (no timer tick, no scheduler). On Raspberry Pi 3, USB/SDIO/timer interrupt handlers can take 100-300 us, exceeding PULSE_TIMEOUT_NS (300 us) and corrupting the read. The ~4 ms window is well within the 10-second softlockup detector threshold and follows the pattern used by w1-gpio and i2c-gpio bit-bang drivers in the mainline kernel.

At registration, the driver logs a notice: each measurement blocks IRQ for up to ~4 ms (5 retries max).

Sensor Type Detection

The driver detects DHT11 vs DHT22 on the first successful measurement using a heuristic based on the data format. Once detected, the type is locked.

GPIO Chip Base Caching

On Pi 5, the GPIO chip has a large base offset (512+). The driver caches the chip base after the first successful lookup, so subsequent registrations use a fast path (gpio_to_desc(base + pin)) instead of a full scan.

Rate Limiting

All measurements (manual and auto-poll) are rate-limited to at least 2 seconds apart. Manual measurements exceeding the rate limit get ERR_TOO_SOON; auto-poll measurements are silently skipped.

Measurement Flag (atomic_cmpxchg)

An atomic flag (sensor->measuring) prevents parallel bit-bang reads on the same GPIO. The flag is claimed with atomic_cmpxchg(&sensor->measuring, 0, 1) -- if it was already 1, another measurement is in progress and the request is rejected.

last_attempt_time Ordering

The last_attempt_time is updated after the cmpxchg succeeds and before the actual read begins. This ensures:

  • Rate-limited rejections do not extend the rate-limit window
  • Only actual measurement attempts (not rejected ones) reset the timer

Safe Module Unload

Module reference counting (try_module_get/module_put in procfs open/release) prevents rmmod while procfs files are open. The exit function sets dht_exiting to reject new opens, then performs a two-phase cleanup: remove procfs entries first, then stop threads.

Sleeping GPIO Chips

The driver checks gpiod_cansleep() and rejects sensors on sleeping GPIO expanders (e.g., I2C GPIO chips). The bit-bang timing loop requires non-sleeping GPIOs.

Configuration File Loading

At module load, /etc/default/dht is read using filp_open/kernel_read. Unknown options produce warnings in dmesg. Missing file is not an error. The parser enforces MAX_SENSORS during parsing to prevent unbounded memory allocation.

Procfs Empty Check

When the driver is unloaded, it checks whether /proc/sensors is empty (via iterate_dir) before removing the directory. If other drivers have entries in /proc/sensors, the directory is left intact.

Poll Thread Error Propagation

dht_start_poll() returns an error code (0 on success, -ENOMEM on failure). All call sites check the return value and propagate errors to userspace or log them in dmesg. This ensures that failed thread creation does not go unnoticed.

Unsafe Pins Mode

The unsafe_pins module parameter controls the maximum allowed GPIO pin number at runtime. When disabled (default), pins are limited to 0-27 (MAX_PIN_NUM). When enabled, the range extends to 0-53 (MAX_PIN_NUM_UNSAFE), allowing access to additional pins on Raspberry Pi 5. Warnings are logged at load time and at registration of pins above 27.

Hot Path Logging

Logging macros (pin_log, pin_dbg) are kept out of the critical bit-bang loop to prevent timing jitter. Debug output is available before and after the measurement, but not during the pulse-width sampling. When dht_debug is off, debug macros compile to a single READ_ONCE check with no side effects.


Known Limitations

  1. GPIO timing sensitivity: The bit-bang protocol requires microsecond-precision timing. High system load can cause read failures. Auto-polling mitigates this with retries.
  2. No I2C/SPI: The DHT protocol is single-wire only. I2C or SPI versions (e.g., SHT3x) require a different driver.
  3. Minimum 2s between reads: DHT sensors need recovery time. The driver enforces this for all measurements.
  4. No interrupt-based reading: The driver uses polling (kthread), not GPIO interrupts. This simplifies the code but adds CPU overhead during reads.
  5. Sleeping GPIO chips not supported: Sensors on I2C GPIO expanders cannot use this driver due to timing requirements.
  6. Max 16 sensors: The MAX_SENSORS limit prevents unbounded memory allocation. Sufficient for typical DHT deployments (rarely more than 8-10 sensors per board).
  7. One sensor per GPIO pin: Each BCM pin can have at most one sensor.
  8. No device tree binding: Sensors are registered dynamically via procfs, not via device tree overlays.
  9. Pins 28-53 require unsafe_pins: Extended GPIO pins are disabled by default. On Pi models other than Pi 5, these pins may conflict with system functions.

Troubleshooting

Symptom Cause Solution
make fails: kernel headers not found Missing linux-headers sudo apt install linux-headers-$(uname -r)
make fails: kernel version too old Kernel < 5.0 Upgrade kernel or use an older driver version
implicit declaration of 'proc_read' Kernel < 5.6, missing shim Ensure dht.c has DHT_PROC_OPS macros
DKMS build fails Missing dkms.conf or headers sudo apt install dkms linux-headers-...
Module fails to load GPIO support not in kernel Enable CONFIG_GPIOLIB in kernel config
Read always fails (status=3) Wiring, pull-up, or timing Check wiring, add 4.7k-10k pull-up
modprobe: dht not found Module not installed make install or DKMS install
rmmod: Module dht is in use Procfs file still open Close all /proc/sensors/dht/ files
Sensor not detected No successful measurement yet Check wiring, try echo 1 > measure
PDE_DATA implicit declaration Kernel >= 5.17, old code Use DHT_PDE_DATA macro
Pin > 27 rejected with -EINVAL unsafe_pins not enabled Load with unsafe_pins=1 if pin is safe
dmesg: unsafe pin mode warning unsafe_pins=1 was specified Expected behavior -- verify pins are safe
Config: max sensors reached More than 16 SENSOR= lines Reduce sensors or check for config errors

Files in This Repository

File Description
dht.c Driver source code
Makefile Build system with pre-build checks
dkms.conf DKMS configuration for automatic rebuilds
LICENSE GNU General Public License v3
VERSION Driver version string (2.0)
Changelog Change log with [Fix N] numbered entries
FULL_REPORT_EN.md Full audit report (English)
FULL_REPORT_RU.md Full audit report (Russian)
README.md This file (English documentation)
README_RU.md Russian documentation

License

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.

Copyright (c) 2026, Chapvic

Acknowledgements

The DHT communication protocol implementation is based on principles described in the Adafruit DHT library and various Linux kernel GPIO drivers. The single-wire bit-bang approach with local_irq_save follows established patterns in the Linux kernel community.

About

A Linux kernel driver for reading temperature and humidity data from DHT11, DHT22, and AM2302 sensors connected to Raspberry Pi GPIO pins (bcm2835), Pi 4 (bcm2711), Pi 5 (bcm2712)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages