本文共 7417 字,大约阅读时间需要 24 分钟。
本篇文章以编写电位器驱动程序为例,详细介绍并总结下设备驱动的开发流程
硬件:am3354(TI)
系统内核:linux3.2
我们在有了板子和选定好使用的内核后,在开始编写驱动之前要查看原理图,即外设使用的那几个引脚。我们还需要在板子文件中(arch/arm/根据厂商芯片名.c)把引脚设置好。因为可能你使用的这几个引脚被用于别的功能了。
在确定好使用那几个引脚后,我们要确定是使用何种框架编写(是按照杂项设备(特殊的字符设备,可以节省设备号),平台设备,字符设备),一般我是习惯用字符设备的那一套流程写
在初始化函数里,我们需要做的事情:确定并申请设备号,把设备和操作函数关联起来,创建设备节点。
static int major =250;//定义主设备号 static int minior=0;int number_of_device= 1;struct cdev cdev;dev_t dev =0;struct class *myclass;
// 模块加载函数static int light_init(void){ int ret; int error; led_init(); //设备引脚方向 dev=MKDEV(major,minior); ret = register_chrdev_region(dev,number_of_device,"res");//申请设备号 if(ret<0) { printk("unable to register driver!\n"); return ret; } //生成设备节点 cdev_init(&cdev,&light_fops);//初始化cdev设备结构体 cdev.owner=THIS_MODULE; cdev.ops=&light_fops; error = cdev_add(&cdev,dev,1);//把设备结构体加入到系统中 if(error) { printk("unable to add driver!\n"); } myclass=class_create(THIS_MODULE,"res");//创建类节点 if(IS_ERR(myclass)) { printk("Err: failed in creating class.\n"); return 0; } printk (KERN_INFO "char device registered\n"); device_create(myclass,NULL, dev, NULL,"res");//创建设备节点 return 0; }
里面用到的几个函数详细解释下
(1)
dev=MKDEV(major,minior);
cdev结构体成员dev_t成员定义了设备号,为32位,使用上面的宏可以通过主设备号和次设备号生成dev_t
(2)
ret = register_chrdev_region(dev,number_of_device,"res");
该函数的作用是向系统申请设备号,参数:设备号,几个设备,设备名字。该函数用于已知起始设备号的情况。成功会返回一个非负整数
(3)
cdev_init(&cdev,&light_fops);//初始化cdev设备结构体
error = cdev_add(&cdev,dev,1);//把设备结构体加入到系统中
这几个函数是要连在一块用,在内核中所有的字符设备都会记录在一个叫kobj_map结构的cdev_map变量中,这个变量包含一个散列表来快速存取所有对象。kobj_map()函数就是把字符设备编号和cdev变量一起保存到散列表里,当后续需要打开一个字符设备文件时,内核通过调用kobj_lookup()函数,根据设备编号就可以找到cdev变量
(4)
myclass=class_create(THIS_MODULE,"res");//创建类节点 if(IS_ERR(myclass)) { printk("Err: failed in creating class.\n"); return 0; } printk (KERN_INFO "char device registered\n"); device_create(myclass,NULL, dev, NULL,"res");//创建设备节点
内核中定义了struct class 结构体,同时内核提供了class_create()函数,可以用来创建一个类,这个类存放在sysfs下面,一旦创建号这个类,在调用device_create()函数来创建设备节点。
在初始化完成后,我们需要完善结构体中的读,写,控制操作函数,方便上层应用的调用
struct file_operations light_fops = { .owner = THIS_MODULE, //.unlocked_ioctl = light_ioctl, .open = light_open, .write =light_write, .release = light_release,};
通过对代码的剖析,我们知道编写字符驱动的框架其实很简单,掌握了本质,事情就变得简单。
附:代码
/*********************** Copyright (C) Enbo.Tech* Date: 2018-05-15* Author: Liwx* Vision: V1.0 * Introduction: this is a simple digital res driver***********************/#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*******************************************/#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))static int major =250;//定义主设备号 static int minior=0;int number_of_device= 1;struct cdev cdev;dev_t dev =0;struct class *myclass;char write_data[10];/*******************************************/void led_up(void){ printk (KERN_INFO" chip select\n"); gpio_set_value(GPIO_TO_PIN(1,15), 0); // select chip gpio_set_value(GPIO_TO_PIN(1,9), 1); // set up mode }void create_pulse(unsigned char n){ int num=0; printk (KERN_INFO" chip pulse is %d\n",n); for(num=0;num < n;num++) { gpio_set_value(GPIO_TO_PIN(1,8), 1); mdelay(10); gpio_set_value(GPIO_TO_PIN(1,8), 0); mdelay(10); }}void led_down(void){ gpio_set_value(GPIO_TO_PIN(1,15), 0); //select chip gpio_set_value(GPIO_TO_PIN(1,9), 0); // set down mode printk (KERN_INFO" chip select\n");}void led_init(void){ int result; printk(KERN_INFO"gpio init\n"); /* Allocating GPIOs and setting direction */ result = gpio_request(GPIO_TO_PIN(1,8), "TTS_INC"); if (result != 0) printk(KERN_INFO"gpio_request(1_8) failed!\n"); result = gpio_request(GPIO_TO_PIN(1,9), "TTS_U/D"); if (result != 0) printk(KERN_INFO"gpio_request(1_9) failed!\n"); result = gpio_request(GPIO_TO_PIN(1,15), "TTS_CS"); if (result != 0) printk(KERN_INFO"gpio_request(1_15) failed!\n"); result = gpio_direction_output(GPIO_TO_PIN(1,8), 1); if (result != 0) printk(KERN_INFO"gpio_direction(1_8) failed!\n"); result = gpio_direction_output(GPIO_TO_PIN(1,9), 1); if (result != 0) printk(KERN_INFO"gpio_direction(1_9) failed!\n"); result = gpio_direction_output(GPIO_TO_PIN(1,15), 1); if (result != 0) printk(KERN_INFO"gpio_direction(1_15) failed!\n"); }MODULE_AUTHOR("Liwx");MODULE_LICENSE("Dual BSD/GPL");// 打开和关闭函数int light_open(struct inode *inode,struct file *filp){ printk (KERN_INFO "open device success!\n"); return 0;}int light_release(struct inode *inode,struct file *filp){ return 0; }/*文件的读操作,上层对此设备调用read时会执行*/ static ssize_t light_write(struct file *filp, char *buf, size_t count, loff_t *ppos) { ssize_t ret =0; if (copy_from_user (write_data, buf, count)) { ret = -EFAULT; } printk (KERN_INFO"Received \n"); if(write_data[0]==1) { printk (KERN_INFO" res up\n"); led_up(); create_pulse(write_data[1]); } else { printk (KERN_INFO" res down\n"); led_down(); create_pulse(write_data[1]); } return count; } struct file_operations light_fops = { .owner = THIS_MODULE, //.unlocked_ioctl = light_ioctl, .open = light_open, .write =light_write, .release = light_release,};// 模块加载函数static int light_init(void){ int ret; int error; led_init(); dev=MKDEV(major,minior); ret = register_chrdev_region(dev,number_of_device,"res"); if(ret<0) { printk("unable to register driver!\n"); return ret; } //生成设备节点 cdev_init(&cdev,&light_fops); cdev.owner=THIS_MODULE; cdev.ops=&light_fops; error = cdev_add(&cdev,dev,1); if(error) { printk("unable to add driver!\n"); } myclass=class_create(THIS_MODULE,"res"); if(IS_ERR(myclass)) { printk("Err: failed in creating class.\n"); return 0; } printk (KERN_INFO "char device registered\n"); device_create(myclass,NULL, dev, NULL,"res"); return 0; }// 模块卸载函数static void light_exit(void){ dev_t devno = MKDEV(major,minior); cdev_del(&cdev); unregister_chrdev_region (devno, number_of_device); device_destroy(myclass, devno); class_destroy(myclass); printk("Goodbye,cruel world!\n"); }module_init(light_init);module_exit(light_exit);
应用程序
#include#include #include #include #include #include #include #include #include #include #include #include #include char cmd_data[2];int main(int argc, char **argv){ int i, n, fd; int oc; extern int optind; const char *count_s; const char *dir_s; int count=0; int dir; fd = open("/dev/res", O_RDWR); if (fd < 0) { printf("can't open /dev/res!\n"); exit(1); } count=atoi(argv[1]); if(count <=100) printf("pulse's number is %d\n",count); else { printf("please input number is less than 100\n"); return 0; } dir=atoi(argv[2]); if(dir == 1) printf("u/d is high level\n"); else printf("u/d is low level\n"); cmd_data[0]=dir; cmd_data[1]=count; int byte=write(fd,cmd_data,2); if(byte < 0) printf("write error\n"); return 0;}
makefile
#!/bin/bash#通知编译器我们要编译模块的哪些源码#这里是编译itop4412_hello.c这个文件编译成中间文件itop4412_hello.oobj-m += res_driver_xd.o #源码目录变量,这里用户需要根据实际情况选择路径#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的KDIR := /home/liwx/src_xd/kernel-3.2#当前目录变量PWD ?= $(shell pwd)#make命名默认寻找第一个目标#make -C就是指调用执行的路径#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0#$(PWD)当前目录变量#modules要执行的操作all: make -C $(KDIR) M=$(PWD) modules #make clean执行的操作是删除后缀为o的文件clean: rm -rf *.o
转载地址:http://egpqb.baihongyu.com/