初入andorid驱动开发之字符设备(二)

本文详细介绍了在Android环境下实现LED灯字符驱动的过程,包括编写测试程序、驱动程序及硬件分析等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一部分,主要说了一下,最简单的字符设备,主要实现在内核中打印的功能,实际中,没有多大用处,这章主要讲如何点亮一个LED灯,并编写测试程序:

1 测试程序的编写:

1.1 Android.mk 文件:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
 LOCAL_SRC_FILES:= led.c
 LOCAL_MODULE:= led
 LOCAL_MODULE_TAGS := eng
 LOCAL_LDLIBS :=  -L$(SYSROOT)/usr/lib -llog
 LOCAL_SHARED_LIBRARIES += \
       libcutils libutils
include $(BUILD_EXECUTABLE)
简单的介绍一下.mk的内容:

LOCAL_PATH:= $(call my-dir) : 这是必须的,指明你当前的文件的路径。(一般用NDK或者在android源码下用mm编译)

   include $(CLEAR_VARS): 必要的,主要清除一些模块的变量。

   LOCAL_SRC_FILES:= led.c  必要的,编译该模块的源代码文件。

   LOCAL_MODULE:= led : 必须的,给你编出的模块命名。如编译共享库,还会自动补充命名。

           LOCAL_MODULE_TAGS := eng  :可选的,user: 指该模块只在user版本下才编译 eng: 指该模块只在eng版本下才编译

  tests: 指该模块只在tests版本下才编译 optional:指该模块在所有版本下都编译

  LOCAL_LDLIBS :=  -L$(SYSROOT)/usr/lib -llog  :需要时用。指明编译该模块时,需要加载在目录下的库。

 LOCAL_SHARED_LIBRARIES += \
       libcutils libutils    :需要时用。指明编译该模块所依赖的共享库。

include $(BUILD_EXECUTABLE) : 以一个可执行程序的方式编译。

这个mk文件就是编译一个可执行程序led。想详细了解mk相关的知识,可参考下面的博客:

http://blog.youkuaiyun.com/cs_lht/article/details/6803638

http://blog.youkuaiyun.com/hudashi/article/details/7059006

1.2 测试程序的编写:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>     /*Unix 标准函数定义*/
#include <fcntl.h>      /*文件控制定义*/
#include <errno.h>  
#include <cutils/log.h>
//#include <delay.h>


int main(int argc,char*argv[])
{
	int fd=-1;
	printf("open start \n");
	fd = open("/dev/led", O_RDWR);
	if (fd < 0)	
	{					//open device failed
    		printf("open fd  error: %s\n", strerror(errno));
        	exit(-1);
 	}
	printf("write 1 \n");
	write(fd,"1",1);
	sleep(2);
	printf("write 0 \n");
	write(fd,"0",1);
	close(fd);
	return 0;
}

这里主要介绍main的一些知识:

当insmod xx.ko之后,若成功在/dev下会生成我们定义设备名的设备节点。

应该明确,我们做驱动开发,应该明确的明白,正在开发的模块,它的项目需求。而根据需求,具体去划分把实现该需求的方法在哪一层次实现,这需要具体模块具体分析。对于LED的项目需求,无非就是控制暗、灭。那谁去控制led的状态,肯定是由用户,或者上层的app。所以,下层驱动只需要提供write的方法即可,从上层读出控制信息,然后判断控制状态,从而控制led的亮灭。

此测试程序,就是一个最简单的测试流程。

1. 打开设备。fd = open("/dev/led", O_RDWR); 这里需要了解一些open函数的第二个参数,以什么方式打开,如读写、非阻塞等。

2. 对设备操作。无非就是read、write、ioctl、poll等。这里主要是写控制状态给驱动程序。1 : 灯亮(write(fd,"1",1););0 : 灯灭(write(fd,"0",1);)。

3. 关闭设备。 close(fd);

2. LED驱动程序的编写:

2.1 程序代码:

<span style="font-size:14px;">#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <mach/gpio.h>
#include <linux/io.h>
#include <mach/hardware.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/uaccess.h>

static struct class *leddrv_class;
static struct device *leddrv_class_dev;
int major;

static unsigned long gpio_va;

//gpio ADDERSS
#define GPIO_OFT(x) ((x) - 0xE0200000)
//GPBCON add
#define GPBCON  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0xE0200040)))
//GPBDAT add
#define GPBDAT  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0xE0200044)))


static int led_drv_open(struct inode *inode, struct file *file)
{
	printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>led_drv_open\n");
	//set the GPB3 output 
	  GPBCON &= ~(0xf<<(4*3));
      GPBCON |= (1<<(4*3));
	 //init the GPB3 output 0 LED OFF
	  GPBDAT &= ~(1<<3);
	
	return 0;
}


static int led_drv_read(struct file *filp, char __user *buff, 
                                         size_t count, loff_t *offp)
{
    char val;
	char leds_status;
	leds_status=1;
    copy_to_user(buff, (const void *)&leds_status, 1);                    
    return 1;
}


