嵌入式Linux中斷驅動

用過STM32的大概都知道,基本每個GPIO管腳都支持中斷模式,這樣在檢測外部插入一個硬件設備時,通過GPIO管腳電平中斷就非常方便。那麼AM3354的片子是否支持GPIO管腳電平中斷呢?答案是肯定的,下面直接上源碼解析:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <mach/irqs.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <asm/signal.h>

#define GPIO_TO_PIN(bank,gpio) ((32*(bank)) + (gpio))

#define IRQ_GPIO_TEST GPIO_TO_PIN(1,19)      //測試中斷管腳
#define LED_GPIO_TEST GPIO_TO_PIN(1,20)     //LED燈管腳

#define NAME "gpio"                                             //驅動名稱

int irq_num = 0;
static int major = 231;                                         //驅動主設備號

static irqreturn_t irq_proc(int irq,void *dev_id)   //中斷處理程序
{
	printk("gpio interupt\n");
	gpio_set_value(LED_GPIO_TEST,1);	
	return IRQ_HANDLED;
}

int gpio_init(void)    //管腳的初始化
{
    // 檢測該管腳是否被佔用;如同佔用直接返回錯誤;未被佔用直接註冊管腳
    // 第一個參數是管腳編號;第二個參數是其取一個名字
	int result = gpio_request(LED_GPIO_TEST,"led");  
	if(result != 0){
	    printk("gpio_request(LED_GPIO_TEST,led) failed!\n");	
	    return -1;
	}
			
	result = gpio_request(IRQ_GPIO_TEST,"irqtest");
	if(result != 0){
	    printk("gpio_request(IRQ_GPIO_TEST,irqtest) failed!\n");		
	    return -1;
	}
	
	// 設置管腳方向爲輸出,並拉低電平值
	result = gpio_direction_output(LED_GPIO_TEST,0); 
	if(result != 0){
	    printk("gpio_direction_output(LED_GPIO_TEST,0) failed\n");
	    return -1;
	}
		
	// 設置管腳方向爲輸入
	result = gpio_direction_input(IRQ_GPIO_TEST);
	if(result != 0){
	    printk("gpio_direction_input(IRQ_GPIO_TEST) failed\n");
	    return -1;
	}
	
	//獲取中斷號
	irq_num = gpio_to_irq(IRQ_GPIO_TEST);
	//通過中斷號註冊中斷
	//第一個參數:申請的硬件中斷號
	///第二個參數:向系統註冊的中斷處理函數,是一個回調函數,中斷髮生時,系統調用這個函數,dev_id參數將被傳遞給它
	///第三個參數:IRQF_TRIGGER_RISING:上升沿觸發
 				  IRQF_TRIGGER_FALLING:下降沿觸發
				  IRQF_TRIGGER_HIGH:高電平觸發
 				  IRQF_TRIGGER_LOW:低電平觸發
 				  IRQF_SAMPLE_RANDOM:爲系統隨機發生器提供支持
 				  IRQF_SHARED:中斷可在設備間共享
 				  IRQF_DISABLED:是否快速中斷
	///第四個參數:設置中斷名稱,通常是設備驅動程序的名稱  在cat /proc/interrupts中可以看到此名稱
	///第五個參數:中斷共享時會用到,一般設置爲這個設備的設備結構體或者NULL
	
	result = request_irq(irq_num, irq_proc, IRQF_TRIGGER_LOW, "gpio_irq",NULL);
	printk("result === %d  irq_num == %d\n", result, irq_num);
	
	//使能中斷號
	enable_irq(irq_num);
	
	return 0;
}

struct gpio_dev
{
	struct cdev cdev;
	unsigned char value;
};
struct gpio_dev *gpio_devp;

int gpio_release(struct inode *inode,struct file *filp)
{
	gpio_free(LED_GPIO_TEST);
	free_irq(irq_num,NULL);
	return 0;
}

int gpio_open(struct inode *inode,struct file *filp)
{
	struct gpio_dev *dev;
	dev = container_of(inode->i_cdev,struct gpio_dev,cdev);
	filp->private_data = dev;
	
	return 0;
}

long gpio_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
	struct gpio_dev *dev = filp->private_data;
	int ret=0;
	dev->value = arg;
	printk("cmd === %d\n",cmd);
	switch(cmd)
	{
		case 1:
			gpio_set_value(LED_GPIO_TEST,1);	
			break;
		case 2:
			gpio_set_value(LED_GPIO_TEST,0);	
			break;
		default:
		{	
			ret = -1;
			break;	
		}					
	}
	return ret;
}

// 添加file_operations 結構體,這個是字符設備驅動的核心結構,所有的應用層調用的函數最終都會調用這個結構下面定義的函數
struct file_operations gpio_fops =
{
	.owner = THIS_MODULE,
	.unlocked_ioctl = gpio_ioctl,
	.open = gpio_open,
	.release = gpio_release,
};

// __init 宏最常用的地方是驅動模塊初始化函數的定義處,其目的是將驅動模塊的初始化函數放入名叫.init.text的輸入段。
// 當內核啓動完畢後,這個段中的內存會被釋放掉供其他使用。
// 執行insmod命令時就會調用這個函數 
static int __init gpio_start(void)
{
	int ret;
	gpio_init();
	printk(KERN_ALERT "gpio modules is install\n");
	// 註冊字符設備,major參數如果等於0,則表示採用系統動態分配的主設備號
	// 
	ret = register_chrdev(major,NAME,&gpio_fops);
	if(ret < 0)
	{
		printk("unable to register gpio driver!\n");
		return -1;
	}
	printk("able to register gpio driver!\n");
	return 0;
}

// __exit,模塊直接編譯進內核或者不允許卸載,被標誌爲__exit的函數會被自動丟棄掉。
// 執行rmmod命令時就會調用這個函數 
static void __exit gpio_cleanup(void)
{
    gpio_free(LED_GPIO_TEST);
	free_irq(irq_num,NULL);
	// 註銷字符設備
	unregister_chrdev(major,NAME);
}
module_init(gpio_start);    //指定初始化函數
module_exit(gpio_cleanup); 	//指定清除函數

MODULE_AUTHOR("chenhao");   //指定作者
MODULE_LICENSE("GPL");  	//指定代碼使用的許可證
MODULE_VERSION("1.0");	    //指定代碼修訂號

注意,在AM3354中不是所有的管腳都支持中斷,這個需要自己去查詢;系統已經使用過的管腳無法重複使用;在Linux環境下不是編譯好的驅動並查看結果,如下圖:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
但此時在【/dev】目錄下並未發現gpio的字符設備,經查閱資料得知,在【insmod】加載驅動後需先執行【mknod】命令才能夠在【/dev】目錄下出現該字符設備,具體命令如下:

mknod /dev/gpio c 231 1

mknod是創建設備文件,但在創建設備文件之前,一定要先寫驅動程序。
1.在驅動程序中是要註冊你自己的設備的,通過register將主次設備號註冊進一個結構體中。
2.通過mknod命令創建的設備節點:是在/dev目錄下創建相應的設備只是爲了應用程序去使用它提供了途徑,它們之間是通過設備號聯繫在一起的,應用程序觸發中斷後系統會去第一步中的那個結構體中尋找對應的設備進行操作。或者通過在第一步中使用一個classdev結構體【字符設備中】創建一個類,讓系統自動爲你創建設備節點。

運行命令成功後在【/dev】目錄下發現該字符設備,如下圖:
在這裏插入圖片描述