上一部分,主要说了一下,最简单的字符设备,主要实现在内核中打印的功能,实际中,没有多大用处,这章主要讲如何点亮一个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的变化。