嵌入式linux驱动开发之点亮led

2019-07-12 23:02发布

这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的。个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门。 不能再扯了,涉及到linux的驱动开发知识面灰常广,再扯文章就会变得灰常长。首先还是回到led驱动的本身上,自从linux被移植到arm上后,做驱动开发的硬件知识要求有所降低,很多都回归到了软件上,这是系统编程的一大特点,当然 ,也不排除有很多设备的驱动需要我们从头做起。 led的驱动虽然看似很简单,但是要描述清楚估计上万字都不一定够用,本篇文章从初学者的角度出发,重点关注在整个软件的开发的流程和思想,而不过多局限与细节的分析,初学者应该首先把握某一类编程的流程和思想,这样才能入门快,进步迅速。作为一个初学者我一直觉得这样入门效率最高。 下面我们进入主题led驱动的书写: 既然是在linux系统下设备驱动开发,就不同于以往我们单片机下设置一个高电平而了事,在linux系统下开发的驱动程序要想在linux正常工作,一定要符合linux系统的规范,linux下设备被分为三个类型字符设备/块设备/网络设备。上述的三种设备不可能面面俱到,因此还提出了一个杂设备作为补充,看了下网上大部分人都把led的驱动设备归到了杂设备,这是为什么呢?原来是友善之臂的手册上把它归为了杂设备,哈哈,所以杂设备这种版本比较流行,木有追求的人们啊! 那我们把它归为那种设备呢?当然是杂设备了,嘿嘿。。。。。。 既然对led我们准备把它作为一个杂设备加入系统,是不是应该有一个名字吧,还应该有个操作符号吧。。。。。。 stop,停止你的YY,关于这个设备的标准形式大神已经帮你定义好了,具体它存在于系统的include下,里面有一个miscdivice.h。   struct miscdevice  {
        int minor;
        const char *name;
        const struct file_operations *fops;
        struct list_head list;
        struct device *parent;
        struct device *this_device;
        const char *nodename;
        mode_t mode;
}; 好吧,原来为了统一规范,我们只需按照标准来填充内容就可以啦!要想正确的使用这个描述设备的结构体,必须清楚的了解到其中的每个成员。天空飘来四个字 f u c k 好,让我们平复一下心情,继续了解它。查了下minor这个单词是次要的,在这里是次设备号的意思。这是因为杂设备为了节约主设备号,采用共用主设备号的方式,次设备号加以区分的方式来描述设备,因此来看这个minor是要必须填写啦!不愧是过了四级的人,第二个直接看懂啦!欧耶~ 之前做过一个了解,这第三个在linux驱动中非常的重要,可以称之为核心,我们很大的工作 都要围绕这个file_operations来操作,因此必须要隆重的研究下这个file_operatios这个结构体。 file_operations这个结构体的存在是linux中将所有设备看做文件的基础,这是为什么呢?因为通俗的说就是这个结构体是文件操作和驱动操作的一个关系映射,对于系统的操作函数(诸如read/write)在这个结构体里都有与之对应的对硬件进行操作的函数。wow这个函数居然如此之酷!这样以来,我们还弄清楚了另外一个问题,就是为什么我们不能直接越过操作系统来操作硬件,都是因为有它啊!可见这个结构体在内核中的地位,以及在linux操作系统中的地位。哈哈下面的几个成员,就先不分析啦!我们这次也用不上,感觉在linux下开发驱动真是个力气活啊! 见过file_operations的厉害之后,我们自然知道 现在只要把这个结构体弄清楚就可以敲代码写驱动啦!so,let‘s go! 首先在系统目录include/linux/fs.h中找到这个牛逼的结构体: struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); }; 复制代码     当我知道有这么多成员的时候,当时我就尿了,不过,还好我们只需要实现本次驱动需要的东东!查看了一下手册上的驱动,欧耶~这次我们只需要研究两个成员ower和ioctl。上面可以看出ower是一个结构体成员就是是那个模块,而ioctl是个函数。 那为什么是ioctl函数呢?这个问题弄清楚灰常重要!因为我们前边说过file_operations内的函数是对文件操作的的映射,我们要控制led,实质是控制IO口的电平,这点不管它操作系统的驱动还是单片机的驱动实质都是一样的啦!此时,我瞬间明白鸟,icctl不就是IO Control的缩写吗?不就是文件的控制IO吗?因为我们要控制led,所以需要控制IO口,要控制IO对应的系统调用函数不就是ioctl吗?我们在驱动中需要做的就是给这个ioctl函数进行编写,然后系统就可以通过调用ioctl这个系统函数来通过file_operactions来关联到真正的驱动函数。欧耶 ~      终于都清楚啦! 现在,软件方面的核心都打通啦!既然是驱动,当然少不了硬件啦! 这四个led与固定的四个IO口连接,像这样的东西开发板原理图上是交代的很清楚的,我们必须按照开发板上的关系来进行。这四个led占用的IO寄存器分别为GPB5/GPB6/GPB7/GPB8;这就 好比当年我们单片机的P0/P1一样一样的,P0/P1都被定义在了reg51.h里面,而这里的IO口同样也被定义到了里,这里我们声明这四个IO口寄存器,这样我们操作IO口就可以控制led了。     static unsigned long led_table []= { S3C2410_GPB(5), S3C2410_GPB(6), S3C2410_GPB(7), S3C2410_GPB(8), };       这里需要注意的是与led连接的GPIO口可以用于输入/输出或者其他功能,我们的开发板上led接的是共阳极的,所以我们需要这些GPIO口作为输出口,只要我们输出低电平就可以让led亮了。既然这个IO口有多种功能,那一定有相关的配置寄存器 。所以我们需要将每个led对应的寄存器定义为输出。     static unsigned int led_cfg_table []={ S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, };     做好了前面的所有工作,现在就是驱动的核心了。如何控制 ?这的确是一个question?这时忽然想起了,前面分析的file_operations,这个玩意不就是系统和驱动的纽带吗?控制led系统需要ioctl函数,所以在flie_operations中我们也需要一个和驱动直接联系的ioctl函数,那么我们命名这个函数为heatnan_leds_ioctl;函数的原型前面file_operaction 中已经给出了,有种直接领表填单的赶脚!而且下面的开发思路也都清晰起来,那就是需要什么样的功能直接参考file_operactions结构体的参数模型就是了。 针对于led的驱动,要实现应用程序控制led的亮灭——>需要系统调用ioctl函数——>要使系统的ioctl函数能够控制硬件——>需要在file_operations中建立一个真正控制led驱动的函数——>新建控制led的函数(这里命名为heatnan_leds_ioctl). 首先我们建立连接关系:
  static struct file_operations dev_fops={ .owner = THIS_MODULE, .ioctl= heatnan_leds_ioctl, };   建立这个核心纽带后,就要书写heatnan_leds_ioctl这个函数啦! 要书写这个函数必须要对这个函数的两种形式有所了解,即需要对ioctl函数做功课,ioctl在系统函数中有三个参数,第三个参数可选,第一个参数代表操作设备的号,第二个参数代表操作命令,第三个可选参数可以以不同数据类型作为参数传递也不是必须的。 与之在file_operactions中对应的ioctl则多了一个参数,它的前两个参数对应系统函数的第一个参数,控制命令则进行原封不动的接收。     static int heatnan_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { if(arg<0||arg>3) { return -EINVAL; } switch(cmd) { case 0: case 1: { s3c2410_gpio_setpin(led_table[arg],!cmd); return 0; } default: return -EINVAL; } } 当你翻开代码一看居然有一些诸如s3c2410_gpio_setpin的函数的时候,你一定心中一声感叹!艹,这个函数那来的? 为什么会有这些函数呢?原因是linux平台对arm体系是有支持的,比如这些基本的函数,当我们开发程序的时候就可以用,有人觉得为什么要用它的自己写不是更cool吗?个人觉得从学习角度来说未尝不可,但从开发觉得还是高效最重要,为什么在应用程序里C++比C更流行,正是因为C++的效率更高,有更多库的支持,因此我觉得不管是我们软件应用开发也好或者硬件的应用开发也好,开发的难度一定是越来越小,开发效率是越来越高!所以,平台给我们提供的函数能用则用! 这个函数一旦建立,我们就可以通过通过操作系统的ioctl函数来间接操作heatnan_leds.ioctl,从而控制led。 下面就是一些程式化的东西了,模块的初始化,模块的退出以及设备的注册等一些列比较俗的问题了。 ×××××××××××××××××××累的赶脚,活剥不起来la,linux驱动开发感觉累累的!@ 下面贴出整个led驱动代码,主要是仿照数据手册写的,这次主要是入门的学习以及对驱动开发的理解。明天就要进入脱离数据手册的驱动编写辣!好赤鸡的感觉!     #include #include #include #include #include #include #include #include #define DEVICE_NAME "heat_leds" static unsigned long led_table []= { S3C2410_GPB(5), S3C2410_GPB(6), S3C2410_GPB(7), S3C2410_GPB(8), }; static unsigned int led_cfg_table []={ S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, }; static int heatnan_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { if(arg<0||arg>3) { return -EINVAL; } switch(cmd) { case 0: case 1: { s3c2410_gpio_setpin(led_table[arg],!cmd); return 0; } default: return -EINVAL; } } static struct file_operations dev_fops={ .owner = THIS_MODULE, .ioctl= heatnan_leds_ioctl, }; static struct miscdevice misc={ .minor=MISC_DYNAMIC_MINOR, .name=DEVICE_NAME, .fops=&dev_fops, }; static int __init led_init(void) { int ret; int i; for(i=0;i<4;i++) { s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); s3c2410_gpio_cfgpin(led_table[i],0); } ret=misc_register(&misc); printk(DEVICE_NAME" initialized "); return ret; } static void __exit led_exit(void) { misc_deregister(&misc); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");   终于tmd写完了这个驱动函数,可如果事情到这里结束那也是相当完美啊!按照昨天的方法顺利把驱动模块化编译成功弄到了开发板上, 结果 。。。。。。。。 结果,烧上驱动测试程序后灯真的没亮,我用lsmod和dev下豆发现了模块正在运行,灯就是不亮。。。。。。 后来我猜想可能是开发板的内核中已经有了led驱动,我这个led驱动和那个led驱动本质是一样的只是名字不一样那个,应该是冲突了。。。。。 于是卸下屏幕观看,开机,灯全亮,加载我的驱动,灯全灭,欧耶~看来我猜的有道理??????? 好了,不说那些桑心的事啦!整理整理发型,明天继续! 等串口线回来了,一定要把这个问题弄清楚,看是不是这样的!如果有遇到这个问题的大牛不妨分享下经验,灰常感谢!   驱动编程有感: 1 驱动编程有种瞻前顾后的感觉,在linux系统中的编程,眼光停留在硬件上远远不够的,还要注意linux系统的外部接口,只有这样才能做到外部接口和自己写的驱动接口完美衔接。 2 感觉驱动开发就像带着脚镣跳舞,底层的硬件的基本相关函数,linux已经支持,上层的系统接口,linux系统也已经制定了,我们能做的,就是再硬件和系统之间合理周旋。是一个非常考验人的活!前期感觉是累,后期恐怕就是智商挑战了吧! 3 分析linux驱动的时候,可以适当采用倒叙分析,先从操作系统接口开始,一步一步找到和硬件完美的契合点。。。