linux驱动--内核定时器&POLL轮询

1.定时器概念

定时器我认为就是由计数器和时钟源组成。

计数器的作用负责计数

时钟源的作用为提供多长时间记一次数。

定时器的特点:

        定时结束后触发一定事件或者中断标志位

定时器的参数:

        位数:最大允许的计数上限(重装载值)

        计数方式:向上、向下、重复计数、单次计数

        时钟频率:输入时钟、预分频值

2:STM32的定时器对比

在STM32中最常见的定时器有一下几种:

系统滴答定时器:

        对应的参数为:

                24bit的计数器,向下计数器,72M主频

基本定时器:功能单一也是最简单的定时器,只有纯定时功能和中断机制

通用定时器:有输出PWM的功能,输入捕获等

高级定时器:相比于通用定时器又多了双向互补的PWM。四驱刹车。

3:软件定时器

软件定时器的底层是由心跳节拍做的一个软定时器。

其中的功能简单往往只有两个功能:1:计数 2:计数完毕后产生一个回调

4:内核的软件定时器的特点

        默认的内核定时器 频率一般为 100 HZ
        跟内核配置有关的 目前我们看到的频率为 300 HZ
        证明他的多长时间记 3 次数呢? 10ms
        不建议大家把内核定时器的频率改成 1000HZ 以上
         低精度的计时器
                如果想要在内核中使用高精度的定时器
                需要调用底层的硬件定时器
                你也可以直接调用内核内部的延时函数
         内核定时器的计数
                单次计数->计数完毕后就不在工作了
                除非你手动再次赋值计数!
         内核定时器的计数依据内核的一个全局变量 jiffies 计数
                举个例子 我让内核计数 10ms –>记 3 个数

                我要给内核定时器赋值的计数值不是 3
                而是 jiffies + 3

5;内核定时器的接口

函数功能:初始化内核定时器核心结构体

头文件:

<linux/timer.h>

函数原型:

timer_setup(timer, callback, flags)//新内核用法
__init_timer(_timer, _fn, _flags)//老版内核

函数参数:

        -timer:

                内核定时器的核心结构体

        -fn:

                void (*func)(struct timer_list *)

                回调函数

        flags:

                一般填0

返回值:NULL

函数功能:向内核中添加定时器

函数原型:

void add_timer(struct timer_list *timer)

函数参数:

        timer:

               你刚才通过 __init_timer 初始化 内核定时器核心结构体指针
                * 由于内核 add_timer 他添加时候直接用定时器的计数值
                结构体里面的 timer->expires 第一次添加的 jiffies + 3 = 1003
                当你的内核定时器 被二次添加的时候
                仍然会使用 timer->expires 第二次添加的 老的那次 jiffies 的当时值
                        + 3 = 1003
                这样就会造成内核错误!
                * 所以我们再调用 add_timer 之前一定要重新赋值 expires ->计数值
函数返回值:
        空       

函数的功能:激活内核的定时器
函数头文件:同上
函数的原型:

int mod_timer(struct timer_list *timer, unsigned long expires)

函数的参数:
        timer:
                初始化核心结构体
        expires:
                你在激活定时器的你必须给个当前你要计数的时间
                计数时间必须是 jiffies + 你要计数的数值
函数返回值:
        成功返回 0
        失败返回 非 0

函数的功能:删除内核的已经添加的定时器
函数头文件:同上
函数的原型:

void del_timer(struct timer_list * timer)

函数的参数:
        timer:
        你只需要提供这个核心结构体即可删除内核的定时器
函数返回值:
        无

6:按键消抖事例

#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/of_gpio.h"
#include "linux/device/class.h"
#include "linux/device.h"
#include "linux/platform_device.h"
#include "linux/miscdevice.h"
#include "asm/uaccess.h"
#include "linux/irq.h"
#include "linux/interrupt.h"
#include "linux/sched.h"
#include "linux/wait.h"
#include "linux/timer.h"

