上篇,學(xué)習(xí)GPIO輸入功能的使用,本篇,來學(xué)習(xí)使用中斷的方式來檢測按鍵的按下。
1 Linux中斷介紹
1.1 中斷的上半部與下半部
中斷處理函數(shù)的執(zhí)行,越快越好,但實(shí)際使用中,某些情況確實(shí)需要比較耗時(shí)是中斷過程,為此,Linux內(nèi)核將中斷分為上半部和下半部兩個(gè)處理部分:
上半部:中斷處理函數(shù),那些處理過程比較快,不會占用很長時(shí)間的處理就可以放在上半部完成
下半部:如果中斷處理過程比較耗時(shí),那么就將這些比較耗時(shí)的代碼提出來,交給下半部去執(zhí)行,這樣中斷處理函數(shù)就會快進(jìn)快出
對于一個(gè)中斷,如何劃分出上下兩部分呢?
對時(shí)間敏感,將其放在上半部
和硬件相關(guān),將其放在上半部
要求不被其他中斷打斷,將其放在上半部
其他所有任務(wù),考慮放在下半部
1.2 下半部的3種實(shí)現(xiàn)方式
1.2.1 軟中斷
Linux內(nèi)核使用softirq_action結(jié)構(gòu)體表示軟中斷:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
一共有 10 個(gè)軟中斷
enum
{
HI_SOFTIRQ = 0, /* 高優(yōu)先級軟中斷 */
TIMER_SOFTIRQ, /* 定時(shí)器軟中斷 */
NET_TX_SOFTIRQ, /* 網(wǎng)絡(luò)數(shù)據(jù)發(fā)送軟中斷 */
NET_RX_SOFTIRQ, /* 網(wǎng)絡(luò)數(shù)據(jù)接收軟中斷 */
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet 軟中斷 */
SCHED_SOFTIRQ, /* 調(diào)度軟中斷 */
HRTIMER_SOFTIRQ, /* 高精度定時(shí)器軟中斷 */
RCU_SOFTIRQ, /* RCU 軟中斷 */
NR_SOFTIRQS
};
要使用軟中斷,必須先使用open_softirq函數(shù)注冊對應(yīng)的軟中斷處理函數(shù):
/**
* nr: 要開啟的軟中斷
* action: 軟中斷對應(yīng)的處理函數(shù)
* return: 無
*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
注冊好軟中斷以后需要通過raise_softirq函數(shù)觸發(fā):
/**
* nr: 要觸發(fā)的軟中斷
* return: 無
*/
void raise_softirq(unsigned int nr)
1.2.2 tasklet
Linux內(nèi)核使用tasklet_struct結(jié)構(gòu)體來表示tasklet:
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一個(gè)tasklet */
unsigned long state; /* tasklet狀態(tài) */
atomic_t count; /* 計(jì)數(shù)器, 記錄對tasklet的引用數(shù) */
void (*func)(unsigned long); /* tasklet執(zhí)行的函數(shù) */
unsigned long data; /* 函數(shù)func的參數(shù) */
};
要使用 tasklet,必須先定義一個(gè)tasklet,然后初始化:
/**
* t: 要初始化的tasklet
* func: tasklet的處理函數(shù)
* data: 要傳遞給func函數(shù)的參數(shù)
* return: 無
*/
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
在上半部(中斷處理函數(shù))中調(diào)用tasklet_schedule函數(shù)就能使tasklet在合適的時(shí)間運(yùn)行:
/**
* t: 要調(diào)度的tasklet
* return: 無
*/
void tasklet_schedule(struct tasklet_struct *t)
1.2.3 工作隊(duì)列
工作隊(duì)列(work queue)是另外一種將中斷的部分工作推后的一種方式,它可以實(shí)現(xiàn)一些tasklet不能實(shí)現(xiàn)的工作,比如工作隊(duì)列機(jī)制可以睡眠。
Linux 內(nèi)核使用work_struct結(jié)構(gòu)體表示一個(gè)工作:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作隊(duì)列處理函數(shù) */
};
這些工作組織成工作隊(duì)列,工作隊(duì)列使用workqueue_struct結(jié)構(gòu)體表示。
在工作隊(duì)列機(jī)制中,將推后的工作交給一個(gè)稱之為工作者線程(worker thread)的內(nèi)核線程去完成。
1.3 中斷API函數(shù)
1.3.1 request_irq中斷請求函數(shù)
/**
* irq: 要申請中斷的中斷號
* handler: 中斷處理函數(shù),當(dāng)中斷發(fā)生以后就會執(zhí)行此中斷處理函數(shù)
* flags: 中斷標(biāo)志
* name: 中斷名字
* dev: 設(shè)備結(jié)構(gòu)體
* return: 0-中斷申請成功, 其他負(fù)值-中斷申請失敗
*/
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
flags中斷標(biāo)志,有下面幾種類型
中斷標(biāo)志 | 描述 |
---|---|
IRQF_SHARED | 多個(gè)設(shè)備共享一個(gè)中斷線, 共享的所有中斷都必須指定此標(biāo)志 |
IRQF_ONESHOT | 單次中斷,中斷執(zhí)行一次就結(jié)束 |
IRQF_TRIGGER_NONE | 無觸發(fā) |
IRQF_TRIGGER_RISING | 上升沿觸發(fā) |
IRQF_TRIGGER_FALLING | 下降沿觸發(fā) |
IRQF_TRIGGER_HIGH | 高電平觸發(fā) |
IRQF_TRIGGER_LOW | 低電平觸發(fā) |
1.3.2 free_irq中斷釋放函數(shù)
/**
* irq: 要釋放中斷的中斷號
* dev: 設(shè)備結(jié)構(gòu)體
* return: 無
*/
void free_irq(unsigned int irq,
void *dev)
1.3.3 irq_handler_t中斷處理函數(shù)
/**
* int: 要處理的中斷號
* void *: 通用指針, 需要與request_irq函數(shù)的dev參數(shù)保持一致
* return: irqreturn_t枚舉類型
*/
irqreturn_t (*irq_handler_t) (int, void *)
irqreturn_t枚舉類型定義:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
1.3.4 中斷使能/禁用函數(shù)
/**
* int: 要使能的中斷號
*/
void enable_irq(unsigned int irq)
/**
* int: 要禁用的中斷號
*/
void disable_irq(unsigned int irq)
1.3.5 獲取中斷號
使用中斷時(shí),中斷信息先寫到了設(shè)備樹里面,然后通過irq_of_parse_and_map函數(shù)從interupts屬性中提取到對應(yīng)的中斷號
/**
* dev: 設(shè)備節(jié)點(diǎn)
* index: 索引號
* return: 中斷號
*/
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
2 軟件編寫
仍使用上篇按鍵實(shí)驗(yàn)中用到的兩個(gè)按鍵:

