加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 背景介紹
    • 創(chuàng)建設(shè)備
    •  
    • 修改設(shè)備樹
    • 驅(qū)動(dòng)代碼
    • 編譯部署
    • 總結(jié)一下
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

手把手帶你寫一個(gè)中斷輸入設(shè)備驅(qū)動(dòng)

2021/11/02
508
閱讀需 12 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

大家好,我是逸珺。首先說聲抱歉,最近迷上釣魚了,有時(shí)候晚上出去夜釣大板鯽了,停更了一段時(shí)間。技術(shù)還是不太到家,遇到幾次大鯉魚都給溜了,心有不甘,所以最近花了比較多的時(shí)間。

言歸正傳,今天來分享一下以前寫一個(gè)中斷輸入設(shè)備驅(qū)動(dòng)案例,希望對有需要的朋友能有所幫助。

背景介紹

在一個(gè)項(xiàng)目中,有這樣一個(gè)需求:

主控芯片采用ZYNQ,需要采集外部一個(gè)脈沖編碼輸入信號,這個(gè)信號是一個(gè)脈沖波形,脈沖數(shù)量代表測量結(jié)果。比如這有可能是一個(gè)電機(jī)的霍爾信號輸出,代表電機(jī)的轉(zhuǎn)速,也有可能是一個(gè)光柵編碼器的脈沖輸出,是什么并不重要。

這個(gè)電路本身,利用光耦實(shí)現(xiàn)了輸入測設(shè)備信號與采集端的電氣隔離。由于PS端該Bank的電平為3.3V,所以光耦的另一側(cè)也是3.3V。

ZYNQ的PS端運(yùn)行Linux程序,所以在這個(gè)場景下,要從應(yīng)用程序的角度將外部輸入信號用起來,就需要實(shí)現(xiàn)這樣一個(gè)設(shè)備驅(qū)動(dòng)程序

 

創(chuàng)建設(shè)備

在ZYNQ下,使用petalinux工具鏈,當(dāng)然本文中對于寫這個(gè)驅(qū)動(dòng)程序本身換成其他的處理器從代碼的角度是類似的。

1.先運(yùn)行一下工具鏈環(huán)境變量腳本:

source /opt/pkg/petalinux/settings.sh 

當(dāng)然也可以不用手動(dòng)這樣運(yùn)行,設(shè)置成linux開發(fā)主機(jī)開機(jī)自動(dòng)運(yùn)行,這里就不贅述怎么設(shè)置了,網(wǎng)上很多介紹。

2.創(chuàng)建設(shè)備

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

這樣在現(xiàn)有的工程下,就自動(dòng)創(chuàng)建設(shè)備文件:

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

 

修改設(shè)備樹

./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。

驅(qū)動(dòng)代碼

修改上面生成的代碼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>
  
/* 設(shè)備節(jié)點(diǎn)名稱 */  
#define DEVICE_NAME       "di-drv"
/* 設(shè)備號個(gè)數(shù) */  
#define DEVID_COUNT       1
/* 驅(qū)動(dòng)個(gè)數(shù) */  
#define DRIVE_COUNT       1
/* 主設(shè)備號 */
#define MAJOR_U
/* 次設(shè)備號 */
#define MINOR_U           0

struct di_dev {
  /* 字符設(shè)備框架 */
  dev_t         devid; //設(shè)備號
  struct cdev     cdev;  //字符設(shè)備
  struct class    *class;  //類
  struct device    *device; //設(shè)備
  struct device_node *nd;   //設(shè)備樹的設(shè)備節(jié)點(diǎn)

  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,
  },
};

/* 中斷服務(wù)函數(shù) */
static irqreturn_t di_handler(int irq, void *dev)
{
  di_char.di_pulses++;
  return IRQ_RETVAL(IRQ_HANDLED);
}

/* open函數(shù)實(shí)現(xiàn), 對應(yīng)到Linux系統(tǒng)調(diào)用函數(shù)的open函數(shù) */  
static int di_drv_open(struct inode *inode_p, struct file *file_p)  
{  
  printk("di_drv module openedn");  
  file_p->private_data = &di_char; 
  return 0;  
}  
    
/* read函數(shù)實(shí)現(xiàn), 對應(yīng)到Linux系統(tǒng)調(diào)用函數(shù)的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函數(shù)實(shí)現(xiàn), 對應(yīng)到Linux系統(tǒng)調(diào)用函數(shù)的close函數(shù) */  
static int di_drv_release(struct inode *inode_p, struct file *file_p)  
{  
  printk("di_drv module releasen");
  return 0;  
}  
      
/* file_operations結(jié)構(gòu)體聲明 */  
static struct file_operations di_fops = {  
  .owner   = THIS_MODULE,  
  .open   = di_drv_open,  
  .read   = di_drv_read,     
  .release = di_drv_release,   
};  
  
