12 H5上实现dht12单总线工作方式的驱动

具体的硬件工作原理可参考: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");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值