《网蜂A8实战演练》——2.LED(混杂设备)

4.3  LED 驱动实例二


4.3.1  LED 驱动实例二源码分析

  LED 驱动实例二源码在 webee210_drivers\3th_led_misc\webee210_led.c


4.3.3.1 模块的入口函数分析
分析一个驱动首先从入口函数开始,而 Webee210_led_init 作为模块的入口函数,主要做了以下几件事:
第一、使用内核提供的 gpio_direction_output 函数将对应的 GPIO 管脚设置为输出,并使用内核提供的 gpio_set_value 函数将对应的 GPIO 管脚输出高电平。这二步结合起来的效果就是,先关闭 webee210 开发板上的 4 个LED。
第二、使用内核提供的 misc_register 函数注册一个混杂(misc)设备,也可以成

为字符设备。


static int __init Webee210_led_init(void)
{
int ret;
int i;
for (i = 0; i < 4; i++)
{
/* 使用 gpio_direction_output 函数前必须调用 gpio_request */
gpio_request(gpio_table[i], NULL);
/* 配置 GPIO 管脚为输出 */
gpio_direction_output(gpio_table[i], gpio_cfg_table[i]);
/* 关闭所有 LED,如果想初始化为点亮 LED,则将第二个参数改为 0 */
gpio_set_value(gpio_table[i],1);
}
/* 注册一个主设备号为 10,次设备号为动态分配的混杂(字符)设备 */
ret = misc_register(&misc);
printk (DEVICE_NAME" initialized\n");
return ret;
}


4.3.3.2 模块的出口函数分析
Webee210_led_exit 作为该驱动的出口函数,只注销混杂设备。


static void __exit Webee210_led_exit(void)
{
misc_deregister(&misc);
}


4.3.3.3 miscdevice 结构体
在入口函数注册混杂(misc)设备的时候,misc_register 函数的参数就需要miscdevice 的实例。


static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
/* 动态分配次设备号 */
.name = DEVICE_NAME,
/* 设备节点为/dev/WEBEE210_LED */
.fops = &led_fops,
/* 文件操作函数集 */
};


细心的读者会发现,misc 结构体里面有个 file_operations 成员。这又回到了file_operations 结构体了。


static struct file_operations led_fops = {
.owner
= THIS_MODULE,
.unlocked_ioctl = webee210_led_ioctl,
};


4.3.3.4 led_fops 结构体成员函数
led_fops 结构体的主要成员只有 webee210_led_ioctl,这个函数正是该 LED驱动的重点对象。


/* 应用程序执行 ioctl 时,最终调用到驱动程序的 webee210_led_ioctl 函数 */
static long webee210_led_ioctl (struct file *filp, unsigned int cmd, unsigned
long arg)
{
if (arg > 4)
{
return -EINVAL;
}
switch(cmd)
{
/* 当应用程序传入 1 时,LED 被点亮 */
case IOCTL_GPIO_ON:
/* 设置 GPIO 引脚的输出电平为低电平 */

gpio_set_value(gpio_table[arg],0);
return 0;
/* 当应用程序传入 0 时,LED 被熄灭 */
case IOCTL_GPIO_OFF:
/* 设置 GPIO 引脚的输出电平为高电平 */
gpio_set_value(gpio_table[arg],1);
return 0;
default:
return -EINVAL;
}
}


webee210_led_ioctl 函数根据应用程序传递进来的 cmd 命令进行分析,然后调用内核提供的 gpio_set_value 函数去设置 GPIO 管脚的输出电平,也就是控制 LED 管脚的高低电平了。


4.3.2  LED 实例二测试程序
LED 实例二测试程序源码在 webee210_drivers\3th_led_misc\ leds.c


/* 省略某些头文件 */
int main(int argc, char **argv)
{
int on;
int led_no;
int fd;
if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d",
&on) != 1 ||
on < 0 || on > 1 || led_no < 1 || led_no > 4)
{
printf("Usage: leds led_no 0|1\n");
return -1;
}
fd = open("/dev/WEBEE210_LED", 0);
if (fd < 0)
{
printf("Can not open leds\n");
return -1;
}
ioctl(fd, on, (led_no-1));
close(fd);

