• Linux驱动之LED驱动编写


    https://www.cnblogs.com/andyfly/p/9467702.html

    从上到下,一个软件系统可以分为:应用程序、操作系统(内核)、驱动程序。结构图如下:我们需要做的就是写出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测试程序,灯全部被点亮,成功运行。

  • 相关阅读:
    php stdClass转数组
    PHP 获取1970年前的时间戳,且为负
    springboot响应格式Resullt封装
    php使用elasticsearch
    day0620211207
    day0820211209
    day022021121
    day0520211206
    day0720211208
    day0320211202
  • 原文地址:https://www.cnblogs.com/shelley-AL/p/10808578.html
Copyright © 2020-2023  润新知