linux驅動1.驅動框架與GPIO操做

1、驅動程序概念介紹

  u-boot的任務是啓動內核,內核的任務是啓動應用程序 ,應用程序會涉及不少文件和硬件操做(固然不會直接操做硬件),好比讀寫文件,點燈、獲取按鍵值。   好比對於控制led燈的用戶程序與驅動程序,最簡單的實現方法是:   應用程序中須要打開led燈,就須要open函數,在內核中的驅動程序中也有對應的led_open函數,這個led_open函數就是用來負責初始化led的引腳功能,應用程序中要調用read函數讀取led燈的狀態,內核中的驅動程序也有led_read函數。這是應用程序與內核中驅動程序一種最簡單的對應方式.   那麼應用程序中的open、read函數最終怎樣調用到驅動程序中的led_open、led_read呢,中間有哪些東西?   在linux中共有4層軟件,以下圖: 如下名詞解釋:node

  • 應用程序:就是被調用的那些庫函數,例如open、read、write... ...
  • C庫(系統調用):其中的其實就是實現open、read這些函數來調用swi val 指令進入內核(函數不一樣val值都會不一樣)
  • 內核: 內核根據swi後面不一樣的值去調用VFS中的system_open/system_read/ system_write等異常處理函數,找到相應的驅動程序(VFS:virtual file system 虛擬文件系統)

例如:linux

int main()
{
	int fd1  fd2;
	int   val=1;
	fd1 = open(「/dev/led」,O_RDWR);    //打開led
	write(fd1, &val, 4);
	fd2 = open(「hello.txt」,O_RDWR);  //打開文本
	write(fd2, &val, 4);
}

  問:上面的應用程序主要實現點燈與打開文本文件,都是用的一樣的函數。可是點燈與打開文本文件的行爲顯然不同。那麼誰來實現這些不同的行爲呢?   答:對於LED燈,有led_open驅動程序。對於文本文件存在於flash設備上,也有對於的驅動程序。system_open、system_read最終會根據打開的不一樣文件,找到底層的不一樣驅動程序,而後調用驅動程序中的硬件操做函數,好比led_open來實現對具體硬件設備的操做。這就是整個的字符設備驅動程序框架。   例如LED,以下圖:   在應用層應用程序中有open、read、write   一樣,在驅動程序中也對應有led_open、led_read、led_write   剩下的就是驅動框架了。數組

2、製做第一個驅動程序

本節目的:   <font color=#FF0000>先講解驅動框架,而後寫出first_drv驅動程序,來打印一些信息</font> 寫出first_drv驅動程序須要如下幾步: 1)寫出驅動程序first_drv_open first_drv_write 2)須要定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write   對於字符設備來講,經常使用file_operations如下幾個成員: 3) 模塊加載函數,經過函數 register_chrdev(major, 「first_drv」, &first_drv_fops) 來註冊字符設備 4)寫驅動的first_drv_init 入口函數來調用這個register_chrdev()註冊函數, 5)經過module_init()來修飾入口函數,使內核知道有這個函數 6)寫驅動的first_drv_exit出口函數,調用這個unregister_chrdev()函數卸載, 7) 經過module_exit()來修飾出口函數 8) 模塊許可證聲明, 最多見的是以MODULE_LICENSE( "GPL v2" )來聲明   接下來咱們編寫並調試驅動程序。網絡

2.一、建立first_drv.c文件

代碼以下:框架

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h> 
/*1寫出驅動程序first_drv_open first_drv_write */
/*inode結構表示具體的文件,file結構體用來追蹤文件在運行時的狀態信息。*/
static int first_drv_open(struct inode *inode, struct file  *file)
{
    printk(「first_drv_open\n」);      //打印,在內核中打印只能用printk()
    return 0;
}

