Skip to content

Add a DHT11 Sensor Driver Example Using GPIO #316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci/non-working
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ intrpt
vkbd
syscall-steal
led
dht11
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
265 changes: 265 additions & 0 deletions examples/dht11.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
* dht11.c - Using GPIO to read temperature and humidity from DHT11 sensor.
*/

#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/version.h>

#include <asm/errno.h>

#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");
35 changes: 35 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down