《网蜂A8实战演练》——1.LED驱动

LED 原理图都网蜂科技的 Webee210 核心板上,如下:


由以上两个图可知,LED1~LED4 对应的 GPIO 口为 GPJ2_0 ~ GPJ2_3,如果要让 LED1 亮,则设置 GPJ2CON[3:0]为输出引脚,设置 GPJ2DAT[0]为输出低电平,即可点亮 LED1 啦,就这么简单^_^。


4.2.2  LED 驱动实例一源码分析
LED 驱动实例一源码在 webee210_drivers\2th_led\led.c


4.2.2.1 模块的入口函数分析
分析一个驱动首先从入口函数开始,而 Webee210_led_init 作为模块的入口函数,主要做了以下几件事:
第一、使用 register_chrdev 注册字符设备,这个函数是内核提供的接口,内核已经帮我们实现了,不用我们操心。(注意了,有些 Linux 发行版本有可能会使用             
           register_chrdev_region 函数来注册字符设备。)

第二、使用内核提供的 class_create、device_create 函数分别创建类、在类下创建设备。
第三、使用内核提供的 ioremap 函数映射 IO 地址,只有映射了,才能够在内核环境下使用这些 IO 地址。

/* 驱动程序的入口函数 */
static int __init Webee210_led_init(void)
{
/* 注册字符设备,第一个参数设置为 0 表示由系统自动分配主设备号 */
major = register_chrdev(0, "led_drv", &led_fops);
/* 创建 led_drv 类 */
led_class = class_create(THIS_MODULE, "led_drv");
/* 在 led_drv 类下创建/dev/LED 设备,供应用程序打开设备*/
led_device = device_create(led_class, NULL,MKDEV(major,0),DEVICE_NAME);
/* 将物理地址映射为虚拟地址 */
gpj2con = (volatile unsigned long *)ioremap(0xE0200280, 16);
gpj2dat = gpj2con + 1;
return 0;
}


4.2.2.2 模块的出口函数分析
驱动的出口函数的主要工作一般是注销 XXX、卸载 XXX,等等类似的清除工作,而 Webee210_led_exit 作为模块的出口函数,主要做了以下几件事:
第一、使用内核提供的 unregister_chrdev 函数注销字符设备
第二、使用内核提供的 device_unregister、class_destroy 函数分别卸载类下的设备、卸载类。
第三、使用内核提供的 iounmap 函数解除映射。

一目了然,这些都是一些清除工作。


/* 驱动程序的出口函数 */
static void __exit Webee210_led_exit(void)
{
unregister_chrdev(major,"led_drv"); /* 注销字符设备 */
device_unregister(led_device);/* 卸载类下的设备 */
class_destroy(led_class);/* 卸载类 */
iounmap(gpj2con);/* 解除映射 */
}


4.2.2.3 file_operations 结构体
在入口函数注册字符设备的时候,register_chrdev 函数的第三个参数就需要file_operations 的实例。


major = register_chrdev(0, "led_drv", &led_fops);
/* file_operations 的实例 */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = webee210_led_open,
.write = webee210_led_write,
};


4.2.2.4 led_fops 结构体成员函数
led_fops 结构体的主要成员有 webee210_led_open、webee210_led_write,
这两个函数正是该 LED 驱动的重点对象。


static volatile unsigned long *gpj2con = NULL;
static volatile unsigned long *gpj2dat = NULL;
/* 应用程序执行 open 时,最终调用到驱动程序的 webee210_led_open 函数 */
static int webee210_led_open(struct inode * inode, struct file * filp)
{
printk("webee210_led_open\n");
/* Webee210 开发板上的 LED1,LED2,LED3,LED4
* 对应 GPJ2_0、GPJ2_1、GPJ2_2、GPJ2_3 引脚
* 配置 GPJ2_0、GPJ2_1、GPJ2_2、GPJ2_3 为输出
*/
*gpj2con |= S5PV210_GPJ2_0_OUTP|S5PV210_GPJ2_1_OUTP|S5PV210_GPJ2_2_OUTP|S5PV210_GPJ2_3_OUTP;
return 0;
}


/* 应用程序执行 write 时,最终调用到驱动程序的 webee210_led_write 函数 */
static int webee210_led_write(struct file * file, const char __user * buffer, size_t count, loff_t * ppos)
{
int val;
printk("webee210_led_write\n");
copy_from_user(&val, buffer, count);
if (val == 1)
{
/* 点灯 */
*gpj2dat &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3));
}
else
{
/* 灭灯 */
*gpj2dat |= (1<<0) | (1<<1) | (1<<2) | (1<<3);
}
return 0;
}


webee210_led_open 函数很简单,只是将 LED1-4 对应的 GPIO 管脚设置为输出功能。
webee210_led_write 函数首先通过内核提供的 copy_from_user 函数,将应用程序传递过来 count 字节的 buf 数据写进 val。( 注意:应用空间和内核空间的包括指针数据   
                                     的传递不能单纯用 memcpy 函数,必须使用 copy_from_user函数或 copy_to_user 函数)。


