手把手带你写一个中断输入设备驱动
今天群里有人問,要開始驅動開發(fā)的話從什么開始比較好。
我說,應該開始去摸索觸摸屏驅動,現在我想了下,觸摸屏驅動可能會難了些,但是從一個GPIO開始,我覺得一定是一件很容易的事情。
所以這篇文章就來了。
大家好,這是我的朋友逸珺。
首先說聲抱歉,最近迷上釣魚了,有時候晚上出去夜釣大板鯽了,停更了一段時間。來幾張魚獲圖片:
技術還是不太到家,遇到幾次大鯉魚都給溜了,心有不甘,所以最近花了比較多的時間。言歸正傳,今天來分享一下以前寫一個中斷輸入設備驅動案例,希望對有需要的朋友能有所幫助。
背景介紹
在一個項目中,有這樣一個需求:
主控芯片采用ZYNQ,需要采集外部一個脈沖編碼輸入信號,這個信號是一個脈沖波形,脈沖數量代表測量結果。比如這有可能是一個電機的霍爾信號輸出,代表電機的轉速,也有可能是一個光柵編碼器的脈沖輸出,是什么并不重要。
這個電路本身,利用光耦實現了輸入測設備信號與采集端的電氣隔離。由于PS端該Bank的電平為3.3V,所以光耦的另一側也是3.3V。
ZYNQ的PS端運行Linux程序,所以在這個場景下,要從應用程序的角度將外部輸入信號用起來,就需要實現這樣一個設備驅動程序:
創(chuàng)建設備
在ZYNQ下,使用petalinux工具鏈,當然本文中對于寫這個驅動程序本身換成其他的處理器從代碼的角度是類似的。
1.先運行一下工具鏈環(huán)境變量腳本:
source?/opt/pkg/petalinux/settings.sh當然也可以不用手動這樣運行,設置成linux開發(fā)主機開機自動運行,這里就不贅述怎么設置了,網上很多介紹。
2.創(chuàng)建設備
petalinux-create?-t?modules?--name?di-drv這樣在現有的工程下,就自動創(chuàng)建設備文件:
./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?<linux/module.h>?? #include?<linux/kernel.h> #include?<linux/init.h>?? #include?<linux/ide.h>?? #include?<linux/types.h>?? #include?<linux/errno.h> #include?<linux/cdev.h> #include?<linux/of.h> #include?<linux/of_address.h> #include?<linux/of_gpio.h> #include?<linux/device.h> #include?<linux/delay.h> #include?<linux/init.h> #include?<linux/gpio.h> #include?<linux/semaphore.h> #include?<linux/timer.h> #include?<linux/of_irq.h> #include?<linux/irq.h> #include?<asm/uaccess.h> #include?<asm/mach/map.h> #include?<asm/io.h>/*?設備節(jié)點名稱?*/?? #define?DEVICE_NAME???????"di-drv" /*?設備號個數?*/?? #define?DEVID_COUNT???????1 /*?驅動個數?*/?? #define?DRIVE_COUNT???????1 /*?主設備號?*/ #define?MAJOR_U /*?次設備號?*/ #define?MINOR_U???????????0struct?di_dev?{/*?字符設備框架?*/dev_t???????? devid;?//設備號struct?cdev?????cdev;??//字符設備struct?class????*class;??//類struct?device??? *device;?//設備struct?device_node?*nd;???//設備樹的設備節(jié)點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系統(tǒng)調用函數的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系統(tǒng)調用函數的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系統(tǒng)調用函數的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框架?**/???/*?獲取設備節(jié)點?*/di_char.nd?=?of_find_node_by_path("/di");if(di_char.nd?==?NULL){printk("di?node?not?foundr\r\n");return?-EINVAL;}/*?獲取節(jié)點中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);/*?創(chuàng)建類?*/di_char.class?=?class_create(THIS_MODULE,?DEVICE_NAME);if(IS_ERR(di_char.class))?{return?PTR_ERR(di_char.class);}/*?創(chuàng)建設備節(jié)點?*/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);/*?刪除設備節(jié)點?*/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,使能剛剛創(chuàng)建的模塊,退出保存。
運行下面的命令進行編譯:
petalinux-build最終在工程目錄下,搜索di-drv.ko,就得到這個驅動的內核模塊文件了,拷貝到目標板的某個文件夾下,運行下面的命令裝載就完成了:
insmod?di-drv.ko這樣在/dev下就會發(fā)現新增一個di-drv設備。
當然也可以直接將該驅動放進內核里,這就需要在內核代碼樹里,添加文件了,這個思路之前有分享過。
總結一下
字符設備是做驅動開發(fā)比較容易掌握的驅動類型,也是大多數項目中,需要自己動手寫的最多的驅動類型。所以還是應該掌握它。才能實現不同的項目需求。至于用戶空間怎么訪問這個設備,這里就不贅述了,一個文件打開操作,再來一個讀取操作就完事了。
推薦閱讀:
專輯|Linux文章匯總
專輯|程序人生
專輯|C語言
我的知識小密圈
關注公眾號,后臺回復「1024」獲取學習資料網盤鏈接。
歡迎點贊,關注,轉發(fā),在看,您的每一次鼓勵,我都將銘記于心~
總結
以上是生活随笔為你收集整理的手把手带你写一个中断输入设备驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海康威视摄像头激活失败的几个原因和方法
- 下一篇: 嵌入式真的没前途?