/*參數filp爲目標文件結構體指針,buffer爲要寫入文件的信息緩衝區,count爲要寫入信息的長度,ppos爲當前的偏移位置,這個值一般是用來判斷寫文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    printk(「first_drv_write\n」);      //打印,在內核中打印只能用printk()
    return 0;
}
 
/*2定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write */
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,     //被使用時阻止模塊被卸載
    .open   =   first_drv_open,      
    .write   =   first_drv_write,   
};

/*4寫first_drv_init入口函數來調用這個register_chrdev()註冊函數*/
int first_drv_init(void)
{
    /*3 register_chrdev註冊字符設備,並設置major=111*/
    /*若是設置major爲0,表示由內核動態分配主設備號,函數的返回值是主設備號*/
	register_chrdev (111, 「first_drv」, &first_drv_fops); //111:主設備號,」first_drv」:設備名
/*register_chrdev做用:在VFS虛擬文件系統中找到字符設備,而後經過主設備號找到內核數組裏對應的位置,最後將設備名字和fops結構體填進去*/
    return 0;
}

/*5 module_init修飾入口函數*/
module_init(first_drv_init);

/*6 寫first_drv_exit出口函數*/
void first_drv_exit(void)
{
	unregister_chrdev (111, 「first_drv」);  //卸載驅動,只須要主設備號和設備名就行 
}

/*7 module_exit修飾出口函數*/
module_exit(first_drv_exit);

/*8許可證聲明, 描述內核模塊的許可權限,若是不聲明LICENSE,模塊被加載時,將收到內核被污染 (kernel tainted)的警告。*/
MODULE_LICENSE( "GPL v2" );

2.二、寫Makefile編譯腳本:

KERN_DIR = /work/system/linux-2.6.22.6   //依賴的內核目錄,前提內核是編譯好的

all:                                
	make -C $(KERN_DIR) M=`pwd` modules   
// M=`pwd`:指定當前目錄
//make -C $(KERN_DIR) 表示將進入(KERN_DIR)目錄,執行該目錄下的Makefile
//等價於在linux-2.6.22.6目錄下執行: make M=(當前目錄) modules
// modules:要編譯的目標文件

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m      += frist_drv.o     //obj-m:內核模塊文件,指將myleds.o編譯成myleds.ko

2.三、編譯、加載

1)make,編譯生成frist_drv.ko文件 2)開發板經過nfs網絡文件系統來加載frist_drv.ko   注:加載以前首先經過 cat /proc/devices來查看字符主設備號111是否被佔用,而後經過 insmod first_drv.ko來掛載, 經過 cat /proc/devices就能看到first_drv已掛載好函數

2.四、經過測試程序測試frist_drv模塊

測試程序first_driver_test.c代碼以下測試

#include <sys/types.h>    //調用sys目錄下types.h文件
#include <sys/stat.h>      //stat.h獲取文件屬性
#include <fcntl.h>
#include <stdio.h>

/*輸入」./first_driver_test」,     agc就等於1, argv[0]= first_driver_test  */
/*輸入」./first_driver_test on」,   agc就等於2, argv[0]= first_driver_test,argv[1]=on;  */

int main(int argc,char **argv) 
{
	int fd1, fd2;
	int val=1;
	fd1 = open("/dev/xxx",O_RDWR);  //打開/dev/xxx設備節點
	if(fd1<0)                   //沒法打開,返回-1
		printf("can't open%d!\n", fd1);
else
		printf("can open%d!\n", fd1);    //打開,返回文件描述符

	write(fd1, &val, 4);              //寫入數據1
	return 0;
}

1)經過「arm-linux-gcc -o first_driver_text first_driver_test.c」指令生成執行文件 2)回到板子串口上使用./first_driver_test來運行,發現若是open()打不開,會返回-1 打印信息:spa

can't open-1!

  緣由:這是由於咱們沒有建立dev/xxx這個設備節點,而後咱們來建立,使它等於剛剛掛載好的first_drv模塊。 3)運行指令:指針

mknod -m 660 /dev/xxx c 111 0            // first_drv模塊的主設備號=111
./first_driver_test

打印信息:調試

