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

来源:互联网 发布:淘宝卖家延长发货时间 编辑:程序博客网 时间:2024/06/05 03:11

上一部分,主要说了一下,最简单的字符设备,主要实现在内核中打印的功能,实际中,没有多大用处,这章主要讲如何点亮一个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 libutilsinclude $(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.csdn.net/cs_lht/article/details/6803638

http://blog.csdn.net/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 interfaceprintk(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>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}#endifreturn 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的变化。



0 0
原创粉丝点击