IMX6ULL驱动学习 -- 内核定时器

时钟源

内核定时器时钟源有硬件定时器提供,定时器频率可以设置,设置好之后,周期性产生定时器中断,系统利用定时器中断计时。中断频率即为系统频率,也叫作节拍率。单位Hz。

  • 节拍率
    常见的节拍率设置有:1000Hz,500Hz,300Hz,250Hz,200Hz,100Hz。频率越高,内核计时任务占用资源越高,但是计时精度越高。一般设置为100Hz。在内核中用宏定义HZ表示。
  • 设置
    配置:
$ make menuconfig
->Kernel Features
	->Timer frequency ( <choice>[=y] )

基础定时器

定时器节拍率设置之后,内核周期性进入中断处理,累计节拍数。内核中使用全局变量记录从系统启动以来的系统节拍数。

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

其中jiffies_64用在64位系统,jiffies用于32位系统。在内核启动时初始化为0,每此进入硬件定时中断累加1,因此64位系统在节拍率为1000时,5.8亿年计数溢出,可忽略不计。但是32位系统只需要49.7天机会溢出,因此需要考虑绕回处理。

绕回处理函数

函数描述
time_after(unkown, known)unkown 通常为 jiffies, known 通常是需要对比的值。
time_before(unkown, known)unkown 通常为 jiffies, known 通常是需要对比的值。
time_after_eq(unkown, known)unkown 通常为 jiffies, known 通常是需要对比的值。
time_before_eq(unkown, known)unkown 通常为 jiffies, known 通常是需要对比的值。

如果unknown超过known,time_after返回真,否则返回假。time_before相反。

  • demo
    实现定时2s。
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
/*************************************
具体的代码
************************************/
/* 判断有没有超时 */
if(time_before(jiffies, timeout)) 
{
	/* 超时未发生 */
} 
else 
{
	/* 超时发生 */
}

时间转换函数

函数描述
int jiffies_to_msecs(const unsigned long j)将 jiffies 类型的参数 j 分别转换为对应的毫秒
int jiffies_to_usecs(const unsigned long j)将 jiffies 类型的参数 j 分别转换为对应的微秒
u64 jiffies_to_nsecs(const unsigned long j)将 jiffies 类型的参数 j 分别转换为对应的纳秒
long msecs_to_jiffies(const unsigned int m)将毫秒转换成jiffies类型
long usecs_to_jiffies(const unsigned int u)将微秒转换成jiffies类型
unsigned long nsecs_to_jiffies(u64 n)将纳秒转换成jiffies类型

用户定时器

Linux定时器使用很简单,只需要提供超时时间和定时器处理函数即可。但是内核定时器并不是周期性运行,超时后会自动关闭,因此如果需要周期性定时,需要在定时器处理函数中重新启动定时器。

  • 定时器结构体
struct timer_list {
	struct list_head entry;
	unsigned long expires; /* 定时器超时时间,单位是节拍数 */
	struct tvec_base *base;
	void (*function)(unsigned long); /* 定时处理函数 */
	unsigned long data; /* 要传递给 function 函数的参数 */
	int slack;
};

用户定时器使用

  • 定义定时器
 struct timer_list timer;        //定时器
  • 初始化定时器
void init_timer(struct timer_list *timer)

....demo....
init_timer(&timerdev.timer);
  • 注册定时器
    注册之后,定时器就会开始运行。
void add_timer(struct timer_list *timer)
  • 定时器处理函数
  • 删除定时器
int del_timer(struct timer_list * timer)

int del_timer_sync(struct timer_list *timer)
同步删除定时器,会等待其他处理器用完定时器再删除。
  • 修改定时器值
    如果定时器未被激活,则此函数会激活定时器
int mod_timer(struct timer_list *timer, unsigned long expires)

内核短延时函数

函数描述
void ndelay(unsigned long nsecs)纳秒延时
void udelay(unsigned long usecs)微秒延时
void mdelay(unsigned long mseces)毫秒延时

API

代码

定时器模块代码

  • timer.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define TIMER_CNT           1