first_drv_open
can open3!
first_drv_write

  經過打印信息發現測試程序裏的open()函數調用了驅動中的first_drv_open(),write()函數調用了驅動中的first_drv_write(),   其中open()函數返回值爲3,是由於描述符0,1,2都已經被控制檯佔用了,因此從3開始

2.五、改進底層驅動,使用動態裝載:

  除了靜態裝載驅動外,還能夠動態裝載,讓系統自動爲咱們驅動設備自動分配設備號 2.5.一、修改first_drv_init入口函數和first_drv_exit 出口函數: 代碼以下:

int major;              //定義一個全局變量,用來保存主設備號
int first_drv_init(void)
{
	/*設置major爲0,由內核動態分配主設備號,函數的返回值是主設備號*/
	major =register_chrdev (0, 「first_drv」, &first_drv_fops);  
	return 0;

}

void first_drv_exit(void)
{
	unregister_chrdev (major, 「first_drv」);  //卸載驅動, 將major填入便可
}

  經過動態分配得出它的主設備號是252(此數字隨機分配),而後重創252的測試程序 運行指令:

rm dev/xxx
mknod -m 660 /dev/xxx c 252 0
./first_driver_test

打印信息:

first_drv_open
can open3!
first_drv_write

2.5.二、每次都要手工建立設備節點,你們確定也會以爲這樣作太麻煩了。   改進方法:可使用自動建立設備節點,Linux有udev、mdev的機制,而咱們的ARM開發板上移植的busybox有mdev機制,而後mdev機制會經過class類來找到相應類的驅動設備來自動建立設備節點 (前提須要有mdev)   問:在哪裏設置了mdev機制?   答:在製做根文件系統之使用裏有介紹 2.5.三、接下來使用insmod自動建立設備節點, rmmod自動註銷設備節點 1)首先建立一個class設備類,class是一個設備的高級視圖,它抽象出低級的實現細節,而後在class類下,建立一個class_device,即類下面建立類的設備:(在C語言中class就是個結構體)

static struct class *firstdrv_class;               //建立一個class類
static struct class_device   *firstdrv_class_devs; //建立類的設備

2)在first_drv_init入口函數中添加:

firstdrv_class= class_create(THIS_MODULE,"firstdrv");  
	//建立類,它會在sys/class目錄下建立firstdrv_class這個類

	firstdrv_class_devs=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");
	//建立類設備,會在sys/class/firstdrv_class類下建立xyz設備,而後mdev經過這個自動建立/dev/xyz這個設備節點,

3)在first_drv_exit出口函數中添加:

class_device_unregister(firstdrv_class_devs);      //註銷類設備,與class_device_create對應
	class_destroy(firstdrv_class);                    //註銷類,與class_create對應

  從新編譯insmod後,會發如今/dev下自動的建立了xyz設備節點   其中在sys/class裏有各類類的設備, 好比sys/class/fristdev下就有xyz   而後mdev經過insmod xxx 就去class找到相應類的驅動設備來自動建立設備節點   問:爲何內容一更改,mdv就能自動運行建立設備節點呢?   答:是由於之前建立根文件系統時候,在etc/init.d/rcS裏添加了這麼一段:

echo /sbin/mdev > /proc/sys/kernel/hotplug             //支持熱拔插

  而後kernel每當設備出現變更時,調用/sbin/mdev來處理對應的信息,使mdev應用程序操做/dev目錄下的設備,進行添加或刪除 4)再修改測試程序裏open函數,將/dev/xxx改成/dev/xyz,這樣就測試模塊,就不須要再mknod了. 驅動程序first_drv_open first_drv_write中只是打印數據,接下便開始來點亮LED.

3、修改第一個程序來點亮LED

本節目的:   <font color=#FF0000>在上一節搭建的驅動框架下添加硬件的操做</font> 硬件的操做(控制LED)主要分爲以下幾步: 1)看原理圖,肯定引腳 2)看2440手冊 3)寫代碼(須要使用ioremap()函數映射虛擬地址,在linux中只能使用虛擬地址) 4)修改上一節的測試程序 5)使用次設備號來控制設備下不一樣的燈

