具体的硬件工作原理可参考:http://blog.csdn.net/jklinux/article/details/73460008
dht12发出的二进制数据0的周期(从下降沿开始)大约78微秒, 数据1的信号周期大约120微秒.
通过捕捉数据脚的下降沿中断的间隔时间来计算接收到的数据是二进制0和1. 但全志方案里的gpio控制器默认是使用32Khz的工作时钟信号,频率过低会丢失中断信号,需要把gpio控制器的时钟信号切换到24Mhz.
PA组IO口的时钟配置寄存器:
另在现用的内核里,当一个IO口作中断使用后,就不可以再作输出使用。所以在发出开始信号到dht12模块前,不可以先请求使用中断,直到开始信号完成后才可以请求中断。数据接收完成后再释放中断.
驱动里加入struct timer_list定时器,用于防止数据传输过程中,发生中断次数不准确而避免用户进程死堵塞的问题.
dht12在设备树里的描述:
mydht12 {
compatible = "mydht12";
data-gpios = <&pio 0 12 GPIO_ACTIVE_HIGH>;
};
驱动代码:
/* mydrv.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#define MYMA 1314
#define COUNT 1
typedef struct {
struct gpio_desc *data_io;
int n; //记录当前接收的数据是第几位
int times[41];
s64 prev_time;
unsigned char data[5]; //存放接收到的温湿度数据
bool sum_checked; //记录数据是否有效,检得校验和
dev_t devid;
struct cdev cdev;
struct class *cls;
struct mutex mutex;
struct mutex mutex_read; //确保只有一个进程来调用read函数
struct timer_list mytimer;
}mypdata;
extern irqreturn_t irq_func(int irqno, void *arg);
ssize_t myread(struct file *fl, char *__user buf, size_t len, loff_t *off)
{
struct inode *ind = fl->f_path.dentry->d_inode;
mypdata *pdata = container_of(ind->i_cdev, mypdata, cdev);
int ret;
mutex_lock(&pdata->mutex_read);
//发出开始信号
pdata->n = 0;
gpiod_direction_output(pdata->data_io, 1);
msleep(100);
gpiod_set_value(pdata->data_io, 0);
msleep(30);
gpiod_set_value(pdata->data_io, 1);
udelay(30);
gpiod_direction_input(pdata->data_io);
ret = request_any_context_irq(gpiod_to_irq(pdata->data_io), irq_func, IRQF_TRIGGER_FALLING, "mydht12", pdata);
if (ret < 0)
return -ENODATA;
ret = mutex_lock_interruptible(&pdata->mutex);
if (ret < 0)
goto out;
if (pdata->sum_checked)
{
sprintf(buf, "%02d.%02d %02d.%02d\n", pdata->data[0], pdata->data[1],
pdata->data[2], pdata->data[3]);
ret = strlen(buf);
}
else
ret = -ENODATA;
out:
free_irq(gpiod_to_irq(pdata->data_io), pdata);
mutex_unlock(&pdata->mutex_read);
return ret;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.read = myread,
};
void timer_func(unsigned long data)
{
mypdata *pdata = (mypdata *)data;
int i, j, sum = 0;
for (i = 0; i < 5; i++)
{
pdata->data[i] = 0;
for (j = 0; j < 8; j++)
{
if (pdata->times[i*8+j+1] > 100)
pdata->data[i] |= 1 << (7-j); //数据从高位开始
}
if (i < 4)
sum += pdata->data[i];
}
pdata->sum_checked = (sum == pdata->data[4]);
// if (pdata->sum_checked)
// printk("in kernel : %d.%d, %d.%d\n", pdata->data[0], pdata->data[1], pdata->data[2], pdata->data[3]);
mutex_unlock(&pdata->mutex);
}
irqreturn_t irq_func(int irqno, void *arg)
{
mypdata *pdata = (mypdata *)arg;
s64 now = ktime_to_us(ktime_get());
if (0 == pdata->n)
pdata->prev_time = now;
else if (pdata->n < ARRAY_SIZE(pdata->times))
{
pdata->times[pdata->n] = now - pdata->prev_time;
pdata->prev_time = now;
}
pdata->n++;
mod_timer(&pdata->mytimer, jiffies+HZ*10/1000); //10ms
return IRQ_HANDLED;
}
int myprobe(struct platform_device *pdev)
{
struct gpio_desc *gpiod = devm_gpiod_get(&pdev->dev, "data", GPIOD_OUT_HIGH);
mypdata *pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
int ret = -ENODEV;
static int mi = 0;
if (IS_ERR(gpiod))
{
printk("gpio get failed\n");
goto err0;
}
pdata->data_io = gpiod;
init_timer(&pdata->mytimer);
pdata->mytimer.function = timer_func;
pdata->mytimer.data = (unsigned long)pdata;
/// cdev ///
pdata->devid = MKDEV(MYMA,mi);
ret = register_chrdev_region(pdata->devid, COUNT, pdev->name);
if (ret < 0)
goto err1;
cdev_init(&pdata->cdev, &fops);
pdata->cdev.owner = THIS_MODULE;
ret = cdev_add(&pdata->cdev, pdata->devid, COUNT);
if (ret < 0)
goto err2;
pdata->cls = class_create(THIS_MODULE, pdev->name);
device_create(pdata->cls, NULL, pdata->devid, NULL, pdev->name);
///
mutex_init(&pdata->mutex);
mutex_lock(&pdata->mutex);
mutex_init(&pdata->mutex_read);
platform_set_drvdata(pdev, pdata);
printk("probe done ...\n");
return 0;
err2:
unregister_chrdev_region(pdata->devid, COUNT);
err1:
devm_kfree(&pdev->dev, pdata);
devm_gpiod_put(&pdev->dev, gpiod);
err0:
return ret;
}
int myremove(struct platform_device *pdev)
{
mypdata *pdata = platform_get_drvdata(pdev);
unregister_chrdev_region(pdata->devid, COUNT);
cdev_del(&pdata->cdev);
device_destroy(pdata->cls, pdata->devid);
class_destroy(pdata->cls);
del_timer(&pdata->mytimer);
devm_gpiod_put(&pdev->dev, pdata->data_io);
devm_kfree(&pdev->dev, pdata);
return 0;
}
struct of_device_id ids[] = {
{.compatible = "mydht12"},
{},
};
struct platform_driver mydrv = {
.probe = myprobe,
.remove = myremove,
.driver = {
.owner = THIS_MODULE,
.name = "mydrv" ,
.of_match_table = ids,
},
};
#define BASE (0x01C20800+0x0218)
static u8 *vaddr;
static int __init test_init(void)
{
//把PA组所属的gpio控制器的工作时钟改为24Mhz
vaddr = ioremap(BASE, SZ_4K);
iowrite32(ioread32(vaddr)|1, vaddr);
return platform_driver_register(&mydrv);
}
static void __exit test_exit(void)
{
iounmap(vaddr);
platform_driver_unregister(&mydrv);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");