/* 模塊加載時(shí)會(huì)調(diào)用的函數(shù) */  
static int __init di_drv_init(void)  
{
  u32 ret = 0;
    
  /* 初始化自旋鎖 */
  spin_lock_init(&di_char.lock);
    
  /** gpio框架 **/   
  /* 獲取設(shè)備節(jié)點(diǎn) */
  di_char.nd = of_find_node_by_path("/di");
  if(di_char.nd == NULL)
  {
    printk("di node not foundrrn");
    return -EINVAL;
  }
    
  /* 獲取節(jié)點(diǎn)中g(shù)pio標(biāo)號 */
  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 treern");
    return -EINVAL;
  }
  printk("di-gpio num = %drn", di_char.di_gpio);

  /* 申請gpio標(biāo)號對應(yīng)的引腳 */
  ret = gpio_request(di_char.di_gpio, "di-drv");
  if(ret != 0)
  {
    printk("Failed to request di_gpiorn");
    return -EINVAL;
  }

  /* 把這個(gè)io設(shè)置為輸入 */
  ret = gpio_direction_input(di_char.di_gpio);
  if(ret < 0)
  {
    printk("Failed to set di_gpio as inputrn");
    return -EINVAL;
  }

  /* 獲取中斷號 */
  di_char.di_irq = gpio_to_irq(di_char.di_gpio);
  printk("di_irq number is %d rn", 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 failedrn", di_char.di_irq);
     return -EFAULT;
  }    

  /* 注冊設(shè)備號 */
  alloc_chrdev_region(&di_char.devid, MINOR_U, DEVID_COUNT, DEVICE_NAME);
    
  /* 初始化字符設(shè)備結(jié)構(gòu)體 */
  cdev_init(&di_char.cdev, &di_fops);
    
  /* 注冊字符設(shè)備 */
  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)建設(shè)備節(jié)點(diǎn) */
  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);

  /* 注銷字符設(shè)備 */
  cdev_del(&di_char.cdev);
    
  /* 注銷設(shè)備號 */
  unregister_chrdev_region(di_char.devid, DEVID_COUNT);
    
  /* 刪除設(shè)備節(jié)點(diǎn) */
  device_destroy(di_char.class, di_char.devid);
    
  /* 刪除類 */
  class_destroy(di_char.class);
    
  printk("DI dev exit okn");  
}  
  
/* 標(biāo)記加載、卸載函數(shù) */  
module_init(di_drv_init);  
module_exit(di_drv_exit);  
  
/* 驅(qū)動(dòng)描述信息 */  
MODULE_AUTHOR("Embinn");  
MODULE_ALIAS("DI input");  
MODULE_DESCRIPTION("DIGITAL INPUT driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");  

這是一個(gè)字符驅(qū)動(dòng)的實(shí)現(xiàn),在真實(shí)項(xiàng)目中,大部分驅(qū)動(dòng)基本已經(jīng)被芯片廠商給實(shí)現(xiàn)了,但是一些特殊項(xiàng)目的自定義需求,往往就需要去實(shí)現(xiàn)自己的驅(qū)動(dòng)。

編譯部署

運(yùn)行以下命令:

petalinux-config -c rootfs

進(jìn)入modules,使能剛剛創(chuàng)建的模塊,退出保存。

運(yùn)行下面的命令進(jìn)行編譯:

petalinux-build 

最終在工程目錄下,搜索di-drv.ko,就得到這個(gè)驅(qū)動(dòng)的內(nèi)核模塊文件了,拷貝到目標(biāo)板的某個(gè)文件夾下,運(yùn)行下面的命令裝載就完成了:

insmod di-drv.ko

這樣在/dev下就會(huì)發(fā)現(xiàn)新增一個(gè)di-drv設(shè)備。

當(dāng)然也可以直接將該驅(qū)動(dòng)放進(jìn)內(nèi)核里,這就需要在內(nèi)核代碼樹里,添加文件了,這個(gè)思路之前有分享過。

總結(jié)一下

字符設(shè)備是做驅(qū)動(dòng)開發(fā)比較容易掌握的驅(qū)動(dòng)類型,也是大多數(shù)項(xiàng)目中,需要自己動(dòng)手寫的最多的驅(qū)動(dòng)類型。所以還是應(yīng)該掌握它。才能實(shí)現(xiàn)不同的項(xiàng)目需求。至于用戶空間怎么訪問這個(gè)設(shè)備,這里就不贅述了,一個(gè)文件打開操作,再來一個(gè)讀取操作就完事了。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

嵌入式客棧,主要分享嵌入式Linux系統(tǒng)構(gòu)建、嵌入式Linux驅(qū)動(dòng)開發(fā)、單片機(jī)技術(shù)、FPGA開發(fā)、信號處理、工業(yè)通訊等技術(shù)主題。歡迎關(guān)注,一起交流,共同進(jìn)步!