简易测温系统IIC设备的驱动编写_东隅已逝桑榆非晚xx的博客-CSDN博客
在链接中的这篇博客是gy906设备的驱动程序,要实现按需进行温度测量就需要使用到按键操作,按键操作需要编写按键中断的相关驱动,按下按键之后能读取gy906设备测量的数据信息,不需要一直读取温度。
使用按键的时候还需要考虑到抖动的问题,需要采用一定时间的延时进行消抖。相关函数和代码说明如下:
每个中断都有一个中断号,通过中断号即可区分不同的中断。在Linux中使用一个int类型的变量表示中断号。在Linux内核中要想使用某个中断是需要申请的,request_irq函数用于申请中断,但是request_irq函数可能会导致睡眠,所以在使用此函数的时候不能在中断上下文或者其他禁止睡眠的代码段中使用request_irq函数。request_irq函数会使能中断,不需要手动去使能中断,函数原型如下:
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
irq表示要申请中断的中断号,handler表示中断处理函数(当中断发生之后就会执行这个函数里面的内容),flags表示中断标志(包括对中断次数、中断触发方式的说明,上升沿or下降沿触发,高电平or低电平触发),name就是设置的中断名字,dev一般情况下就是表示的设备,dev会传递给irq_handler_t结构体所需的第二个参数。
free_irq函数:中断使用完成后需要通过此函数释放掉相应的中断。如果中断的标志为不是IRQF_SHARED的话,free_irq函数会删除中断处理函数并且禁止中断。free_irq函数原型如下:
void free_irq(unsigned int irq
void *dev)
irq表示要释放的中断。
irqreturn_t(*irq_handler_t)(int, void*)函数:中断处理函数,第一个参数是中断处理函数需要的中断号,第二个参数是一个指向void类型的指针,需要与request_irq函数的dev参数保持一致。irqreturn_t是中断处理函数的返回值,返回值类型定义是一个枚举类型的结构,如下所述。一般中断服务函数返回值使用的形式为:return IRQ_RETVAL(IRQ_HANDLED).
enum irqreturn{
IRQ_NONE =(0 << 0),
IRQ_HANDLED =(1 << 0),
IRQ_WAKE_THREAD =(1 << 1),
};
typedef enum irqreturn irqreturn_t;
编写驱动的时候需要用到中断号,中断信息写在了设备树中,可以通过irq_of_parse_and_map函数从interupts属性中提取到对应的设备中断号,函数原型:
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
dev是指设备节点,index为索引号,interupts属性可能包含多条中断信息,通过index指定要获取的信息,函数返回值便是中断号。如果使用GPIO节点作为中断控制器,可以使用gpio_to_irq函数来获取对应的中断号。int gpio_to_irq(unsigned int gpio),gpio为要获取的GPIO编号,返回值是对应的中断号,在本项目中的按键中断就是GPIO节点作为中断控制器,采用中断的方式并且采用定时器实现按键消抖。
到这里,基本函数已经了解了,接下来就是进行按键中断的编写了。
编写按键中断驱动和编写IIC设备驱动步骤相同,首先需要添加设备树节点:
embeded_key:embeded_key{
status = "okay";
compatible = "embeded,key";
embeded_gpio = <&gpio2 11 IRQ_TYPE_EDGE_RISING>;
pinctrl-names = "default";
pinctrl-0 = <&embeded_key_pin>;
};
&pinctrl{
embeded_keys{
embeded_key_pin: embeded_key_pin{
rockchip,pins = <2 11 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
}
接着定义中断设备结构体,这部分都是后续驱动代码中所需要用到的变量:
/*中断设备结构体*/
struct embeded_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*类结构体*/
struct device *device; /*设备*/
struct device_node *nd; /*设备节点*/
int irq_gpio; /*中断对应的gpio*/
int irq_mode; /*中断模式*/
int irq; /*中断号*/
unsigned int ev_press;
unsigned int key_val;
};
struct embeded_dev my_embeded_dev;
对一些固定的值进行宏定义:
#define CLASS_NAME "embeded_key_class"
#define DEV_NAME "embeded_key"
#define KEY_COUNT 1
接下来是platform下的驱动模块加载函数和卸载函数以及驱动注册相关的函数:
static int key_remove(struct platform_device *pdev){
printk("embededkey_exit\r\n");
/* 删除字符设备 */
cdev_del(&my_embeded_dev.cdev);
/* 释放字符设号 */
unregister_chrdev_region(my_embeded_dev.devid, KEY_COUNT);
/* 摧毁设备 */
device_destroy(my_embeded_dev.class, my_embeded_dev.devid);
/*摧毁类*/
class_destroy(my_embeded_dev.class);
free_irq(my_embeded_dev.irq, &my_embeded_dev);
return 0;
}
static struct of_device_id key_match_table[] = {
{ .compatible = "embeded,key",},
{},
};
static struct platform_driver key_driver = {
.driver = {
.name = "embeded-key",
.owner = THIS_MODULE,
.of_match_table = key_match_table,
},
.probe = key_probe,
.remove = key_remove,
};
static int embededkey_init(void){
return platform_driver_register(&key_driver);
}
static void embededkey_exit(void){
platform_driver_unregister(&key_driver);
}
module_init(embededkey_init);
module_exit(embededkey_exit);
MODULE_AUTHOR("KMX");
MODULE_LICENSE("GPL");
在驱动加载完成之后执行的probe函数除了注册驱动所需要的设备号等基础操作之外,在按键驱动的编写中还增加了对于中断的获取和申请等操作:
static int key_probe(struct platform_device *pdev){
int ret = 0;
enum of_gpio_flags flag;
printk("embededkey Probe\n");
/* 申请设备号 */
ret = alloc_chrdev_region(&my_embeded_dev.devid, 0, KEY_COUNT, DEV_NAME);
if (ret < 0) {
printk("my_embeded_dev chrdev_region err!\r\n");
goto fail_devid;
}
/* 注册字符设备 */
my_embeded_dev.cdev.owner = key_fops.owner;
cdev_init(&my_embeded_dev.cdev, &key_fops);
ret = cdev_add(&my_embeded_dev.cdev, my_embeded_dev.devid, KEY_COUNT);
if (ret < 0) {
goto fail_cdev;
}
/* ⾃动创建设备节点 */
my_embeded_dev.class = class_create(key_fops.owner, CLASS_NAME);
if (IS_ERR(my_embeded_dev.class)) {
ret = PTR_ERR(my_embeded_dev.class);
goto fail_class;
}
my_embeded_dev.device = device_create(my_embeded_dev.class, NULL,
my_embeded_dev.devid, NULL, DEV_NAME);
if (IS_ERR(my_embeded_dev.device)) {
ret = PTR_ERR(my_embeded_dev.device);
goto fail_device;
}
/* 获取设备树的属性内容 */
my_embeded_dev.nd = pdev->dev.of_node;
if (my_embeded_dev.nd == NULL) {
printk("can't find num");
ret = -EINVAL;
goto fail_findnd;
}
/* 获取GPIO */
my_embeded_dev.irq_gpio = of_get_named_gpio_flags(my_embeded_dev.nd,
"embeded_gpio", 0, &flag);
if (my_embeded_dev.irq_gpio < 0) {
printk("can't find key gpio");
ret = -EINVAL;
goto fail_findnd;
} else {
printk("key gpio num = %d\r\n", my_embeded_dev.irq_gpio);
}
my_embeded_dev.irq_mode = flag;
//获取中断编号
my_embeded_dev.irq = gpio_to_irq(my_embeded_dev.irq_gpio);
if (my_embeded_dev.irq) {
/* 申请IO */
ret = devm_gpio_request(&pdev->dev,my_embeded_dev.irq_gpio,
"embeded_key");
if (ret < 0) {
printk("failde to request the key gpio\r\n");
ret = -EINVAL;
goto fail_findnd;
}else{
printk("failde to gpio_to_irq\r\n");
}
//申请中断
ret = request_irq(my_embeded_dev.irq, embeded_key_irq,
my_embeded_dev.irq_mode, "embeded_key", &my_embeded_dev);
if (ret != 0){
free_irq(my_embeded_dev.irq, &my_embeded_dev);
printk("failde to request irq\r\n");
ret = -EINVAL;
goto fail_findnd;
}
printk("embeded key create success\n");
return 0;
fail_findnd:
device_destroy(my_embeded_dev.class, my_embeded_dev.devid);
fail_device:
class_destroy(my_embeded_dev.class);
fail_class:
cdev_del(&my_embeded_dev.cdev);
fail_cdev:
unregister_chrdev_region(my_embeded_dev.devid, KEY_COUNT);
fail_devid:
return ret;
}
接下来是对中断处理函数的编写:
static irqreturn_t embeded_key_irq(int irq, void *dev_id) {
my_embeded_dev.key_val = gpio_get_value(my_embeded_dev.irq_gpio);
my_embeded_dev.ev_press = 1;
wake_up_interruptible(&button_waitq);
printk("Enter embeded_key_irq !\n");
return IRQ_HANDLED;
}
最后是编写设备操作集合函数,关于按键驱动的话,只需要读取按键信息:
DECLARE_WAIT_QUEUE_HEAD(button_waitq);//注册⼀个等待队列button_waitq
ssize_t key_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos){
int ret = -1;
printk("key_read\n");
wait_event_interruptible(button_waitq, my_embeded_dev.ev_press);
ret = copy_to_user(ubuf, &my_embeded_dev.key_val, 1);//将取得的按键值传给上层应⽤
my_embeded_dev.ev_press = 0;//按键已经处理可以继续睡眠
if (ret){
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
return 0;
}
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read,
};
在调按键的过程中,发现有抖动现象,需要对按键驱动的程序进行消抖,采用定时器进行消抖
为了消除按键抖动,我是将上述程序中的中断处理函数部分替换为“开启定时器”,在定时器开启之后进行10ms的延迟,定时器启动就会进入定时器服务函数,将之前中断服务函数中的内容写入定时器服务函数中,这样就可以实现按键消抖了。
static irqreturn_t embeded_key_irq(int irq, void *dev_id) {
struct embeded_dev *dev = (struct embeded_dev *)dev_id;
dev->ev_press = 0;
dev->timer.data = (volatile long)dev_id; /*定时器timer是需要在设备结构体里面新添加的*/
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10)); /*使用此函数将会使定时器能够周期运行,mod_timer使用时如果定时器还没有激活的话此函数会激活定时器*/
return IRQ_RETVAL(IRQ_HANDLED);
}
接着是将前面中断处理函数的内容写在定时器服务函数中了:
/*定时器服务函数,用于按键消抖,定时器到了时间之后再次读取按键值,若按键仍然处于按下
则表示按键有效*/
void timer_function(unsigned long arg){
unsigned char value;
struct embeded_dev *dev = (struct embeded_dev *)arg;
dev->key_val = gpio_get_value(my_embeded_dev.irq_gpio); //读取IO值
if(dev->key_val == 0){ //按键仍然处于按下状态
my_embeded_dev.ev_press = 1;
wake_up_interruptible(&button_waitq);
printk("Enter embeded_key_irq !\n"); //按键中断触发成功
}
}