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.
- Overview
- DHT Protocol Architecture
- Supported Hardware
- Wiring
- Quick Start
- Build & Install
- DKMS Installation
- Kernel Compatibility
- Module Loading
- Configuration File
- Procfs Interface
- Sensor Registration
- Reading Data
- Auto-Polling
- Manual Measurement
- Sensor Type Detection
- systemd Service
- Bash Examples
- Python Examples
- Error Codes
- Configuration Parameters
- Debug Mode
- Architecture
- Known Limitations
- Troubleshooting
- Files in This Repository
- License
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/sensorsdirectory: coexists with other sensor drivers - Configuration file: optional
/etc/default/dhtfor 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_pinsparameter
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).
- Host start signal: MCU pulls the data line low for at least 18 ms (driver uses 20 ms)
- Host release: MCU switches the line to input; pull-up resistor brings it high
- Sensor response (20-40 us after release):
- Low pulse for ~80 us
- High pulse for ~80 us
- 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
- End of frame: line returns to idle high state
| 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.
| 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.
| 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).
| 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.
| 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.
- 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)
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)
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
- BCM GPIO pins 0-27 are supported by default (safe mode)
- Pins 28-53 can be enabled with the
unsafe_pinsmodule 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
# 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# Raspberry Pi OS / Debian / Ubuntu
sudo apt install build-essential linux-headers-$(uname -r)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 artifactsmake install # builds, installs to /lib/modules/$(uname -r)/, runs depmod
sudo modprobe dhtThe 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.
make uninstall # searches all module directories for dht.ko and removes it
sudo modprobe -r dht# Example: build on x86 for Raspberry Pi 5 (arm64)
make ARCH=arm64 \
CROSS_COMPILE=aarch64-linux-gnu- \
KDIR=/path/to/rpi-kernel/build| 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 (Dynamic Kernel Module Support) automatically rebuilds the module when the kernel is updated.
sudo apt install dkmssudo 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 dhtsudo dkms status
lsmod | grep dht
cat /proc/sensors/dht/versionPACKAGE_NAME="dht"
PACKAGE_VERSION="2.0"
BUILT_MODULE_NAME[0]="dht"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"The driver supports Linux kernels from 5.0 through 6.18+. API differences are handled at compile time using preprocessor macros.
#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#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 | 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 |
sudo insmod dht.ko
sudo insmod dht.ko dht_debug=1
sudo insmod dht.ko unsafe_pins=1sudo modprobe dht
sudo modprobe dht dht_debug=1
sudo modprobe dht unsafe_pins=1dmesg | 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)sudo rmmod dht| 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) |
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.
The driver reads an optional configuration file at /etc/default/dht during module load. If the file is missing, the driver loads with defaults.
Lines starting with # and empty lines are ignored. Unknown options and invalid values produce warnings in dmesg.
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) |
# /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-pollDEBUGis applied firstAUTO_INTERVALis applied nextSENSOR=entries are processed in order
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
| 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 |
| 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 |
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.
echo 4 | sudo tee /proc/sensors/dht/exportThis:
- Validates the pin number (0-27 in safe mode, 0-53 with
unsafe_pins) - Checks for duplicate registration
- Resolves the GPIO descriptor (with chip base caching)
- Requests the GPIO line
- Creates procfs entries under
/proc/sensors/dht/gpio4/ - Performs an initial measurement
- Logs IRQ blocking latency notice in
dmesg
echo 4 | sudo tee /proc/sensors/dht/unexportecho 4 | sudo tee /proc/sensors/dht/export
echo 17 | sudo tee /proc/sensors/dht/export
echo 22 | sudo tee /proc/sensors/dht/exportMaximum: 16 sensors (MAX_SENSORS).
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.
cat /proc/sensors/dht/gpio4/value
# Output: H=45.2
# T=23.1
cat /proc/sensors/dht/gpio4/status_code
# Output: 0If the last measurement failed, value still returns the last successful reading. Check status_code to verify data freshness.
echo 5 | sudo tee /proc/sensors/dht/gpio4/interval
echo -1 | sudo tee /proc/sensors/dht/gpio4/intervalecho 10 | sudo tee /proc/sensors/dht/auto_interval
echo -1 | sudo tee /proc/sensors/dht/auto_interval- 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
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)
The driver automatically detects the sensor type (DHT11 or DHT22) from the data format on the first successful measurement.
- If the combined 16-bit humidity value > 1000: DHT11 (integer format, byte 0 > 100)
- If decimal bytes are 0 and values are within DHT11 ranges (H <= 100%, T <= 50C): DHT11
- 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:00Zecho "dht" | sudo tee -a /etc/modules
echo "dht" | sudo tee -a /etc/modules-load.d/dht.confCreate /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[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#!/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"#!/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#!/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#!/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#!/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#!/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")#!/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#!/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}")| 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 | 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 |
| 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) |
| 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 |
Method 1: Module parameter
sudo insmod dht.ko dht_debug=1Method 2: procfs at runtime
echo 1 | sudo tee /proc/sensors/dht/debugMethod 3: Configuration file
# /etc/default/dht
DEBUG=1
[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.
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.
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).
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
- GPIO timing sensitivity: The bit-bang protocol requires microsecond-precision timing. High system load can cause read failures. Auto-polling mitigates this with retries.
- No I2C/SPI: The DHT protocol is single-wire only. I2C or SPI versions (e.g., SHT3x) require a different driver.
- Minimum 2s between reads: DHT sensors need recovery time. The driver enforces this for all measurements.
- No interrupt-based reading: The driver uses polling (kthread), not GPIO interrupts. This simplifies the code but adds CPU overhead during reads.
- Sleeping GPIO chips not supported: Sensors on I2C GPIO expanders cannot use this driver due to timing requirements.
- Max 16 sensors: The
MAX_SENSORSlimit prevents unbounded memory allocation. Sufficient for typical DHT deployments (rarely more than 8-10 sensors per board). - One sensor per GPIO pin: Each BCM pin can have at most one sensor.
- No device tree binding: Sensors are registered dynamically via procfs, not via device tree overlays.
- 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.
| 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 |
| 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 |
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
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.