自己对驱动的一些见解
---led驱动的代码,编译,加载,测试过程
(我是新手,写这些希望能够帮助新新手。同时也希望牛人们鄙视后给些意见和建议)
l 首先,介绍一下驱动的概念:
设备驱动是一种在应用程序和硬件设备之间的特殊程序,相当于硬件的接口。驱动对应用程序而言,透明化了硬件设备。
在没有操作系统的情况下,设备驱动的接口直接提交给应用软件工程师,应用软件没有跨越任何层次就可以直接访问设备驱动的接口。在存在操作系统时,需要将设备驱动融入到操作系统中,应用程序是通过调用操作系统的API来实现对硬件的操作,所以设备驱动必须设计面向操作系统的接口。
l 接着,介绍一下驱动的流程
实验必备(以我的为例):linux系统(宿主机上的虚拟机),arm板(带有2.6.32.2内核的mini2440),2.6.32.2的内核源码。
首先需要配置2.6.32.2的内核源码,使其可以编译驱动文件(在虚拟机上的linux系统下操作的)。一般的随ARM板配套的软件包里都有已经裁剪过的内核,只是没有编译,这样就大大简化了我们的工作量,步骤如下:
1,解压2.6.32.2内核(已经裁剪过的),修改内核主目录下的Makefile文件,在下面两处做些修改:
ARCH ?= $(SUBARCH) 修改为:ARCH = arm
CROSS_COMPILE ?= 修改为:CROSS_COMPILE = arm-linux-
2,修改内核的时钟频率为12MHZ。
打开源码文件 arch/arm/mach-s3c2440/mach-smdk2440.c,将16.9344MHZ改为12MHZ。
3,在内核根目录下找到配置文件mini2440-defconfig(mini2440把几个常用配置文件放在根目录下,如果没有可以进入arch/arm/configs 下的s3c2410_defconfig找到), 将其改名为.config。
4,然后敲入命令:make menuconfig,进入图形化配置内核,作为新手,其他的不用改,直接退出保存。
5,敲入命令:make Image,开始编译内核生成镜像文件。
至此,可以编译驱动的内核生成了。
然后,就进入正题了---编写驱动程序。
编写驱动程序条例要清晰,驱动程序没有像应用程序main那样的入口,替代main的是module_init(function)中的function函数。我个人总结编写驱动的思路:
1,分配设备号
2,申请注册设备(主体部分,包括初始化设备,初始化端口,定义函数 的实现等)
3,删除设备
4,释放设备号。
写驱动是为了操作硬件,当然离不开原理图了,查看原理图,确定驱动需要控制的引脚。比如,我要控制ARM板子上四个LED灯,让她们依次不间断的闪烁。我就需要控制GPB5,GPB6,GPB7,GPB8四个引脚。我写的LED驱动代码如下:
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/device.h>
#include<linux/types.h>
#include<linux/ioctl.h>
#include<linux/errno.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<linux/gpio.h>
#include<linux/fs.h>
#include<asm/io.h>
#include<mach/regs-gpio.h>
#define DEVICE_NAME "myled"
#define DEVICE_MAJOR 251
static int led_major = DEVICE_MAJOR;
struct s3c2410_led_dev
{
struct cdev cdev;
int status;
};
static struct s3c2410_led_dev dev;
static int s3c2410_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int s3c2410_release(struct inode *inode, struct file *filp)
{
return 0;
}
static int s3c2410_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case 0:
s3c2410_gpio_setpin(S3C2410_GPB(5), 0);
s3c2410_gpio_setpin(S3C2410_GPB(6), 1);
s3c2410_gpio_setpin(S3C2410_GPB(7), 1);
s3c2410_gpio_setpin(S3C2410_GPB(8), 1);
break;
case 1:
s3c2410_gpio_setpin(S3C2410_GPB(5), 1);
s3c2410_gpio_setpin(S3C2410_GPB(6), 0);
s3c2410_gpio_setpin(S3C2410_GPB(7), 1);
s3c2410_gpio_setpin(S3C2410_GPB(8), 1);
break;
case 2:
s3c2410_gpio_setpin(S3C2410_GPB(5), 1);
s3c2410_gpio_setpin(S3C2410_GPB(6), 1);
s3c2410_gpio_setpin(S3C2410_GPB(7), 0);
s3c2410_gpio_setpin(S3C2410_GPB(8), 1);
break;
case 3:
s3c2410_gpio_setpin(S3C2410_GPB(5), 1);
s3c2410_gpio_setpin(S3C2410_GPB(6), 1);
s3c2410_gpio_setpin(S3C2410_GPB(7), 1);
s3c2410_gpio_setpin(S3C2410_GPB(8), 0);
break;
default:
return -EINVAL;
}
return 0;
}
static struct file_operations s3c2410_led_fops =
{
.owner = THIS_MODULE,
.open = s3c2410_open,
.ioctl = s3c2410_ioctl,
.release = s3c2410_release,
};
static int __init s3c2410_led_init(void)
{
int result;
/*******apply the device number*********/
dev_t devno = MKDEV(led_major, 0);
if(led_major)
{
result = register_chrdev_region(devno, 1, DEVICE_NAME);
}
else
{
result = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
led_major = MAJOR(devno);
}
if(result < 0)
{
return result;
}
/********register LED device**********/
devno = MKDEV(led_major, 0);
cdev_init(&dev.cdev, &s3c2410_led_fops);
dev.cdev.owner = THIS_MODULE;
dev.cdev.ops = &s3c2410_led_fops;
result = cdev_add(&dev.cdev, devno, 1);
/********init the IO_PORT***********/
unsigned int i;
for(i = 5; i < 9; i++)
{
s3c2410_gpio_cfgpin(S3C2410_GPB(i), S3C2410_GPIO_OUTPUT);
s3c2410_gpio_pullup(S3C2410_GPB(i), 1);
s3c2410_gpio_setpin(S3C2410_GPB(i), 1);
}
printk(DEVICE_NAME"Initialized---\n");
return 0;
}
static void __exit s3c2410_led_exit(void)
{
cdev_del(&dev.cdev);
unregister_chrdev_region(MKDEV(led_major, 0), 1);
}
module_init(s3c2410_led_init);
module_exit(s3c2410_led_exit);
MODULE_LICENSE("GPL");
测试程序如下:
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/types.h"
#include"sys/ioctl.h"
#include"termios.h"
#include"sys/stat.h"
#include"fcntl.h"
#include"sys/time.h"
#define PATH "/dev/myled"
int main()
{
int fd = open(PATH, O_RDWR);
if(fd < 0)
{
printf("Open error!\n");
exit(1);
}
printf("Led test show. Press ctrl+c to exit\n");
while(1)
{
ioctl(fd, 0);
sleep(1);
ioctl(fd, 1);
sleep(1);
ioctl(fd, 2);
sleep(1);
ioctl(fd, 3);
sleep(1);
}
close(fd);
exit(0);
}
Makefile文件如下:
ifneq ($(KERNELRELEASE),)
obj-m:=myled.o
//myled-objs:=myled.o
else
KDIR :=/home/yeh2011/arm_kernel/linux-2.6.32.2
all:
make -C $(KDIR) M=$(PWD) modules
app:
arm-linux-gcc led_app.c -o led_app_more
clean:
rm *.ko *.mod.* *.markers *.o *.symvers *.order led_app -f
endif
敲入make命令,生成myled.ko 文件。我在执行这一步的时候出现了很多问题,比如:
1, h:18:26: error: linux/bounds.h: No such file or directory
解决方法:在内核目录中执行make prepare命令。
2,/lib/libc.so.6: version `GLIBC_2.8' not found (required by scripts/mod/modpost)
解决方法:好像是安装一个什么库吧,好像是叫ncurses吧,具体的给忘了。
3, 总是提示S3C2410_GPB5未定义(正确代码之前的错误代码中用到的)。这个问题纠结我了好久啊,两周之后,突然不经意的看见一片文章就是讲述的这个问题,然后自己好好的理解了,没想到,还真的给解决了,原来是2.4和2.6的版本问题,旧版本直接通过S3C2410_GPmn来定义端口GPIOm的第n个引脚,比如GOIOB的第5个引脚引用为S3C2410_GPB5,在新版本中改为采用S3C2410_GPm(n)的方式,比如GPIOB的第5个引脚就改为S3C2410_GPB(5)。不止是引脚做了变动,对引脚的配置也做了一定程度上的修改:比如将引脚GPB(5)设置为输出模式,则采用了下面的函数:S3C2410_gpio_cfgpin(S3C2410_GPB(5),S3C2410_GPIO_OUTPUT)。
以上这些函数,宏定义都需要头文件:#include"linux/gpio.h"。
驱动的加载:
生成.ko 文件之后,接下来的问题就简单了。将.ko 文件和led_app文件烧到板子上,修改其权限为可执行。然后insmod myled.ko将其加载到操作系统上,此时会显示DEVICE_NAME"Initialized,表示加载成功。
然后需要在板子上新建设备节点。我根据自己的代码:
mknod /dev/myled c 251 0
其中,myled 是我在程序中定义的设备的名字,c表示是字符设备,251是我在程序中定义的设备的主设备号,0为次设备号。
当创建成功之后,直接执行led_app文件,这时,ARM 板上的四个led灯就会不停地依次闪烁。
至此,LED驱动程序开发算是完成了。在整个过程中,自己学到了不少东西,包括一些技术上的知识,我感觉更重要的是学会了遇到问题怎么去解决问题,怎么能让自己手头上的资源发挥到他们最大的价值。这些东西会让我终身受益。
当然要感谢我的师兄郝东东,还有许多热心的网友。
如果有什么问题,QQ:787689011,欢迎交流问题 。