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


    字符设备驱动开发

    配置开发环境

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

    • Ctrl+Shift+P//打开控制台
      
      • 1
    • C/C++:Edit configuration(JSON)
      
      • 1
    • 在生成的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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

    编写程序

    1. 编写驱动程序chrdevbase.c

      #include 
      #include 
      #include 
      #include 
      #include 
      #include 
      
      #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");
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 字符设备注册函数

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

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

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

        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:设备名字
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
      • 释放设备号

        void unregister_chrdev_region(dev_t from, unsigned count)
        /*
        此函数有两个参数:
        from:要释放的设备号。
        count: 表示从 from 开始,要释放的设备号数量。
        */
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
    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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74

    编译测试程序

    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
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
    2. 编译应用程序

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

        file chrdevbaseApp
        
        • 1
    3. 测试程序

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

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

        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
        
        • 1
      • 设置好以后启动 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
        
        • 1
      • 在开发板终端安装模块

        • 使用insmod安装

        • 使用rmmod卸载

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

          depmod//即可
          
          • 1
        • 安装完成

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

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

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

        mknod /dev/chrdevbase c 200 0
        
        • 1

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

      • 应用程序测试

        • 读操作

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

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

        rmmod chrdevbase.ko
        
        • 1
  • 相关阅读:
    泰山OFFICE技术讲座:文字边框高度研究
    冥想第六百二十三天
    前端智能化实践——可微编程
    Java版工程行业管理系统源码-专业的工程管理软件- 工程项目各模块及其功能点清单
    c++ 标准库
    Reactor 之 手把手教你 Spring Boot 整合 Reactor
    【差分演化算法相关文献总结】
    Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day5】 —— 基础篇2
    当GPT遇到程序分析:在GPTScan中实现智能合约逻辑漏洞检测
    【linux命令讲解大全】045.网络数据分析利器:深度解读 tcpdump 抓包工具的使用方法
  • 原文地址:https://blog.csdn.net/weixin_43739167/article/details/126312526