學(xué)習(xí)過 UCOS 或 FreeRTOS 的同學(xué)應(yīng)該知道,UCOS 或 FreeRTOS 是需要一個硬件定時器提供系統(tǒng)時鐘,一般使用 Systick 作為系統(tǒng)時鐘源。
同理,Linux 要運行,也是需要一個系統(tǒng)時鐘的,至于這個系統(tǒng)時鐘是由哪個定時器提供的,有興趣的讀者可以去研究一下 Linux 內(nèi)核。不過對于Linux 驅(qū)動編寫者來說,不需要深入研究這些具體的實現(xiàn),只需要掌握相應(yīng)的 API 函數(shù)即可。
Linux 內(nèi)核中有大量的函數(shù)需要時間管理,比如周期性的調(diào)度程序、延時程序、對于我們驅(qū)動編寫者來說最常用的定時器。硬件定時器提供時鐘源,時鐘源的頻率可以設(shè)置, 設(shè)置好以后就周期性地產(chǎn)生定時中斷,系統(tǒng)使用定時中斷來計時。
中斷周期性產(chǎn)生的頻率就是系統(tǒng)頻率,也叫做節(jié)拍率(tick rate)(有的資料也叫系統(tǒng)頻率),比如 1000Hz,100Hz 等等說的就是系統(tǒng)節(jié)拍率。
系統(tǒng)節(jié)拍率是可以設(shè)置的,單位是 Hz,在編譯 Linux 內(nèi)核的時候可以通過圖形化界面設(shè)置系統(tǒng)節(jié)拍率,按照如下路徑打開配置界面:
->KernelFeatures ->Timerfrequency([=y])
效果圖:

在內(nèi)核文件路徑通過輸入:make menuconfig指令打開圖形化配置界面!

可以在.config文件中找到對應(yīng)的配置,在內(nèi)核文件路徑通過輸入:gedit .config指令打開:

CONFIG_HZ 為 100, Linux 內(nèi)核會使用 CONFIG_HZ 來設(shè)置自己的系統(tǒng)時鐘。打開文件 include/asm-generic/param.h,有如下內(nèi)容:
#ifndef__ASM_GENERIC_PARAM_H #define__ASM_GENERIC_PARAM_H #include#undefHZ #defineHZCONFIG_HZ/*Internalkerneltimerfrequency*/ #defineUSER_HZ100/*someuserinterfacesare*/ #defineCLOCKS_PER_SEC(USER_HZ)/*in"ticks"liketimes()*/ #endif/*__ASM_GENERIC_PARAM_H*/
宏 HZ 就是 CONFIG_HZ,因此 HZ=100,后面編寫 Linux驅(qū)動的時候會常常用到 HZ,因為 HZ 表示一秒的節(jié)拍數(shù),也就是頻率。
高節(jié)拍率會提高系統(tǒng)時間精度,如果采用 100Hz 的節(jié)拍率,時間精度就是 10ms,采用1000Hz 的話時間精度就是 1ms,精度提高了 10 倍。
高精度時鐘能夠以更高的精度運行,時間測量也更加準(zhǔn)確。高節(jié)拍率會導(dǎo)致中斷的產(chǎn)生更加頻繁,頻繁的中斷會加劇系統(tǒng)的負(fù)擔(dān), 1000Hz 和 100Hz的系統(tǒng)節(jié)拍率相比,系統(tǒng)要花費 10 倍的“精力”去處理中斷。
中斷服務(wù)函數(shù)占用處理器的時間增加,但是現(xiàn)在的處理器性能都很強大,所以采用 1000Hz 的系統(tǒng)節(jié)拍率并不會增加太大的負(fù)載壓力。
根據(jù)自己的實際情況,選擇合適的系統(tǒng)節(jié)拍率,本教程全部采用默認(rèn)的 100Hz 系統(tǒng)節(jié)拍率。
Linux 內(nèi)核使用全局變量 jiffies 來記錄系統(tǒng)從啟動以來的系統(tǒng)節(jié)拍數(shù),系統(tǒng)啟動的時候會將 jiffies 初始化為 0,jiffies 定義在文件 include/linux/jiffies.h 中,定義如下:

