【IMX6ULL学习笔记之驱动学习02】LED字符设备驱动

发布于:2023-01-19 ⋅ 阅读:(324) ⋅ 点赞:(0)

字符设备驱动开发

配置开发环境

  • 新建一个VScode文档,先配置开发环境

  • Ctrl+Shift+P//打开控制台
    
  • C/C++:Edit configuration(JSON)
    
  • 在生成的c_cpp_properties.json中添加Linux开发需要包含的头文件

    {
        "configurations": [
            {
                "name": "Linux",
                "includePath": [
                    "${workspaceFolder}/**",
                    "/home/lux/Linux/linux/linux-lux/include",
                    "/home/lux/Linux/linux/linux-lux/arch/arm/include",
                    "/home/lux/Linux/linux/linux-lux/arch/arm/include/generated"
                ],
                "defines": [],
                "compilerPath": "/usr/bin/gcc",
                "cStandard": "c11",
                "cppStandard": "c++17",
                "intelliSenseMode": "clang-x64"
            }
        ],
        "version": 4
    }
    

编写程序

  1. 编写驱动程序chrdevbase.c

    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/ide.h>
    #include <linux/init.h>
    #include <linux/module.h>
    
    #define CHRDEVBASE_MAJOR    200             //主设备号
    #define CHRDEVBASE_NAME     "chrdevbase"    //设备名
    
    static char readbuff[100];//读缓冲区
    static char writebuff[100];//写缓冲区
    static char kerneldata[]={"kernel data!"};
    
    static int chrdevbase_open(struct inode *inode,struct file *filp)
    {
        return 0;
    }
    
    static ssize_t chrdevbase_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
    {
        int retvalue = 0;
    
        memcpy(readbuff,kerneldata,sizeof(kerneldata));
        retvalue = copy_to_user(buf,readbuff,cnt);
        if (retvalue == 0)
        {
            printk("kernel senddata ok!\r\n");
        }
        else
        {
            printk("Kernek senddata failed!\r\n");
        }
        
        return 0;
    }
    
    static ssize_t chrdevbase_write(struct file *filp,
                                    const char __user *buf,
                                    size_t cnt,loff_t *offt)
    {
        int retvalue = 0;
    
        retvalue = copy_from_user(writebuff,buf,cnt);
        if (retvalue == 0)
        {
            printk("kernel recevdata:%s\r\n",writebuff);
        }
        else
        {
            printk("kernel recevdata failed!\r\n");
        }
    
        return 0;
        
    }
    
    static int chrdevbase_release(struct inode *inode,struct file *filp)
    {
        return 0;
    }
    
    static struct file_operations chrdevbase_fops = {
        .owner = THIS_MODULE,
        .open = chrdevbase_open,
        .read = chrdevbase_read,
        .write = chrdevbase_write,
        .release = chrdevbase_release,
    };
    
    
    static int __init chrdevbase_init(void)
    {
        int retvalue = 0;
        retvalue = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&chrdevbase_fops);
    
        if (retvalue < 0)
        {
            printk("chrdevbase driver register failed\r\n");
        }
    
        printk("chrdevbase init done!\r\n");
        
        return 0;
    }
    
    static void __exit chrdevbase_exit(void)
    {
        unregister_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME);
        printk("chrdevbase_exit done!\r\n");
    }
    
    module_init(chrdevbase_init);
    module_exit(chrdevbase_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Lux");
    
    • 字符设备注册函数

      static int __init chrdevbase_init(void)
      {
      }
      module_init(chrdevbase_init);
      
    • 字符设备注销函数

      static void __exit chrdevbase_exit(void)
      {
      }
      module_exit(chrdevbase_exit);
      
    • 添加LICENSE和作者信息

      MODULE_LICENSE("GPL");
      MODULE_AUTHOR("Lux");
      
    • 动态分配设备号

      int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
      /*
      函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:
      dev:保存申请到的设备号。
      baseminor: 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这
      些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
      增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
      count: 要申请的设备号数量。
      name:设备名字
      */
      
    • 释放设备号

      void unregister_chrdev_region(dev_t from, unsigned count)
      /*
      此函数有两个参数:
      from:要释放的设备号。
      count: 表示从 from 开始,要释放的设备号数量。
      */
      
  2. 编写应用程序chrdevbaseApp.c

    #include "stdio.h"
    #include "unistd.h"
    #include "sys/types.h"
    #include "sys/stat.h"
    #include "fcntl.h"
    #include "stdlib.h"
    #include "string.h"
    
    //数组 usrdata 是测试 APP 要向 chrdevbase 设备写入的数据
    static char usrdata[] = {"user data!"};
    
    
    int main(int argc, char const *argv[])
    {
        int fd,retvalue;
        char *filename;
        char readbuf[100],writebuf[100];
    /*判断运行测试APP的时候输入的参数是不是为3个,main函数的argc参数表示参数数量,argv[]保存着具体的参数,如果参数不为 3 个的话就表示测试APP用法错误
    	第一个参数表示运行chrdevbaseAPP这个软件,
    	第二个参数表示测试APP要打开/dev/chrdevbase这个设备。
    	第三个参数就是要执行的操作,1表示从chrdevbase中读取数据,2表示向chrdevbase 写数据。*/
        if(argc != 3)
        {
            printf("Error Usage!\r\n");
            return -1;
        }
    	//获取要打开的设备文件名字, argv[1]保存着设备名字
        filename = argv[1];
    	//调用 C 库中的 open 函数打开设备文件: /dev/chrdevbase。
        fd = open(filename,O_RDWR);
    
        if (fd < 0)
        {
            printf("Can not open file %s\r\n",filename);
            return -1;
        }
        /*判断 argv[2]参数的值是 1 还是 2,因为输入命令的时候其参数都是字符串格式的,因此需要借助 atoi 函数将字符串格式的数字转换为真实的数字
        当 argv[2]为 1 的时候表示要从 chrdevbase 设备中读取数据,一共读取 50 字节的数据,读取到的数据保存在 readbuf 中,读取成功以后就在终端上打印		出读取到的数据。*/
        if (atoi(argv[2])==1)
        {
            retvalue = read(fd,readbuf,50);
            if (retvalue < 0)
            {
                printf("read file %s failed\r\n",filename);
            }
            else
            {
                printf("read data %s\r\n",readbuf);
            }
            
        }
    /*当 argv[2]为 2 的时候表示要向 chrdevbase 设备写数据。*/
        if (atoi(argv[2])==2)
        {
            memcpy(writebuf,usrdata,sizeof(usrdata));
            retvalue = write(fd,writebuf,50);
            if (retvalue < 0)
            {
                printf("wtite file %s\r\n",filename);
            }
            
        }
        //对 chrdevbase 设备操作完成以后就关闭设备。
        retvalue = close(fd);
        if (retvalue < 0)
        {
            printf("can not open file %s \r\n",filename);
            return -1;
        }
        
        
        return 0;
    }
    
    

编译测试程序

  1. 编译驱动程序

    • 将.c文件编译为.ko的模块文件

    • 创建Makefile文件

      #KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,
      KERNELDIR := /home/lux/Linux/linux/linux-lux
      #CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取
      CURRENT_PATH := $(shell pwd)
      #obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块
      obj-m := chrdevbase.o
      
      build: kernel_modules
      
      #具体的编译命令,后面的 modules 表示编译模块, -C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。 
      #M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件
      kernel_modules:
      	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
      clean:
      	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
      
  2. 编译应用程序

    • arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
      
    • 查看文件类型

      file chrdevbaseApp
      
  3. 测试程序

    • 为了方便测试, Linux 系统选择通过 TFTP 从网络启动,并且使用 NFS 挂载网络根文件系统,确保 uboot 中 bootcmd 环境变量的值为:

      tftp 80800000 zImage;tftp 83000000 imx6ull-lux-emmc.dtb;bootz 80800000 - 83000000
      

      bootargs 环境变量的值为:

      console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.31.224:/home/lux/Linux/nfs/lux_rootfs ip=192.168.31.55:192.168.31.224:192.168.31.1:255.255.255.0::eth0:off
      
    • 设置好以后启动 Linux 系统,检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。 注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。不一样的是后面的“4.1.15”,这里要根据你所使用的 Linux 内核版本来设置否则 modprobe 命令无法加载驱动模块

    • 将 chrdevbase.ko 和 chrdevbaseAPP 复制到 rootfs/lib/modules/4.1.15 目录中,命令如下:

      sudo cp chrdevbase.ko chrdevbaseApp /home/lux/Linux/nfs/lux_rootfs/lib/modules/4.1.15/ -f
      
    • 在开发板终端安装模块

      • 使用insmod安装

      • 使用rmmod卸载

      • 使用modprobe安装,如果报错"can`t open ‘modules.dep’:no such file or directory",执行

        depmod//即可
        
      • 安装完成

      • 查看当前系统安装的模块

        lsmod
        
      • 查看当前系统中有没有chrdevbase这个设备

        cat /proc/devices
        
    • 创建设备结点文件(应用程序就是通过操作这个设备节点文件来完成对具体设备的操作 )

      mknod /dev/chrdevbase c 200 0
      

      其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看

    • 应用程序测试

      • 读操作

        ./chrdevbaseApp /dev/chrdevbase 1
        
      • 写操作

        ./chrdevbaseApp /dev/chrdevbase 2
        
    • 卸载模块

      rmmod chrdevbase.ko
      
本文含有隐藏内容,请 开通VIP 后查看