3.一、看led引腳

  看原理圖能夠肯定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6

3.二、看2440手冊

  配置GPFCON15:0的位[8:9]、位[10:11]、位[12:13] 都等於0x01(輸出模式)   控制GPFDAT7:0中的位4~6來使燈亮滅(低電平亮)

3.三、寫代碼

1)添加全局變量:

volatile unsigned long *GPFcon=NULL;       
volatile unsigned long *GPFdat=NULL;

2)first_drv_init入口函數中使用ioremap()映射虛擬地址:

GPFcon = ioremap(0x56000050, 16);   //ioremap:物理地址映射,返回虛擬地址
GPFdat=GPFcon+1;             //long:32位,因此GPFdat=0x56000050+(32/8)

3)first_drv_exit出口函數中註銷虛擬地址:

iounmap(GPFcon);          //註銷虛擬地址

4)first_drv_open函數中添加配置GPFCON:

*GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12)); 
*GPFcon|=    ((0X01<<8)| (0X01<<10)| (0X01<<12));

5)first_drv_write函數中添加拷貝應用層數據,而後來控制GPFDAT:

/*copy_to_user():將數據上給用戶*/
copy_from_user(&val,buf,count);      //從用戶(應用層)拷貝數據 
if(val==1)                  //點燈(低電平亮)
{  
	*GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6)); 
}
else                  //滅燈
{
	*GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6));    
}

3.四、修改測試程序main()

代碼以下:

int main(int argc,char **argv) //argc:參數個數,argv數組
{
	int fd1, fd2;
	int val=1;
	fd1 = open("/dev/xyz",O_RDWR);  //打開/dev/xxx設備節點
	if(fd1<0)                   //沒法打開,返回-1
		printf("can't open%d!\n", fd1); 
	if(argc!=2)
	{
		printf("Usage:\n");
		printf("%s <on|off>",argv[0]);
		return 0;
	}

	if(strcmp(argv[1],"on")==0)   //開燈
	{
		printf("led on...\n");
		val=1;
	} 
	else                         //關燈
	{
		printf("led off...\n");
		val=0;
	}

	write(fd1, &val, 4);
	return 0;
}

  當輸入first_driver_text on點3個燈, 不然關3個燈   若參數不等於2時,不能控制點燈   問:若是咱們想分別控制不一樣的燈,該怎麼作?   答:可使用次設備號,次設備號就是用來區分同一設備下不一樣子設備

3.五、使用次設備號來控制設備下不一樣的燈

  咱們先來看下面兩個函數MAJOR和MINOR,分別是提取主次設備號

minor=MINOR(inode->i_rdev);    //open函數中提取次設備號
major=MAJOR(inode->i_rdev);    //open函數中提取主設備號

minor=MINOR (file->f_dentry->d_inode->i_rdev);  //write/read函數中提取次設備號
major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函數中提取主設備號

思路以下: 在測試程序中:   經過dev[1]來open打開不一樣的子設備節點,而後經過dev[2]來write寫入數據   實例: first_driver_text led1 on //點亮led1 在first_dev.c驅動文件中:   first_drv_init函數中建立不一樣的子設備節點   first_drv_exti函數中註銷不一樣的子設備節點   first_drv_open函數中經過MINOR(inode->i_rdev)來初始化不一樣的燈   first_drv_write函數中經過MINOR(file->f_dentry->d_inode->i_rdev)來控制不一樣的燈 以下圖,insmod後自動註冊3個設備節點 測試程序以下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
 /*
  *  ledtest <dev> <on|off>
  */

void print_usage(char *file)         //報錯打印幫助
{
    printf("Usage:\n");
    printf("%s <dev> <on|off>\n",file);
    printf("eg. \n");
    printf("%s /dev/leds on\n", file);
    printf("%s /dev/leds off\n", file);
    printf("%s /dev/led1 on\n", file);
    printf("%s /dev/led1 off\n", file);
} 