jiffies_64 和 jiffies 其實是同一個東西, jiffies_64 用于 64 位系統(tǒng),而 jiffies 用于 32 位系統(tǒng)。當(dāng)訪問 jiffies 的時候其實訪問的是 jiffies_64 的低 32 位,使用 get_jiffies_64 這個函數(shù)可以獲取 jiffies_64 的值。
在 32 位的系統(tǒng)上讀取 jiffies 的值,在 64 位的系統(tǒng)上 jiffes 和 jiffies_64表示同一個變量,因此也可以直接讀取 jiffies 的值。所以不管是 32 位的系統(tǒng)還是 64 位系統(tǒng),都可以使用 jiffies。
|繞回
前面說了 HZ 表示每秒的節(jié)拍數(shù),jiffies 表示系統(tǒng)運行的 jiffies 節(jié)拍數(shù),所以 jiffies/HZ 就是系統(tǒng)運行時間,單位為秒。
不管是 32 位還是 64 位的 jiffies,都有溢出的風(fēng)險,溢出以后會重新從 0 開始計數(shù),相當(dāng)于繞回來了,因此有些資料也將這個現(xiàn)象也叫做繞回。
假如 HZ 為最大值 1000 的時候,32 位的 jiffies 只需要 49.7 天就發(fā)生了繞回,對于 64 位的 jiffies 來說大概需要5.8 億年才能繞回,因此 jiffies_64 的繞回忽略不計。處理 32 位 jiffies 的繞回顯得尤為重要,Linux 內(nèi)核提供了如表 50.1.1.1 所示的幾個 API 函數(shù)來處理繞回。

可以在這個文件中找到定義:

如果 unkown 超過 known 的話,time_after 函數(shù)返回真,否則返回假。如果 unkown 沒有超過 known 的話 time_before 函數(shù)返回真,否則返回假。time_after_eq 函數(shù)和 time_after 函數(shù)類似,只是多了判斷等于這個條件。同理,time_before_eq 函數(shù)和 time_before 函數(shù)也類似。比如要判斷某段代碼執(zhí)行時間有沒有超時,此時就可以使用如下所示代碼:
unsignedlongtimeout;
timeout=jiffies+(2*HZ);/*超時的時間點*/
/*************************************
具體的代碼
************************************/
/*判斷有沒有超時*/
if(time_before(jiffies,timeout))
{
/*超時未發(fā)生*/
}
else
{
/*超時發(fā)生*/
}
timeout 就是超時時間點,比如我們要判斷代碼執(zhí)行時間是不是超過了 2 秒,那么超時時間點就是 jiffies+(2*HZ),如果 jiffies 大于 timeout 那就表示超時了,否則就是沒有超時。
為了方便開發(fā),Linux 內(nèi)核提供了幾個 jiffies 和 ms、us、ns 之間的轉(zhuǎn)換函數(shù):

