手把手带你写一个中断输入设备驱动

嵌入式大杂烩 2022-06-06 21:35

今天来分享一下以前写一个中断输入设备驱动案例,希望对有需要的朋友能有所帮助。

背景介绍

在一个项目中,有这样一个需求:

主控芯片采用ZYNQ,需要采集外部一个脉冲编码输入信号,这个信号是一个脉冲波形,脉冲数量代表测量结果。比如这有可能是一个电机的霍尔信号输出,代表电机的转速,也有可能是一个光栅编码器的脉冲输出,是什么并不重要。

这个电路本身,利用光耦实现了输入测设备信号与采集端的电气隔离。由于PS端该Bank的电平为3.3V,所以光耦的另一侧也是3.3V。

ZYNQ的PS端运行Linux程序,所以在这个场景下,要从应用程序的角度将外部输入信号用起来,就需要实现这样一个设备驱动程序:

创建设备

在ZYNQ下,使用petalinux工具链,当然本文中对于写这个驱动程序本身换成其他的处理器从代码的角度是类似的。

1.先运行一下工具链环境变量脚本:

source /opt/pkg/petalinux/settings.sh

当然也可以不用手动这样运行,设置成linux开发主机开机自动运行,这里就不赘述怎么设置了,网上很多介绍。

2.创建设备

petalinux-create -t modules --name di-drv

这样在现有的工程下,就自动创建设备文件:

./project-spec/meta-user/recipes-modules/di-drv/files/di-drv.c

修改设备树

./project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

中添加

/include/ "system-conf.dtsi" / {   
  amba {
     pinctrl_di_default: di-default {   
       mux {   
         groups = "gpio0_0_grp"; function = "gpio0";   
       };   

       conf {   
          pins = "MIO0";   
          io-standard =;   
          bias-high-impedance;   
          slew-rate =;   
       };   
    };           
  };

  di {
    compatible = "di-drv";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_di_default>;
    di-gpios = <&gpio0 0 0>;   
  };      
};

本文中,假定使用的IO引脚为PS_MIO0。

驱动代码

修改上面生成的代码di-drv.c

