ARM开发之linux字符型驱动的编写----LED驱动为例

相应头文件:

#include<linux/module.h>  ---->模块化驱动的必须头文件

#include<linux/kernel.h> ---->ARRAY_SIZE(),prink()
#include<linux/fs.h>  ----> 文件操作集
#include<linux/uaccess.h>  ----> copy_to_user(),copy_from_user()
#include<linux/cdev.h>  ---->字符设备驱动的必须头文件
#include<linux/io.h> ---->声名了ioremap(),iounmap()
#include<linux/ioport.h> ----->声名了request_mem_region,release_mem_region
#include<linux/device.h> ---->声名了class,device相关的函数
#include <mach/gpio.h> -----> readl , writel



开发步骤:

0.驱动开发的简单架构

1.定义设备结构体

2.申请设备号

3.定义文件操作集

4.设备初始化

5.注册设备

6.申请物理内存区

7.通过映射物理地址来获得相应虚拟地址

8.创建设备的类

9.创建设备文件结点

10.应用程序的编写

11.代码演示


0.驱动开发的简单架构

驱动代码虽然也是用C语言编写,但没有main函数。取而代之有起始宏( module_init() )和结束宏( module_exit() ),宏中指定了驱动开始嵌入内核时执行的函数和驱动被移出内核时所执行的函数,另外还有MODULE_LICENSE(“GPL”),也是必须的,指定了GPL授权。

当在开发板执行命令:insmod 驱动名 ;时,就把驱动作为模块嵌入到内核中,此时会自动执行module_init()指定的函数。

当在开发板执行命令:rmmod 驱动名 ;时,就把驱动模块从内核中移出,此时会自动执行module_exit()指定的函数。

static int __init  start(void)       //初始函数的格式,函数名可以自己定
{

}

static void __exit end(void)        //退出函数的格式,函数名可以自己定
{

}

module_init(start);
module_exit(end);
MODULE_LICENSE("GPL");


1.定义设备结构体

直接定义一个 cdev 结构体变量即可。cdev结构体在头文件 #include<linux/cdev.h>中有定义。接下的操作都是以这个结构体变量为主体的,这个变量就相当于要写的驱动程序本身。 

如:
static struct cdev cdev;


2.申请设备号

0.设备号:一个设备号由主设备号和次设备号组成。主设备号代表该驱动属于什么种类的驱动,而次设备号代表驱动在当前种类驱动中的第几个。

1.设备号的用处是:用于标识这个设备,就如人的名字一样,电脑标识的一般是数字

2.注册设备号的函数有动态注册和静态注册的

动态:

alloc_chrdev_region( *设备号,起始次设备号,注册个数,名字 );

static dev_t dev_num;  //定义设备号变量
static const char dev_name[]="led";
int ret;

ret = alloc_chrdev_region(&dev_num,0,1,dev_name);
if(ret<0)
{
     printk("申请设备号失败\n");  //从内核打印信息用printk
}
动态申请设备号,会自动生成设备号并放到dev_num 变量中。


静态:

用MKDEV(主设备号,次设备号)生成指定的设备号,如:

static int major =10;
static int minor = 5;
static dev_t dev_num;

dev_num = MKDEV(major,minor);
静态申请要人工指定主设备号和次设备号,这样一来,设备号就确定了。

注销函数:unregister_chrdev_region( dev_num,1 );//用于注销申请的设备号



3.定义文件操作集
在linux内核源码的 include/linux 下的 fs.h 文件中,指定了一个文件操作集的结构体,里面包含驱动可以使用的系统调用函数。

有驱动程序绝对要有相对应的应用程序,因为驱动程序一般是用于被应用程序调用的。而应用程序与驱动程序间是如何联系的呢?

拿上图的 int (*open) (struct inode *, struct file *); 为例,是一个函数指针,用于指向一个函数(假设指向函数a)。意思是,但应用程序调用 open()函数时,驱动程序就自动执行函数a。注意的是,int (*open) (struct inode *, struct file *); 为例,自己写open对应的函数时,格式一定要与int (*open) (struct inode *, struct file *)一样,只允许函数名自己定义。最后会有代码会演示。


所以,我们可以根据自己要用什么函数来定义自己的文件操作集。把结构体上的函数指针赋值就行了。

还有.owner = THIS_MODULE 一定要写,用于初始化。记住就好

/*下面是指定当应用程序调用open函数时,驱动程序会调用device_open函数,同理还有write函数,这里特别的是release是指应用程序的close()函数*/
static const struct file_operations fops={
	.owner = THIS_MODULE,
	.open = device_open,
	.write = device_write,
	.release = device_release,
};