struct timer_list * mytimer;
struct device_node * node =NULL;
struct platform_device * xydkeydev = NULL;
int gpio_num;
struct miscdevice * keymisc;
struct file_operations * ops;
int keyirqnum;
uint8_t value = 0;
int cond = 0;
//加一个 等待队列的结构体变量
DECLARE_WAIT_QUEUE_HEAD(xyd_key_wait);
//xyd_key_wait 他就是我创建的等待队列结构体的变量的 名字
//定时器的回调
void mytimer_out_callback(struct timer_list * tim)
{
    //一定按键按下了且按下了 10ms 的时间
    value = gpio_get_value(gpio_num);
    if(value == 1)
    {
        cond = 1;//原则上解除阻塞后再次判断
        wake_up_interruptible(&xyd_key_wait);
    }
}
irqreturn_t mykey_irqhandler(int irqnum, void * arg)
{
    mod_timer(mytimer,jiffies + 3);//10ms
    return 0;
}
int xyd_key_open(struct inode * i , struct file * dev)
{
    //1: 获取中断号
    int ret = 0;
    keyirqnum = platform_get_irq(xydkeydev,0);
    printk("keyirqnum == %d\r\n",keyirqnum);
    if(keyirqnum < 0)
    {
        printk("ERROR!: keyirq is Error!\r\n");
        return -EIO;
    }
//2:使能
//enable_irq(keyirqnum);//老版本的没有设备树 确实需要使能
//3: 向内核注册一个中断
ret = devm_request_irq(&xydkeydev->dev,\
    keyirqnum,\
    mykey_irqhandler,\
    IRQ_TYPE_NONE,\
    "key_irq",NULL);
    ret = ret;
    return 0;
}
int xyd_key_close(struct inode * i , struct file * dev)
{
    //1: 释放中断
    free_irq(keyirqnum,NULL);
    //2: 失能中断
    //disable_irq(keyirqnum);
    return 0;
}
ssize_t xyd_key_read(struct file * file, char __user * buf, size_t size, loff_t * offt)
{
    cond = 0;
    wait_event_interruptible(xyd_key_wait,cond);//不管你按键有没有按下现阻塞在说
    int ret = copy_to_user(buf,&value,1);
    value = 0xFF;
    ret = ret ;
    return 1;
}
int xyd_key_probe(struct platform_device * dev)
{
    node = dev->dev.of_node;
    xydkeydev = dev;//1: 获取 GPIO 的信息
    gpio_num = of_get_named_gpio(node,"xyd-gpios",0);
    //2: 申请 + 设置 输入
    gpio_request(gpio_num,"xyd_key");
    gpio_direction_input(gpio_num);
    //3:注册杂项设备
    mytimer = kzalloc(sizeof(struct timer_list), GFP_KERNEL);
    //定时器的初始化
    __init_timer(mytimer,mytimer_out_callback,0);
    keymisc = kzalloc(sizeof(struct miscdevice), GFP_KERNEL);
    ops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
    ops->owner = THIS_MODULE;
    ops->open = xyd_key_open;
    ops->release= xyd_key_close;
    ops->read = xyd_key_read;
    keymisc->minor = 255;
    keymisc->name = "xyd_key";
    keymisc->fops = ops;
    return misc_register(keymisc);
}
struct of_device_id xyd_id_table={
    .compatible = "xydkey",//匹配名字
    };
struct platform_driver myxyd_key_driver={
    .driver={
        .name = "xyd_key",
        .of_match_table = &xyd_id_table,
    },
    .probe = xyd_key_probe,
};
//加载函数
static int __init mykey_init(void)
{
    return platform_driver_register(&myxyd_key_driver);//注册设备的驱
动信息
}
//卸载函数
static void __exit mykey_exit(void)
{ }
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");

7.内核POLL接口

7.1: poll 函数在驱动的作用

1: 内核的层驱动开发的接口里面
        支持 poll 函数接口的!
        想要上层可以调用 poll 来控制监测设备文件
        需要内核层实现 poll 函数
2: 内核层的 poll 常常用于 内核层的数据和上层的数据同步
        UART 的驱动往往用的就是内核层 poll 做数据同步!
        UART 在 Linux 下就是一个设备文件!
        /dev/tty0->以这个文件为例子
        ///其中一种写法
        int fd = open("/dev/tty0",O_RDWR);
        while(1)
        {
                read(fd,buf,1024);//UART 的数据读取
        //内核 UART 的 read 没有阻塞的!
        //你的读取得到的数据 buf->99.9999% 读到的数据都是空的/上次的
        //你怎么知道人家发送数据了呢? ->数据难同步!
}

// 第二种写法
int fd = open("/dev/tty0",O_RDWR);
struct pollfd fds={
        .fds = fd,

        .events = POLLIN,
        .revents = 0,
};
while(1)
{
        poll(&fds,1, -1);
        read(fd,buf,1024);//这样的话 fds 经过 poll 轮询阻塞
        //可以在有数据的时候 再读数据!
}
实际上 内核层的 poll 函数的实现内部原理用的还是 等待队列

7.2:内核层 poll 函数

__poll_t (*poll) (struct file *, struct poll_table_struct *);
实现方法:
         你要在注册设备文件的 内核接口操作集合结构体写上有内核层 poll 函数
实现这个函数
__poll_t myxyd_poll(struct file * f, struct poll_table_struct * pts);
在注册里面告诉内核设备支持 poll
        struct file_operations ops;
        ops.poll= myxyd_poll;

7.3 调用一个函数 poll_wait

poll_wait(struct file * filp, wait_queue_head_t * wait_address,poll_table * p)

        其中 第一个参数 直接传 poll 函数的第一个参数
        其中 第三个参数 直接传 poll 函数第二个参数
        其中 第二个参数 你需要创建一个等待队列 提供进去
        * 你提供的等待队列 你需要唤醒
        wake_up_interruptible();

7.4 :在合适的时间返回合适的值

内核层的 poll 如果返回的是 0-> 内核层 poll 就会调用等待队列产生阻塞!
内核层的 poll 如果返回的是 POLLIN/POLLOUT/POLLERR
内核层的 poll 就会通知上层的 poll
这个设备文件可读/可写/出错
并且把返回值 给上层 POLL 里面结构体的 revents
以按键为例子:
        什么时候返回 0
        当按键没有按下的时候 ->返回 0
        什么时候返回 POLLIN
        按键安息的时候


 

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值