| 定時器
定時器是一個很常用的功能,需要周期性處理的工作都要用到定時器。定時器大體分兩類,一個是硬件定時器,一個是軟件定時器,而軟件定時器需要硬件定時器做基礎(chǔ),通過軟件的方式使用無限拓展(理論上)的軟件定時器,使用了操作系統(tǒng)后,往往是使用軟件定時器,可以不需要再對硬件定時器進行初始化配置。
Linux 內(nèi)核定時器使用很簡單,只需要提供超時時間(相當(dāng)于定時值)和定時處理函數(shù)即可,當(dāng)超時時間到了以后設(shè)置的定時處理函數(shù)就會執(zhí)行,和我們使用硬件定時器的套路一樣,只是使用內(nèi)核定時器不需要做一大堆的寄存器初始化工作。
在使用內(nèi)核定時器的時候要注意一點,內(nèi)核定時器并不是周期性運行的,超時以后就會自動關(guān)閉,因此如果想要實現(xiàn)周期性定時,那么就需要在定時處理函數(shù)中重新開啟定時器。Linux 內(nèi)核使用 timer_list 結(jié)構(gòu)體表示內(nèi)核定時器,timer_list 定義在文件include/linux/timer.h 中,定義如下(省略掉條件編譯):
structtimer_list{
structlist_headentry;
unsignedlongexpires;
structtvec_base*base;/*定時器超時時間,單位是節(jié)拍數(shù)*/
void(*function)(unsignedlong);/*定時處理函數(shù)*/
unsignedlongdata;/*要傳遞給function函數(shù)的參數(shù)*/
intslack;
};
要使用內(nèi)核定時器首先要先定義一個 timer_list 變量,表示定時器,tiemr_list 結(jié)構(gòu)體的expires 成員變量表示超時時間,單位為節(jié)拍數(shù)。比如現(xiàn)在需要定義一個周期為 2 秒的定時器,那么這個定時器的超時時間就是 jiffies+(2*HZ),因此 expires=jiffies+(2*HZ)。function 就是定時器超時以后的定時處理函數(shù),要做的工作或處理就放到這個函數(shù)里面,需要根據(jù)需求編寫這個定時處理函數(shù)。
API函數(shù)
init_timer 函數(shù)
init_timer 函數(shù)負(fù)責(zé)初始化 timer_list 類型變量,當(dāng)我們定義了一個 timer_list 變量以后一定要先用 init_timer 初始化一下。init_timer 函數(shù)原型如下:
/* timer:要初始化定時器。 返回值:沒有返回值。 */ voidinit_timer(structtimer_list*timer)
add_timer 函數(shù)
add_timer 函數(shù)用于向 Linux 內(nèi)核注冊定時器,使用 add_timer 函數(shù)向內(nèi)核注冊定時器以后,定時器就會開始運行,函數(shù)原型如下:
/* timer:要注冊的定時器。 返回值:沒有返回值。 */ voidadd_timer(structtimer_list*timer)
del_timer 函數(shù)
del_timer 函數(shù)用于刪除一個定時器,不管定時器有沒有被激活,都可以使用此函數(shù)刪除。在多處理器系統(tǒng)上,定時器可能會在其他的處理器上運行,因此在調(diào)用 del_timer 函數(shù)刪除定時器之前要先等待其他處理器的定時處理器函數(shù)退出。del_timer 函數(shù)原型如下:
/* timer:要刪除的定時器。 返回值:0,定時器還沒被激活;1,定時器已經(jīng)激活 */ intdel_timer(structtimer_list*timer)
del_timer_sync 函數(shù)
del_timer_sync 函數(shù)是 del_timer 函數(shù)的同步版,會等待其他處理器使用完定時器再刪除,del_timer_sync 不能使用在中斷上下文中。del_timer_sync 函數(shù)原型如下所示:
/* timer:要刪除的定時器。 返回值:0,定時器還沒被激活;1,定時器已經(jīng)激活。 */ intdel_timer_sync(structtimer_list*timer)
mod_timer 函數(shù)
mod_timer 函數(shù)用于修改定時值,如果定時器還沒有激活的話,mod_timer 函數(shù)會激活定時器!函數(shù)原型如下:
/* timer:要修改超時時間(定時值)的定時器。 expires:修改后的超時時間。 返回值:0,調(diào)用 mod_timer 函數(shù)前定時器未被激活;1,調(diào)用 mod_timer 函數(shù)前定時器已被激活。 */ intmod_timer(structtimer_list*timer,unsignedlongexpires)
內(nèi)核定時器一般的使用流程如下所示:
structtimer_listtimer;/*定義定時器*/
/*定時器回調(diào)函數(shù)*/
voidfunction(unsignedlongarg)
{
/*
*定時器處理代碼
*/
/*如果需要定時器周期性運行的話就使用mod_timer
*函數(shù)重新設(shè)置超時值并且啟動定時器。
*/
mod_timer(&dev->timertest,jiffies+msecs_to_jiffies(2000));
}
/*初始化函數(shù)*/
voidinit(void)
{
init_timer(&timer);/*初始化定時器*/
timer.function=function;/*設(shè)置定時處理函數(shù)*/
timer.expires=jffies+msecs_to_jiffies(2000);/*超時時間2秒*/
timer.data=(unsignedlong)&dev;/*將設(shè)備結(jié)構(gòu)體作為參數(shù)*/
add_timer(&timer);/*啟動定時器*/
}
/*退出函數(shù)*/
voidexit(void)
{
del_timer(&timer);/*刪除定時器*/
/*或者使用*/
del_timer_sync(&timer);
}
Linux 內(nèi)核短延時函數(shù)
有時候需要在內(nèi)核中實現(xiàn)短延時,尤其是在 Linux 驅(qū)動中。Linux 內(nèi)核提供了毫秒、微秒和納秒延時函數(shù):