return 0;
}
测试程序首先打开/dev/ WEBEE210_LED 设备文件,这个设备文件正是刚才的 LED 驱动程序创建的,即相当于找到 LED 硬件资源。然后通过 ioctl 将数据传入驱动,最后会调用到驱动程序里的 webee210_led_ioctl 函数,它会根据应用程序传递的命令,进行解析。


4.3.3  LED 驱动编译与测试
在 webee210_led.c 文件里,如果缺少第 92 行,在加载驱动的时候就很可能会出现 BUG,但居然不影响使用。
gpio_request(gpio_table[i], NULL);如果没有上面这行,在第一次加载驱动的时候,就可能会出现下面这样的BUG , 究 其 原 因 就 是 调 用 gpio_direction_output 函 数 前 , 必 须 先 调 用gpio_request 函 数 。 如 果 不 信 , 你 可 以 在 Source Insight 里 搜 索
gpio_direction_output 函数,会发现任何一个调用 gpio_direction_output 函数的文件,事先都有调用 gpio_request 函数。当然了,这个 BUG 的解决就是在第92 行,添加上 gpio_request 函数的调用。


那么 Webee 是如何解决这个 BUG 的呢?很简单,Webee 将下面这条信息到谷歌上一搜。
drivers/gpio/gpiolib.c:103 gpio_ensure_requested+0x58/0xa8()
就搜出一堆类似的问题,其中第一个链接就是问题的答案。
http://e2e.ti.com/support/embedded/linux/f/354/p/119946/427889.aspx


看到这里问题就解决了,一般来说,你遇到的问题,前人早就遇到了,所以大家不要太过害怕 BUG 的出现。当然啦,谷歌上搜索的东西一般都是全英文的,所以还需要具备一定的英语阅读能力,别大学读完了,英语水平回到了小学哟。


4.3.4  LED 驱动实例二测试结果与现象


4.4  本章小结
通过学习本章内容,你将能够初步了解什么是字符设备驱动,如何编写字符设备驱动,编写字符设备驱动的流程等。本章还通过二个不同的 LED 驱动实例来讲解了字符驱动的编写。

实例一是原原本本的字符设备驱动,没有借用内核提供的 gpio 接口函数来设置 gpio 管脚,而是自定义来 gpio 管脚再通过 ioremap 函数来操作设置 gpio管脚。里面还包含了,如何自动创建设备节点。至于如何手工创建设备节点比较简单,在工作中不常用,这里就没有写出来了。有兴趣的读者,自己百度去。
实例二是通过注册一个混杂(misc)设备间接实现字符设备驱动,并且通过内核提供的 gpio 接口函数来设置 gpio 管脚。在编译测试过程中,遇到一些 BUG,还告诉大家如何解决 BUG,通过 Webee 的亲身体验,来提升大家的各种能力。最后,这里先说明一点,驱动程序里,大量的使用了内核提供的接口函数,比如:注册什么什么,创建什么什么,这些都是内核提供的接口函数,不用我们操心,这些接口函数的实现并不是我们编写驱动的重点,你只需要知道要使用什么接口函数来注册、创建、映射等等。学习驱动,最关键的就是学习每种驱动的
框架层次,而不是注重在每一个很小很小的细节。


附录源码:

< driver / webee210_led.c >
/*
 * Name:Webee210_led.c
 * Copyright (C) 2014 Webee.JY  (2483053468@qq.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>

#define DEVICE_NAME "WEBEE210_LED"

#define S5PV210_GPJ2_0_OUTP 	(1<<0)
#define S5PV210_GPJ2_1_OUTP 	(1<<4)
#define S5PV210_GPJ2_2_OUTP 	(1<<8)
#define S5PV210_GPJ2_3_OUTP 	(1<<12)

/* ioctl的命令参数 */
#define IOCTL_GPIO_ON	1
#define IOCTL_GPIO_OFF	0

/* Webee210的LED1~4对应的GPIO引脚 */
static unsigned long gpio_table [] =
{
	S5PV210_GPJ2(0),
	S5PV210_GPJ2(1),
	S5PV210_GPJ2(2),
	S5PV210_GPJ2(3),
};