int main(int argc, char **argv)
{
    int fd;
    char* filename;
    char val;
    
    if (argc != 3)       
    {
        print_usage(argv[0]);
        return 0;
     }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("error, can't open %s\n", filename);
        return 0;
    }

    if (!strcmp("on", argv[2]))
    {
        // 亮燈
        val = 0;
        write(fd, &val, 1);
    }

    else if (!strcmp("off", argv[2]))
    {
        // 滅燈
        val = 1;
        write(fd, &val, 1);
    }
    else        //數據輸入錯誤,打印幫助提示
    {
        print_usage(argv[0]);
        return 0;
    }     
    return 0;
}

驅動程序以下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static struct class *firstdrv_class;               //建立一個class類
static struct class_device   *firstdrv_class_devs[4]; //建立類的設備,led,led1,led2,led3

volatile unsigned long *GPFcon=NULL;
volatile unsigned long *GPFdat=NULL;

/*1寫出驅動程序first_drv_open first_drv_write */
static int first_drv_open(struct inode *inode, struct file  *file)
{
	int minor=MINOR(inode->i_rdev);
	printk("first_drv_open\n");      //打印,在內核中打印只能用printk()
	GPFcon = ioremap(0x56000050, 16);   //ioremap:物理地址映射,返回虛擬地址
	GPFdat=GPFcon+1;                   //long:32位,因此GPFdat=0x56000050+(32/8)

	switch(minor)
	{
	case 0:                              //進入led設備,控制全部led
		*GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12));
		*GPFcon|=    ((0X01<<8)| (0X01<<10)| (0X01<<12));
		break;

	case 1:                              //進入led1設備,控制 led1
		*GPFcon&=~ ((0X3<<8) );
		*GPFcon|=    (0X1<<8) ;
		break;

	case 2:                                                 //進入led2設備,控制 led2
		*GPFcon&=~ ((0X3<<10) );
		*GPFcon|=  (0X1<<10) ;
		break;

	case 3:                              //進入led3設備,控制 led3
		*GPFcon&=~ ((0X3<<12) );
		*GPFcon|=    ((0X1<<12) );
		break;
	}
	return 0;
}

/*參數filp爲目標文件結構體指針,buffer爲要寫入文件的信息緩衝區,count爲要寫入信息的長度,ppos爲當前的偏移位置,這個值一般是用來判斷寫文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	int minor=MINOR(file->f_dentry->d_inode->i_rdev);
	copy_from_user(&val,buf,count);      //經過用戶(應用層)拷貝數據
	switch(minor)
	{
	case 0:                                               //進入led設備,控制全部led
		printk("led0,%d\n",val);

		if(val)       //開燈
		{
			*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); 
			*GPFdat|=      ((0X0<<4)| (0X0<<5)| (0X0<<6));
		}
		else     //關燈
		{
			*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6));
			*GPFdat|=  ((0X1<<4)| (0X1<<5)| (0X1<<6));
		}
		break;

	case 1:                                               //進入led1設備,控制 led1
		printk("led1,%d\n",val);
		if(val)      //開燈
		{
			*GPFdat&=~ (0X1<<4);
			*GPFdat|=  (0X0<<4);
		}
		else         //關燈
		{
			*GPFdat&=~  (0X1<<4); 
			*GPFdat|=   (0X1<<4);
		}
		break;

	case 2:                                         //進入led2設備,控制 led2
		printk("led2,%d\n",val);
		if(val)      //開燈
		{
			*GPFdat&=~ (0X1<<5);
			*GPFdat|=      (0X0<<5);
		}
		else         //關燈
		{
			*GPFdat&=~  (0X1<<5);
			*GPFdat|=      (0X1<<5);
		}
		break;

	case 3:                                               //進入led3設備,控制 led3
		printk("led3,%d\n",val);
		if(val)      //開燈
		{
			*GPFdat&=~ (0X1<<6);
			*GPFdat|=      ( 0X0<<6);
		}
		else         //關燈
		{
			*GPFdat&=~ (0X1<<6); 
			*GPFdat|=  (0X1<<6);
		}
		break;
	}
	return 0;
}

/*2定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write */
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,     //被使用時阻止模塊被卸載
    .open   =   first_drv_open, 
    .write   =   first_drv_write,
};
 
