1. 程式人生 > >手把手教你寫Linux裝置驅動---定時器(一)(基於友善之臂4412開發板)

手把手教你寫Linux裝置驅動---定時器(一)(基於友善之臂4412開發板)

這個專題我們來說下Linux中的定時器。

Linux核心中,有這樣的一個定時器,叫做核心定時器,核心定時器用於控制某個函式,也就是定時器將要處理的函式在未來的某個特定的時間內執行。核心定時器註冊的處理函式只執行一次,即不是迴圈執行的。如果對延遲的精度要求不高的話,最簡單的實現方法如下---忙等待:
Unsigned long  j = jiffies + jit_delay * HZ;
While(jiffies  <  j)
{

         ……
}

下面來說下具體的引數代表的含義:

jiffies:全域性變數,用來記錄自系統啟動以來產生的節拍總數。啟動時核心將該變數初始化為0

此後每次時鐘中斷處理程式增加該變數的值。每一秒鐘中斷次數

HZjiffies一秒內增加HZ。系統執行時間 = jiffie/HZ.

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");
接下來,開啟我們開發板串列埠,觀察執行結果:

果然,定時器在開發板啟動後的若干時間後,就周而復始的去開啟和關閉我們板子上的蜂鳴器了。