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% 读到的数据都是空的/上次的 //你怎么知道人家发送数据了呢? ->数据难同步! } |
// 第二种写法 .events = POLLIN, |
7.2:内核层 poll 函数
实现方法:你要在注册设备文件的 内核接口操作集合结构体写上有内核层 poll 函数 实现这个函数 在注册里面告诉内核设备支持 pollstruct 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 按键安息的时候 |