字符设备驱动模版-中断

字符设备驱动模版-中断

思维导图在线高清查看:https://www.helloimg.com/i/2025/01/27/679791b5257c0.png
在这里插入图片描述

修改设备树

1添加pinctrl节点

  • 1创建对应的节点

    • 在 iomuxc 节点的 imx6ul-evk 子节点下
  • 2添加“fsl,pins”属性

  • 3在“fsl,pins”属性中添加PIN配置信息

    • imx6ul-pinfunc.h

2添加设备节点

  • 1创建设备节点

  • 2描述设备树节点的地址和长度所占大小

  • 3指定设备的兼容性

  • 4指定设备所使用的引脚控制器的名称,default默认值

  • 5添加pinctrl信息

  • 6添加GPIO属性信息

  • 7指定中断控制器的节点路径

  • 8设置中断源

  • 9指定设备的状态

3检查PIN是否被其他外设使用

  • 检查 pinctrl 设置

  • 如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用

4查看节点是否创建成功

  • 1编译make dtbs

  • 2拷贝sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/alientek/linux/tftp/ -f

  • 3启动开发板

    • cd /proc/device-tree/

    • cd 节点/

    • ls

驱动程序

1基本宏定义

2中断 IO 描述结构体

  • struct irq_keydesc {
    }

    • 使用 irq_keydesc 结构体即可描述一个按键中断
  • 1gpio

    • int gpio
  • 2中断号

    • int iqrnum
  • 3按键对应的键值

    • unsigned char value
  • 4名字

    • char name[10]
  • 5中断服务函数

    • irqreturn_t (*handler)(int, void *)

3设备结构体

  • struct imx6uirq_dev{
    }

  • 1设备号

    • dev_t devid
  • 2cdev

    • struct cdev cdev
  • 3类

    • struct class *class
  • 4设备

    • struct device *device
  • 5主设备号

    • int major
  • 6次设备号

    • int minor
  • 7设备节点

    • struct device_node *nd
  • 8有效的按键键值

    • atomic_t keyvalue
  • 9标记是否完成一次完成的按键

    • atomic_t releasekey
  • 10定义一个定时器

    • struct timer_list timer
  • 11按键描述数组

    • struct irq_keydesc irqkeydesc[KEY_NUM]
  • 12当前的按键号

    • unsigned char curkeynum

4中断服务函数

  • static irqreturn_t key0_handler(int irq, void *dev_id)

    • @param - irq : 中断号

    • @param - dev_id : 设备结构

    • @return : 中断执行结果

  • 具体步骤

    • 1将 dev_id 转换为 struct imx6uirq_dev 结构体指针,并将其赋值给 dev 变量

      • 这样可以通过 dev 变量来访问设备的其他成员
    • 2具体服务操作

  • 用到的其他函数

    • 修改定时值

      • int mod_timer(struct timer_list *timer, unsigned long expires)

        • 如果定时器还没有激活的话,mod_timer 函数会激活定时器
      • timer:要修改超时时间(定时值)的定时器

      • expires:修改后的超时时间

      • 返回值:0,调用 mod_timer 函数前定时器未被激活;1,调用 mod_timer 函数前定时器已
        被激活

5定时器服务函数

  • void timer_function(unsigned long arg)

    • @param – arg : 设备结构变量

    • @return : 无

  • 具体步骤

    • 1将 arg 转换为 struct imx6uirq_dev 结构体指针,并将其赋值给 dev 变量

      • 这样可以通过 dev 变量来访问设备的其他成员
    • 2具体服务操作

  • 用到的其他函数

    • 1获取某个IO的值

      • #define gpio_get_value __gpio_get_value
        int __gpio_get_value(unsigned gpio)

      • gpio:要获取的 GPIO 标号

      • 返回值:非负值,得到的 GPIO 值;负值,获取失败

    • 2给原子变量赋值

      • void atomic_set(atomic_t *v, int i)

      • 向 v 写入 i 值