#include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  /* 设备节点名称 */ #define DEVICE_NAME "di-drv" /* 设备号个数 */ #define DEVID_COUNT       1 /* 驱动个数 */ #define DRIVE_COUNT       1 /* 主设备号 */ #define MAJOR_U /* 次设备号 */ #define MINOR_U           0 struct di_dev { /* 字符设备框架 */ dev_t devid; //设备号 struct cdev cdev; //字符设备 struct class *class; //类 struct device *device; //设备 struct device_node *nd; //设备树的设备节点 spinlock_t lock; //自旋锁变量 int di_gpio; //DI gpio号 __u32         di_pulses;//DI counter  unsigned int di_irq; //DI 中断号 }; static struct di_dev di_char = { .cdev = {
    .owner = THIS_MODULE,
  },
}; /* 中断服务函数 */ static irqreturn_t di_handler(int irq, void *dev) {
  di_char.di_pulses++; return IRQ_RETVAL(IRQ_HANDLED);
} /* open函数实现, 对应到Linux系统调用函数的open函数 */ static int di_drv_open(struct inode *inode_p, struct file *file_p) {  
  printk("di_drv module opened\n");  
  file_p->private_data = &di_char; return 0;  
} /* read函数实现, 对应到Linux系统调用函数的read操作 */ static ssize_t di_drv_read(struct file *file_p, char __user *buf, size_t len, loff_t *loff_t_p) { unsigned long flags; int ret; union e_int_conv{
  __u8  buf[8];
  __u32 di_raw;
 }; /* 获取锁 */ spin_lock_irqsave(&di_char.lock, flags); union e_int_conv di;
  di.di_raw.di = di_char.di_pulses;
  ret  = copy_to_user(buf, di.buf, 8); /* 释放锁 */ spin_unlock_irqrestore(&di_char.lock, flags); return ret ? ret : 4;
} /* release函数实现, 对应到Linux系统调用函数的close函数 */ static int di_drv_release(struct inode *inode_p, struct file *file_p) {  
  printk("di_drv module release\n"); return 0;  
} /* file_operations结构体声明 */ static struct file_operations di_fops = { .owner   = THIS_MODULE,  
  .open   = di_drv_open,  
  .read   = di_drv_read,     
  .release = di_drv_release,   
}; /* 模块加载时会调用的函数 */ static int __init di_drv_init(void) {
  u32 ret = 0; /* 初始化自旋锁 */ spin_lock_init(&di_char.lock); /** gpio框架 **/ /* 获取设备节点 */ di_char.nd = of_find_node_by_path("/di"); if(di_char.nd == NULL)
  {
    printk("di node not foundr\r\n"); return -EINVAL;
  } /* 获取节点中gpio标号 */ di_char.di_gpio = of_get_named_gpio(di_char.nd, "di-gpios", 0); if(di_char.di_gpio < 0)
  {
    printk("Failed to get di-gpios from device tree\r\n"); return -EINVAL;
  }
  printk("di-gpio num = %d\r\n", di_char.di_gpio); /* 申请gpio标号对应的引脚 */ ret = gpio_request(di_char.di_gpio, "di-drv"); if(ret != 0)
  {
    printk("Failed to request di_gpio\r\n"); return -EINVAL;
  } /* 把这个io设置为输入 */ ret = gpio_direction_input(di_char.di_gpio); if(ret < 0)
  {
    printk("Failed to set di_gpio as input\r\n"); return -EINVAL;
  } /* 获取中断号 */ di_char.di_irq = gpio_to_irq(di_char.di_gpio);
  printk("di_irq number is %d \r\n", di_char.di_irq); /* 申请中断 */ ret = request_irq(di_char.di_irq,
             di_handler,
             IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "di-drv", NULL); if(ret < 0)
  {
     printk("di_irq %d request failed\r\n", di_char.di_irq); return -EFAULT;
  } /* 注册设备号 */ alloc_chrdev_region(&di_char.devid, MINOR_U, DEVID_COUNT, DEVICE_NAME); /* 初始化字符设备结构体 */ cdev_init(&di_char.cdev, &di_fops); /* 注册字符设备 */ cdev_add(&di_char.cdev, di_char.devid, DRIVE_COUNT); /* 创建类 */ di_char.class = class_create(THIS_MODULE, DEVICE_NAME); if(IS_ERR(di_char.class)) 
  { return PTR_ERR(di_char.class);
  } /* 创建设备节点 */ di_char.device = device_create( di_char.class, NULL, 
                    di_char.devid, NULL, 
                    DEVICE_NAME ); if(IS_ERR(di_char.device)) 
  { return PTR_ERR(di_char.device);
  }

  di_char.di_pulses = 0; return 0;  
} /* 卸载模块 */ static void __exit di_drv_exit(void) { /* 释放gpio */ gpio_free(di_char.di_gpio); /* 释放中断 */ free_irq(di_char.di_irq, NULL); /* 注销字符设备 */ cdev_del(&di_char.cdev); /* 注销设备号 */ unregister_chrdev_region(di_char.devid, DEVID_COUNT); /* 删除设备节点 */ device_destroy(di_char.class, di_char.devid); /* 删除类 */ class_destroy(di_char.class);
    
  printk("DI dev exit ok\n");  
} /* 标记加载、卸载函数 */ module_init(di_drv_init);  
module_exit(di_drv_exit); /* 驱动描述信息 */ MODULE_AUTHOR("Embinn");  
MODULE_ALIAS("DI input");  
MODULE_DESCRIPTION("DIGITAL INPUT driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");

这是一个字符驱动的实现,在真实项目中,大部分驱动基本已经被芯片厂商给实现了,但是一些特殊项目的自定义需求,往往就需要去实现自己的驱动。

编译部署

运行以下命令:

petalinux-config -c rootfs

进入modules,使能刚刚创建的模块,退出保存。

运行下面的命令进行编译:

petalinux-build

最终在工程目录下,搜索di-drv.ko,就得到这个驱动的内核模块文件了,拷贝到目标板的某个文件夹下,运行下面的命令装载就完成了:

insmod di-drv.ko

这样在/dev下就会发现新增一个di-drv设备。