int major;              //定義一個全局變量,用來保存主設備號
int first_drv_init(void)
{
    int i;
    /*3 register_chrdev註冊字符設備*/
    /*若是設置major爲0,表示由內核動態分配主設備號,函數的返回值是主設備號*/
    major=register_chrdev (0, "first_drv", &first_drv_fops);

    firstdrv_class= class_create(THIS_MODULE,"firstdrv");
	//建立類,它會在sys目錄下建立firstdrv這個類
    firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led");
	//建立類設備,它會在firstdrv_class類下建立led設備,而後mdev經過這個自動建立/dev/xyz這個設備節點
 
	for(i=1;i<4;i++)   //建立led1 led2 led3 設備節點,控制led1 led2 led3
	{
		firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i);
	}
	return 0;
}

/*6 寫first_drv_exit出口函數*/
void first_drv_exit(void)
{
	int i;
	unregister_chrdev (major, "first_drv");  //卸載驅動,只須要主設備號和設備名就行
	class_destroy(firstdrv_class);                      //註銷類,與class_create對應

	for(i=0;i<4;i++)                              //註銷類設備led,led1,led2,led3
		class_device_unregister(firstdrv_class_devs[i]);

	iounmap(GPFcon);          //註銷虛擬地址
}

/*5 module_init修飾入口函數*/
module_init(first_drv_init);
 
/*7 module_exit修飾出口函數*/
module_exit(first_drv_exit);

MODULE_LICENSE("GPL v2");  //聲明許可證

4、查詢方式來寫按鍵驅動程序

本節目的:   <font color=#FF0000>寫second程序,內容:經過查詢方式驅動按鍵</font>

4.一、寫出框架

1)寫file_oprations結構體,second_drv_open函數,second_drv_read函數 2)寫入口函數,並自動建立設備節點,修飾入口函數 3)寫出口函數,並自動註銷設備節點,修飾出口函數 4)寫MODULE_LICENSE(「GPL v2」)聲明函數許可證 5)在入口函數中,利用class_create和class_device_create自動建立設備節點 6)在出口函數中,利用class_destroy和class_device_unregister註銷設備節點

4.二、編譯並加載

  寫Makefile並編譯後,放在板子上insmod後,看看lsmod、cat /porc/devices、 ls -l /dev/second是否加載成功

4.三、在框架中實現硬件操做

1)看原理圖和2440手冊,肯定用什麼寄存器控制按鍵引腳   肯定按鍵0~3分別是GPF0,GPF2,GPG3,GPG11   因爲是使用查詢模式,並非外部中斷模式   因此配置 GPFCON(0x56000050)的位[0:1]、位[4:5]等於0x00(輸入模式)   GPGCON(0x56000060)的位[6:7]、位[22:23]等於0x00(輸入模式)   經過GPGDAT (0x56000054) 和GPGDAT(0x56000064)來查詢按鍵狀態 2)寫代碼   init入口函數中使用ioremap()函數映射寄存器虛擬地址   exit出口函數中使用iounmap()函數註銷虛擬地址   open函數中配置GPxCON初始化按鍵   read函數中先檢查讀出的字符是不是4個,而後獲取GPxDAT狀態,用key_vals[4]數組保存4個按鍵值,最後使用 copy_to_user(buf, key_vals,sizeof(key_vals)) 上傳給用戶層

4.四、寫測試程序並測試