无注销函数



4.设备初始化

函数:cdev_init( *设备结构体变量,*文件操作集 );

作用:把 设备结构体变量 和 文件操作集 结合起来。相当于告诉内核这个驱动用哪些系统调用函数。

cdev_init(&cdev,&fops);
无注销函数。



5.注册设备

函数:cdev_add( *设备结构体变量,设备号变量,注册设备个数 );

作用:把 设备结构体变量 赋予 他 一个设备号。

失败会返回一个负值的错误码

ret = cdev_add(&cdev,dev_num,1);
	if(ret<0)
	{
		printk("cdev add failed\n");
		goto failed_cdev_add;
	}

对应注销函数:cdev_del(*cdev结构体变量);



6.申请物理内存区

写驱动一定要看原理图和用户手册的,申请物理内存区就是获取相应虚拟地址的前一个过程,先要把 设备(例子LED灯)所在的物理地址作为资源申请到内核中。

我的开发板的操控LED灯的寄存器如下:

控制寄存器地址是:0XE0200280;数据寄存器地址是:0XE0200284(怎么找寄存器这里不详细说)


函数:request_mem_region( 起始物理地址,申请多大字节,给这段内存资源一个名字 );

返回:返回一个 struct resource 结构体的指针变量

static struct resource *res = NULL;
res = request_mem_region(0xe0200280,8,"GPJ2CON_MEM");
	if(res==NULL)
	{
		printk("failed to request mem\n");
		
	}
至于为什么第二个参数填8,因为我们ARM开发板,一个寄存器大小是32位。也就是4个字节,那我操作的是2个寄存器,所有填8.

对应注销函数:void release_mem_region(0XE0200280,8);



7.通过映射物理地址来获得相应虚拟地址

有操作系统的,操作的是虚拟地址。所以要通过相对的物理地址映射出虚拟地址给系统操作,从而实现操作系统操作物理地址。

函数:ioremap( 起始物理地址,多少个字节 );

返回虚拟地址起始地址。

	unsigned int GPJ2CON_VA=NULL;
       GPJ2CON_VA = ioremap(0xe0200280,8);
	if(GPJ2CON_VA==NULL)
	{
		printk("failed to ioremap\n");
		
	}

对应的注销函数:void iounmap(GPJ2CON_VA);



8.创建设备的类class

应用程序调用驱动程序是通过打开 根目录/dev下的 设备文件结点来调用的,而文件结点需要分类。要先创建类class,才能创建设备文件结点。

创建类成功后,可以在路径 /sys/class 下看到,而且进入类后,可以看到属于该类的 设备文件结点。

static struct class *class = NULL; ---->这个要设成全局变量
class = class_create(THIS_MODULE,"class_name"); 
对应的注销函数: void class_destroy(class变量);


9.创建设备文件结点device
函数:device_create( class,NULL,设备号,NULL,“文件结点的名字” );
作用:通过设备号把 设备结构体变量 与 文件结点联系起来。换句话说就是给 设备结构体变量 弄一个文件结点。
参数:第一个,设备文件结点属于的类变量
第二个,该设备的父设备,一般为NULL
第三个,设备号变量
第四个,给 文件结点的数据,一般为NULL
第五个,该文件结点的名字
static struct device* dev_device = NULL;
dev_device = device_create(dev_class,NULL,dev_num,NULL,"led_device");
	if(dev_device == NULL)
	{
		printk("failed to create device\n");
	}
对应的注销函数:void device_destroy( class变量,设备号变量 );
注销时,一定要先注销文件结点,才能注销所在的class类变量


10.应用程序的编写:
1.用open打开 /dev下自己写的 设备文件结点
2.对其进行读写操作
3.copy_from_user()和copy_to_user()的使用

copy_from_user(wbuf,buf,len);用于内核空间(驱动程序)获取用户空间(应用程序)给的数据,如应用程序用write函数往驱动的设备文件结点写数据,
驱动程序就用copy_from_user获取。
参数:把第二个参数复制给第一个参数,len表示复制的字节大小。

copy_to_user(buf,wbuf,len)用于内核空间(驱动程序)发数据给用户空间(应用程序)。
参数:也是第二个参数复给第一个参数,第二个参数指内核空间buf,第一个指用户空间buf。

11.代码演示
LED驱动程序:实现4栈LED灯轮流闪烁
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/device.h>

