一、内核定时器原理
内核当前时间通过 jiffies
(内核时钟节拍数) 获取,它是一位64位的变量。
在linux内核启动的时候,jiffies开始(按照一定频率)增加。
在驱动中可以直接使用jiffies获取当前时间。
定时器每增加1走的时间由频率决定,定时器的频率可以通过 make menuconfig 进行选配,选配后的结果在 .config 文件中保存,选项是 CONFIG_HZ。
linux-5.10.61内核 CONFIG_HZ=100,周期是1/100s,定时器每增加1走10ms。
ubuntu的内核 CONFIG_HZ=250,定时器每增加1走4ms。
二、定时器API
1.分配对象
struct timer_list {
struct hlist_node entry; //构成内核链表成员
unsigned long expires; //定时器到期时间
void (*function)(struct timer_list *);//定时器处理函数(定时时间到执行的函数)
u32 flags; //新版本内核添加,一般填写为0
};
struct timer_list mytimer;
2.对象初始化
mytimer.expires = jiffies+HZ; //定时1s
timer_setup(&mytimer, 定时器处理函数, 0);
//HZ是内核中定义的一个宏,+HZ总是1s
3.启动定时器
void add_timer(struct timer_list *timer)
功能:启动定时器,这个定时器只会执行一次
备注:add_timer只能调用一次,多次调用内核会崩溃
int mod_timer(struct timer_list *timer, unsigned long expires)
功能:再次启动定时器
4.删除定时器
int del_timer(struct timer_list *timer)
三、使用定时器让LED灯闪烁
实现了六盏灯同亮同灭
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include "mynode.h"
/*设备树文件
myleds{
core_leds{
led1 = <&gpioz 5 0>;
led2 = <&gpioz 6 0>;
led3 = <&gpioz 7 0>;
};
extend_leds{
led1 = <&gpioe 10 0>;
led2 = <&gpiof 10 0>;
led3 = <&gpioe 8 0>;
};
};
*/
struct device_node *core_leds, *extend_leds;
int core_gpiono[3];
int extend_gpiono[3];
char *cname[3]={"led1","led2","led3"};
struct timer_list mytimer;
int flag = 0;
void timer_handler(struct timer_list *timerlist){
if(flag){
gpio_set_value(core_gpiono[0],1);
gpio_set_value(core_gpiono[1],1);
gpio_set_value(core_gpiono[2],1);
gpio_set_value(extend_gpiono[0],1);
gpio_set_value(extend_gpiono[1],1);
gpio_set_value(extend_gpiono[2],1);
flag=0;
}else{
gpio_set_value(core_gpiono[0],0);
gpio_set_value(core_gpiono[1],0);
gpio_set_value(core_gpiono[2],0);
gpio_set_value(extend_gpiono[0],0);
gpio_set_value(extend_gpiono[1],0);
gpio_set_value(extend_gpiono[2],0);
flag=1;
}
//再次启动定时器
mod_timer(&mytimer,jiffies+HZ);
}
static __init int mynode_init(void){
int ret;
int i;
/***初始化gpio***/
//1. 获取设备节点
core_leds = of_find_node_by_path("/myleds/core_leds");
if(NULL == core_leds){
pr_err("of_find_node_by_name failed\n");
ret = -ENODATA;
goto err0;
}
extend_leds = of_find_node_by_path("/myleds/extend_leds");
if(NULL == extend_leds){
pr_err("of_find_node_by_name failed\n");
ret = -ENODATA;
goto err0;
}
//2. 获取gpio号
for(i=0;i<3;i++){
core_gpiono[i] = of_get_named_gpio(core_leds,cname[i],0);
if(core_gpiono[i]<0){
pr_err("core_gpiono of_get_named_gpio failed\n");
ret = core_gpiono[i];
goto err0;
}
}
for(i=0;i<3;i++){
extend_gpiono[i] = of_get_named_gpio(extend_leds,cname[i],0);
if(core_gpiono[i]<0){
pr_err("extend_gpiono of_get_named_gpio failed\n");
ret = core_gpiono[i];
goto err0;
}
}
//3. 申请GPIO号
for(i=0;i<3;i++){
ret = gpio_request(core_gpiono[i],NULL);
if(ret){
pr_err("core_gpiono gpio_request failed\n");
goto err1;
}
}
for(i=0;i<3;i++){
ret = gpio_request(extend_gpiono[i],NULL);
if(ret){
pr_err("extend_gpiono gpio_request failed\n");
goto err2;
}
}
//4. 初始化led为输出,低电平
for(i=0;i<3;i++){
gpio_direction_output(core_gpiono[i],0);
gpio_direction_output(extend_gpiono[i],0);
}
/***初始化定时器***/
//初始化对象
mytimer.expires = jiffies + HZ;
timer_setup(&mytimer,timer_handler,0);
//启动定时器
add_timer(&mytimer);
return 0;
err2:
for(--i;i>=0;i--){
gpio_free(extend_gpiono[i]);
}
i=3;
err1:
for(--i;i>=0;i--){
gpio_free(core_gpiono[i]);
}
err0:
return ret;
}
static __exit void mynode_exit(void){
int i;
for(i=0;i<3;i++){
gpio_set_value(core_gpiono[i],0);
gpio_set_value(extend_gpiono[i],0);
}
for(i=0;i<3;i++){
gpio_free(extend_gpiono[i]);
gpio_free(core_gpiono[i]);
}
}
module_init(mynode_init);
module_exit(mynode_exit);
MODULE_LICENSE("GPL");
四、使用定时器对按键进行消抖
- 注:GPIO仅需要读取其状态即可,不存在竞态的问题,因此无需申请GPIO号,仅获取即可。
- GPIO在任意复用模式下都支持读操作。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
/*设备树文件
mykeys{
interrupt-parent = <&gpiof>;
interrupts = <9 0>, <7 0>, <8 0>;
keys_no = <&gpiof 9 0>, <&gpiof 7 0>, <&gpiof 8 0>;
};
*/
struct device_node *devnode; //获取的设备树节点
unsigned int irq[3]; //软中断号
unsigned int key_gpiono[3]; //gpio号
struct timer_list mytimer; //定时器
char *irq_name[3]={"key1_irq","key2_irq","key3_irq"};
irqreturn_t my_irq_handle(int irq, void *dev){
mod_timer(&mytimer,jiffies+1); //按下按键启动定时器
return IRQ_HANDLED;
}
void timer_handler(struct timer_list *timerlist){
if(!gpio_get_value(key_gpiono[0])){
printk("key1 down...\n");
}
if(!gpio_get_value(key_gpiono[1])){
printk("key2 down...\n");
}
if(!gpio_get_value(key_gpiono[2])){
printk("key3 down...\n");
}
}
static __init int mynode_init(void){
int ret,i;
/******初始化gpio******/
//定时器初始化完会进入一次定时器中断处理函数,如果此时没有获取gpio号就会出错,因此初始化gpio就要放在定时器初始化前面
//获取节点
devnode=of_find_node_by_path("/mykeys");
if(devnode == NULL){
pr_err("of_find_node_by_path error\n");
ret = -EINVAL;
goto err0;
}
//获取gpio号
for(i=0;i<3;i++){
key_gpiono[i] = of_get_named_gpio(devnode,"keys_no",i);
if(!key_gpiono[i]){
pr_err("of_get_named_gpio error\n");
ret = key_gpiono[i];
goto err0;
}
}
/******初始化定时器******/
mytimer.expires=jiffies+1; //定时10ms
timer_setup(&mytimer,timer_handler,0);
//如果先初始化中断,在定时器未初始化时触发中断的话,就会导致mod_timer第一个参数为空指针导致崩溃
//因此先初始化定时器,再初始化中断
/******注册中断******/
//获取软中断号
for(i=0;i<3;i++){
irq[i]=irq_of_parse_and_map(devnode,i);
if(!irq[i]){
pr_err("irq_of_parse_and_map error\n");
ret = -EINVAL;
goto err1;
}
}
//注册中断
for(i=0;i<3;i++){
ret = request_irq(irq[i],my_irq_handle,IRQF_TRIGGER_FALLING,irq_name[i],NULL);
if(ret){
pr_err("request_irq error\n");
goto err2;
}
}
//开启定时器
add_timer(&mytimer);
return 0;
err2:
for(--i;i>=0;i--){
free_irq(irq[i],NULL);
}
err1:
del_timer(&mytimer);
err0:
return ret;
}
static __exit void mynode_exit(void){
int i;
del_timer(&mytimer);
for(i=0;i<3;i++){
free_irq(irq[i],NULL);
}
}
module_init(mynode_init);
module_exit(mynode_exit);
MODULE_LICENSE("GPL");