static int led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{

    char val[1];
    copy_from_user(val,buf,1);
#if 0    //user the system interface
	printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>led_drv_write\n");
	gpio_set_value(S5PV210_GPB(3), ((val[0]=='1')?1:0));
	printk("led--%s %d \n",__FUNCTION__,(int)val[0]);
#endif
#if 1 
	printk("val[0]=%d >>>>>>>>>>>>>>>>>>>>\n",val[0]);
	if(val[0] == '1' || val[0] == "1")
	{
		printk("%s      1\n",__FUNCTION__);
		GPBDAT |= 1<<3;  //output 1 led on
	}
	else
	{
		printk("%s      0\n",__FUNCTION__);
		</span>GPBDAT &= ~(1<<3);<span style="font-size:14px;">// out 0 led off
	}
#endif
	return 0;
}


static struct file_operations led_drv_fops = {
    .owner  =   THIS_MODULE,   
    .open   =   led_drv_open,     
    .write	=	led_drv_write,	  
    .read	=	led_drv_read,
};

static int led_drv_init(void){
	printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>led_drv_init\n");
    gpio_va = ioremap(0xE0200000, 0x100000);
	if (!gpio_va) {
		return -EIO;
	}
	major = register_chrdev(0, "led_drv", &led_drv_fops); 
	leddrv_class = class_create(THIS_MODULE, "leddrv");
	leddrv_class_dev = device_create(leddrv_class, NULL, MKDEV(major, 0), NULL, "led"); 
	return 0;
}

static void led_drv_exit(void){
	printk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>led_drv_exit\n");	
	unregister_chrdev(major, "led_drv"); 
	device_unregister(leddrv_class_dev);
	class_destroy(leddrv_class);
	iounmap(gpio_va);
}

module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
</span>

2.2 对硬件的分析:

我的开发板设备是S5PV210的,其子GPB3连接的LED灯,GPB3输出高电平灯亮,反之则暗。

   

根据硬件原理图的分析,我们知道,首先需要把GPB3设为输出,然后根据读到的控制命令,控制引脚输出高低电平。

第一步:把GPB3设置为输出引脚,所以我们需要设置该引脚的控制寄存器, 1. GPBCON &= ~(0xf<<(4*3))  把GPBCON[3]设为0  2. GPBCON |= (1<<(4*3)) 把GPBCON[3]设为1,即为输出。 

第二步:控制GPB3输出高低电平,所以我们需要设置该引脚的数据寄存器,1. GPBDAT |= 1<<3 把GPBDAT[3]设置为1,输出高电平 2. GPBDAT &= ~(1<<3) 把GPBDAT[3]设置为0,输出低电平。

上面两步,这是我们当初学习51单片机最基本的操作,但是,当时是在裸版下玩的,可以直接控制寄存器,现在我们是基于android下玩,内核层是基于linux框架的,当然需要按照它的机制来。这里假如你现在需要了解详细的,你需要好好研究linux。

2.3 对代码几个关键点的讲解

1. ioremap、iounmap

要想真正的理解这两个函数,你需要对linux的内存的机制和操作有着较多的研究,了解现代的内存管理机制,页机制等等,这里只简单的介绍一下函数的使用和一些基本知识点。如需详细了解可看《深入理解linux内核》这本书。

ioremap(unsigned long phys_addr, unsigned long size) 
入口: phys_addr:要映射的起始的IO地址; 
size:要映射的空间的大小;  
功能: 将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问;

在内核访问这些地址必须分配给这段内存以虚拟地址,这正是ioremap的意义所在 ,需要注意的是,物理内存已经"存在"了,无需alloc page给这段地址了. 为了使软件访问I/O内存,必须为设备分配虚拟地址.这就是ioremap的工作.这个函数专门用来为I/O内存区域分配虚拟地址(空间).对于直接映射的I/O地址ioremap不做任何事情。


2. copy_from_user copy_to_user

这里需要了解运行态,用户空间、内核空间及其的权限等。

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)

功能:将用户空间中的内容拷贝到内核空间中

参数:*to:目的地址,内核空间

  *from:源地址,用户空间

 n: 复制内容的字节数

这个led驱动程序,主要用到这个函数,即write中,从用户空间读数据,然后判断控制状态,从而控制引脚输出高低电平。

       static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

功能:将内核空间中的内容拷贝到用户空间中

参数:*to:目的地址,用户空间

  *from:源地址,内核空间

 n: 复制内容的字节数

注意:在使用这两个函数和上面的函数时候,最好加上判断,若失败,则退出,有时还有释放之前申请的资源。在使用这两个函数,需特别注意,小心溢出的问题,容易引起oops的问题。


到此,LED的字符驱动基本完毕了,关于ioremap的大小,你可以自己按照你控制的需求来定义大小了,我这随便定义的。整套的测试,把驱动生成的KO,push到设备中,把生成的测试程序push到设备中,注意更改该权限(chmod 777 xxx),然后insmod xx.ko,最后./xxx,则你会看到你的led的变化。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值