嵌入式linux开发の字符设备驱动程序的开发流程

2019-07-12 14:39发布

本篇文章以编写电位器驱动程序为例,详细介绍并总结下设备驱动的开发流程 硬件: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! "); 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! "); } myclass=class_create(THIS_MODULE,"res");//创建类节点 if(IS_ERR(myclass)) { printk("Err: failed in creating class. "); return 0; } printk (KERN_INFO "char device registered "); 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. "); return 0; } printk (KERN_INFO "char device registered "); 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 "); 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); 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 "); } void led_init(void) { int result; printk(KERN_INFO"gpio init "); /* 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! "); result = gpio_request(GPIO_TO_PIN(1,9), "TTS_U/D"); if (result != 0) printk(KERN_INFO"gpio_request(1_9) failed! "); result = gpio_request(GPIO_TO_PIN(1,15), "TTS_CS"); if (result != 0) printk(KERN_INFO"gpio_request(1_15) failed! "); result = gpio_direction_output(GPIO_TO_PIN(1,8), 1); if (result != 0) printk(KERN_INFO"gpio_direction(1_8) failed! "); result = gpio_direction_output(GPIO_TO_PIN(1,9), 1); if (result != 0) printk(KERN_INFO"gpio_direction(1_9) failed! "); result = gpio_direction_output(GPIO_TO_PIN(1,15), 1); if (result != 0) printk(KERN_INFO"gpio_direction(1_15) failed! "); } MODULE_AUTHOR("Liwx"); MODULE_LICENSE("Dual BSD/GPL"); // 打开和关闭函数 int light_open(struct inode *inode,struct file *filp) { printk (KERN_INFO "open device success! "); 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 "); if(write_data[0]==1) { printk (KERN_INFO" res up "); led_up(); create_pulse(write_data[1]); } else { printk (KERN_INFO" res down "); 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! "); 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! "); } myclass=class_create(THIS_MODULE,"res"); if(IS_ERR(myclass)) { printk("Err: failed in creating class. "); return 0; } printk (KERN_INFO "char device registered "); 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! "); } 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! "); exit(1); } count=atoi(argv[1]); if(count <=100) printf("pulse's number is %d ",count); else { printf("please input number is less than 100 "); return 0; } dir=atoi(argv[2]); if(dir == 1) printf("u/d is high level "); else printf("u/d is low level "); cmd_data[0]=dir; cmd_data[1]=count; int byte=write(fd,cmd_data,2); if(byte < 0) printf("write error "); return 0; } makefile #!/bin/bash #通知编译器我们要编译模块的哪些源码 #这里是编译itop4412_hello.c这个文件编译成中间文件itop4412_hello.o obj-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