zoukankan      html  css  js  c++  java
  • 嵌入式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 
    ");
        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 !! 
    ");
        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! 
    ");
    
        return 0;
    }
    
    static void __exit led_exit(void){
        printk(" led_exit 
    ");
    }
    
    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"
    /* 操作的引脚 */
    #define LED_PIN 1
    /* 内核态用户态交互缓冲 */
    static char recv_msg[20];
    
    /* 自动创建设备树:类 */
    static struct class *led_control_class = NULL;
    /* 自动创建设备树:设备 */
    static struct device *led_control_device = NULL;
    
    /* led初始化 */
    static int led_open(struct inode *inode, struct file *filep){
        printk("GPIO init 
    ");
        /* 校验 */
        if(!gpio_is_valid(LED_PIN)){
            printk("Error wrong gpio number !!
    ");
            return;
        }
        gpio_request(LED_PIN, "led_ctr");
        /* 设置direction为输出 */
        gpio_direction_output(LED_PIN,1);
        /* 初始置为高电平 */
        gpio_set_value(LED_PIN,1);
        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! 
    ");
                gpio_set_value(LED_PIN, 1);
            }
            /* 如果用户输入了其他标记,代表关闭 */
            else{
                printk("LED off! 
    ");
                gpio_set_value(LED_PIN, 0);
            }
        }else{
            printk("ERROR occur when writing!!
    ");
            return -EIO;
        }
        return count;
    }
    
    /* led注销 */
    static int led_release(struct inode *inode, struct file *filep){
        printk("Release !! 
    ");
        gpio_free(LED_PIN);
        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!
    ");
            return -EIO;
        }
        printk("register success, major number is %d 
    ",ret);
       
        /* 2. 自动注册设备:首先创建类 */
        led_control_class = class_create(THIS_MODULE, LED_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! 
    ");
    
        return 0;
    }
    
    /* 设备注销 */
    static void __exit led_exit(void){
        printk(" led_exit 
    ");
        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. 自动注册设备树,需要先进行class_create,之后才能进行device_create. 

    4. 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目录中去:

    sudo mkdir /mnt/sdb2             //创建一个临时目录
    sudo mount /dev/sdb2 /mnt/sdb2   //将sdb2挂载到此临时目录
    sudo cp led1.ko /mnt/sdb2/media //拷贝到sdb2/media目录下
    sudo sync
    sudo umount /dev/sdb2
    

    拷贝完毕后,利用sudo minicom启动串口监听,之后去media目录,安装我们的驱动看看:

    可以看到,驱动被成功的安装了,可以用lsmod命令看一下:

    可以看到驱动成功的被加载了。那么这时候,我们通过cat /proc/devices命令看看字符设备是否成功注册:

    这里我们也可以清晰的看到模块被注册完毕了。

    之后我们利用rmmod led1.ko命令卸载下看看:

    可以看到模块被成功卸载,同时再利用cat /proc/devices命令执行,发现字符设备已被注销掉了。

    我们这里重新将驱动加载上,然后执行如下命令:

    #关闭led
    echo off > /dev/LED
    #打开led
    echo on > /dev/LED
    

    通过交换执行命令,我们发现板子上的led不时的亮灭,整体驱动成功!

     

    参考资料:

    纯代码点灯方式    :https://www.cnblogs.com/y4247464/p/12379992.html
    这篇参考文章也不错(此人写的一系列文章都可以的) :https://www.cnblogs.com/y4247464/p/12370190.html
    vscode搭建内核开发环境  :https://blog.mxslly.com/archives/170.html
    linux设备驱动程序--gpio控制   :https://www.cnblogs.com/downey-blog/p/10501709.html

  • 相关阅读:
    Java编程思想(第三版) 学习笔记
    Python 技术专题
    Interview Tech Knowledge
    Perl语言的多线程(一)
    教你如何拍好人像摄影
    c#,将pdf文件转换成图片文件。
    c#,使用WPF实现iPhone的短信框效果
    c#中利用WMI对象获取物理内存和可用内存大小信息
    c#,使用WMI对象获取系统的DPI。
    c# 调用Microsoft XPS Document Writer打印机,将Pdf文件转换成Xps文件
  • 原文地址:https://www.cnblogs.com/scy251147/p/14920656.html
Copyright © 2011-2022 走看看