#define TIMER_NAME          "timer"
#define CLOSE_CMD           (_IO(0xEF,0x1))     //关闭定时器
#define OPEN_CMD            (_IO(0xEF,0x2))     //打开定时器
#define SETPERIOD_CMD       (_IO(0xEF,0x3))     //设置定时器周期命令

#define LEDOFF      0   //关灯
#define LEDON       1   //开灯


//gpioled设备结构体
struct  timer_dev
{
    /* data */
    dev_t devid;                //设备号
    struct cdev cdev;           //cdev
    struct class *class;        //类
    struct device *device;      //设备
    int major;                  //主设备号
    int minor;                  //次设备号
    struct  device_node *nd;    //设备节点
    int led_gpio;               //使用的GPIO编号
    int timeperiod;             //定时器周期,单位ms
    struct timer_list timer;    //定义定时器
    spinlock_t lock;            //定义自旋锁
};

struct timer_dev timerdev;//timer设备
/*
初始化led IO,open函数打开驱动时初始化GPIO
*/
static int led_init(void)
{
    
    timerdev.nd = of_find_node_by_path("/light");//获取设备节点
    if(timerdev.nd == NULL)
    {
        return -EINVAL;
    }
    timerdev.led_gpio = of_get_named_gpio(timerdev.nd,"light-gpio",0);//获取GPIO编号
    if(timerdev.led_gpio < 0)
    {
        printk("can't find light-gpio!\r\n");
        
    }
    return 0;
}

/*
@description      :   打开设备
@param - inode    :   传递给驱动的inode   
@param - filp     :   设备文件 
@return           :   0 成功; 其他 失败
*/
static int timer_open(struct inode *inode, struct file *file)
{
    int ret = 0;
    file->private_data = &timerdev;        //设置私有数据
    
    timerdev.timeperiod = 1000;//默认定时器为1s
    ret = led_init();
    if(ret < 0)
    {
        return ret;
    }
    return 0;
}
/*
ioctl函数

*/
static long timer_unlocked_ioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{
    struct timer_dev *dev = (struct timer_dev *)filp->private_data;
    int timerperiod;
    unsigned long flags;

    switch (cmd)
    {
    case CLOSE_CMD:
        del_timer_sync(&dev->timer);
        break;
    case OPEN_CMD:
        spin_lock_irqsave(&dev->lock,flags);
        timerperiod = dev->timeperiod;
        spin_unlock_irqrestore(&dev->lock,flags);
        mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperiod));
        break;
    case SETPERIOD_CMD:
        spin_lock_irqsave(&dev->lock,flags);
        dev->timeperiod = arg;

        spin_unlock_irqrestore(&dev->lock,flags);
        mod_timer(&dev->timer,jiffies + msecs_to_jiffies(arg));
        break;
    default:
        break;
    }
    return 0;
}

/*   设备操作函数集合   */
static struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_unlocked_ioctl,
};

void timer_function(unsigned long arg)
{
    struct timer_dev *dev = (struct timer_dev*)arg;
    static int sta = 1;
    int timerperiod;
    unsigned long flags;
    sta = !sta;//每次状态取反,翻转LED

    gpio_set_value(dev->led_gpio,sta);

    //重启定时器
    spin_lock_irqsave(&dev->lock,flags);
    timerperiod = dev->timeperiod;
    spin_unlock_irqrestore(&dev->lock,flags);
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timeperiod));

}

/*
@description      :   驱动入口函数  
@param -          :   无 
@return           :   无
*/
static int __init timer_init(void)
{
    //初始化自旋锁
    spin_lock_init(&timerdev.lock);


    
    //注册字符设备驱动

    //1.创建设备号
    if(timerdev.major)
    {
        timerdev.devid = MKDEV(timerdev.major,0);
        register_chrdev_region(timerdev.devid,TIMER_CNT,TIMER_NAME);
    }
    else
    {
        alloc_chrdev_region(&timerdev.devid,0,TIMER_CNT,TIMER_NAME);//申请设备号
        timerdev.major = MAJOR(timerdev.devid);//获取主设备号
        timerdev.minor = MINOR(timerdev.devid);//获取次设备号
    }

    printk("timerdev major=%d,minor=%d\r\n",timerdev.major,timerdev.minor);

    //2.初始化cdev
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev,&timer_fops);

    //3.添加cdev
    cdev_add(&timerdev.cdev,timerdev.devid,TIMER_CNT);

    //4.创建类
    timerdev.class = class_create(THIS_MODULE,TIMER_NAME);
    if(IS_ERR(timerdev.class))
    {
        return PTR_ERR(timerdev.class);
    }
    //5.创建设备
    timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,TIMER_NAME);
    if(IS_ERR(timerdev.device))
    {
        return PTR_ERR(timerdev.device);
    }

    //6.初始化timer,设置定时器回调函数,未设置周期,因此不会激活定时器
    init_timer(&timerdev.timer);
    timerdev.timer.function = timer_function;
    timerdev.timer.data = (unsigned long)&timerdev;
    return 0;

}

