Created
June 29, 2015 12:15
-
-
Save gnitnaw/744d237d6ea2fd84e756 to your computer and use it in GitHub Desktop.
DHT11 driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* dht11.c | |
* | |
* dht11 - Device driver for reading values from DHT11 temperature and humidity sensor. | |
* insmod ./dht11.ko gpio_pin=2 | |
* To read the values from the sensor: cat /dev/dht11 | |
* | |
* Copyright (C) 2015 Wan-Ting CHEN <wanting@gmail.com> | |
* This program is free software; you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation; either version 2 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program; if not, write to the Free Software | |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
*/ | |
#include <linux/mutex.h> // mutex | |
#include <linux/module.h> | |
#include <linux/kernel.h> | |
#include <linux/fs.h> // file_operations | |
#include <linux/cdev.h> // Char Device Registration | |
#include <linux/gpio.h> // gpio control | |
#include <linux/interrupt.h> // interrupt | |
#include <linux/delay.h> // udelay, msleep | |
#include <linux/device.h> // class_create | |
#include <linux/types.h> // dev_t | |
#include <linux/completion.h> // completion | |
//#include <linux/hrtimer.h> | |
//#include <linux/time.h> | |
//#include <linux/string.h> | |
#include <asm/uaccess.h> // for put_user | |
#define DHT11_DRIVER_NAME "DHT11" | |
#define SUCCESS 0 | |
#define BUF_LEN 80 // Max length of the message from the device | |
#define DATABIT 40 | |
#define TOTAL_INT_BLOCK (DATABIT+1) | |
#define DHT11_DATA_BIT_LOW 26 | |
#define DHT11_DATA_BIT_HIGH 70 | |
// module parameters | |
static struct timeval lasttv = {0, 0}; | |
static struct timeval tv = {0, 0}; | |
//static struct hrtimer htimer; | |
// Forward declarations | |
static int open_dht11(struct inode *, struct file *); | |
static int close_dht11(struct inode *, struct file *); | |
static ssize_t read_dht11(struct file *, char *, size_t, loff_t *); | |
static void clear_interrupts(void); | |
static unsigned int timeBit[TOTAL_INT_BLOCK]; | |
struct dht11_bit { | |
unsigned int Temperature[2]; | |
unsigned int Humidity[2]; | |
unsigned int checksum; | |
}; | |
// Global variables are declared as static, so are global within the file. | |
static char msg[BUF_LEN]; // The msg the device will give when asked | |
static char *msg_Ptr; | |
static unsigned int nBit = 0; | |
static unsigned int gpio_pin = 2; //Default GPIO pin | |
static dev_t driverno ; //Default driver number | |
static struct cdev *gpio_cdev; | |
static struct class *gpio_class = NULL; | |
static struct device* gpio_device = NULL; | |
static struct completion gpio_completion; | |
static struct dht11_bit dht11; | |
//struct mutex gpio_mutex; | |
static DEFINE_MUTEX(gpio_mutex); | |
//Operations that can be performed on the device | |
static struct file_operations fops = { | |
.owner = THIS_MODULE, | |
.read = read_dht11, | |
.open = open_dht11, | |
.release = close_dht11 | |
}; | |
static unsigned char dht11_decode_byte(int *timing, int threshold) | |
{ | |
unsigned char ret = 0; | |
int i; | |
for (i = 0; i < 8; ++i) { | |
ret <<= 1; | |
if (timing[i] >= threshold) ++ret; | |
} | |
return ret; | |
} | |
static int dht11_decode(void) { | |
int i, bitM = DHT11_DATA_BIT_LOW * 3 / 2, bitN = DHT11_DATA_BIT_HIGH*2/3, wtime=0; | |
for (i=0; i<nBit; ++i) { | |
printk("Bit %d\t%d\n",i, timeBit[i]); | |
if (timeBit[i]>bitM && timeBit[i]<bitN) { | |
++wtime; | |
} | |
} | |
if (wtime>0) { | |
printk("Poor resolution \n"); | |
return 1; | |
} | |
dht11.Humidity[0] = dht11_decode_byte(&timeBit[1], bitM); | |
dht11.Humidity[1] = dht11_decode_byte(&timeBit[9], bitM); | |
dht11.Temperature[0] = dht11_decode_byte(&timeBit[17], bitM); | |
dht11.Temperature[1] = dht11_decode_byte(&timeBit[25], bitM); | |
dht11.checksum = dht11_decode_byte(&timeBit[33], bitM); | |
if (dht11.Temperature[0]+dht11.Temperature[1]+dht11.Humidity[0]+dht11.Humidity[1] == dht11.checksum) return 0; | |
else return 2; | |
} | |
// IRQ handler - where the timing takes place | |
static irqreturn_t irq_handler(int i, void *blah) | |
{ | |
do_gettimeofday(&tv); | |
int data = (int) ((tv.tv_sec-lasttv.tv_sec)*1000000 + (tv.tv_usec - lasttv.tv_usec)); | |
int signal = gpio_get_value(gpio_pin); | |
lasttv = tv; //Save last interrupt time | |
// use the GPIO signal level | |
if (signal==0) { | |
timeBit[nBit++] = data; | |
} | |
if (nBit >= TOTAL_INT_BLOCK) complete(&gpio_completion); | |
return IRQ_HANDLED; | |
} | |
static int setup_interrupts(void) | |
{ | |
int result; | |
result = request_irq(gpio_to_irq(gpio_pin), (irq_handler_t) irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, DHT11_DRIVER_NAME, NULL); | |
switch (result) { | |
case -EBUSY: | |
printk(KERN_ERR DHT11_DRIVER_NAME ": IRQ %d is busy\n", INTERRUPT_GPIO0); | |
return -EBUSY; | |
case -EINVAL: | |
printk(KERN_ERR DHT11_DRIVER_NAME ": Bad irq number or handler\n"); | |
return -EINVAL; | |
default: | |
printk(KERN_INFO DHT11_DRIVER_NAME ": Interrupt %04x obtained\n", gpio_to_irq(gpio_pin)); | |
break; | |
}; | |
return 0; | |
} | |
static int __init dht11_init_module(void) | |
{ | |
int ret; | |
printk(KERN_INFO DHT11_DRIVER_NAME " gpio_request \n"); | |
ret = gpio_request(gpio_pin, DHT11_DRIVER_NAME); | |
if(ret < 0) { | |
printk(KERN_ERR DHT11_DRIVER_NAME " Unable to request GPIOs: %d\n", ret); | |
goto exit_gpio_request; | |
} | |
// Get driver number | |
printk(KERN_INFO DHT11_DRIVER_NAME " alloc_chrdev_region \n"); | |
ret=alloc_chrdev_region(&driverno,0,1,DHT11_DRIVER_NAME); | |
if (ret) { | |
printk(KERN_EMERG DHT11_DRIVER_NAME " alloc_chrdev_region failed\n"); | |
goto exit_gpio_request; | |
} | |
printk(KERN_INFO DHT11_DRIVER_NAME " DRIVER No. of %s is %d\n", DHT11_DRIVER_NAME, MAJOR(driverno)); | |
printk(KERN_INFO DHT11_DRIVER_NAME " cdev_alloc\n"); | |
gpio_cdev = cdev_alloc(); | |
if(gpio_cdev == NULL) | |
{ | |
printk(KERN_EMERG DHT11_DRIVER_NAME " Cannot alloc cdev\n"); | |
ret = -ENOMEM; | |
goto exit_unregister_chrdev; | |
} | |
printk(KERN_INFO DHT11_DRIVER_NAME " cdev_init\n"); | |
cdev_init(gpio_cdev,&fops); | |
gpio_cdev->owner=THIS_MODULE; | |
printk(KERN_INFO DHT11_DRIVER_NAME " cdev_add\n"); | |
ret=cdev_add(gpio_cdev,driverno,1); | |
if (ret) | |
{ | |
printk(KERN_EMERG DHT11_DRIVER_NAME " cdev_add failed!\n"); | |
goto exit_cdev; | |
} | |
printk(KERN_INFO DHT11_DRIVER_NAME " Create class \n"); | |
gpio_class = class_create(THIS_MODULE, DHT11_DRIVER_NAME); | |
if (IS_ERR(gpio_class)) | |
{ | |
printk(KERN_ERR DHT11_DRIVER_NAME " class_create failed\n"); | |
ret = PTR_ERR(gpio_class); | |
goto exit_cdev; | |
} | |
printk(KERN_INFO DHT11_DRIVER_NAME " Create device \n"); | |
if ((gpio_device=device_create(gpio_class,NULL, driverno, NULL, DHT11_DRIVER_NAME)) == NULL) { | |
printk(KERN_ERR DHT11_DRIVER_NAME " device_create failed\n"); | |
ret = -1; | |
goto exit_cdev; | |
} | |
mutex_init(&gpio_mutex); | |
init_completion(&gpio_completion); | |
return 0; | |
exit_cdev: | |
cdev_del(gpio_cdev); | |
exit_unregister_chrdev: | |
unregister_chrdev_region(driverno, 1); | |
exit_gpio_request: | |
gpio_free(gpio_pin); | |
return ret; | |
} | |
static void __exit dht11_exit_module(void) | |
{ | |
printk(KERN_INFO DHT11_DRIVER_NAME " %s\n", __func__); | |
device_destroy(gpio_class, driverno); | |
class_destroy(gpio_class); | |
cdev_del(gpio_cdev); | |
unregister_chrdev_region(driverno, 1); | |
gpio_free(gpio_pin); | |
} | |
// Called when a process wants to read the dht11 "cat /dev/dht11" | |
static int open_dht11(struct inode *inode, struct file *file) | |
{ | |
char result[3]; //To say if the result is trustworthy or not | |
int retry = 0, ret; | |
if (!mutex_trylock(&gpio_mutex)) { | |
printk(KERN_ERR DHT11_DRIVER_NAME " another process is accessing the device\n"); | |
return -EBUSY; | |
} | |
reinit_completion(&gpio_completion); | |
printk(KERN_INFO DHT11_DRIVER_NAME " Start setup (read_dht11)\n"); | |
start_read: | |
nBit = 0; | |
gpio_direction_output(gpio_pin, 0); | |
msleep(20); // DHT11 needs min 18mS to signal a startup | |
gpio_direction_output(gpio_pin, 1); | |
udelay(40); // Stay high for a bit before swapping to read mode | |
gpio_direction_input(gpio_pin); | |
//Start timer to time pulse length | |
do_gettimeofday(&lasttv); | |
// Set up interrupts | |
setup_interrupts(); | |
//Give the dht11 time to reply | |
ret = wait_for_completion_killable_timeout(&gpio_completion,HZ); | |
clear_interrupts(); | |
//Check if the read results are valid. If not then try again! | |
if(dht11_decode()==0) sprintf(result, "OK"); | |
else { | |
retry++; | |
sprintf(result, "BAD"); | |
if(retry == 10) goto return_result; //We tried 5 times so bail out | |
ssleep(1); | |
goto start_read; | |
} | |
return_result: | |
sprintf(msg, "Humidity: %d.%d%%\nTemperature: %d.%dC\nResult:%s\n", dht11.Humidity[0], dht11.Humidity[1], dht11.Temperature[0], dht11.Temperature[1], result); | |
msg_Ptr = msg; | |
printk("strlen is %d", strlen(msg)); | |
return SUCCESS; | |
} | |
// Called when a process closes the device file. | |
static int close_dht11(struct inode *inode, struct file *file) | |
{ | |
mutex_unlock(&gpio_mutex); | |
printk(KERN_INFO DHT11_DRIVER_NAME ": Device release (close_dht11)\n"); | |
return 0; | |
} | |
// Clear the GPIO edge detect interrupts | |
static void clear_interrupts(void) | |
{ | |
free_irq(gpio_to_irq(gpio_pin), NULL); | |
} | |
// Called when a process, which already opened the dev file, attempts to read from it. | |
static ssize_t read_dht11(struct file *filp, // see include/linux/fs.h | |
char *buffer, // buffer to fill with data | |
size_t length, // length of the buffer | |
loff_t * offset) | |
{ | |
if (*msg_Ptr == '\0') return 0; | |
if (copy_to_user(buffer, msg_Ptr, strlen(msg)+1)!=0 ) return -EFAULT; | |
msg_Ptr += strlen(msg); | |
return strlen(msg); | |
} | |
module_init(dht11_init_module); | |
module_exit(dht11_exit_module); | |
MODULE_DESCRIPTION("DHT11 temperature/humidity sendor driver for Raspberry Pi GPIO."); | |
MODULE_AUTHOR("Wan-Ting CHEN"); | |
MODULE_LICENSE("GPL"); | |
// Command line paramaters for gpio pin and driver major number | |
module_param(driverno, int, S_IRUGO); | |
MODULE_PARM_DESC(driverno, "Driver handler major value"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
obj-m += dht11.o | |
all: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules | |
clean: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is modified form
http://www.tortosaforum.com/raspberrypi/dht11km.tar