/* 将对应的GPIO引脚设置为输出 */
static unsigned int gpio_cfg_table [] =
{
	S5PV210_GPJ2_0_OUTP,
	S5PV210_GPJ2_1_OUTP,
	S5PV210_GPJ2_2_OUTP,
	S5PV210_GPJ2_3_OUTP,
};

/* 应用程序执行ioctl时,最终调用到驱动程序的webee210_led_ioctl函数 */
static long webee210_led_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
	if (arg > 4)
	{
		return -EINVAL;
	}

	switch(cmd)
	{
		/* 当应用程序传入1时,LED被点亮 */
		case IOCTL_GPIO_ON:
			/* 设置GPIO引脚的输出电平为低电平 */
			gpio_set_value(gpio_table[arg],0); 
			return 0;
			
		/* 当应用程序传入0时,LED被熄灭 */
		case IOCTL_GPIO_OFF:
			/* 设置GPIO引脚的输出电平为高电平 */
			gpio_set_value(gpio_table[arg],1); 
			return 0;

		default:
			return -EINVAL;
	}
}

static struct file_operations led_fops = {
	.owner			=	THIS_MODULE,
	.unlocked_ioctl	=	webee210_led_ioctl,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,	/* 动态分配次设备号 */
	.name  = DEVICE_NAME,			/* 设备节点为/dev/WEBEE210_LED */
	.fops  = &led_fops,				/* 文件操作函数集 */
};

/* 驱动程序的入口函数 */ 
static int __init Webee210_led_init(void)
{
	int ret;
	int i;
	
	for (i = 0; i < 4; i++)
	{
		/* 使用gpio_direction_output函数前必须调用gpio_request */
		gpio_request(gpio_table[i], NULL);
		
		/* 配置GPIO管脚为输出 */
		gpio_direction_output(gpio_table[i], gpio_cfg_table[i]);
		
		/* 关闭所有LED,如果想初始化为点亮LED,则将第二个参数改为0 */
		gpio_set_value(gpio_table[i],1); 
	}

	/* 注册一个主设备号为10,次设备号为动态分配的混杂(字符)设备 */
	ret = misc_register(&misc);

	printk (DEVICE_NAME" initialized\n");

	return ret;
}

/* 驱动程序的出口函数 */ 
static void __exit Webee210_led_exit(void)
{
	misc_deregister(&misc);
}

/* 用于修饰入口/出口函数,换句话说,相当于
 * 告诉内核驱动程序的入口/出口函数在哪里
 */
module_init(Webee210_led_init);
module_exit(Webee210_led_exit);

/* 该驱动支持的协议、作者、描述 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("webee");
MODULE_DESCRIPTION("Character drivers for leds");

< driver / Makefile >
ifneq ($(KERNELRELEASE),)
	obj-m :=webee210_led.o
else
	module-objs :=webee210_led.o
	KERNELDIR :=/home/gec/linux_kernel/linux2.6.35.7/
	PWD :=$(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
	$(RM)  *.ko *.mod.c *.mod.o *.o *.order *.symvers *.cmd

< app / leds.c >
/*
 * Name:leds.c
 * Copyright (C) 2014 Webee.JY  (2483053468@qq.com)
 *
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
	int on;
	int led_no;
	int fd;
	if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||
	    on < 0 || on > 1 || led_no < 1 || led_no > 4)
	{
		printf("Usage: leds led_no 0|1\n");
		return -1;
	}
	fd = open("/dev/WEBEE210_LED", 0);
	if (fd < 0) 
	{
		printf("Can not open leds\n");
		return -1;
	}
	
	ioctl(fd, on, (led_no-1));
	
	close(fd);
	return 0;
}

< app / Makefile >
#
#  General Makefile

Exec := leds
Obj := leds.c
CC := arm-linux-gcc

$(Exec) : $(Obj)
	$(CC) -o $@ $(Obj) $(LDLIBS$(LDLIBS-$(@)))

clean:
	rm -vf $(Exec) *.elf *.o


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值