學習過 UCOS 或 FreeRTOS 的同學應該知道,UCOS 或 FreeRTOS 是需要一個硬件定時器提供系統(tǒng)時鐘,一般使用 Systick 作為系統(tǒng)時鐘源。
同理,Linux 要運行,也是需要一個系統(tǒng)時鐘的,至于這個系統(tǒng)時鐘是由哪個定時器提供的,有興趣的讀者可以去研究一下 Linux 內核。不過對于Linux 驅動編寫者來說,不需要深入研究這些具體的實現(xiàn),只需要掌握相應的 API 函數(shù)即可。
Linux 內核中有大量的函數(shù)需要時間管理,比如周期性的調度程序、延時程序、對于我們驅動編寫者來說最常用的定時器。硬件定時器提供時鐘源,時鐘源的頻率可以設置, 設置好以后就周期性地產(chǎn)生定時中斷,系統(tǒng)使用定時中斷來計時。
中斷周期性產(chǎn)生的頻率就是系統(tǒng)頻率,也叫做節(jié)拍率(tick rate)(有的資料也叫系統(tǒng)頻率),比如 1000Hz,100Hz 等等說的就是系統(tǒng)節(jié)拍率。
系統(tǒng)節(jié)拍率是可以設置的,單位是 Hz,在編譯 Linux 內核的時候可以通過圖形化界面設置系統(tǒng)節(jié)拍率,按照如下路徑打開配置界面:
->KernelFeatures ->Timerfrequency([=y])
效果圖:
在內核文件路徑通過輸入:make menuconfig指令打開圖形化配置界面!
可以在.config文件中找到對應的配置,在內核文件路徑通過輸入:gedit .config指令打開:
CONFIG_HZ 為 100, Linux 內核會使用 CONFIG_HZ 來設置自己的系統(tǒng)時鐘。打開文件 include/asm-generic/param.h,有如下內容:
#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驅動的時候會常常用到 HZ,因為 HZ 表示一秒的節(jié)拍數(shù),也就是頻率。
高節(jié)拍率會提高系統(tǒng)時間精度,如果采用 100Hz 的節(jié)拍率,時間精度就是 10ms,采用1000Hz 的話時間精度就是 1ms,精度提高了 10 倍。
高精度時鐘能夠以更高的精度運行,時間測量也更加準確。高節(jié)拍率會導致中斷的產(chǎn)生更加頻繁,頻繁的中斷會加劇系統(tǒng)的負擔, 1000Hz 和 100Hz的系統(tǒng)節(jié)拍率相比,系統(tǒng)要花費 10 倍的“精力”去處理中斷。
中斷服務函數(shù)占用處理器的時間增加,但是現(xiàn)在的處理器性能都很強大,所以采用 1000Hz 的系統(tǒng)節(jié)拍率并不會增加太大的負載壓力。
根據(jù)自己的實際情況,選擇合適的系統(tǒng)節(jié)拍率,本教程全部采用默認的 100Hz 系統(tǒng)節(jié)拍率。
Linux 內核使用全局變量 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)。當訪問 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,都有溢出的風險,溢出以后會重新從 0 開始計數(shù),相當于繞回來了,因此有些資料也將這個現(xiàn)象也叫做繞回。
假如 HZ 為最大值 1000 的時候,32 位的 jiffies 只需要 49.7 天就發(fā)生了繞回,對于 64 位的 jiffies 來說大概需要5.8 億年才能繞回,因此 jiffies_64 的繞回忽略不計。處理 32 位 jiffies 的繞回顯得尤為重要,Linux 內核提供了如表 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 內核提供了幾個 jiffies 和 ms、us、ns 之間的轉換函數(shù):
| 定時器
定時器是一個很常用的功能,需要周期性處理的工作都要用到定時器。定時器大體分兩類,一個是硬件定時器,一個是軟件定時器,而軟件定時器需要硬件定時器做基礎,通過軟件的方式使用無限拓展(理論上)的軟件定時器,使用了操作系統(tǒng)后,往往是使用軟件定時器,可以不需要再對硬件定時器進行初始化配置。
Linux 內核定時器使用很簡單,只需要提供超時時間(相當于定時值)和定時處理函數(shù)即可,當超時時間到了以后設置的定時處理函數(shù)就會執(zhí)行,和我們使用硬件定時器的套路一樣,只是使用內核定時器不需要做一大堆的寄存器初始化工作。
在使用內核定時器的時候要注意一點,內核定時器并不是周期性運行的,超時以后就會自動關閉,因此如果想要實現(xiàn)周期性定時,那么就需要在定時處理函數(shù)中重新開啟定時器。Linux 內核使用 timer_list 結構體表示內核定時器,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; };
要使用內核定時器首先要先定義一個 timer_list 變量,表示定時器,tiemr_list 結構體的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ù)負責初始化 timer_list 類型變量,當我們定義了一個 timer_list 變量以后一定要先用 init_timer 初始化一下。init_timer 函數(shù)原型如下:
/* timer:要初始化定時器。 返回值:沒有返回值。 */ voidinit_timer(structtimer_list*timer)
add_timer 函數(shù)
add_timer 函數(shù)用于向 Linux 內核注冊定時器,使用 add_timer 函數(shù)向內核注冊定時器以后,定時器就會開始運行,函數(shù)原型如下:
/* timer:要注冊的定時器。 返回值:沒有返回值。 */ voidadd_timer(structtimer_list*timer)
del_timer 函數(shù)
del_timer 函數(shù)用于刪除一個定時器,不管定時器有沒有被激活,都可以使用此函數(shù)刪除。在多處理器系統(tǒng)上,定時器可能會在其他的處理器上運行,因此在調用 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,調用 mod_timer 函數(shù)前定時器未被激活;1,調用 mod_timer 函數(shù)前定時器已被激活。 */ intmod_timer(structtimer_list*timer,unsignedlongexpires)
內核定時器一般的使用流程如下所示:
structtimer_listtimer;/*定義定時器*/ /*定時器回調函數(shù)*/ voidfunction(unsignedlongarg) { /* *定時器處理代碼 */ /*如果需要定時器周期性運行的話就使用mod_timer *函數(shù)重新設置超時值并且啟動定時器。 */ mod_timer(&dev->timertest,jiffies+msecs_to_jiffies(2000)); } /*初始化函數(shù)*/ voidinit(void) { init_timer(&timer);/*初始化定時器*/ timer.function=function;/*設置定時處理函數(shù)*/ timer.expires=jffies+msecs_to_jiffies(2000);/*超時時間2秒*/ timer.data=(unsignedlong)&dev;/*將設備結構體作為參數(shù)*/ add_timer(&timer);/*啟動定時器*/ } /*退出函數(shù)*/ voidexit(void) { del_timer(&timer);/*刪除定時器*/ /*或者使用*/ del_timer_sync(&timer); }
Linux 內核短延時函數(shù)
有時候需要在內核中實現(xiàn)短延時,尤其是在 Linux 驅動中。Linux 內核提供了毫秒、微秒和納秒延時函數(shù):
| LED閃爍
通過設置一個定時器來實現(xiàn)周期性的閃爍 LED 燈,通過這個案例來學習定時器的基本使用,這個實驗不需要看應用層。
簡單使用型:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include //#include #defineCHRDEVBASE_CNT1/*設備號個數(shù)*/ #defineCHRDEVBASE_NAME"chrdevbase"/*名字*/ /*chrdevbase設備結構體*/ structnewchr_dev{ dev_tdevid;/*設備號*/ structcdevcdev;/*cdev*/ structclass*class;/*類*/ structdevice*device;/*設備*/ intmajor;/*主設備號*/ intminor;/*次設備號*/ structdevice_node*nd;/*設備節(jié)點*/ intled_gpio;/*led所使用的GPIO編號*/ structtimer_listtimer;/*定義一個定時器*/ }; structnewchr_devchrdevbase;/*自定義字符設備*/ /* *@description:LED硬件初始化 *@param:無 *@return:無 */ staticintled_hal_init(void) { intret=0; /*設置LED所使用的GPIO*/ /* 1、獲取設備節(jié)點:gpioled */ chrdevbase.nd=of_find_node_by_path("/gpioled"); if(chrdevbase.nd==NULL){ printk("chrdevbasenodecantnotfound! "); return-EINVAL; }else{ printk("chrdevbasenodehasbeenfound! "); } /*2、獲取設備樹中的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、設置?GPIO1_IO03?為輸出,并且輸出高電平,默認關閉?LED?燈?*/ ????ret?=?gpio_direction_output(chrdevbase.led_gpio,?1); ????if(ret?0)?{ ????????printk("can't?set?gpio! "); ????} ????return?0; } /* ?*?@description????????:?打開設備 ?*?@param?-?inode?????:?傳遞給驅動的inode ?*?@param?-?filp?????:?設備文件,file結構體有個叫做private_data的成員變量 ?*???????????????????????一般在open的時候將private_data指向設備結構體。 ?*?@return?????????????:?0?成功;其他?失敗 ?*/ static?int?chrdevbase_open(struct?inode?*inode,?struct?file?*filp) { ????printk("[BSP]chrdevbase?open! "); ????filp->private_data=&chrdevbase;/*設置私有數(shù)據(jù)*/ return0; } /* *@description:從設備讀取數(shù)據(jù) *@param-filp:要打開的設備文件(文件描述符) *@param-buf:返回給用戶空間的數(shù)據(jù)緩沖區(qū) *@param-cnt:要讀取的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:讀取的字節(jié)數(shù),如果為負值,表示讀取失敗 */ staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt) { printk("chrdevbaseread! "); return0; } /* *@description:向設備寫數(shù)據(jù) *@param-filp:設備文件,表示打開的文件描述符 *@param-buf:要寫給設備寫入的數(shù)據(jù) *@param-cnt:要寫入的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:寫入的字節(jié)數(shù),如果為負值,表示寫入失敗 */ staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt) { printk("chrdevbasewrite! "); return0; } /* *@description:關閉/釋放設備 *@param-filp:要關閉的設備文件(文件描述符) *@return:0成功;其他失敗 */ staticintchrdevbase_release(structinode*inode,structfile*filp) { printk("[BSP]release! "); return0; } /* *設備操作函數(shù)結構體 */ staticstructfile_operationschrdevbase_fops={ .owner=THIS_MODULE, .open=chrdevbase_open, .read=chrdevbase_read, .write=chrdevbase_write, .release=chrdevbase_release, }; /*定時器回調函數(shù)*/ voidtimer_function(unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)arg; staticintsta=1; sta=!sta;/*每次都取反,實現(xiàn)LED燈反轉*/ gpio_set_value(dev->led_gpio,sta); /*重啟定時器*/ mod_timer(&dev->timer,jiffies+msecs_to_jiffies(500)); } /* *@description:驅動入口函數(shù) *@param:無 *@return:0成功;其他失敗 */ staticint__initchrdevbase_init(void) { /*初始化硬件*/ led_hal_init(); /*注冊字符設備驅動*/ /*1、創(chuàng)建設備號*/ if(chrdevbase.major){/*定義了設備號*/ chrdevbase.devid=MKDEV(chrdevbase.major,0); register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME); }else{/*沒有定義設備號*/ alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申請設備號*/ chrdevbase.major=MAJOR(chrdevbase.devid);/*獲取主設備號*/ chrdevbase.minor=MINOR(chrdevbase.devid);/*獲取次設備號*/ } 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)建設備*/ 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:驅動出口函數(shù) *@param:無 *@return:無 */ staticvoid__exitchrdevbase_exit(void) { gpio_set_value(chrdevbase.led_gpio,1);/*卸載驅動的時候關閉LED*/ del_timer_sync(&chrdevbase.timer);/*刪除timer*/ /*注銷字符設備*/ cdev_del(&chrdevbase.cdev);/*刪除cdev*/ unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注銷設備號*/ device_destroy(chrdevbase.class,chrdevbase.devid);/*銷毀設備*/ class_destroy(chrdevbase.class);/*銷毀類*/ printk("[BSP]chrdevbaseexit! "); } /* *將上面兩個函數(shù)指定為驅動的入口和出口函數(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ù)*/ #defineCHRDEVBASE_NAME"chrdevbase"/*名字*/ /*chrdevbase設備結構體*/ structnewchr_dev{ .... structtimer_listtimer;/*定義一個定時器*/ }; structnewchr_devchrdevbase;/*自定義字符設備*/ /* *@description:LED硬件初始化 *@param:無 *@return:無 */ staticintled_hal_init(void) { ...... return0; } /* *@description:打開設備 *@param-inode:傳遞給驅動的inode *@param-filp:設備文件,file結構體有個叫做private_data的成員變量 *一般在open的時候將private_data指向設備結構體。 *@return:0成功;其他失敗 */ staticintchrdevbase_open(structinode*inode,structfile*filp) { ...... return0; } /* *@description:從設備讀取數(shù)據(jù) *@param-filp:要打開的設備文件(文件描述符) *@param-buf:返回給用戶空間的數(shù)據(jù)緩沖區(qū) *@param-cnt:要讀取的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:讀取的字節(jié)數(shù),如果為負值,表示讀取失敗 */ staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt) { ...... return0; } /* *@description:向設備寫數(shù)據(jù) *@param-filp:設備文件,表示打開的文件描述符 *@param-buf:要寫給設備寫入的數(shù)據(jù) *@param-cnt:要寫入的數(shù)據(jù)長度 *@param-offt:相對于文件首地址的偏移 *@return:寫入的字節(jié)數(shù),如果為負值,表示寫入失敗 */ staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt) { ...... return0; } /* *@description:關閉/釋放設備 *@param-filp:要關閉的設備文件(文件描述符) *@return:0成功;其他失敗 */ staticintchrdevbase_release(structinode*inode,structfile*filp) { ...... return0; } /* *設備操作函數(shù)結構體 */ staticstructfile_operationschrdevbase_fops={ .owner=THIS_MODULE, .open=chrdevbase_open, .read=chrdevbase_read, .write=chrdevbase_write, .release=chrdevbase_release, }; /*定時器回調函數(shù)*/ voidtimer_function(unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)arg; staticintsta=1; sta=!sta;/*每次都取反,實現(xiàn)LED燈反轉*/ gpio_set_value(dev->led_gpio,sta); /*重啟定時器*/ mod_timer(&dev->timer,jiffies+msecs_to_jiffies(500)); } /* *@description:驅動入口函數(shù) *@param:無 *@return:0成功;其他失敗 */ staticint__initchrdevbase_init(void) { /*初始化硬件*/ led_hal_init(); /*注冊字符設備驅動*/ /*1、創(chuàng)建設備號*/ ...... /*2、初始化cdev*/ ...... /*3、添加一個cdev*/ ...... /*4、創(chuàng)建類*/ ...... /*5、創(chuàng)建設備*/ ...... /*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:驅動出口函數(shù) *@param:無 *@return:無 */ staticvoid__exitchrdevbase_exit(void) { gpio_set_value(chrdevbase.led_gpio,1);/*卸載驅動的時候關閉LED*/ del_timer_sync(&chrdevbase.timer);/*刪除timer*/ /*注銷字符設備*/ cdev_del(&chrdevbase.cdev);/*刪除cdev*/ unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注銷設備號*/ device_destroy(chrdevbase.class,chrdevbase.devid);/*銷毀設備*/ class_destroy(chrdevbase.class);/*銷毀類*/ printk("[BSP]chrdevbaseexit! "); } /* *將上面兩個函數(shù)指定為驅動的入口和出口函數(shù) */ module_init(chrdevbase_init); module_exit(chrdevbase_exit); /* *LICENSE和作者信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
| ioctl
上邊那個定時器案例是固定周期, 可用借助ioctl來動態(tài)修改定時器周期!
驅動:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #defineCHRDEVBASE_CNT1/*設備號個數(shù)*/ #defineCHRDEVBASE_NAME"chrdevbase"/*名字*/ #defineCLOSE_CMD(_IO(0xEF,0x01))/*關閉定時器*/ #defineOPEN_CMD(_IO(0xEF,0x02))/*打開定時器*/ #defineSETPERIOD_CMD(_IO(0xEF,0x03))/*設置定時器周期命令*/ #defineLEDON1/*開燈*/ #defineLEDOFF0/*關燈*/ /*chrdevbase設備結構體*/ structnewchr_dev{ dev_tdevid;/*設備號*/ structcdevcdev;/*cdev*/ structclass*class;/*類*/ structdevice*device;/*設備*/ intmajor;/*主設備號*/ intminor;/*次設備號*/ structdevice_node*nd;/*設備節(jié)點*/ intled_gpio;/*led所使用的GPIO編號*/ inttimeperiod;/*定時周期(ms)*/ structtimer_listtimer;/*定義一個定時器*/ }; structnewchr_devchrdevbase;/*自定義字符設備*/ /* *@description:LED硬件初始化 *@param:無 *@return:無 */ staticintled_hal_init(void) { intret=0; /*設置LED所使用的GPIO*/ /*1、獲取設備節(jié)點:gpioled*/ chrdevbase.nd=of_find_node_by_path("/gpioled"); if(chrdevbase.nd==NULL){ printk("chrdevbasenodecantnotfound! "); return-EINVAL; }else{ printk("chrdevbasenodehasbeenfound! "); } /*2、獲取設備樹中的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、設置?GPIO1_IO03?為輸出,并且輸出高電平,默認關閉?LED?燈?*/ ????ret?=?gpio_direction_output(chrdevbase.led_gpio,?1); ????if(ret?0)?{ ????????printk("can't?set?gpio! "); ????} ????return?0; } /* ?*?@description????????:?打開設備 ?*?@param?-?inode?????:?傳遞給驅動的inode ?*?@param?-?filp?????:?設備文件,file結構體有個叫做private_data的成員變量 ?*???????????????????????一般在open的時候將private_data指向設備結構體。 ?*?@return?????????????:?0?成功;其他?失敗 ?*/ static?int?chrdevbase_open(struct?inode?*inode,?struct?file?*filp) { ????int?ret?=?0; ????printk("[BSP]chrdevbase?open! "); ????filp->private_data=&chrdevbase;/*設置私有數(shù)據(jù)*/ chrdevbase.timeperiod=1000;/*默認周期為1s*/ ret=led_hal_init();/*初始化LEDIO*/ return0; } /* *@description:ioctl函數(shù), *@param–filp:要打開的設備文件(文件描述符) *@param-cmd:應用程序發(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:/*關閉定時器*/ //等待其他處理器使用完定時器再刪除 del_timer_sync(&dev->timer); break; caseOPEN_CMD:/*打開定時器*/ timerperiod=dev->timeperiod; mod_timer(&dev->timer,jiffies+msecs_to_jiffies(timerperiod)); break; caseSETPERIOD_CMD:/*設置定時器周期*/ dev->timeperiod=arg; mod_timer(&dev->timer,jiffies+msecs_to_jiffies(arg)); break; default: break; } return0; } /* *設備操作函數(shù)結構體 */ staticstructfile_operationschrdevbase_fops={ .owner=THIS_MODULE, .open=chrdevbase_open, .unlocked_ioctl=timer_unlocked_ioctl, }; /*定時器回調函數(shù)*/ voidtimer_function(unsignedlongarg) { structnewchr_dev*dev=(structnewchr_dev*)arg; staticintsta=1; sta=!sta;/*每次都取反,實現(xiàn)LED燈反轉*/ gpio_set_value(dev->led_gpio,sta); /*重啟定時器*/ mod_timer(&dev->timer,jiffies+msecs_to_jiffies(dev->timeperiod)); } /* *@description:驅動入口函數(shù) *@param:無 *@return:0成功;其他失敗 */ staticint__initchrdevbase_init(void) { /*注冊字符設備驅動*/ /*1、創(chuàng)建設備號*/ if(chrdevbase.major){/*定義了設備號*/ chrdevbase.devid=MKDEV(chrdevbase.major,0); register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME); }else{/*沒有定義設備號*/ alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申請設備號*/ chrdevbase.major=MAJOR(chrdevbase.devid);/*獲取主設備號*/ chrdevbase.minor=MINOR(chrdevbase.devid);/*獲取次設備號*/ } 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)建設備*/ 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:驅動出口函數(shù) *@param:無 *@return:無 */ staticvoid__exitchrdevbase_exit(void) { gpio_set_value(chrdevbase.led_gpio,1);/*卸載驅動的時候關閉LED*/ del_timer_sync(&chrdevbase.timer);/*刪除timer*/ /*注銷字符設備*/ cdev_del(&chrdevbase.cdev);/*刪除cdev*/ unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注銷設備號*/ device_destroy(chrdevbase.class,chrdevbase.devid);/*銷毀設備*/ class_destroy(chrdevbase.class);/*銷毀類*/ printk("[BSP]chrdevbaseexit! "); } /* *將上面兩個函數(shù)指定為驅動的入口和出口函數(shù) */ module_init(chrdevbase_init); module_exit(chrdevbase_exit); /* *LICENSE和作者信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
應用:
#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))/*關閉定時器*/ #defineOPEN_CMD(_IO(0XEF,0x2))/*打開定時器*/ #defineSETPERIOD_CMD(_IO(0XEF,0x3))/*設置定時器周期命令*/ /* *@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]; /*打開驅動文件*/ 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)?/*?關閉?LED?燈?*/ ????????????cmd?=?CLOSE_CMD; ????????else?if(cmd?==?2)?/*?打開?LED?燈?*/ ????????????cmd?=?OPEN_CMD; ????????else?if(cmd?==?3)?{ ????????????cmd?=?SETPERIOD_CMD;?/*?設置周期值?*/ ????????????printf("Input?Timer?Period:"); ????????????ret?=?scanf("%d",?&arg); ????????????if?(ret?!=?1)?{?/*?參數(shù)輸入錯誤?*/ ????????????????return?1;?/*?防止卡死?*/ ????????????} ????????} ????????ioctl(fd,?cmd,?arg);?/*?控制定時器的打開和關閉?*/ ????} ????close(fd); ????return?0; }
使用:
上文就是簡單介紹了一下定時器, 簡單使用了一下定時器, 后邊根據(jù)各自需求進一步深入學習.
審核編輯:劉清
-
定時器
+關注
關注
23文章
3250瀏覽量
114882 -
FreeRTOS
+關注
關注
12文章
484瀏覽量
62202 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21653 -
時鐘源
+關注
關注
0文章
93瀏覽量
15979 -
定時中斷
+關注
關注
0文章
19瀏覽量
8580
原文標題:i.MX6ULL|時間管理和內核定時器
文章出處:【微信號:玩轉單片機,微信公眾號:玩轉單片機】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論