当然也可以直接将该驱动放进内核里,这就需要在内核代码树里,添加文件了,这个思路之前有分享过。

总结一下

字符设备是做驱动开发比较容易掌握的驱动类型,也是大多数项目中,需要自己动手写的最多的驱动类型。所以还是应该掌握它。才能实现不同的项目需求。至于用户空间怎么访问这个设备,这里就不赘述了,一个文件打开操作,再来一个读取操作就完事了。

在公众号聊天界面回复1024,可获取嵌入式资源;回复 m ,可查看文章汇总。

嵌入式大杂烩 专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论
  • 2024年12月09日 环洋市场咨询机构出版了一份详细的、综合性的调研分析报告【全球电机控制系统芯片 (SoC)行业总体规模、主要厂商及IPO上市调研报告,2024-2030】。本报告研究全球电机控制系统芯片 (SoC)总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析电机控制系统芯片 (SoC)市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从电机控制系统芯片 (SoC)产品类型细分、应用细分、企业、地区等角度,进行定量和定性分析,包括产量、产值、均价
    GIRtina 2024-12-09 11:32 53浏览
  • 自20世纪60年代问世以来,光耦合器彻底改变了电子系统实现电气隔离和信号传输的方式。通过使用光作为传输信号的媒介,光耦合器消除了直接电气连接的需求,确保了安全性和可靠性。本文记录了光耦合器技术的发展,重点介绍了关键创新、挑战以及这一不可或缺组件的未来发展。 过去:起源和早期应用光耦合器的发明源于处理高压或嘈杂环境的系统对安全电气隔离的需求。早期的光耦合器由LED和光电晶体管的简单组合组成,可提供可靠的隔离,但具有明显的局限性:低速:早期的光耦合器速度慢,频率响应有限,不适合高速数字通信
    腾恩科技-彭工 2024-12-06 16:28 135浏览
  • 开发板在默认情况下,OpenHarmony系统开机后 30 秒会自动息屏,自动息屏会让不少用户感到麻烦,触觉智能教大家两招轻松取消自动息屏。使用触觉智能Purple Pi OH鸿蒙开发板演示,搭载了瑞芯微RK3566四核处理器,Laval鸿蒙社区推荐开发板,已适配全新OpenHarmony5.0 Release系统,SDK源码全开放!SDK源码中修改修改以下文件参数:base/powermgr/power_manager/services/native/profile/power_mode_co
    Industio_触觉智能 2024-12-09 11:39 46浏览
  • “SPI转CAN-FD”是嵌入式开发领域的常用方法,它极大地促进了不同通信接口之间的无缝连接,并显著降低了系统设计的复杂性。飞凌嵌入式依托瑞芯微RK3562J处理器打造的OK3562J-C开发板因为内置了SPI转CAN-FD驱动,从而原生支持这一功能。该开发板特别设计了一组SPI引脚【P8】,专为SPI转CAN-FD应用而引出,为用户提供了极大的便利。MCP2518FD是一款在各行业中都有着广泛应用的CAN-FD控制器芯片,本文就将为大家介绍如何在飞凌嵌入式RK3562J开发板上适配MCP251
    飞凌嵌入式 2024-12-07 14:30 68浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-09 08:53 63浏览
  • 光耦合器对于确保不同电路部分之间的电气隔离和信号传输至关重要。通过防止高压干扰敏感元件,它们可以提高安全性和可靠性。本指南将指导您使用光耦合器创建一个简单的电路,介绍其操作的基本原理和实际实施。光耦合器的工作原理光耦合器包含一个LED和一个光电晶体管。当LED接收到信号时,它会发光,激活光电晶体管,在保持隔离的同时传输信号。这使其成为保护低功耗控制电路免受高压波动影响的理想选择。组件和电路设置对于这个项目,我们将使用晶体管输出光耦合器(例如KLV2002)。收集以下组件:光耦合器、1kΩ电阻(输
    克里雅半导体科技 2024-12-06 16:34 218浏览
  •   在外地五年,回家就是大清洁、大清理,忙活了一个多月,废旧物品实在太多太多!除了老旧或出故障了的家具家电外,就是以前职业生涯养成的研究分析习惯而收留和拆解下的电子电器电脑整机、部件、零件、材料、辅料等等。  家具家电不说了,说说保留的各种各样机械、电器、电气、电子类的吧!有好有坏,扔又舍不得,满满的回忆,历史的烙印。留又没有用,拆解做分析和学习是忙不过来的了,加之,到如今个人做拆解的学习目的已经淡化了,也是因为用不着了。以前是研究学习和促进废物利用,理想也梦想能唤起循环使用产业链,结论是根本不
    自做自受 2024-12-08 22:59 136浏览
  • 在Python中,线程的启动和管理是一个复杂而关键的过程。通过上述代码截图,我们可以深入了解Python中线程启动和处理的具体实现。以下是对图1中内容的详细解析: 4.3.3、启动线程 当调用`rt_thread_startup`函数时,该函数负责将指定线程的状态更改为就绪状态,并将其放入相应优先级的队列中等待调度。这一步骤确保了线程能够被操作系统识别并准备执行。如果新启动的线程的优先级高于当前正在运行的线程,系统将立即切换到这个高优先级线程,以保证重要任务的及时执行。 //```c /*
    丙丁先生 2024-12-06 12:30 110浏览
  • 随着各行各业对可靠、高效电子元件的需求不断增长,国产光耦合器正成为全球半导体市场的重要参与者。这些元件利用先进的制造工艺和研究驱动的创新,弥补了高性能和可负担性之间的差距。本文探讨了国产光耦合器日益突出的地位,重点介绍了其应用和技术进步。 关键技术进步国产光耦合器制造商在提高性能和多功能性方面取得了重大进展。高速光耦合器现在能够处理快速数据传输,使其成为电信和工业自动化中不可或缺的一部分。专为电力电子设计的栅极驱动器光耦合器可确保电动汽车和可再生能源逆变器等高压系统的精确控制。采用碳化
    克里雅半导体科技 2024-12-06 16:34 145浏览
  • 光耦合器以其提供电气隔离的能力而闻名,广泛应用于从电源到通信系统的各种应用。尽管光耦合器非常普遍,但人们对其特性和用途存在一些常见的误解。本文将揭穿一些最常见的误解,以帮助工程师和爱好者做出更明智的决策。 误解1:光耦合器的使用寿命较短事实:虽然光耦合器内部的LED会随着时间的推移而退化,但LED材料和制造工艺的进步已显著提高了其使用寿命。现代光耦合器的设计使用寿命为正常工作条件下的数十年。适当的热管理和在推荐的电流水平内工作可以进一步延长其使用寿命。误解2:光耦合器对于现代应用来说太
    腾恩科技-彭工 2024-12-06 16:29 176浏览
  • 进入11月中下旬,智能手机圈再度热闹起来。包括华为、小米、OPPO、vivo等诸多手机厂商,都在陆续预热发布新机,其中就包括华为Mate 70、小米Redmi K80、vivo的S20,IQOO Neo10等热门新机,这些热门新机的集中上市迅速吸引了全行业的目光。而在诸多手机厂商集体发布新机的背后,是智能手机行业的“触底反弹”。据机构数据显示,2024年第三季度,中国智能手机市场出货量约为6878万台,同比增长3.2%,连续四个季度保持同比增长,显然新一轮手机换机潮已在加速到来。憋了三年,国内智
    刘旷 2024-12-09 10:43 60浏览
  • 学习如何在 MYIR 的 ZU3EG FPGA 开发板上部署 Tiny YOLO v4,对比 FPGA、GPU、CPU 的性能,助力 AIoT 边缘计算应用。(文末有彩蛋)一、 为什么选择 FPGA:应对 7nm 制程与 AI 限制在全球半导体制程限制和高端 GPU 受限的大环境下,FPGA 成为了中国企业发展的重要路径之一。它可支持灵活的 AIoT 应用,其灵活性与可编程性使其可以在国内成熟的 28nm 工艺甚至更低节点的制程下实现高效的硬件加速。米尔的 ZU3EG 开发板凭借其可重
    米尔电子嵌入式 2024-12-06 15:53 155浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