今天来分享一下以前写一个中断输入设备驱动案例,希望对有需要的朋友能有所帮助。
背景介绍
在一个项目中,有这样一个需求:
主控芯片采用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
这样在现有的工程下,就自动创建设备文件:
./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 = <1>;bias-high-impedance; slew-rate = <0>; }; }; };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#defineDEVICE_NAME"di-drv"#defineDEVID_COUNT 1#defineDRIVE_COUNT 1#defineMAJOR_U#defineMINOR_U 0structdi_dev{dev_tdevid;structcdevcdev;structclass*class;structdevice*device;structdevice_node*nd;spinlock_tlock;intdi_gpio;__u32 di_pulses;unsignedintdi_irq;};staticstructdi_devdi_char={ .cdev = { .owner = THIS_MODULE, }, };staticirqreturn_tdi_handler(intirq,void*dev){ di_char.di_pulses++;returnIRQ_RETVAL(IRQ_HANDLED); }staticintdi_drv_open(struct inode *inode_p, struct file *file_p){printk("di_drv module opened\n"); file_p->private_data = &di_char;return0; }staticssize_tdi_drv_read(struct file *file_p,char__user *buf,size_tlen,loff_t*loff_t_p){unsignedlongflags;intret;unione_int_conv{__u8 buf[8]; __u32 di_raw; };spin_lock_irqsave(&di_char.lock, flags);unione_int_convdi;di.di_raw.di = di_char.di_pulses; ret =copy_to_user(buf, di.buf,8);spin_unlock_irqrestore(&di_char.lock, flags);returnret ? ret :4; }staticintdi_drv_release(struct inode *inode_p, struct file *file_p){printk("di_drv module release\n");return0; }staticstructfile_operationsdi_fops={ .owner = THIS_MODULE, .open = di_drv_open, .read = di_drv_read, .release = di_drv_release, };staticint__initdi_drv_init(void){ u32 ret =0;spin_lock_init(&di_char.lock);di_char.nd =of_find_node_by_path("/di");if(di_char.nd ==NULL) {printk("di node not foundr\r\n");return-EINVAL; }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);ret =gpio_request(di_char.di_gpio,"di-drv");if(ret !=0) {printk("Failed to request di_gpio\r\n");return-EINVAL; }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)) {returnPTR_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)) {returnPTR_ERR(di_char.device); } di_char.di_pulses =0;return0; }staticvoid__exitdi_drv_exit(void){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 -crootfs
进入modules,使能刚刚创建的模块,退出保存。
运行下面的命令进行编译:
petalinux-build
最终在工程目录下,搜索di-drv.ko,就得到这个驱动的内核模块文件了,拷贝到目标板的某个文件夹下,运行下面的命令装载就完成了:
insmoddi-drv.ko
这样在/dev下就会发现新增一个di-drv设备。
当然也可以直接将该驱动放进内核里,这就需要在内核代码树里,添加文件了,这个思路之前有分享过。
总结一下
字符设备是做驱动开发比较容易掌握的驱动类型,也是大多数项目中,需要自己动手写的最多的驱动类型。所以还是应该掌握它。才能实现不同的项目需求。至于用户空间怎么访问这个设备,这里就不赘述了,一个文件打开操作,再来一个读取操作就完事了。
原作者:嵌入式客栈逸珺