手把手教你寫Linux裝置驅動---定時器(一)(基於友善之臂4412開發板)
這個專題我們來說下Linux中的定時器。
在Linux核心中,有這樣的一個定時器,叫做核心定時器,核心定時器用於控制某個函式,也就是定時器將要處理的函式在未來的某個特定的時間內執行。核心定時器註冊的處理函式只執行一次,即不是迴圈執行的。如果對延遲的精度要求不高的話,最簡單的實現方法如下---忙等待:Unsigned long j = jiffies + jit_delay * HZ;
While(jiffies < j)
{
……
}
下面來說下具體的引數代表的含義:
jiffies:全域性變數,用來記錄自系統啟動以來產生的節拍總數。啟動時核心將該變數初始化為0;
此後每次時鐘中斷處理程式增加該變數的值。每一秒鐘中斷次數
jiffies用途:計算流逝時間和時間管理
jiffies內部表示:
extern u64 jiffies_64;
extern unsigned long volatilejiffies; //位長更系統有關32/64---->
|
|
32位:497天后溢位
64位:……
在定時器中有這樣一個概念,度量時間差:
時鐘中斷由系統的定時硬體以週期性的時間間隔產生,這個間隔說白了其實就是頻率由核心根據HZ來確定,HZ是一個與體系結構無關的常數,可以配置為(50-1200),在X86平臺,它的值被預設為1000 ;
定時器在核心中相關的標頭檔案以及資料結構如下:
#include <linux/timer.h> /*timer*/
#include <asm/uaccess.h> /*jiffies*/
struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ //定時器可以作為連結串列的一個節點 struct list_head entry; //定時值基於jiffies unsigned long expires; //定時器內部值 struct tvec_base *base; //定時器處理函式 void (*function)(unsigned long); //定時器處理函式引數 unsigned long data; int slack; #ifdef CONFIG_TIMER_STATS int start_pid; void *start_site; char start_comm[16]; #endif #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
定時器最基本的使用方法可以使用下面這兩個個核心提供的巨集:
//初始化定時器
#define init_timer(timer)\
init_timer_key((timer), NULL, NULL)
//註冊一個定時器
#define setup_timer(timer, fn, data)\
setup_timer_key((timer), NULL, NULL, (fn), (data))
新增一個定時器
void add_timer(struct timer_list *timer)
刪除一個定時器
int del_timer(struct timer_list *timer)
那麼寫一個定時器的具體步驟是什麼?
1、初始化核心定時器
2、設定定時器執行函式的引數(可有可無)
3、設定定時時間
4、設定定時器函式
5、啟動定時器
接下來,我們結合一個簡單的驅動來了解這個過程,這個驅動非常簡單,就是開機後,5s鍾後,開發板上的蜂鳴器就會每隔1s鍾交替響。
先來看看開發板的蜂鳴器的原理圖:
(1)蜂鳴器介面位於電路板的底板,看電路圖可知道是高電平有效。
(2)相對應的找到核心板的介面。由此可知,我們的蜂鳴器是GPD0_0
接下來找資料手冊,找到對應的暫存器,然後配置它就可以了。
2、查資料手冊,找到相關的暫存器,並配置
(1)找到GPD0CON,地址是0x114000A0,我們需要配置GPD0CON(0)為輸出狀態。也就是寫0x1這個值到這個暫存器。
(2)找到GPD0DAT這個暫存器,用於配置蜂鳴器的高低電平,實體地址是0x114000A4,剛好與上一個差4個位元組的偏移
我們只要對這個暫存器寫1和寫0,那麼蜂鳴器就可以叫起來了,哈哈。是不是很簡單?
整個簡單的驅動程式碼如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/timer.h> /*timer*/
#include <asm/uaccess.h> /*jiffies*/
#include <linux/delay.h>
//裝置名稱
#define DEVICE_NAME "Bell"
//裝置GPIO引腳
#define BUZZER_GPIO EXYNOS4_GPD0(0)
//定義一個定時器連結串列
struct timer_list timer;
static void Bell_init()
{
//1、請求gpio,相當於註冊gpio
gpio_request(BUZZER_GPIO,DEVICE_NAME);
//2、呼叫板級驅動的函式,將gpio配置成輸出狀態
s3c_gpio_cfgpin(BUZZER_GPIO, S3C_GPIO_OUTPUT);
//3、設定gpio為0,表示低電平,蜂鳴器高電平就會響
gpio_set_value(BUZZER_GPIO,0);
}
void timer_function(unsigned long value)
{
while(value)
{
//設定gpio為1,表示高電平,蜂鳴器高電平就會響
gpio_set_value(BUZZER_GPIO,1);
printk("BUZZER ON\n");
mdelay(1000);
//設定gpio為0,表示低電平,蜂鳴器高電平就會響
gpio_set_value(BUZZER_GPIO,0);
printk("BUZZER OFF\n");
mdelay(1000);
}
}
static int __init tiny4412_Bell_init(void)
{
//bell init
Bell_init();
//初始化核心定時器
init_timer(&timer);
//給執行的函式傳參
timer.data= 1;
//當前jiffies的值加上5秒鐘之後
timer.expires= jiffies + (5 * HZ);
//如果超時了就執行這個函式
timer.function= timer_function;
//啟動定時器
add_timer(&timer);
return 0 ;
}
static void __exit tiny4412_Bell_exit(void)
{
//釋放gpio
gpio_free(BUZZER_GPIO);
//刪除註冊的定時器
del_timer(&timer);
}
module_init(tiny4412_Bell_init);
module_exit(tiny4412_Bell_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 BELL Driver");
接下來,開啟我們開發板串列埠,觀察執行結果:果然,定時器在開發板啟動後的若干時間後,就周而復始的去開啟和關閉我們板子上的蜂鳴器了。