4.2.3
LED 实例一测试程序
LED 实例一测试程序源码在 webee210_drivers\2th_led\ led_test.c


/* 省略某些头文件 */
/* led_test on
* led_test off
*/
int main(int argc ,char *argv[])
{
int fd;
int val = 0;
fd = open("/dev/LED",O_RDWR);
if (fd < 0)

{
printf("open error\n");
}
if (argc != 2)
{
printf("Usage:\n");
printf("%s <on|off>\n",argv[0]);
return 0;
}
if(strncmp(argv[1],"on",2) == 0)
{
val = 1;
}
else if (strncmp(argv[1],"off",3) == 0)
{
val = 0;
}
write(fd,&val,4);
return 0;
}


测试程序首先打开/dev/LED 设备文件,这个设备文件正是刚才的 LED 驱动程序创建的,即相当于找到 LED 硬件资源。然后将第二个参数保存下来,将 val数据写到/dev/LED,这样一来,最后就会该驱动的 webee210_led_write 函数。最终还是操作硬件上的 GPIO 引脚,这就回到了裸机知识了。


4.2.4  LED 驱动实例一测试结果与现象


将编译生成的 led.ko 和 led_test 拷贝到开发板,按照上图步骤测试。
执行./led_test off 时,webee210 核心板上的 4 个 LED 灯同时被熄灭。执行./led_test on 时,webee210 核心板上的 4 个 LED 灯同时被点亮。


附录源码:

< driver / led.c >
/*
 * Name: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/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/device.h> 	//class_create

#define DEVICE_NAME "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)

static struct class *led_class;
static struct device *led_device;
int major;

static volatile unsigned long *gpj2con = NULL;
static volatile unsigned long *gpj2dat = NULL;

/* 应用程序执行open时,最终调用到驱动程序的webee210_led_open函数 */
static int webee210_led_open(struct inode * inode, struct file * filp)
{
	printk("webee210_led_open\n");

	/*  Webee210开发板上的LED1,LED2,LED3,LED4
	 *  对应GPJ2_0、GPJ2_1、GPJ2_2、GPJ2_3引脚
	 *	配置GPJ2_0、GPJ2_1、GPJ2_2、GPJ2_3为输出
	 */
	*gpj2con |= S5PV210_GPJ2_0_OUTP|S5PV210_GPJ2_1_OUTP|S5PV210_GPJ2_2_OUTP|S5PV210_GPJ2_3_OUTP;
	return 0;
}

/* 应用程序执行write时,最终调用到驱动程序的webee210_led_write函数 */
static int webee210_led_write(struct file * file, const char __user * buffer, size_t count, loff_t * ppos)
{
	int val;
	printk("webee210_led_write\n");

	copy_from_user(&val, buffer, count);

	if (val == 1)
	{
		/* 点灯 */
		*gpj2dat &= ~((1<<0) | (1<<1) | (1<<2) | (1<<3));
	}
	else
	{
		/* 灭灯 */
		*gpj2dat |= (1<<0) | (1<<1) | (1<<2) | (1<<3);
	}
	return 0;
}

static const struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.open		= webee210_led_open,
	.write      = webee210_led_write,
};


/* 驱动程序的入口函数 */ 
static int __init Webee210_led_init(void)
{
	/* 注册字符设备,第一个参数设置为0表示由系统自动分配主设备号 */
	major = register_chrdev(0, "led_drv", &led_fops);

	/* 创建led_drv类 */
	led_class = class_create(THIS_MODULE, "led_drv");

	/* 在led_drv类下创建/dev/LED设备,供应用程序打开设备*/
	led_device = device_create(led_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);

	/* 将物理地址映射为虚拟地址 */
	gpj2con = (volatile unsigned long *)ioremap(0xE0200280, 16);
	gpj2dat = gpj2con + 1;
	
	return 0;
}

/* 驱动程序的出口函数 */ 
static void __exit Webee210_led_exit(void)
{
	unregister_chrdev(major,"led_drv");	/* 注销字符设备 */
	device_unregister(led_device);  	/* 卸载类下的设备 */
	class_destroy(led_class);			/* 卸载类 */
	iounmap(gpj2con);					/* 解除映射 */
}

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

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


< Makefile >
ifneq ($(KERNELRELEASE),)
	obj-m :=led.o
else
	module-objs :=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 / led_test.c >
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

/* led_test on
 * led_test off
 */ 
int main(int argc ,char *argv[])

{
	int fd;
	int val = 0;
	fd = open("/dev/LED",O_RDWR);
	if (fd < 0)
	{
		printf("open error\n");
	}
	
	if (argc != 2)
	{
		printf("Usage:\n");
		printf("%s <on|off>\n",argv[0]);
		return 0;
	}
	if(strncmp(argv[1],"on",2) == 0)
	{
		val = 1;
	}
	else if (strncmp(argv[1],"off",3) == 0)
	{
		val = 0;
	} 
	
	write(fd,&val,4);
	
	return 0;
}

< Makefile >
#
#  General Makefile

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

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

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



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值