From b82b5df6bc07f365e2c93c53ab8845620fa69a8f Mon Sep 17 00:00:00 2001 From: jeremy90307 Date: Thu, 1 May 2025 08:16:54 +0800 Subject: [PATCH] Add a DHT11 Sensor Driver Example Using GPIO Use GPIO to communicate with the DHT11 sensor. Test detail: - Tested on Raspberry Pi 5B with Raspberry Pi OS (Debian 12, Linux version 6.6.74) - Verify that the DHT11 driver compiles and loads successfully - Verify that temperature and humidity data can be read correctly from the DHT11 sensor --- .ci/non-working | 1 + examples/Makefile | 1 + examples/dht11.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++ lkmpg.tex | 35 ++++++ 4 files changed, 302 insertions(+) create mode 100644 examples/dht11.c diff --git a/.ci/non-working b/.ci/non-working index 6f2c90eb..61350571 100644 --- a/.ci/non-working +++ b/.ci/non-working @@ -4,3 +4,4 @@ intrpt vkbd syscall-steal led +dht11 diff --git a/examples/Makefile b/examples/Makefile index df05b032..074404b3 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -32,6 +32,7 @@ obj-m += vinput.o obj-m += vkbd.o obj-m += static_key.o obj-m += led.o +obj-m += dht11.o PWD := $(CURDIR) diff --git a/examples/dht11.c b/examples/dht11.c new file mode 100644 index 00000000..703ecac5 --- /dev/null +++ b/examples/dht11.c @@ -0,0 +1,265 @@ +/* + * dht11.c - Using GPIO to read temperature and humidity from DHT11 sensor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GPIO_PIN_4 575 +#define DEVICE_NAME "dht11" +#define DEVICE_CNT 1 + +static char msg[64]; + +struct dht11_dev { + dev_t dev_num; + int major_num, minor_num; + struct cdev cdev; + struct class *cls; + struct device *dev; +}; + +static struct dht11_dev dht11_device; + +/* Define GPIOs for LEDs. + * TODO: According to the requirements, search /sys/kernel/debug/gpio to + * find the corresponding GPIO location. + */ +static struct gpio dht11[] = { { GPIO_PIN_4, GPIOF_OUT_INIT_HIGH, "Signal" } }; + +static int dht11_read_data(void) +{ + int timeout; + uint8_t sensor_data[5] = { 0 }; + uint8_t i, j; + + gpio_set_value(dht11[0].gpio, 0); + mdelay(20); + gpio_set_value(dht11[0].gpio, 1); + udelay(30); + gpio_direction_input(dht11[0].gpio); + udelay(2); + + timeout = 300; + while (gpio_get_value(dht11[0].gpio) && timeout--) + udelay(1); + + if (timeout == -1) + return -ETIMEDOUT; + + timeout = 300; + while (!gpio_get_value(dht11[0].gpio) && timeout--) + udelay(1); + + if (timeout == -1) + return -ETIMEDOUT; + + timeout = 300; + while (gpio_get_value(dht11[0].gpio) && timeout--) + udelay(1); + + if (timeout == -1) + return -ETIMEDOUT; + + for (j = 0; j < 5; j++) { + uint8_t byte = 0; + for (i = 0; i < 8; i++) { + timeout = 300; + while (gpio_get_value(dht11[0].gpio) && timeout--) + udelay(1); + + if (timeout == -1) + return -ETIMEDOUT; + + timeout = 300; + while (!gpio_get_value(dht11[0].gpio) && timeout--) + udelay(1); + + if (timeout == -1) + return -ETIMEDOUT; + + udelay(50); + byte <<= 1; + if (gpio_get_value(dht11[0].gpio)) + byte |= 0x01; + } + sensor_data[j] = byte; + } + + if (sensor_data[4] != (uint8_t)(sensor_data[0] + sensor_data[1] + + sensor_data[2] + sensor_data[3])) + return -EIO; + + gpio_direction_output(dht11[0].gpio, 1); + sprintf(msg, "Humidity: %d%%\nTemperature: %d deg C\n", sensor_data[0], + sensor_data[2]); + + return 0; +} + +static int device_open(struct inode *inode, struct file *file) +{ + int ret, retry; + + for (retry = 0; retry < 5; ++retry) { + ret = dht11_read_data(); + if (ret == 0) + return 0; + msleep(10); + } + gpio_direction_output(dht11[0].gpio, 1); + + return ret; +} + +static int device_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t device_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + int msg_len = strlen(msg); + + if (*offset >= msg_len) + return 0; + + size_t remain = msg_len - *offset; + size_t bytes_read = min(length, remain); + + if (copy_to_user(buffer, msg + *offset, bytes_read)) + return -EFAULT; + + *offset += bytes_read; + + return bytes_read; +} + +static struct file_operations fops = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + .owner = THIS_MODULE, +#endif + .open = device_open, + .release = device_release, + .read = device_read +}; + +/* Initialize the module - Register the character device */ +static int __init dht11_init(void) +{ + int ret = 0; + + /* Determine whether dynamic allocation of the device number is needed. */ + if (dht11_device.major_num) { + dht11_device.dev_num = + MKDEV(dht11_device.major_num, dht11_device.minor_num); + ret = register_chrdev_region(dht11_device.dev_num, DEVICE_CNT, + DEVICE_NAME); + } else { + ret = alloc_chrdev_region(&dht11_device.dev_num, 0, DEVICE_CNT, + DEVICE_NAME); + } + + /* Negative values signify an error */ + if (ret < 0) { + pr_alert("Failed to register character device, error: %d\n", ret); + return ret; + } + + pr_info("Major = %d, Minor = %d\n", MAJOR(dht11_device.dev_num), + MINOR(dht11_device.dev_num)); + + /* Prevents module unloading while operations are in use */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + dht11_device.cdev.owner = THIS_MODULE; +#endif + + cdev_init(&dht11_device.cdev, &fops); + ret = cdev_add(&dht11_device.cdev, dht11_device.dev_num, 1); + if (ret) { + pr_err("Failed to add the device to the system\n"); + goto fail1; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + dht11_device.cls = class_create(DEVICE_NAME); +#else + dht11_device.cls = class_create(THIS_MODULE, DEVICE_NAME); +#endif + if (IS_ERR(dht11_device.cls)) { + pr_err("Failed to create class for device\n"); + ret = PTR_ERR(dht11_device.cls); + goto fail2; + } + + dht11_device.dev = device_create(dht11_device.cls, NULL, + dht11_device.dev_num, NULL, DEVICE_NAME); + if (IS_ERR(dht11_device.dev)) { + pr_err("Failed to create the device file\n"); + ret = PTR_ERR(dht11_device.dev); + goto fail3; + } + + pr_info("Device created on /dev/%s\n", DEVICE_NAME); + + ret = gpio_request(dht11[0].gpio, dht11[0].label); + + if (ret) { + pr_err("Unable to request GPIOs for dht11: %d\n", ret); + goto fail4; + } + + ret = gpio_direction_output(dht11[0].gpio, 1); + + if (ret) { + pr_err("Failed to set GPIO %d direction\n", dht11[0].gpio); + goto fail5; + } + + return 0; + +fail5: + gpio_free(dht11[0].gpio); + +fail4: + device_destroy(dht11_device.cls, dht11_device.dev_num); + +fail3: + class_destroy(dht11_device.cls); + +fail2: + cdev_del(&dht11_device.cdev); + +fail1: + unregister_chrdev_region(dht11_device.dev_num, DEVICE_CNT); + + return ret; +} + +static void __exit dht11_exit(void) +{ + gpio_set_value(dht11[0].gpio, 0); + gpio_free(dht11[0].gpio); + + device_destroy(dht11_device.cls, dht11_device.dev_num); + class_destroy(dht11_device.cls); + cdev_del(&dht11_device.cdev); + unregister_chrdev_region(dht11_device.dev_num, DEVICE_CNT); +} + +module_init(dht11_init); +module_exit(dht11_exit); + +MODULE_LICENSE("GPL"); diff --git a/lkmpg.tex b/lkmpg.tex index c899194b..b8a22415 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -1933,6 +1933,41 @@ \subsection{Control the LED's on/off state} sudo rmmod led \end{codebash} +\subsection{DHT11 sensor} +\label{sec:gpio_dht11} +The DHT11 sensor is a well-known entry-level sensor commonly used to measure humidity and temperature. +In this subsection, we will use GPIO to communicate through a single data line. +The DHT11 communication protocol can be referred to in the \href{https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf?srsltid=AfmBOoppls-QTd864640bVtbK90sWBsFzJ_7SgjOD2EpwuLLGUSTyYnv}{datasheet}. + +In the implementation, the data pin of the DHT11 sensor is connected to GPIO4 on the Raspberry Pi. +The sensor's VCC and GND pins are connected to 3.3V and GND, respectively. +For more details about the Raspberry Pi pin assignments, refer to \href{https://pinout.xyz/}{Raspberry Pi Pinout}. +The materials used include a Raspberry Pi 5, a DHT11 sensor, and jumper wires. + +\samplec{examples/dht11.c} +Make and install the module: +\begin{codebash} +make +sudo insmod dht11.ko +\end{codebash} + +Check the Output of the DHT11 Sensor: +\begin{codebash} +sudo cat /dev/dht11 +\end{codebash} + +Expected Output: +\begin{verbatim} +$ sudo cat /dev/dht11 +Humidity: 61% +Temperature: 30°C +\end{verbatim} + +Finally, remove the module: +\begin{codebash} +sudo rmmod dht11 +\end{codebash} + \section{Scheduling Tasks} \label{sec:scheduling_tasks} There are two main ways of running tasks: tasklets and work queues.