linux驱动1.驱动框架与GPIO操作

自闭症网瘾萝莉.ら 提交于 2020-12-12 19:42:58

一、驱动程序概念介绍

  u-boot的任务是启动内核,内核的任务是启动应用程序 ,应用程序会涉及很多文件和硬件操作(当然不会直接操作硬件),比如读写文件,点灯、获取按键值。   比如对于控制led灯的用户程序与驱动程序,最简单的实现方法是:   应用程序中需要打开led灯,就需要open函数,在内核中的驱动程序中也有对应的led_open函数,这个led_open函数就是用来负责初始化led的引脚功能,应用程序中要调用read函数读取led灯的状态,内核中的驱动程序也有led_read函数。这是应用程序与内核中驱动程序一种最简单的对应方式.   那么应用程序中的open、read函数最终怎样调用到驱动程序中的led_open、led_read呢,中间有哪些东西?   在linux中共有4层软件,如下图: 以下名词解释:

  • 应用程序:就是被调用的那些库函数,例如open、read、write... ...
  • C库(系统调用):其中的其实就是实现open、read这些函数来调用swi val 指令进入内核(函数不同val值都会不同)
  • 内核: 内核根据swi后面不同的值去调用VFS中的system_open/system_read/ system_write等异常处理函数,找到相应的驱动程序(VFS:virtual file system 虚拟文件系统)

例如:

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   剩下的就是驱动框架了。

二、制作第一个驱动程序

本节目的:   <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.1、创建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.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.3、编译、加载

1)make,编译生成frist_drv.ko文件 2)开发板通过nfs网络文件系统来加载frist_drv.ko   注:加载之前首先通过 cat /proc/devices来查看字符主设备号111是否被占用,然后通过 insmod first_drv.ko来挂载, 通过 cat /proc/devices就能看到first_drv已挂载好

2.4、通过测试程序测试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 打印信息:

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.5、改进底层驱动,使用动态装载:

  除了静态装载驱动外,还可以动态装载,让系统自动为我们驱动设备自动分配设备号 2.5.1、修改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.2、每次都要手工创建设备节点,大家肯定也会觉得这样做太麻烦了。   改进方法:可以使用自动创建设备节点,Linux有udev、mdev的机制,而我们的ARM开发板上移植的busybox有mdev机制,然后mdev机制会通过class类来找到相应类的驱动设备来自动创建设备节点 (前提需要有mdev)   问:在哪里设置了mdev机制?   答:在制作根文件系统之使用里有介绍 2.5.3、接下来使用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.

三、修改第一个程序来点亮LED

本节目的:   <font color=#FF0000>在上一节搭建的驱动框架下添加硬件的操作</font> 硬件的操作(控制LED)主要分为如下几步: 1)看原理图,确定引脚 2)看2440手册 3)写代码(需要使用ioremap()函数映射虚拟地址,在linux中只能使用虚拟地址) 4)修改上一节的测试程序 5)使用次设备号来控制设备下不同的灯

3.1、看led引脚

  看原理图可以确定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6

3.2、看2440手册

  配置GPFCON15:0的位[8:9]、位[10:11]、位[12:13] 都等于0x01(输出模式)   控制GPFDAT7:0中的位4~6来使灯亮灭(低电平亮)

3.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.4、修改测试程序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.5、使用次设备号来控制设备下不同的灯

  我们先来看下面两个函数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");  //声明许可证

四、查询方式来写按键驱动程序

本节目的:   <font color=#FF0000>写second程序,内容:通过查询方式驱动按键</font>

4.1、写出框架

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.2、编译并加载

  写Makefile并编译后,放在板子上insmod后,看看lsmod、cat /porc/devices、 ls -l /dev/second是否加载成功

4.3、在框架中实现硬件操作

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.4、写测试程序并测试

1)写测试程序Secondtest.c   此测试程序使用read(fd,val,sizeof(val));函数读取内核层的数据   使用此测试程序的用法就是./Secondtest 2)后台运行测试程序   使用./ Secondtest & 后台运行测试程序   后台会一直运行这个程序,当我们有按键按下时,就会打印数据出来,如下图:    3)top指令观察CPU占有率   通过top命令可以发现这个./ Secondtext占了CPU的99%时间   原因:我们的Secondtext测试程序一直在while中通过查询方式读取按键状态,这样的效率是非常低的.   接下来开始使用中断方式来改进按键驱动程序,提高效率。

4.5、本节代码

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");
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!