6初始化外设IO

  • static int 设备_init(void)

  • 具体步骤

    • 1获取设备节点

    • 2提取 GPIO

    • 3初始化 key 所使用的 IO,并且设置成中断模式

      • 1缓冲区清零

      • 2组合名字

      • 3申请GPIO

      • 4设置GPIO为输入

      • 5获取中断号

    • 4申请中断

    • 5创建定时器

  • 用到的其他函数

    • 1通过路径来查找指定的节点

      • of_find_node_by_path(const char *path)

      • path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个
        节点的全路径。

      • 返回值:找到的节点,如果为 NULL 表示查找失败

    • 2获取 GPIO 编号

      • int of_get_named_gpio
        (struct device_node *np,
        const char *propname,
        int index

      • np:设备节点

      • propname:包含要获取 GPIO 信息的属性名

      • index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO
        的编号,如果只有一个 GPIO 信息的话此参数为 0

      • 返回值:正值,获取到的 GPIO 编号;负值,失败

    • 3将一块内存区域的内容设置为指定的值

      • void *memset(void *ptr, int value, size_t num)

      • 常见用途是将内存区域清零,即将所有字节设置为0

      • ptr:指向要设置值的内存区域的指针

      • value:要设置的值,以整数形式表示

      • num:要设置的字节数

    • 4将格式化的数据写入字符串缓冲区

      • str:指向目标字符串缓冲区的指针,用于存储格式化后的数据

      • format:格式化字符串,指定了要写入缓冲区的数据的格式

      • …:可变参数列表,根据 format 字符串中的格式化指示符,指定要写入缓冲区的数据

    • 5申请一个 GPIO 管脚

      • int gpio_request(unsigned gpio, const char *label)

      • gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。

      • label:给 gpio 设置个名字

      • 返回值:0,申请成功;其他值,申请失败

    • 6设置某个 GPIO 为输入

      • int gpio_direction_input(unsigned gpio)

      • gpio:要设置为输出的 GPIO 标号

      • 返回值:0,设置成功;负值,设置失败

    • 7获取中断号

      • unsigned int irq_of_parse_and_map(struct device_node *dev,
        int index)

        • 可以处理未注册的GPIO引脚,并且更加灵活
      • dev:设备节点

      • index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息

      • 返回值:中断号

    • 8申请中断

      • int request_irq(unsigned int irq,
        irq_handler_t handler,
        unsigned long flags,
        const char *name,
        void *dev)

      • irq:要申请中断的中断号

      • handler:中断处理函数,当中断发生以后就会执行此中断处理函数

      • flags:中断标志

      • name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字

      • dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将
        dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数

      • 返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经
        被申请了

    • 9初始化 timer_list 类型变量

      • void init_timer(struct timer_list *timer)

      • timer:要初始化定时器

      • 返回值:没有返回值

7打开设备

  • static 设备_open(struct inode *inode, struct file *filp)

    • @param – inode : 传递给驱动的 inode,文件和目录的元数据结构

    • @param - filp : 文件指针结构体(文件描述)

    • @return : 0成功;其他 失败

  • 具体步骤

    • 1设置私有数据

8从设备读取数据

  • static ssize_t 设备_read(struct file *filp, char __user *buf,
    size_t cnt, loff_t *offt)

    • @param – filp : 要打开的设备文件(文件描述符)

    • @param – buf : 返回给用户空间的数据缓冲区

    • @param - cnt : 要读取的数据长度

    • @param – offt : 相对于文件首地址的偏移

    • @return : 读取的字节数,如果为负值,表示读取失败

  • 具体步骤

    • 判断是否按下

      • 按下

        • 判断是否属于松开后按下

          • 松开后按下

            • 最高位置1
          • 未松开后按下

            • return -EINVAL
          • 按下标志清零

      • 未按下

        • return -EINVAL
  • 用到的其他函数

    • 1内核空间的数据到用户空间的复制

      • static inline long copy_to_user(void __user *to, const void *from, unsigned long n)

      • to 表示目的

      • from 表示源

      • n 表示要复制的数据长度

      • 返回值:如果复制成功,返回值为 0,如果复制失败则返回负数

    • 2给原子变量赋值

      • void atomic_set(atomic_t *v, int i)

      • 向 v 写入 i 值

9设备操作函数

10驱动入口函数

  • static int __init 设备_init(void)

  • 用到的其他函数

    • 1初始化自旋锁

      • int spin_lock_init(spinlock_t *lock)

      • lock:指向 spinlock_t 类型的自旋锁变量的指针

      • 返回值是一个整数 int,表示初始化是否成功。通常情况下,返回值为 0 表示初始化成功,非零值表示初始化失败

    • 2将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号

      • MKDEV(ma,mi)
    • 3注册设备号

      • int register_chrdev_region(dev_t from, unsigned count, const char *name)

        • 定了设备的主设备号和次设备号
      • int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

        • 没有指定设备号
      • from 是要申请的起始设备号,也就是给定的设备号

      • count 是要申请的数量,一 般都是一个

      • name 是设备名字

      • 返回值是一个整数 int,表示注册是否成功。通常情况下,返回值为 0 表示注册成功,负数表示注册失败

    • 4初始化cdev

      • void cdev_init(struct cdev *cdev, const struct file_operations *fops)

      • cdev 就是要初始化的 cdev 结构体变量

      • fops 就是字符设备文件操作函数集合

    • 5向 Linux 系统添加字符设备(cdev 结构体变量)

      • int cdev_add(struct cdev *p, dev_t dev, unsigned count)

      • p 指向要添加的字符设备(cdev 结构体变量

      • dev 就是设备所使用的设备号

      • count 是要添加的设备数量

      • 返回值是一个整数 int,表示添加是否成功。通常情况下,返回值为 0 表示添加成功,负数表示添加失败

    • 6类创建

      • struct class *class_create (struct module *owner, const char *name)

        • 是一个·宏函数
      • owner 一般为 THIS_MODULE

      • name 是类名字

      • 返回值是个指向结构体 class 的指针,也就是创建的类

    • 7创建设备

      • struct device *device_create(struct class *class,
        struct device *parent,
        dev_t devt,
        void *drvdata,
        const char *fmt, …)

      • class 就是设备要创建哪个类下面

      • 参数 parent 是父设备,一般为 NULL,也就是没有父设备

      • devt 是设备号

      • drvdata 是设备可能会使用的一些数据,一般为 NULL

      • fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx 这个设备文件

      • 返回值就是创建好的设备

    • 8初始化 timer_list 类型变量

      • void init_timer(struct timer_list *timer)

      • timer:要初始化定时器

      • 返回值:没有返回值

  • 具体步骤

    • 1初始化自旋锁

    • 2注册字符设备驱动

      • 1注册设备号

        • 1.1指定设备号

          • 1.1-1主设备号和次设备号合并为一个设备号

          • 1.1-2申请设备号

        • 1.2未指定设备号

          • 1.2-1申请设备号

          • 1.2-2获取主设备号

          • 1.2-3获取次设备号

        • 1.3输出主次设备号信息

      • 2初始化cdev

      • 3添加cdev

      • 4创建类

      • 5创建设备

      • 6初始化按键

        • 1初始化按键有效值为无效

        • 2初始化按键是否按下标志位为0

        • 3调用按键初始化函数

11驱动出口函数

  • static void __exit 设备_exit(void)

  • 用到的其他函数

    • 1用于设置某个GPIO的值

      • void __gpio_set_value(unsigned gpio, int value)

      • gpio:要设置的 GPIO 标号

      • value:要设置的值

    • 2删除定时器

      • int del_timer_sync(struct timer_list *timer)

        • 会等待其他处理器使用完定时器再删除
      • int del_timer(struct timer_list * timer)

        • 不管定时器有没有被激活,都可以使用此函数删除
      • timer:要删除的定时器

      • 返回值:0,定时器还没被激活;1,定时器已经激活

    • 3从 Linux 内核中删除相应的字符设备

      • void cdev_del(struct cdev *p)

      • p 就是要删除的字符设备

    • 4设备号释放

      • void unregister_chrdev_region(dev_t from, unsigned count)

      • from:要释放的设备号

      • count:表示从 from 开始,要释放的设备号数量

    • 5设备删除

      • void device_destroy(struct class *class, dev_t devt)

      • class 是要删除的设备所处的类

      • devt 是要删除的设备号

    • 6类删除

      • void class_destroy(struct class *cls)

      • cls 就是要删除的类

    • 7释放中断

      • void free_irq(unsigned int irq,
        void *dev)

      • irq:要释放的中断

      • dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断
        只有在释放最后中断处理函数的时候才会被禁止掉。

  • 具体步骤

    • 1删除定时器

    • 2释放中断

    • 3注销字符设备驱动

      • 1删除cdev

      • 2注销设备号

      • 3注销设备

      • 4注销类

12指定出入口

  • module_init(xxx_init)

  • module_exit(xxx_exit)

13LICENSE作者信息

  • MODULE_LICENSE(“”)

  • MODULE_AUTHOR(“”)

测试APP

实现的内容如下:

通过不断的读取/dev/imx6uirq 文件来获取按键值,当按键
按下以后就会将获取到的按键值输出在终端上

具体步骤

  • 主程序

    • 1判断命令参数传输数量是否正确

    • 2打开设备驱动

    • 3while语句

      • 1读取命令

      • 2判断命令,执行具体操作

    • 4关闭文件

用到的其他函数

  • 1打开设备驱动

    • int open(const char *pathname, int flags)

    • pathname:要打开的设备或者文件名

    • flags:文件打开模式

    • 返回值:如果文件打开成功的话返回文件的文件描述符

  • 2接收键盘数据

    • int scanf(const char *format, …);

    • format 是格式控制字符串,指定了要读取的数据的类型和格式

    • … 表示可变参数,用于指定要读取的变量

    • 返回值:返回非1表示参数输入错误

  • 3读取一行字符串并存储到指定的字符数组中

    • char *gets(char *str)

    • str 存储的数组

  • 4向驱动发送控制信息

    • 对应应用程序的timer_unlocked_ioctl函数

    • ioctl(fd, cmd, arg)

    • filp 是对应的设备文件

    • cmd 是应用程序发送过来的命令信息

    • arg 是应用程序发送过来的参数

运行测试

编译

  • 修改Makefile

  • make -j32

  • arm-linux-gnueabihf-gcc ledApp.c -o ledApp

将驱动程序和APP移到根文件下

  • sudo cp chrdevbase.ko chrdevbaseApp /home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -f

运行

  • 1进入到目录 lib/modules/4.1.15 中

  • 2depmod //第一次加载驱动的时候需要运行此命令

  • 3modprobe gpioled.ko //加载驱动

    • 报错

      • modprobe: module ath.ko not found in modules.dep

        • 编辑modules.dep
          vim /lib/modules/$(shell uname -r)/modules.dep
          增加:
          kernel/drivers/wifi/ath.ko: //其中kernel/drivers/wifi/为驱动文件路径
  • 4输入“lsmod”命令即可查看当前系统中存在的模块

  • 5查看中断是否成功注册

    • cat /proc/interrupts
  • 6用命令测试

    • ./ledApp /dev/gpioled 1 //打开 LED 灯

    • ./ledApp /dev/gpioled 0 //关闭 LED 灯

  • 7卸载驱动

    • rmmod gpioled.ko

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/68415.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SH】Windows禁用Alt+F4关机、重启、注销等功能,只保留关闭应用的功能

文章目录 组策略编辑器参考文档 组策略编辑器 亲测有效! 1、按winr,输入gpedit.msc,回车。 2、找到》用户配置》管理模板》“开始”菜单和任务栏。 3、在右侧找到删除并阻止访问“关机”、“重新启动”、“睡眠”和“休眠”命令&#xff0c…

【深度学习】线性回归的简洁实现

线性回归的简洁实现 在过去的几年里,出于对深度学习强烈的兴趣,许多公司、学者和业余爱好者开发了各种成熟的开源框架。 这些框架可以自动化基于梯度的学习算法中重复性的工作。 目前,我们只会运用: (1)通…

C++中的显式构造和隐式构造

文章目录 一、概述二、显式构造函数的使用三、隐式构造函数的使用四、显式和隐式的适用场景 一、概述 在 C 中,构造函数可以分为 显式构造 和 隐式构造,它们的区别主要体现在构造函数的调用方式上。 1.显式构造(Explicit Constructor&#…

A7. Jenkins Pipeline自动化构建过程,可灵活配置多项目、多模块服务实战

服务容器化构建的环境配置构建前需要解决什么下面我们带着问题分析构建的过程:1. 如何解决jenkins执行环境与shell脚本执行环境不一致问题?2. 构建之前动态修改项目的环境变量3. 在通过容器打包时避免不了会产生比较多的不可用的镜像资源,这些资源要是不及时删除掉时会导致服…

浅谈文献阅读(reference)对留学论文写作的重要性

很多留学生在写作留学论文时,拿到题目后就急于求成立马动笔写作。可是写着写着就会陷入非常迷惘的境地,不知道如何继续。当然这其中有很多原因,但其中最重要的一条,就是在写作英语论文之前,没有进行足够的知识积累&…

提升企业内部协作的在线知识库架构与实施策略

内容概要 在当前快速变化的商业环境中,企业对于提升内部协作效率的需求愈显迫切。在线知识库作为信息存储与共享的平台,成为了推动企业数字化转型的重要工具。本文将深入探讨如何有效打造与实施在线知识库,强调架构设计、知识资产分类管理及…

网络工程师 (3)指令系统基础

一、寻址方式 (一)指令寻址 顺序寻址:通过程序计数器(PC)加1,自动形成下一条指令的地址。这是计算机中最基本、最常用的寻址方式。 跳跃寻址:通过转移类指令直接或间接给出下一条指令的地址。跳…

【数据结构】_以SLTPushBack(尾插)为例理解单链表的二级指针传参

目录 1. 第一版代码 2. 第二版代码 3. 第三版代码 前文已介绍无头单向不循环链表的实现,详见下文: 【数据结构】_不带头非循环单向链表-CSDN博客 但对于部分方法如尾插、头插、任意位置前插入、任意位置前删除的相关实现,其形参均采用了…

【Samba】Ubuntu20.04 Windows 共享文件夹

【Samba】Ubuntu20.04 Windows 共享文件夹 前言整体思路检查 Ubuntu 端 和 Windows 网络通信是否正常创建共享文件夹安装并配置 Samba 服务器安装 Samba 服务器创建 Samba 用户编辑 Samba 配置文件重启 Samba 服务器 在 Windows 端 访问 Ubuntu 的共享文件夹 前言 本文基于 Ub…

Linux初识——基本指令(2)

本文将继续从上篇末尾讲起,讲解我们剩下的基本指令 一、剩余的基本指令 1、mv mv指令是move(移动)的缩写,其功能为:1.剪切文件、目录。2.重命名 先演示下重命名,假设我想把当前目录下的di34改成dir5 那…

Android - 通过Logcat Manager简单获取Android手机的Log

由于工作需要,经常需要获取Android手机的Log。 平常都是通过adb命令来获取,每次都要写命令。 偶然的一个机会,我从外网发现了一个工具 Logcat Manager,只需要通过简单的双击即可获取Android的Log,这里也分享一下。 目…

c++学习第十三天

创作过程中难免有不足,若您发现本文内容有误,恳请不吝赐教。 提示:以下是本篇文章正文内容,下面案例可供参考 一、vector 1.介绍 1. vector是表示可变大小数组的序列容器。 2. 就像数组一样,vector也采用的连续存储空…

「数学::质数」分解质因子 / LeetCode 2521(C++)

概述 由算数基本定理,我们知道任意一个大于1的自然数可以表示为一些质数的乘积: LeetCode 2521: 给你一个正整数数组 nums ,对 nums 所有元素求积之后,找出并返回乘积中 不同质因数 的数目。 注意: 质数 是…

docker-compose Zookeeper 集群搭建

文章目录 前言docker-compose Zookeeper 集群搭建1. Zookeeper下载2. 制作Dockerfile文件3. 构建镜像4. docker-compose 管理5. docker-compose构建/启动6. 验证6.1 docker ps6.2 使用 zkCli.sh 连接并验证集群 前言 如果您觉得有用的话,记得给博主点个赞&#xff0…

WIN11 UEFI漏洞被发现, 可以绕过安全启动机制

近日,一个新的UEFI漏洞被发现,可通过多个系统恢复工具传播,微软已经正式将该漏洞标记为追踪编号“CVE-2024-7344”。根据报告的说明,该漏洞能让攻击者绕过安全启动机制,并部署对操作系统隐形的引导工具包。 据TomsH…

R语言学习笔记之高效数据操作

一、概要 数据操作是R语言的一大优势,用户可以利用基本包或者拓展包在R语言中进行复杂的数据操作,包括排序、更新、分组汇总等。R数据操作包:data.table和tidyfst两个扩展包。 data.table是当前R中处理数据最快的工具,可以实现快…

【数据结构】 并查集 + 路径压缩与按秩合并 python

目录 前言模板朴素实现路径压缩按秩合并按树高为秩按节点数为秩 总结 前言 并查集的基本实现通常使用森林来表示不同的集合,每个集合用一棵树表示,树的每个节点有一个指向其父节点的指针。 如果一个节点是它自己的父节点,那么它就是该集合的代…

低代码系统-产品架构案例介绍、得帆云(八)

产品名称 得帆云DeCode低代码平台-私有化 得帆云DeMDM主数据管理平台 得帆云DeCode低代码平台-公有云 得帆云DePortal企业门户 得帆云DeFusion融合集成平台 得帆云DeHoop数据中台 名词 概念 云原生 指自己搭建的运维平台,区别于阿里云、腾讯云 Dehoop 指…

【PyTorch】5.张量索引操作

目录 1. 简单行、列索引 2. 列表索引 3. 范围索引 4. 布尔索引 5. 多维索引 个人主页:Icomi 在深度学习蓬勃发展的当下,PyTorch 是不可或缺的工具。它作为强大的深度学习框架,为构建和训练神经网络提供了高效且灵活的平台。神经网络作为…

基于微信小程序高校课堂教学管理系统 课堂管理系统微信小程序(源码+文档)

目录 一.研究目的 二.需求分析 三.数据库设计 四.系统页面展示 五.免费源码获取 一.研究目的 困扰管理层的许多问题当中,高校课堂教学管理也是不敢忽视的一块。但是管理好高校课堂教学又面临很多麻烦需要解决,如何在工作琐碎,记录繁多的情况下将高校课堂教学的当前情况反…