• Linux驱动之LED驱动编写


    从上到下,一个软件系统可以分为:应用程序、操作系统(内核)、驱动程序。结构图如下:我们需要做的就是写出open、read、write等驱动层的函数。一个LED驱动的步骤如下:

    1、查看原理图,确定需要控制的IO端口

    2、查看芯片手册,确定IO端口的寄存器地址

    3、编写驱动代码

    4、确定应用程序功能,编写测试代码。

    5、编写Makefile,编译驱动代码与测试代码,在开发板上运行

    1、查看原理图,确定需要控制的IO端口

    打开原理图,确定需要控制的IO端口为GPF4、GPF5、GPF6。

    2、查看芯片手册,确定IO端口的寄存器地址,可以看到它的基地址为0x56000050

     

    3、编写驱动代码,编写驱动代码的步骤如下:

     1)、编写出口、入口函数。

      a、首先利用register_chrdev函数如果第一个参数为0的话那么会自动分配一个主设备号为Firstmajor ;第二个参数firstled_drv会是这个字符设备的名称可以利用命令cat /proc/devices看到;第三个参数是它的first_drv_fops结构体,这个结构体是字符设备中最主要的,后面再说明。

      b、接着利用class_create函数创建一个firt_drv_class类。它的第一个参数指向这个模块,第二个参数为类的名称。再利用class_device_create创建四个设备节点,第一个参数为类、第三个参数为设备号,第五个参数为设备节点的名称,第六个参数为次设备号。这样的话会在加载驱动之后自动在/dev目录下创建四个设备文件。

      c、ioremap函数重映射函数,将物理地址转换成虚拟地址

      d、a-c为驱动入口函数,在驱动出口函数会将a-c创建的东西全部删除。

      e、module_init与module_exit表示在insmod与rmmod的时候内核会调用first_ledsdrv_init与first_ledsdrv_exit

    /*
     * 执行insmod命令时就会调用这个函数 
     */
    static int __init first_ledsdrv_init(void)
    {
        int minor;//次设备号
        Firstmajor = register_chrdev(0, "firstled_drv", &first_drv_fops);//注册first_drv_fops结构体到字符设备驱动表,0表示自动分配主设备号
        if(Firstmajor<0)
        {
                  printk(" first_drv can't register major number
    ");
                  return Firstmajor;
            }
    
        firt_drv_class = class_create(THIS_MODULE, "leds");//创建类 
        
        firt_drv_class_dev[0] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, 0), NULL, "leds");//创建设备节点
        if (unlikely(IS_ERR(firt_drv_class_dev[0])))
                return PTR_ERR(firt_drv_class_dev[0]);
    
        for(minor=1;minor<4;minor++)
        {
            firt_drv_class_dev[minor] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, minor), NULL, "led%d",minor);//创建设备节点
            if (unlikely(IS_ERR(firt_drv_class_dev[minor])))
                return PTR_ERR(firt_drv_class_dev[minor]);
        }
    
        gpfcon = ioremap(0x56000050 , 16);//重映射,将物理地址变换为虚拟地址
        gpfdat = gpfcon + 1;
        
        printk("firstdrv module insmoded
    ");
        return 0;
    }
    
    /*
     * 执行rmmod命令时就会调用这个函数 
     */
    static void __exit first_ledsdrv_exit(void)
    {
        int i;
        for(i=0;i<4;i++)
            class_device_unregister(firt_drv_class_dev[i]);//删除设备节点
            
        class_destroy(firt_drv_class);//删除类
    
        iounmap(gpfcon);//删除重映射分配的地址
        
        unregister_chrdev(Firstmajor, "firstled_drv");//将rst_drv_fops结构体从字符设备驱动表中删除
        printk("firstdrv module rmmod
    ");
    }
    
    
    /* 这两行指定驱动程序的初始化函数和卸载函数 */
    module_init(first_ledsdrv_init);
    module_exit(first_ledsdrv_exit);

     2)、添加file_operations 结构体,这个是字符设备驱动的核心结构,所有的应用层调用的函数最终都会调用这个结构下面定义的函数。

    static struct file_operations first_drv_fops = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   first_ledsdrv_open,     
        .write    =    first_ledsdrv_write,       
    };

    其中THIS_MODULE在linux/module.h中定义,它执向__this_module的地址

    84    extern struct module __this_module;
    85    #define THIS_MODULE (&__this_module)

    而__this_module这个变量是在编译的时候由modpost程序生成的,它的结构如下:

    struct module __this_module
    __attribute__((section(".gnu.linkonce.this_module"))) = {
     .name = KBUILD_MODNAME,
     .init = init_module,
    #ifdef CONFIG_MODULE_UNLOAD
     .exit = cleanup_module,
    #endif
    };

    3)、分别编写file_operations 结构体下的open、wrtie函数。当应用程序调用系统调用led设备的open与write时最终内核会定位到驱动层的open与write函数。

    其中open函数的功能是根据打开的设备文件初始化相应的io口为输出口

    static int first_ledsdrv_open(struct inode *inode, struct file *file)
    {
        int minor = MINOR(inode->i_rdev);//取得次设备号,根据次设备号来配置IO端口
    
        switch(minor)
            {
                case 0:
                    *gpfcon &= ~((3 << 8)  | (3 << 10) | (3 << 12));//先清0 :8,9,10,11,12,13
                    *gpfcon |= ((1 << 8)  | (1 << 10) | (1 << 12));//再置1:8,10,12break;
                    printk("initialize leds
    ");
                    break;
                case 1:
                    *gpfcon &= ~((3 << 8) );//先清0 :8,9,10,11,12,13
                    *gpfcon |= ((1 << 8));//再置1:8,10,12break;
                    printk("initialize led1
    ");
                    break;
                case 2:
                    *gpfcon &= ~( (3 << 10));//先清0 :8,9,10,11,12,13
                    *gpfcon |= ( (1 << 10) );//再置1:8,10,12break;
                    printk("initialize led2
    ");
                    break;
                case 3:
                    *gpfcon &= ~((3 << 12));//先清0 :8,9,10,11,12,13
                    *gpfcon |= ((1 << 12));//再置1:8,10,12break;
                    printk("initialize led3
    ");
                    break;
                default:break;
            }
        
        
    //    printk("hello this is open
    ");
        return 0;
    }

    write函数的功能是根据设备文件以及向设备写入的值来操作相应的IO口做相应的动作

    static ssize_t first_ledsdrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    {
        char val;
        int ret;
        int minor = MINOR(file->f_dentry->d_inode->i_rdev);//根据文件取出次设备号
        
        ret = copy_from_user(&val, buf, count);//ret返回0表示拷贝成功
    
        if(!ret)
        {
            switch(minor)
            {
                case 0:
                    if(val==1)
                    {
                        *gpfdat &= ~((1 << 4) | (1<<5) | (1<<6));//点灯
                         printk("leds on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1 << 4) | (1<<5) | (1<<6));//灭灯
                        printk("leds off
    ");
                    }
                    break;
                case 1:
                    if(val==1)
                    {
                        *gpfdat &= ~((1 << 4));//点灯
                         printk("led1 on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1 << 4));//灭灯
                        printk("led1 off
    ");
                    }
                    break;
                case 2:
                    if(val==1)
                    {
                        *gpfdat &= ~((1<<5));//点灯
                         printk("led2 on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1<<5));//灭灯
                        printk("led2 off
    ");
                    }
                    break;
                case 3:
                    if(val==1)
                    {
                        *gpfdat &= ~((1<<6));//点灯
                         printk("led3 on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1<<6));//灭灯
                        printk("led3 off
    ");
                    }
                    break;
                default:break;
            }
        }
        else
            printk("copy from user wrong!!!!%d  %d
    ",ret,count);
    //    printk("hello this is write
    ");
        return 0;
    }

    4)、下面是整个LED驱动的整体代码

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <asm/io.h>        //含有iomap函数iounmap函数
    #include <asm/uaccess.h>//含有copy_from_user函数
    #include <linux/device.h>//含有类相关的处理函数
    
    
    
    static struct class *firt_drv_class;//
    static struct class_device *firt_drv_class_dev[4];//类下面的设备
    static int Firstmajor;
    
    static unsigned long *gpfcon = NULL;
    static unsigned long *gpfdat = NULL;
    
    static int first_ledsdrv_open(struct inode *inode, struct file *file)
    {
        int minor = MINOR(inode->i_rdev);//取得次设备号,根据次设备号来配置IO端口
    
        switch(minor)
            {
                case 0:
                    *gpfcon &= ~((3 << 8)  | (3 << 10) | (3 << 12));//先清0 :8,9,10,11,12,13
                    *gpfcon |= ((1 << 8)  | (1 << 10) | (1 << 12));//再置1:8,10,12break;
                    printk("initialize leds
    ");
                    break;
                case 1:
                    *gpfcon &= ~((3 << 8) );//先清0 :8,9,10,11,12,13
                    *gpfcon |= ((1 << 8));//再置1:8,10,12break;
                    printk("initialize led1
    ");
                    break;
                case 2:
                    *gpfcon &= ~( (3 << 10));//先清0 :8,9,10,11,12,13
                    *gpfcon |= ( (1 << 10) );//再置1:8,10,12break;
                    printk("initialize led2
    ");
                    break;
                case 3:
                    *gpfcon &= ~((3 << 12));//先清0 :8,9,10,11,12,13
                    *gpfcon |= ((1 << 12));//再置1:8,10,12break;
                    printk("initialize led3
    ");
                    break;
                default:break;
            }
        
        
    //    printk("hello this is open
    ");
        return 0;
    }
    
    
    static ssize_t first_ledsdrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    {
        char val;
        int ret;
        int minor = MINOR(file->f_dentry->d_inode->i_rdev);//根据文件取出次设备号
        
        ret = copy_from_user(&val, buf, count);//ret返回0表示拷贝成功
    
        if(!ret)
        {
            switch(minor)
            {
                case 0:
                    if(val==1)
                    {
                        *gpfdat &= ~((1 << 4) | (1<<5) | (1<<6));//点灯
                         printk("leds on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1 << 4) | (1<<5) | (1<<6));//灭灯
                        printk("leds off
    ");
                    }
                    break;
                case 1:
                    if(val==1)
                    {
                        *gpfdat &= ~((1 << 4));//点灯
                         printk("led1 on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1 << 4));//灭灯
                        printk("led1 off
    ");
                    }
                    break;
                case 2:
                    if(val==1)
                    {
                        *gpfdat &= ~((1<<5));//点灯
                         printk("led2 on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1<<5));//灭灯
                        printk("led2 off
    ");
                    }
                    break;
                case 3:
                    if(val==1)
                    {
                        *gpfdat &= ~((1<<6));//点灯
                         printk("led3 on
    ");
                    }
                    else if(val == 0)
                    {
                        *gpfdat |= ((1<<6));//灭灯
                        printk("led3 off
    ");
                    }
                    break;
                default:break;
            }
        }
        else
            printk("copy from user wrong!!!!%d  %d
    ",ret,count);
    //    printk("hello this is write
    ");
        return 0;
    }
    
    
    static struct file_operations first_drv_fops = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   first_ledsdrv_open,     
        .write    =    first_ledsdrv_write,       
    };
    
    
    /*
     * 执行insmod命令时就会调用这个函数 
     */
    static int __init first_ledsdrv_init(void)
    {
        int minor;//次设备号
        Firstmajor = register_chrdev(0, "firstled_drv", &first_drv_fops);//注册first_drv_fops结构体到字符设备驱动表,0表示自动分配主设备号
        if(Firstmajor<0)
        {
                  printk(" first_drv can't register major number
    ");
                  return Firstmajor;
            }
    
        firt_drv_class = class_create(THIS_MODULE, "leds");//创建类 
        
        firt_drv_class_dev[0] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, 0), NULL, "leds");//创建设备节点
        if (unlikely(IS_ERR(firt_drv_class_dev[0])))
                return PTR_ERR(firt_drv_class_dev[0]);
    
        for(minor=1;minor<4;minor++)
        {
            firt_drv_class_dev[minor] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, minor), NULL, "led%d",minor);//创建设备节点
            if (unlikely(IS_ERR(firt_drv_class_dev[minor])))
                return PTR_ERR(firt_drv_class_dev[minor]);
        }
    
        gpfcon = ioremap(0x56000050 , 16);//重映射,将物理地址变换为虚拟地址
        gpfdat = gpfcon + 1;
        
        printk("firstdrv module insmoded
    ");
        return 0;
    }
    
    /*
     * 执行rmmod命令时就会调用这个函数 
     */
    static void __exit first_ledsdrv_exit(void)
    {
        int i;
        for(i=0;i<4;i++)
            class_device_unregister(firt_drv_class_dev[i]);//删除设备节点
            
        class_destroy(firt_drv_class);//删除类
    
        iounmap(gpfcon);//删除重映射分配的地址
        
        unregister_chrdev(Firstmajor, "firstled_drv");//将rst_drv_fops结构体从字符设备驱动表中删除
        printk("firstdrv module rmmod
    ");
    }
    
    
    /* 这两行指定驱动程序的初始化函数和卸载函数 */
    module_init(first_ledsdrv_init);
    module_exit(first_ledsdrv_exit);
    
    
    MODULE_LICENSE("GPL");//不加的话加载会有错误提醒
    MODULE_AUTHOR("andylu");//作者
    MODULE_VERSION("0.0.0");//版本
    MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");//简单的描述

     4、确定应用程序功能,编写测试代码。应用程序功能为打开不同设备文件操作不同的IO口。代码如下:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    
    
    /*
      *  ledtest <dev> <on|off>
      */
    void print_usage(char *file)
    {
        printf("Usage:
    ");
        printf("%s <dev> <on|off>
    ",file);
        printf("eg. 
    ");
        printf("%s /dev/leds on
    ", file);
        printf("%s /dev/leds off
    ", file);
        printf("%s /dev/led1 on
    ", file);
        printf("%s /dev/led1 off
    ", file);
    }
    
    
    int main(int argc, char **argv)
    {
        int fd;
        char* filename=NULL;
        char val;
        
        filename = argv[1];
        
        fd = open(filename, O_RDWR);//打开dev/firstdrv设备文件
        if (fd < 0)//小于0说明没有成功
        {
            printf("error, can't open %s
    ", filename);
            return 0;
        }
        
        if(argc !=3)
        {
            print_usage( argv[1]);//打印用法
        }
    
        if(!strcmp(argv[2], "on"))
            val = 1;
       else
           val = 0;
       
        write(fd, &val, 1);//操作LED
        
       return 0;
    }

    5、编写Makefile,编译驱动代码与测试代码,在开发板上运行

    Makefile源码如下:

    KERN_DIR = /work/system/linux-2.6.22.6
    
    all:
            make -C $(KERN_DIR) M=`pwd` modules //M='pwd'表示当前目录。这句话的意思是利用内核目录下的Makefile规则来编译当前目录下的模块
    
    clean:
            make -C $(KERN_DIR) M=`pwd` modules clean
            rm -rf modules.order
    
    obj-m   +=first_drv.o//调用内核目录下Makefile编译时需要用到这个参数

    1)、然后在当前目录下make后编译出first_drv.ko文件

    2)、arm-linux-gcc -o first_test first_test.c编译出first_test测试程序

    3)、cp first_drv.ko first_test /work/nfs_root将编译出来的文件拷贝到开发板挂接的网络文件系统上

    4)、执行insmod first_drv.ko加载驱动。

    5)、./first_test /dev/leds on测试程序,灯全部被点亮,成功运行。

  • 相关阅读:
    关于python3导出excel图片链接转图片且图片内嵌表格内实现
    ios下载指定版本frida
    frida打印类下方法模板
    IDEA 报错 Package 'java.util' is declared in module 'java.base', which is not in the module graph
    linux统计当前路径下的文件数量
    Mac 中配置鼠标右键 Open Tabby here
    取消vim粘贴时自动注释功能
    mac 安装字体
    scp 命令提示 Permission denied, please try again.解决方法
    VScode 中如何取消设置文件内容可以滚动到最后一行之后
  • 原文地址:https://www.cnblogs.com/andyfly/p/9467702.html
Copyright © 2020-2023  润新知