| LED閃爍
通過設(shè)置一個定時器來實現(xiàn)周期性的閃爍 LED 燈,通過這個案例來學(xué)習(xí)定時器的基本使用,這個實驗不需要看應(yīng)用層。
簡單使用型:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include //#include #defineCHRDEVBASE_CNT1/*設(shè)備號個數(shù)*/ #defineCHRDEVBASE_NAME"chrdevbase"/*名字*/ /*chrdevbase設(shè)備結(jié)構(gòu)體*/ structnewchr_dev{ dev_tdevid;/*設(shè)備號*/ structcdevcdev;/*cdev*/ structclass*class;/*類*/ structdevice*device;/*設(shè)備*/ intmajor;/*主設(shè)備號*/ intminor;/*次設(shè)備號*/ structdevice_node*nd;/*設(shè)備節(jié)點*/ intled_gpio;/*led所使用的GPIO編號*/ structtimer_listtimer;/*定義一個定時器*/ }; structnewchr_devchrdevbase;/*自定義字符設(shè)備*/ /* *@description:LED硬件初始化 *@param:無 *@return:無 */ staticintled_hal_init(void) { intret=0; /*設(shè)置LED所使用的GPIO*/ /* 1、獲取設(shè)備節(jié)點:gpioled */ chrdevbase.nd=of_find_node_by_path("/gpioled"); if(chrdevbase.nd==NULL){ printk("chrdevbasenodecantnotfound! "); return-EINVAL; }else{ printk("chrdevbasenodehasbeenfound! "); } /*2、獲取設(shè)備樹中的gpio屬性,得到LED所使用的LED編號*/ chrdevbase.led_gpio=of_get_named_gpio(chrdevbase.nd,"led-gpio",0); if(chrdevbase.led_gpio0)?{ ????????printk("can't?get?led-gpio"); ????????return?-EINVAL; ????} ????printk("led-gpio?num?=?%d ",?chrdevbase.led_gpio); ????/*?3、設(shè)置?GPIO1_IO03?為輸出,并且輸出高電平,默認(rèn)關(guān)閉?LED?燈?*/ ????ret?=?gpio_direction_output(chrdevbase.led_gpio,?1); ????if(ret?0)?{ ????????printk("can't?set?gpio! "); ????} ????return?0; } /* ?*?@description????????:?打開設(shè)備 ?*?@param?-?inode?????:?傳遞給驅(qū)動的inode ?*?@param?-?filp?????:?設(shè)備文件,file結(jié)構(gòu)體有個叫做private_data的成員變量 ?*???????????????????????一般在open的時候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。 ?*?@return?????????????:?0?成功;其他?失敗 ?*/ static?int?chrdevbase_open(struct?inode?*inode,?struct?file?*filp) { ????printk("[BSP]chrdevbase?open! "); ????filp->private_data=&chrdevbase;/*設(shè)置私有數(shù)據(jù)*/ return0; } /* *@description:從設(shè)備讀取數(shù)據(jù) *@param-filp:要打開的設(shè)備文件(文件描述符) *@param-buf:返回給用戶空間的數(shù)據(jù)緩沖區(qū) *@param-cnt:要讀取的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:讀取的字節(jié)數(shù),如果為負(fù)值,表示讀取失敗 */ staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt) { printk("chrdevbaseread! "); return0; } /* *@description:向設(shè)備寫數(shù)據(jù) *@param-filp:設(shè)備文件,表示打開的文件描述符 *@param-buf:要寫給設(shè)備寫入的數(shù)據(jù) *@param-cnt:要寫入的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:寫入的字節(jié)數(shù),如果為負(fù)值,表示寫入失敗 */ staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt) { printk("chrdevbasewrite! "); return0; } /* *@description:關(guān)閉/釋放設(shè)備 *@param-filp:要關(guān)閉的設(shè)備文件(文件描述符) *@return:0成功;其他失敗 */ staticintchrdevbase_release(structinode*inode,structfile*filp) { printk("[BSP]release! "); return0; } /* *設(shè)備操作函數(shù)結(jié)構(gòu)體 */ staticstructfile_operationschrdevbase_fops={ .owner=THIS_MODULE, .open=chrdevbase_open, .read=chrdevbase_read, .write=chrdevbase_write, .release=chrdevbase_release, }; /*定時器回調(diào)函數(shù)*/ voidtimer_function(unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)arg; staticintsta=1; sta=!sta;/*每次都取反,實現(xiàn)LED燈反轉(zhuǎn)*/ gpio_set_value(dev->led_gpio,sta); /*重啟定時器*/ mod_timer(&dev->timer,jiffies+msecs_to_jiffies(500)); } /* *@description:驅(qū)動入口函數(shù) *@param:無 *@return:0成功;其他失敗 */ staticint__initchrdevbase_init(void) { /*初始化硬件*/ led_hal_init(); /*注冊字符設(shè)備驅(qū)動*/ /*1、創(chuàng)建設(shè)備號*/ if(chrdevbase.major){/*定義了設(shè)備號*/ chrdevbase.devid=MKDEV(chrdevbase.major,0); register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME); }else{/*沒有定義設(shè)備號*/ alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申請設(shè)備號*/ chrdevbase.major=MAJOR(chrdevbase.devid);/*獲取主設(shè)備號*/ chrdevbase.minor=MINOR(chrdevbase.devid);/*獲取次設(shè)備號*/ } printk("newcheledmajor=%d,minor=%d ",chrdevbase.major,chrdevbase.minor); /*2、初始化cdev*/ chrdevbase.cdev.owner=THIS_MODULE; cdev_init(&chrdevbase.cdev,&chrdevbase_fops); /*3、添加一個cdev*/ cdev_add(&chrdevbase.cdev,chrdevbase.devid,CHRDEVBASE_CNT); /*4、創(chuàng)建類*/ chrdevbase.class=class_create(THIS_MODULE,CHRDEVBASE_NAME); if(IS_ERR(chrdevbase.class)){ returnPTR_ERR(chrdevbase.class); } /*5、創(chuàng)建設(shè)備*/ chrdevbase.device=device_create(chrdevbase.class,NULL,chrdevbase.devid,NULL,CHRDEVBASE_NAME); if(IS_ERR(chrdevbase.device)){ returnPTR_ERR(chrdevbase.device); } /*6、初始化timer*/ init_timer(&chrdevbase.timer); chrdevbase.timer.function=timer_function; chrdevbase.timer.expires=jiffies+msecs_to_jiffies(500); chrdevbase.timer.data=(unsignedlong)&chrdevbase; add_timer(&chrdevbase.timer); return0; } /* *@description:驅(qū)動出口函數(shù) *@param:無 *@return:無 */ staticvoid__exitchrdevbase_exit(void) { gpio_set_value(chrdevbase.led_gpio,1);/*卸載驅(qū)動的時候關(guān)閉LED*/ del_timer_sync(&chrdevbase.timer);/*刪除timer*/ /*注銷字符設(shè)備*/ cdev_del(&chrdevbase.cdev);/*刪除cdev*/ unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注銷設(shè)備號*/ device_destroy(chrdevbase.class,chrdevbase.devid);/*銷毀設(shè)備*/ class_destroy(chrdevbase.class);/*銷毀類*/ printk("[BSP]chrdevbaseexit! "); } /* *將上面兩個函數(shù)指定為驅(qū)動的入口和出口函數(shù) */ module_init(chrdevbase_init); module_exit(chrdevbase_exit); /* *LICENSE和作者信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
套路分析:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #defineCHRDEVBASE_CNT1/*設(shè)備號個數(shù)*/ #defineCHRDEVBASE_NAME"chrdevbase"/*名字*/ /*chrdevbase設(shè)備結(jié)構(gòu)體*/ structnewchr_dev{ .... structtimer_listtimer;/*定義一個定時器*/ }; structnewchr_devchrdevbase;/*自定義字符設(shè)備*/ /* *@description:LED硬件初始化 *@param:無 *@return:無 */ staticintled_hal_init(void) { ...... return0; } /* *@description:打開設(shè)備 *@param-inode:傳遞給驅(qū)動的inode *@param-filp:設(shè)備文件,file結(jié)構(gòu)體有個叫做private_data的成員變量 *一般在open的時候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。 *@return:0成功;其他失敗 */ staticintchrdevbase_open(structinode*inode,structfile*filp) { ...... return0; } /* *@description:從設(shè)備讀取數(shù)據(jù) *@param-filp:要打開的設(shè)備文件(文件描述符) *@param-buf:返回給用戶空間的數(shù)據(jù)緩沖區(qū) *@param-cnt:要讀取的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:讀取的字節(jié)數(shù),如果為負(fù)值,表示讀取失敗 */ staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt) { ...... return0; } /* *@description:向設(shè)備寫數(shù)據(jù) *@param-filp:設(shè)備文件,表示打開的文件描述符 *@param-buf:要寫給設(shè)備寫入的數(shù)據(jù) *@param-cnt:要寫入的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:寫入的字節(jié)數(shù),如果為負(fù)值,表示寫入失敗 */ staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt) { ...... return0; } /* *@description:關(guān)閉/釋放設(shè)備 *@param-filp:要關(guān)閉的設(shè)備文件(文件描述符) *@return:0成功;其他失敗 */ staticintchrdevbase_release(structinode*inode,structfile*filp) { ...... return0; } /* *設(shè)備操作函數(shù)結(jié)構(gòu)體 */ staticstructfile_operationschrdevbase_fops={ .owner=THIS_MODULE, .open=chrdevbase_open, .read=chrdevbase_read, .write=chrdevbase_write, .release=chrdevbase_release, }; /*定時器回調(diào)函數(shù)*/ voidtimer_function(unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)arg; staticintsta=1; sta=!sta;/*每次都取反,實現(xiàn)LED燈反轉(zhuǎn)*/ gpio_set_value(dev->led_gpio,sta); /*重啟定時器*/ mod_timer(&dev->timer,jiffies+msecs_to_jiffies(500)); } /* *@description:驅(qū)動入口函數(shù) *@param:無 *@return:0成功;其他失敗 */ staticint__initchrdevbase_init(void) { /*初始化硬件*/ led_hal_init(); /*注冊字符設(shè)備驅(qū)動*/ /*1、創(chuàng)建設(shè)備號*/ ...... /*2、初始化cdev*/ ...... /*3、添加一個cdev*/ ...... /*4、創(chuàng)建類*/ ...... /*5、創(chuàng)建設(shè)備*/ ...... /*6、初始化timer*/ init_timer(&chrdevbase.timer); chrdevbase.timer.function=timer_function; chrdevbase.timer.expires=jiffies+msecs_to_jiffies(500); chrdevbase.timer.data=(unsignedlong)&chrdevbase; add_timer(&chrdevbase.timer); return0; } /* *@description:驅(qū)動出口函數(shù) *@param:無 *@return:無 */ staticvoid__exitchrdevbase_exit(void) { gpio_set_value(chrdevbase.led_gpio,1);/*卸載驅(qū)動的時候關(guān)閉LED*/ del_timer_sync(&chrdevbase.timer);/*刪除timer*/ /*注銷字符設(shè)備*/ cdev_del(&chrdevbase.cdev);/*刪除cdev*/ unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注銷設(shè)備號*/ device_destroy(chrdevbase.class,chrdevbase.devid);/*銷毀設(shè)備*/ class_destroy(chrdevbase.class);/*銷毀類*/ printk("[BSP]chrdevbaseexit! "); } /* *將上面兩個函數(shù)指定為驅(qū)動的入口和出口函數(shù) */ module_init(chrdevbase_init); module_exit(chrdevbase_exit); /* *LICENSE和作者信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
| ioctl
上邊那個定時器案例是固定周期, 可用借助ioctl來動態(tài)修改定時器周期!
驅(qū)動:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #defineCHRDEVBASE_CNT1/*設(shè)備號個數(shù)*/ #defineCHRDEVBASE_NAME"chrdevbase"/*名字*/ #defineCLOSE_CMD(_IO(0xEF,0x01))/*關(guān)閉定時器*/ #defineOPEN_CMD(_IO(0xEF,0x02))/*打開定時器*/ #defineSETPERIOD_CMD(_IO(0xEF,0x03))/*設(shè)置定時器周期命令*/ #defineLEDON1/*開燈*/ #defineLEDOFF0/*關(guān)燈*/ /*chrdevbase設(shè)備結(jié)構(gòu)體*/ structnewchr_dev{ dev_tdevid;/*設(shè)備號*/ structcdevcdev;/*cdev*/ structclass*class;/*類*/ structdevice*device;/*設(shè)備*/ intmajor;/*主設(shè)備號*/ intminor;/*次設(shè)備號*/ structdevice_node*nd;/*設(shè)備節(jié)點*/ intled_gpio;/*led所使用的GPIO編號*/ inttimeperiod;/*定時周期(ms)*/ structtimer_listtimer;/*定義一個定時器*/ }; structnewchr_devchrdevbase;/*自定義字符設(shè)備*/ /* *@description:LED硬件初始化 *@param:無 *@return:無 */ staticintled_hal_init(void) { intret=0; /*設(shè)置LED所使用的GPIO*/ /*1、獲取設(shè)備節(jié)點:gpioled*/ chrdevbase.nd=of_find_node_by_path("/gpioled"); if(chrdevbase.nd==NULL){ printk("chrdevbasenodecantnotfound! "); return-EINVAL; }else{ printk("chrdevbasenodehasbeenfound! "); } /*2、獲取設(shè)備樹中的gpio屬性,得到LED所使用的LED編號*/ chrdevbase.led_gpio=of_get_named_gpio(chrdevbase.nd,"led-gpio",0); if(chrdevbase.led_gpio0)?{ ????????printk("can't?get?led-gpio"); ????????return?-EINVAL; ????} ????printk("led-gpio?num?=?%d ",?chrdevbase.led_gpio); ????/*?3、設(shè)置?GPIO1_IO03?為輸出,并且輸出高電平,默認(rèn)關(guān)閉?LED?燈?*/ ????ret?=?gpio_direction_output(chrdevbase.led_gpio,?1); ????if(ret?0)?{ ????????printk("can't?set?gpio! "); ????} ????return?0; } /* ?*?@description????????:?打開設(shè)備 ?*?@param?-?inode?????:?傳遞給驅(qū)動的inode ?*?@param?-?filp?????:?設(shè)備文件,file結(jié)構(gòu)體有個叫做private_data的成員變量 ?*???????????????????????一般在open的時候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。 ?*?@return?????????????:?0?成功;其他?失敗 ?*/ static?int?chrdevbase_open(struct?inode?*inode,?struct?file?*filp) { ????int?ret?=?0; ????printk("[BSP]chrdevbase?open! "); ????filp->private_data=&chrdevbase;/*設(shè)置私有數(shù)據(jù)*/ chrdevbase.timeperiod=1000;/*默認(rèn)周期為1s*/ ret=led_hal_init();/*初始化LEDIO*/ return0; } /* *@description:ioctl函數(shù), *@param–filp:要打開的設(shè)備文件(文件描述符) *@param-cmd:應(yīng)用程序發(fā)送過來的命令 *@param-arg:參數(shù) *@return:0成功;其他失敗 */ staticlongtimer_unlocked_ioctl(structfile*filp,unsignedintcmd,unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)filp->private_data; inttimerperiod; switch(cmd){ caseCLOSE_CMD:/*關(guān)閉定時器*/ //等待其他處理器使用完定時器再刪除 del_timer_sync(&dev->timer); break; caseOPEN_CMD:/*打開定時器*/ timerperiod=dev->timeperiod; mod_timer(&dev->timer,jiffies+msecs_to_jiffies(timerperiod)); break; caseSETPERIOD_CMD:/*設(shè)置定時器周期*/ dev->timeperiod=arg; mod_timer(&dev->timer,jiffies+msecs_to_jiffies(arg)); break; default: break; } return0; } /* *設(shè)備操作函數(shù)結(jié)構(gòu)體 */ staticstructfile_operationschrdevbase_fops={ .owner=THIS_MODULE, .open=chrdevbase_open, .unlocked_ioctl=timer_unlocked_ioctl, }; /*定時器回調(diào)函數(shù)*/ voidtimer_function(unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)arg; staticintsta=1; sta=!sta;/*每次都取反,實現(xiàn)LED燈反轉(zhuǎn)*/ gpio_set_value(dev->led_gpio,sta); /*重啟定時器*/ mod_timer(&dev->timer,jiffies+msecs_to_jiffies(dev->timeperiod)); } /* *@description:驅(qū)動入口函數(shù) *@param:無 *@return:0成功;其他失敗 */ staticint__initchrdevbase_init(void) { /*注冊字符設(shè)備驅(qū)動*/ /*1、創(chuàng)建設(shè)備號*/ if(chrdevbase.major){/*定義了設(shè)備號*/ chrdevbase.devid=MKDEV(chrdevbase.major,0); register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME); }else{/*沒有定義設(shè)備號*/ alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申請設(shè)備號*/ chrdevbase.major=MAJOR(chrdevbase.devid);/*獲取主設(shè)備號*/ chrdevbase.minor=MINOR(chrdevbase.devid);/*獲取次設(shè)備號*/ } printk("newcheledmajor=%d,minor=%d ",chrdevbase.major,chrdevbase.minor); /*2、初始化cdev*/ chrdevbase.cdev.owner=THIS_MODULE; cdev_init(&chrdevbase.cdev,&chrdevbase_fops); /*3、添加一個cdev*/ cdev_add(&chrdevbase.cdev,chrdevbase.devid,CHRDEVBASE_CNT); /*4、創(chuàng)建類*/ chrdevbase.class=class_create(THIS_MODULE,CHRDEVBASE_NAME); if(IS_ERR(chrdevbase.class)){ returnPTR_ERR(chrdevbase.class); } /*5、創(chuàng)建設(shè)備*/ chrdevbase.device=device_create(chrdevbase.class,NULL,chrdevbase.devid,NULL,CHRDEVBASE_NAME); if(IS_ERR(chrdevbase.device)){ returnPTR_ERR(chrdevbase.device); } /*6、初始化timer*/ init_timer(&chrdevbase.timer); chrdevbase.timer.function=timer_function; chrdevbase.timer.expires=jiffies+msecs_to_jiffies(500); chrdevbase.timer.data=(unsignedlong)&chrdevbase; //add_timer(&chrdevbase.timer); return0; } /* *@description:驅(qū)動出口函數(shù) *@param:無 *@return:無 */ staticvoid__exitchrdevbase_exit(void) { gpio_set_value(chrdevbase.led_gpio,1);/*卸載驅(qū)動的時候關(guān)閉LED*/ del_timer_sync(&chrdevbase.timer);/*刪除timer*/ /*注銷字符設(shè)備*/ cdev_del(&chrdevbase.cdev);/*刪除cdev*/ unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注銷設(shè)備號*/ device_destroy(chrdevbase.class,chrdevbase.devid);/*銷毀設(shè)備*/ class_destroy(chrdevbase.class);/*銷毀類*/ printk("[BSP]chrdevbaseexit! "); } /* *將上面兩個函數(shù)指定為驅(qū)動的入口和出口函數(shù) */ module_init(chrdevbase_init); module_exit(chrdevbase_exit); /* *LICENSE和作者信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
應(yīng)用:
#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"
/*命令值*/
#defineCLOSE_CMD(_IO(0XEF,0x1))/*關(guān)閉定時器*/
#defineOPEN_CMD(_IO(0XEF,0x2))/*打開定時器*/
#defineSETPERIOD_CMD(_IO(0XEF,0x3))/*設(shè)置定時器周期命令*/
/*
*@description:main主程序
*@param-argc:argv數(shù)組元素個數(shù)
*@param-argv:具體參數(shù)
*@return:0成功;其他失敗
*/
intmain(intargc,char*argv[])
{
intfd,ret;
char*filename;
unsignedintcmd;
unsignedintarg;
charwritebuf[100];
unsignedcharstr[100];
if(argc!=2){
printf("[APP]ErrorUsage!
");
return-1;
}
filename=argv[1];
/*打開驅(qū)動文件*/
fd=open(filename,O_RDWR);
if(fd0){
????????printf("[APP]Can't?open?file?%s
",?filename);
????????return?-1;
????}
????while?(1)?{
????????printf("Input?CMD:");
????????ret?=?scanf("%d",?&cmd);
????????if?(ret?!=?1)?{?/*?參數(shù)輸入錯誤?*/
????????????return?1;?/*?防止卡死?*/
????????}
????????if(cmd?==?1)?/*?關(guān)閉?LED?燈?*/
????????????cmd?=?CLOSE_CMD;
????????else?if(cmd?==?2)?/*?打開?LED?燈?*/
????????????cmd?=?OPEN_CMD;
????????else?if(cmd?==?3)?{
????????????cmd?=?SETPERIOD_CMD;?/*?設(shè)置周期值?*/
????????????printf("Input?Timer?Period:");
????????????ret?=?scanf("%d",?&arg);
????????????if?(ret?!=?1)?{?/*?參數(shù)輸入錯誤?*/
????????????????return?1;?/*?防止卡死?*/
????????????}
????????}
????????ioctl(fd,?cmd,?arg);?/*?控制定時器的打開和關(guān)閉?*/
????}
????close(fd);
????return?0;
}
使用:

上文就是簡單介紹了一下定時器, 簡單使用了一下定時器, 后邊根據(jù)各自需求進一步深入學(xué)習(xí).
審核編輯:劉清
-
定時器
+關(guān)注
關(guān)注
23文章
3368瀏覽量
123631 -
FreeRTOS
+關(guān)注
關(guān)注
14文章
499瀏覽量
66941 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
321瀏覽量
23212 -
時鐘源
+關(guān)注
關(guān)注
0文章
110瀏覽量
16755 -
定時中斷
+關(guān)注
關(guān)注
0文章
19瀏覽量
8820
原文標(biāo)題:i.MX6ULL|時間管理和內(nèi)核定時器
文章出處:【微信號:玩轉(zhuǎn)單片機,微信公眾號:玩轉(zhuǎn)單片機】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Linux驅(qū)動開發(fā)-內(nèi)核定時器
移植NXP官方linux 5.4內(nèi)核到i.MX6ULL開發(fā)板
如何在i.MX6ULL睡眠時停止刷新LCD?
【i.MX6UL/i.MX6ULL開發(fā)常見問題】單獨編譯內(nèi)核,uboot生成很多文件,具體用哪一個?
I.MX6ULL終結(jié)者開發(fā)板裸機仿真jlink調(diào)試
i.MX6ULL核心板資源
初識 i.MX6ULL 寄存器
ARM裸機篇之i.MX6ULL處理器資料分享
飛凌i.MX6ULL開發(fā)板的評測,再次進階擁有更高的性價比
基于NXP i.MX6ULL處理器的FETMX6ULL-C核心板
基于i.MX6ULL應(yīng)用處理器的FETMX6ULL-C核心板
Linux內(nèi)核定時器
基于i.MX6ULL的掉電檢測設(shè)計與軟件測試
i.MX6ULL時間管理和內(nèi)核定時器深入研究
評論