嵌入式linux与物联网进阶之路五:嵌入式驱动方式点亮LED
Posted 程序诗人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式linux与物联网进阶之路五:嵌入式驱动方式点亮LED相关的知识,希望对你有一定的参考价值。
简化的驱动框架
话说前面章节讲到了如何利用嵌入式驱动开发的方式进行驱动开发。由于其学习路线相比于裸机开发来说,上手难度稍微大一些,而且代码量也相对来说较多,所以对刚上手的人来说是颇有难度的。本章节,我们将以一个类似于Hello World点灯的例子,来讲解在linux下如何进行内核驱动的开发。
工欲善其事,必先利其器,开始之前,我们需要先将驱动开发用到的主体框架搭建一下,这里由于上节讲过,我这里直接贴上来:
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/string.h> #define LED_MAJOR 200 #define LED_NAME "LED" static int led_open(struct inode *inode, struct file *filep){ printk("GPIO init \\n"); return 0; } static int led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos){ return count; } static int led_release(struct inode *inode, struct file *filep){ printk("Release !! \\n"); return 0; } static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .release = led_release, }; static int __init led_init(void){ printk("led control device init success! \\r\\n"); return 0; } static void __exit led_exit(void){ printk(" led_exit \\r\\n"); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("CXSR");
可以看到,整个驱动开发框架无非是如下几个东西,首先是module_init和module_exit函数,之后就是file_operations结构体,结构体中有对文件的读,写,打开,关闭等等,我们只需要把我们用到的操作去实现就行了。由于这部分整体比较固化,所以这里我不再赘述了。
由于在荔枝派中,我选择了A1口作为我的控制口,所以我先尝试利用GPIO手动控制了一下,整体控制流程如下:
1、 进入sys/class/gpio目录下 2、 执行echo 1 > export命令,可以看出来创建了gpio1的文件夹 3、 进入gpio1文件夹,cat direction查看其值为in,通过vi direction,将其值改为out后, wq保存。 4、 运行 echo 1 > value 命令,则可以设置A1口高电平,设置echo 0 > value 命令,则可以设置A1口低电平。
通过如上步骤,我们发现,我们可以控制板子上的LED灯的亮灭了。之所以能这么控制,是因为我们编译生成的根文件中,包含了对GPIO的支持,所以使得我们很容易的进行控制。这里的内容,之后我们会用到,先继续。
LED驱动编码
接下来,我们就开始针对刚才的驱动框架模板,来慢慢的填写我们的内容吧。
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/string.h> #define LED_MAJOR 200 #define LED_NAME "LED" /*led引脚,这里是A1脚*/ #define LED_PIN 1 static int gpio_status; /*用户态-内核态交互缓冲区*/ static char recv_msg[20]; /*class名称, 会创建到/sys/class/目录下 */ static const char *CLASS_NAME = "led_control_class"; /*led分类,由此分类可以创建出设备*/ static struct class *led_control_class = NULL; /*led设备,由此可以实现led控制*/ static struct device *led_control_device = NULL; /*gpio初始设置*/ static void gpio_config(void){ if(!gpio_is_valid(LED_PIN)){ printk("Error wrong gpio number !!\\n"); return; } gpio_request(LED_PIN, "led_ctr"); /*设置引脚为输出状态*/ gpio_direction_output(LED_PIN,1); /*初始置为高电平*/ gpio_set_value(LED_PIN,1); gpio_status = 1; }
/*gpio关闭设置*/ static void gpio_deconfig(void){ gpio_free(LED_PIN); }
/*初始灯*/ static int led_open(struct inode *inode, struct file *filep){ printk("GPIO init \\n"); gpio_config(); return 0; }
/*为灯的缓冲区进行数据交换*/ static int led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos){ int cnt = _copy_from_user(recv_msg, buf, count); if(0 == cnt){
/*on命令, 则开灯*/ if(0 == memcmp(recv_msg, "on", 2)){ printk("LED on! \\n"); gpio_set_value(LED_PIN, 1); gpio_status = 1; }
/*off命令,则关灯*/
else{ printk("LED off! \\n"); gpio_set_value(LED_PIN, 0); gpio_status = 0; } }else{ printk("ERROR occur when writing!!\\n"); return -EIO; } return count; }
/*灯设备关闭*/ static int led_release(struct inode *inode, struct file *filep){ printk("Release !! \\n"); gpio_deconfig(); return 0; } /*文件操作结构体*/ static const struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .release = led_release, };
/*设备初始化*/ static int __init led_init(void){ int ret = 0; /* 1. 注册设备 */ ret = register_chrdev(LED_MAJOR, LED_NAME,&led_fops); if(ret <0){ printk("register fail!\\r\\n"); return -EIO; } printk("register success, major number is %d \\r\\n",ret);
/* 2. 创建设备分类 */ led_control_class = class_create(THIS_MODULE, CLASS_NAME); if(IS_ERR(led_control_class)){ unregister_chrdev(LED_MAJOR, LED_NAME); return -EIO; }
/* 3. 创建设备 */ led_control_device = device_create(led_control_class, NULL, MKDEV(LED_MAJOR,0), NULL,LED_NAME); if(IS_ERR(led_control_device)){ class_destroy(led_control_class); unregister_chrdev(LED_MAJOR, LED_NAME); return -EIO; } printk("led control device init success! \\r\\n"); return 0; }
/*设备注销*/ static void __exit led_exit(void){ printk(" led_exit \\r\\n"); device_destroy(led_control_class, MKDEV(LED_MAJOR,0)); class_unregister(led_control_class); class_destroy(led_control_class); unregister_chrdev(LED_MAJOR,LED_NAME); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("CXSR");
由于代码部分,我做了详尽的注释,所以这里不再过多的解释了。这里唯一需要注意的几点就是:
1. 用户态和内核态的数据交换,是需要通过copy_from_user或者copy_to_user来进行。
2. 设备初始化,需要先利用register_chrdev来注册字节设备,之后才能进行设备的创建操作。
3. insmod和rmmod命令的执行,分别对应led_init函数和led_exit函数。
Makefile制作
既然代码写好了,我们这里就需要来编译生成ko文件才行。我们来手写一个Makefile文件。
KERNELDIR := /home/scy/linux-mi/linux-f1c100s-480272lcd-test/ CURRENT_PATH := $(shell pwd) obj-m := led1.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
可以看到,整个文件内容比较少,其中KERNELDIR代表linux内核源码的目录,CURRENT_PATH则代表当前路径,kernel_modules则代表我们mak命令要执行的内容,需要注意的是,如果你的板子是ARM架构的,这里要加上ARCH=arm,如果你在编译内核的时候,有用到自己的编译交叉链,那么这里也需要指定一下CROSS_COMPILE,否则可能会因为内核编译方式不一样,导致生成的ko文件,不能被板子上的系统所加载。
执行make命令,可以看到如下熟悉的输出。
之后,我们就可以看到led1.ko文件被生成了。
开发板上跑起来
由于ko文件已经被生成,所以这里我们拿下tf卡,然后通过如下的命令,将我们的led1.ko,拷贝到media目录中去:
以上是关于嵌入式linux与物联网进阶之路五:嵌入式驱动方式点亮LED的主要内容,如果未能解决你的问题,请参考以下文章
嵌入式linux与物联网进阶之路一:基于荔枝派nano的u-boot移植