為了理解簡單,本次程序暫不實(shí)現(xiàn)中斷的下半部邏輯,直接將整個(gè)中斷處理過程都放到中斷的上半部中處理。
2.1 修改設(shè)備樹文件
在上篇key實(shí)驗(yàn)代碼的基礎(chǔ)上,修改imx6ull-myboard.dts,主要是修改key子節(jié)點(diǎn),添加中斷,修改后內(nèi)容如下:
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "myboard-irq-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key1-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; /* SW2 */
key2-gpio = <&gpio5 11 GPIO_ACTIVE_LOW>; /* SW4 */
interrupt-parent = <&gpio5>;
interrupts = < 1 IRQ_TYPE_EDGE_BOTH
11 IRQ_TYPE_EDGE_BOTH >;
status = "okay";
};
2.2 按鍵中斷驅(qū)動程序
2.2.1 硬件初始化與中斷配置
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
/* 設(shè)備樹中獲取key節(jié)點(diǎn) */
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL)
{
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
imx6uirq.irqkeydesc[0].gpio = of_get_named_gpio(imx6uirq.nd ,"key1-gpio", 0);
imx6uirq.irqkeydesc[1].gpio = of_get_named_gpio(imx6uirq.nd ,"key2-gpio", 0);
if ((imx6uirq.irqkeydesc[0].gpio < 0)||(imx6uirq.irqkeydesc[1].gpio < 0))
{
printk("can't get key\r\n");
return -EINVAL;
}
printk("key1_gpio=%d, key2_gpio=%d\r\n", imx6uirq.irqkeydesc[0].gpio, imx6uirq.irqkeydesc[1].gpio);
/* 初始化key所使用的IO,并且設(shè)置成中斷模式 */
for (i = 0; i < KEY_NUM; i++)
{
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name)); /* 緩沖區(qū)清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "key%d", i+1); /* 組合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); /* 取到對應(yīng)的中斷號 */
printk("key%d:gpio=%d, irqnum=%d\r\n",i+1,
imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
/* 申請中斷 */
imx6uirq.irqkeydesc[0].handler = key1_handler;
imx6uirq.irqkeydesc[1].handler = key2_handler;
imx6uirq.irqkeydesc[0].value = KEY1VALUE;
imx6uirq.irqkeydesc[1].value = KEY2VALUE;
for (i = 0; i < KEY_NUM; i++)
{
/* 中斷請求函數(shù) */
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
imx6uirq.irqkeydesc[i].name,
&imx6uirq);
if(ret < 0)
{
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 創(chuàng)建定時(shí)器 */
init_timer(&imx6uirq.timer1);
imx6uirq.timer1.function = timer1_function;
init_timer(&imx6uirq.timer2);
imx6uirq.timer2.function = timer2_function;
return 0;
}
中斷檢測到按鍵按下后,為了消除按鍵抖動,這里使用定時(shí)器來進(jìn)行按鍵消抖,因?yàn)楸敬螌?shí)驗(yàn)用到兩個(gè)按鍵,所以就先也使用兩個(gè)定時(shí)器。
2.2.2 中斷服務(wù)函數(shù)
static irqreturn_t key1_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->timer1.data = (volatile long)dev_id;
mod_timer(&dev->timer1, jiffies + msecs_to_jiffies(10)); /* 10ms定時(shí) */
return IRQ_RETVAL(IRQ_HANDLED);
}
中斷函數(shù)檢測到按鍵按下后,會開啟一個(gè)10ms的定時(shí)器,用來按鍵消抖。
2.2.3 定時(shí)器服務(wù)函數(shù)
定時(shí)器的10ms到達(dá)之后,會觸發(fā)定時(shí)器服務(wù)函數(shù),此時(shí)再次讀取按鍵的值,若仍為按下,則是按鍵真的按下了,若10ms后又檢測不到按鍵了,則說明是按鍵抖動導(dǎo)致的按鍵誤觸發(fā)。
void timer1_function(unsigned long arg)
{
unsigned char value;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
keydesc = &dev->irqkeydesc[0];
value = gpio_get_value(keydesc->gpio); /* 讀取IO值 */
if(value == 1) /* 按下按鍵 */
{
printk("get key1: high\r\n");
atomic_set(&dev->keyvalue, keydesc->value);
}
else /* 按鍵松開 */
{
printk("key1 release\r\n");
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 標(biāo)記松開按鍵,即完成一次完整的按鍵過程 */
}
}
2.2.4 按鍵讀取函數(shù)
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) /* 有按鍵按下 */
{
//printk("releasekey!\r\n");
if (keyvalue & 0x80)
{
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
}
else
{
goto data_error;
}
atomic_set(&dev->releasekey, 0); /* 按下標(biāo)志清零 */
}
else
{
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
2.3 按鍵中斷驅(qū)動程序
按鍵中斷的應(yīng)用程序,使用上篇的按鍵檢測的應(yīng)用程序即可
3 實(shí)驗(yàn)
編譯設(shè)備樹與驅(qū)動文件(irqkey-BSp.ko),使用上篇的按鍵應(yīng)用程序(key-App),按下按鍵,會打印get key,松開按鍵,會打印key release。

4 總結(jié)
本篇主要介紹了Linux中斷的使用方法,通過按鍵來進(jìn)行中斷實(shí)驗(yàn)測試,并使用Linux定時(shí)器進(jìn)行按鍵去抖。

-
嵌入式
+關(guān)注
關(guān)注
5147文章
19626瀏覽量
316598 -
Linux
+關(guān)注
關(guān)注
87文章
11506瀏覽量
213390 -
中斷
+關(guān)注
關(guān)注
5文章
905瀏覽量
42688 -
i.MX6
+關(guān)注
關(guān)注
1文章
37瀏覽量
16547
發(fā)布評論請先 登錄
瑞芯微RK3506 vs NXP i.MX6ULL

618盛夏狂歡,米爾電子攜手恩智浦開啟年度技術(shù)盛宴!

NXP i.MX 91開發(fā)板#支持快速創(chuàng)建基于Linux?的邊緣器件

如何在i.MX6ULL睡眠時(shí)停止刷新LCD?
如何維護(hù)i.MX6ULL的安全內(nèi)核?
如何在i.MX6ULL定制板上啟用IO Expander PCA6416A的控制?
嵌入式學(xué)習(xí)-飛凌嵌入式ElfBoard ELF 1板卡-Pinctrl和GPIO子系統(tǒng)之Pinctrl子系統(tǒng)
飛凌嵌入式ElfBoard ELF 1板卡-Pinctrl和GPIO子系統(tǒng)之Pinctrl子系統(tǒng)
嵌入式學(xué)習(xí)-飛凌嵌入式ElfBoard ELF 1板卡-開發(fā)板適配之USB_OTG
飛凌嵌入式ElfBoard ELF 1板卡-開發(fā)板適配之USB_OTG
【新品】i.MX6ULL工業(yè)嵌入式核心板!NXP低功耗MPU,LCD顯示

i.MX Linux開發(fā)實(shí)戰(zhàn)指南—基于野火i.MX系列開發(fā)板
在NXP源碼基礎(chǔ)上如何適配ELF 1開發(fā)板的UART功能

使用TPS6521815 PMIC為NXP i.MX 6ULL、6UltraLite供電

評論