static void __exit timer_exit(void)
{


    gpio_set_value(timerdev.led_gpio,1);
    del_timer_sync(&timerdev.timer);

    //注销字符驱动
    cdev_del(&timerdev.cdev);//删除cdev
    unregister_chrdev_region(timerdev.devid,TIMER_CNT);
    device_destroy(timerdev.class,timerdev.devid);
    class_destroy(timerdev.class);
    
}

module_init(timer_init);
module_exit(timer_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");

定时器测试应用代码

  • timerApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"


#define CLOSE_CMD           (_IO(0xEF,0x1))     //关闭定时器
#define OPEN_CMD            (_IO(0xEF,0x2))     //打开定时器
#define SETPERIOD_CMD       (_IO(0xEF,0x3))     //设置定时器周期命令

int main(int argc, char *argv[])
{
    int fd,ret;
    char *filename;
    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    if(argc != 2)
    {
        printf("error usage!\r\n");
        return -1;
    }

    filename = argv[1];

    //打开LED驱动
    fd = open(filename,O_RDWR);

    if(fd<0)
    {
        printf("file %s open failed! \r\n",argv[1]);
        return -1;
    }

    while (1)
    {
        printf("Input CMD:");
        ret = scanf("%d",&cmd);
        if(ret != 1)//参数输入错误
        {
            gets(str);//防止卡死
        }

        if(cmd == 1)
            cmd = CLOSE_CMD;
        else if (cmd == 2)
        {
            cmd = OPEN_CMD;
        }
        else if (cmd == 3)
        {
            cmd = SETPERIOD_CMD;//设置周期
            printf("Input Timer Period:");
            ret = scanf("%d",&arg);
            if(ret != 1)//参数输入错误
            {
                gets(str);//防止卡死
            }
        }
        ioctl(fd,cmd,arg);//控制定时器打开关闭
    }
    close(fd);
}

Makefile

  • Makefile
KERNELDIR := /home/book/arm/imx6ull/ebf_6ull_linux
CURRENT_PATH := $(shell pwd)

obj-m := timer.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

make.sh

  • make.sh
#!/bin/bash
sudo make -j12  ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
arm-linux-gnueabihf-gcc timerApp.c -o timerApp
sudo rm -r .*.cmd *.mod.* *.o *.symvers *.order
sudo cp *.ko /home/book/arm/imx6ull/eth_file/modules
sudo cp *App /home/book/arm/imx6ull/eth_file/modules

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
韦东山老师为啥要录升级版嵌入式视频?200x年左右,嵌入式Linux在全世界、在中国刚刚兴起。我记得我2005年进入中兴时,全部门的人正在努力学习Linux。在2008年,我写了一本书《嵌入式Linux应用开发完全手册》。它的大概内容是:裸机、U-boot、Linux内核、Linux设备驱动。那时还没有这样讲解整个系统的书,芯片厂家Linux开发包也还不完善,从bootloader到内核,再到设备驱动都不完善。有全系统开发能力的人也很少。于是这书也就恰逢其时,变成了畅销书。我也根据这个思路录制了视频:裸机、U-boot、Linux内核、Linux设备驱动。收获些许名声,带领很多人进入Linux世界。11年过去了,嵌入式Linux世界发生了翻天覆地的变化① 基本系统能用芯片厂家都会提供完整的U-boot、Linux内核、芯片上硬件资源的驱动。方案厂家会做一些定制,比如加上某个WIFI模块,会添加这个WIFI模块的驱动。你可以使用厂家的原始方案,或是使用/借鉴方案商的方案,做出一个“能用”的产品。② 基础驱动弱化;高级驱动专业化基础的驱动,比如GPIO、UART、SPI、I2C、LCD、MMC等,有了太多的书籍、视频、示例代码,修修改改总是可以用的。很多所谓的驱动工程师,实际上就是“调参工程师”。我们群里有名的火哥,提出了一个概念:这些驱动就起一个“hardware enable”的作用。高级的驱动,比如USB、PCIE、HDMI、MIPI、GPU、WIFI、蓝牙、摄像头、声卡。体系非常复杂,很少有人能讲清楚,很多时候只是一笔带过。配置一下应用层工具就了事,能用就成。这些高级驱动,工作中需要专门的人来负责,非常专业。他们是某一块的专家,比如摄像头专家、音频专家。③ 项目为王你到一个公司,目的是把产品做出来,会涉及APP到内核驱动全流程。中小公司玩不起华为中兴的配置,需要的是全面手。大公司里,只负责很小很小一块的镙丝钉,位置也不太稳固啊。所以,如果你不是立志成为某方面的专家,那就做一个全栈工程师吧。④ 调试很重要都说代码是3分写7分调,各种调试调优技术,可以为你的升职加薪加一把火。基于上述4点,我录制的全新视频将有这些特点:1. 快速入门,2. 实战项目,3. 驱动大全,4. 专题,5. 授人以渔,6. 要做任务另外,我们会使用多款芯片同时录制,先讲通用的原理,再单独讲各个板子的操作。这些芯片涵盖主流芯片公司的主流芯片,让你学习工作无缝对接。1.快速入门入门讲究的是快速,入门之后再慢慢深入,特别是对于急着找工作的学生,对于业余时间挑灯夜读的工作了的人,一定要快!再从裸机、U-boot、内核驱动这样的路线学习就不适合了,时间就拉得太长了。搞不好学了后面忘了前面。并且实际工作中并不需要你去弄懂U-boot,会用就行:U-boot比驱动还复杂。讲哪些内容?怎么讲呢?混着讲比如先讲LED APP,知道APP怎么调用驱动,再讲LED硬件原理和裸机,最后讲驱动的编写。这样可以快速掌握嵌入式Linux的整套开发流程,不必像以前那样光学习裸机就花上1、2个月。而里面的裸机课程,也会让你在掌握硬件操作的同时,把单片机也学会了。讲基础技能中断、休眠-唤醒、异步通知、阻塞、内存映射等等机制,会配合驱动和APP来讲解。这些技能是嵌入式Linux开发的基础。而这些驱动,只会涉及LED、按制、LCD等几个驱动。掌握了这些输入、输出的驱动和对应的APP后,你已经具备基本的开发能力了。讲配置我们从厂家、从方案公司基本上都可以拿到一套完整的开发环境,怎么去配置它?需要懂shell和python等配置脚本。效果效率优先以前我都是现场写代码、现场写文档,字写得慢,降低了学习效率。这次,效果与效率统一考虑,不再追求所有东西都现场写。容易的地方可先写好代码文档,难的地方现场写。2.实战项目会讲解这样的涉及linux网关/服务器相关项目(不限于,请多提建议):                   定位为:快速掌握项目开发经验,丰满简历。涉及的每一部分都会讲,比如如果涉及蓝牙,在这里只会讲怎么使用,让你能写出程序;如果要深入,可以看后面的蓝牙专题。3. 驱动大全包括基础驱动、高级驱动。这些驱动都是独立成章,深入讲解。虽然基础驱动弱化了,但是作为Linux系统开发人员,这是必备技能,并且从驱动去理解内核是一个好方法。在讲解这些驱动时,会把驱动的运行环境,比如内核调度,进程线程等概念也讲出来,这样就可以搭建一个知识体系。没有这些知识体系的话,对驱动的理解就太肤浅了,等于在Linux框架下写裸机,一叶障目,不见泰山。定位为:工具、字典,用到再学习。4. 专题想深入学习的任何内容,都可独立为专题。比如U-boot专题、内核内存管理专题、systemtap调试专题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值