/*第一步:定义设备结构体cdev*/
static struct cdev cdev;
static dev_t dev_num;
static const char device_name[]="led_device";
static struct resource *py_mem = NULL;
static unsigned int *GPJ2CON_VA = NULL;
static unsigned int *GPJ2DAT_VA = NULL; 
static struct class *dev_class = NULL;
static struct device *dev_device = NULL;
char wbuf[1];

static int device_open(struct inode *inode, struct file *f)
{
	printk("device open..\n");
	*GPJ2CON_VA &=~0xffff;
	*GPJ2CON_VA |=0x1111;
	*GPJ2DAT_VA &=~0xf;
	*GPJ2DAT_VA |=0xf;
	return 0;
}

static int device_release(struct inode *inode, struct file *f)
{
	printk("device close...\n");
	*GPJ2DAT_VA|=0xf;
	return 0;
}

static ssize_t device_write(struct file *f, const char __user*buf,
	 size_t len, loff_t *t)
{
	int ret;
	ret = copy_from_user(wbuf,buf,len);
	if(ret!=0)
	{
		printk("failed to copy from user\n");
		return -1;
	}

	if(wbuf[0]=='0')
	{
		*GPJ2DAT_VA &=~0xf;
		*GPJ2DAT_VA |=0xe;
	}

	if(wbuf[0]=='1')
	{
		*GPJ2DAT_VA &=~0xf;
		*GPJ2DAT_VA |=0xd;
	}

	if(wbuf[0]=='2')
	{
		*GPJ2DAT_VA &=~0xf;
		*GPJ2DAT_VA |=0xb;
	}

	if(wbuf[0]=='3')
	{
		*GPJ2DAT_VA &=~0xf;
		*GPJ2DAT_VA |=0x7;
	}
	return 0;
}

/*第三步:定义文件操作集*/
static const struct file_operations fops={
	.owner = THIS_MODULE,
	.open = device_open,
	.write = device_write,
	.release = device_release,
};

static int __init led_init(void)
{
	int ret;

	/*第二步:申请设备号*/
	ret = alloc_chrdev_region(&dev_num,0,1,device_name);
	if(ret<0)
	{
		printk("cannot register dev num\n");
		return -1;
	}

	/*第四步:设备初始化*/
	cdev_init(&cdev,&fops);

	/*第五步:注册设备*/
	ret = cdev_add(&cdev,dev_num,1);
	if(ret<0)
	{
		printk("cdev add failed\n");
		goto failed_cdev_add;
	}

	/*第六步:申请物理内存( 地址 )区:0xe0200280~0xe0200287*/
	py_mem = request_mem_region(0xe0200280,8,"GPJ2CON_MEM");
	if(py_mem==NULL)
	{
		printk("failed to request mem\n");
		goto failed_request_mem;
	}

	/*第七步:通过有映射物理地址获得相应虚拟地址*/
	GPJ2CON_VA = ioremap(0xe0200280,8);
	if(GPJ2CON_VA==NULL)
	{
		printk("failed to ioremap\n");
		goto failed_ioremap;
	}

		/*GPJ2CON_VA为int型,占4个字节,+1相当于移4个字节
		(32个位)*/
	GPJ2DAT_VA = GPJ2CON_VA+1;

	/*第八步:创建设备的类*/
	dev_class = class_create(THIS_MODULE,"led_class");
	if(dev_class == NULL)
	{
		printk("failed to create class\n");
		goto failed_create_class;
	}

	/*第九步:创建设备文件结点*/
	dev_device = device_create(dev_class,NULL,dev_num,NULL,"led_device");
	if(dev_device == NULL)
	{
		printk("failed to create device\n");
		goto failed_create_device;
	}

	printk("init completed!\n");
	return 0;

failed_create_device:
	class_destroy(dev_class);

failed_create_class:
	iounmap(GPJ2CON_VA);

failed_ioremap:
	release_mem_region(0xe0200280,8);

failed_request_mem:
	cdev_del(&cdev);

failed_cdev_add:
	unregister_chrdev_region(dev_num,1);

	return -1;
}	

static void __exit led_exit(void)
{
	class_destroy(dev_class);
	iounmap(GPJ2CON_VA);
	release_mem_region(0xe0200280,8);
	cdev_del(&cdev);
	unregister_chrdev_region(dev_num,1);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");


应用程序:
#include <stdio.h>
#include <fcntl.h>

int main()
{
	char buf[1];
	char i;

	int fd = open("/dev/led_device",O_WRONLY);
	if(fd<0)
	{
		perror("failed to open");
		return -1;
	}
	while(1)
	{
		for(i='0';i<'4';i++)
		{
			buf[0]=i;
			write(fd,buf,1);
			sleep(1);
		}
	}
	return 0;
}


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值