1)寫測試程序Secondtest.c   此測試程序使用read(fd,val,sizeof(val));函數讀取內核層的數據   使用此測試程序的用法就是./Secondtest 2)後臺運行測試程序   使用./ Secondtest & 後臺運行測試程序   後臺會一直運行這個程序,當咱們有按鍵按下時,就會打印數據出來,以下圖:    3)top指令觀察CPU佔有率   經過top命令能夠發現這個./ Secondtext佔了CPU的99%時間   緣由:咱們的Secondtext測試程序一直在while中經過查詢方式讀取按鍵狀態,這樣的效率是很是低的.   接下來開始使用中斷方式來改進按鍵驅動程序,提升效率。

4.五、本節代碼

Secondtest測試程序代碼以下:

#include <sys/types.h>    //調用sys目錄下types.h文件
#include <sys/stat.h>      //stat.h獲取文件屬性
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

/*secondtext            while一直獲取按鍵信息   */
int main(int argc,char **argv)
{
	int fd,ret;
	unsigned char val[4];
	fd=open("/dev/buttons",O_RDWR); 
	if(fd<0)
	{
		printf("can't open!!!\n");
		return -1;
	}

	while(1)
	{
		ret=read(fd,val,sizeof(val));
		if(ret<0)
		{
			printf("read err!\n");     
			continue;
		}

		if((val[0]&val[1]&val[2]&val[3])==0)
			printf("key0=%d,key1=%d,key2=%d,key3=%d\n",val[0],val[1],val[2],val[3]); 
	}
	return 0;
}

second.c按鍵驅動代碼以下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static struct class *seconddrv_class;               //建立一個class類
static struct class_device   *seconddrv_class_devs; //建立類的設備

volatile unsigned long *GPFcon;       
volatile unsigned long *GPFdat;
volatile unsigned long *GPGcon;       
volatile unsigned long *GPGdat;

static int second_drv_open(struct inode *inode, struct file  *file)
{
    /*初始化按鍵*/   
    /* 配置 GPFCON(0x56000050)的位[0:1]、位[4:5]等於0x00(輸入模式)
    GPGCON(0x56000060)的位[6:7]、位[22:23]等於0x00*/
    *GPFcon&=~((0x3<<0)|(0x3<<4));
    *GPGcon&=~((0x3<<6)|(0x3<<22));

    return 0;
}

static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	unsigned char key_vals[4];

	/*按鍵0~3分別是GPF0,GPF2,GPG3,GPG11*/
	if(count!=sizeof(key_vals))
		return EINVAL;       

	key_vals[0]=(*GPFdat>>0)&0X01;   
	key_vals[1]=(*GPFdat>>2)&0X01;           
	key_vals[2]=(*GPGdat>>3)&0X01; 
	key_vals[3]=(*GPGdat>>11)&0X01;   

	/*上傳給用戶層*/
	if(copy_to_user(buf,key_vals,sizeof(key_vals)))
		return EFAULT;
	return 0;
}

 
static struct file_operations second_drv_fops={
	.owner = THIS_MODULE,
	.open = second_drv_open,
	.read = second_drv_read,};

volatile int second_major;             //保存主設備號
static int second_drv_init(void)
{
    second_major=register_chrdev(0,"second_drv",&second_drv_fops);  //建立驅動
    seconddrv_class=class_create(THIS_MODULE,"second_dev");    //建立類名
    seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons");  

    /*申請虛擬地址,而後配置寄存器*/
    /*  GPFCON(0x56000050)
      GPGCON(0x56000060) */
    GPFcon=ioremap(0x56000050,16);
    GPFdat=GPFcon+1;   

    GPGcon=ioremap(0x56000060,16);
    GPGdat=GPGcon+1;

	return 0;
}

static int second_drv_exit(void)
{
	unregister_chrdev(second_major,"second_drv");            //卸載驅動
	class_device_unregister(seconddrv_class_devs);         //卸載類設備
	class_destroy(seconddrv_class);                         //卸載類  

	/*註銷虛擬地址*/
	iounmap(GPFcon);
	iounmap(GPGcon);

	return 0;
}

module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE("GPL v2");
相關文章
相關標籤/搜索