[導讀] 大家好,我是逸珺。
首先說聲抱歉,最近迷上釣魚了,有時候晚上出去夜釣大板鯽了,停更了一段時間。來幾張魚獲圖片:

言歸正傳,今天來分享一下以前寫一個中斷輸入設備驅動案例,希望對有需要的朋友能有所幫助。
在一個項目中,有這樣一個需求:

主控芯片采用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 = <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>
/* 設備節點名稱 */
#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設備。
當然也可以直接將該驅動放進內核里,這就需要在內核代碼樹里,添加文件了,這個思路之前有分享過。
字符設備是做驅動開發比較容易掌握的驅動類型,也是大多數項目中,需要自己動手寫的最多的驅動類型。所以還是應該掌握它。才能實現不同的項目需求。至于用戶空間怎么訪問這個設備,這里就不贅述了,一個文件打開操作,再來一個讀取操作就完事了。
—— The End ——
歡迎轉發、留言、點贊、分享,感謝您的支持!