?? learn-lumit-step-15-readme.txt
字號(hào):
Learn lumit Step 15 : 中斷按鈕實(shí)驗(yàn)
++++++++++++++++++++++++++++++++++++++++++++++++++++++
所有前面例子中提到的實(shí)驗(yàn),無論是輸入設(shè)備或者是輸出設(shè)備,都是采用順序執(zhí)行的
方式,沒有涉及到程序的異步執(zhí)行。但即使是單片機(jī)也有中斷,導(dǎo)致程序需要對中斷進(jìn)行
處理,因此就不得不涉及到 ARM 處理器的一些體系結(jié)構(gòu)方面的知識(shí)。
這一節(jié)我們以板上的 int0 中斷按鈕為例,介紹一下如何處理和實(shí)現(xiàn)系統(tǒng)設(shè)備的中斷。
首先需要對 ARM 的中斷處理流程做一個(gè)簡單介紹。
ARM 分為多個(gè)處理器狀態(tài),其中最常用的是 SVC 和 IRQ 狀態(tài),每種狀態(tài)的區(qū)別主要
在于有一些寄存器是只有自己可見,而其他狀態(tài)不可見的。SVC 可以理解成就是系統(tǒng)態(tài),
我們前面所介紹的程序都是在 SVC 態(tài)執(zhí)行的,而 IRQ 就是系統(tǒng)發(fā)生中斷后,自動(dòng)跳轉(zhuǎn)到
IRQ 狀態(tài),同時(shí) CPU 會(huì)自動(dòng)到 0x18 地址處執(zhí)行這里的處理指令。
我們以 int0 例子中的代碼為例,具體說明這個(gè)過程。相比以前的例子這里多了一個(gè)
startup.s 作為進(jìn)入 main 之前需要首先執(zhí)行的啟動(dòng)代碼。它所要做的事情主要有如下的
這五個(gè)工作:
1) 建立 IRQ 態(tài)的堆棧指針 sp_irq
2) 建立 SVC 態(tài)的堆棧指針 sp_svc
3) 保存 __main 的入口到 lr 寄存器
4) CPU 進(jìn)入 SVC 狀態(tài),同時(shí)開中斷。
5) 跳轉(zhuǎn)到 __main 入口,執(zhí)行 C 語言代碼
; startup.s
; **********************************************************************
; * Set up the stack pointer to point
; **********************************************************************
;set up irq stack
mov r0, #0xd2 ; make irq mode with all irqs disabled
msr cpsr_cxsf, r0
MOV sp, #0x70000
;set up svc stack
mov r0, #0xd3 ; make svc mode with all irqs disabled
msr cpsr_cxsf, r0
MOV sp, #0x80000
; **********************************************************************
; * Get the address of the C entry point.
; **********************************************************************
LDR lr, =__main
; **********************************************************************
; * Enable the interrupt while staying in the supervisor mode
; **********************************************************************
MOV r0, #Mode_Svc:OR:F_Bit
MSR cpsr_c, r0
MOV pc, lr
對比上面的代碼,這幾個(gè)工作為系統(tǒng)支持 IRQ 中斷狀態(tài)做了必要的準(zhǔn)備,其中最重要的
就是為 IRQ 狀態(tài)下的 SP 堆棧寄存器做了賦值,建立了 IRQ 中斷態(tài)的堆棧空間。
進(jìn)入 main 函數(shù)后,系統(tǒng)主要執(zhí)行了如下代碼:
int main( void )
{
int i = 1;
install_irq_handler( irq_handler );
led_init();
int0_install_irq_hooker( int0_hooker );
// should be after hooker installation
int0_init();
while(i++)
{
led_one_light(i%3);
led_delay( 100 );
led_one_dark(i%3);
}
return 0;
}
可以看到,程序結(jié)構(gòu)還是很清晰的,從函數(shù)命名上基本就能理解大致的執(zhí)行流程。
1) 為系統(tǒng)注冊一個(gè)中斷處理函數(shù) irq_handler ,是在匯編文件 startup.s 中實(shí)現(xiàn)的。
2) 為 int0 設(shè)備的中斷處理安裝一個(gè)用戶來實(shí)現(xiàn)的鉤子函數(shù) int0_hooker 。
3) 初始化 led 設(shè)備,便于程序執(zhí)行過程中有一個(gè)簡單的輸出結(jié)果顯示。
4) 初始化 int0 設(shè)備,開始允許 int0 中斷。
5) 進(jìn)入主程序流程中,在一個(gè)無限循環(huán)中,依次點(diǎn)亮 led 0, led 1, led 2 三個(gè)燈。
在 main.c 主文件中,irq 中斷的管理和注冊是通過一個(gè)中斷向量數(shù)組來實(shí)現(xiàn)的。
這個(gè)函數(shù)指針的數(shù)組,代表了 S3C4510 CPU 的 21 個(gè)中斷源的中斷處理函數(shù)的入口。
系統(tǒng)在中斷發(fā)生后,首先進(jìn)入到 irq_handler 中,對 cpu 寄存器上下文狀態(tài)做必要的
保存(堆棧操作)后,調(diào)用 C 語言級(jí)的 do_irq 函數(shù)。在這個(gè)函數(shù)中,根據(jù)當(dāng)前中斷源
的中斷號(hào) irq_source,來查詢系統(tǒng)維護(hù)的中斷向量數(shù)組,看是否已經(jīng)有了某個(gè)設(shè)備注冊
了該中斷號(hào)的中斷處理函數(shù)。如果有則跳轉(zhuǎn)過去執(zhí)行該中斷處理函數(shù)。
-----------------------------------------------------------------------------------
; startup.s
IMPORT do_irq
EXPORT irq_handler
irq_handler
SUB lr, lr, #4
STMFD sp!, {r0-r12, lr} ; push r0-r12 register file and lr( pc return address )
MRS r4, spsr
STMFD sp!, {r4} ; push current spsr_cxsf_irq ( =cpsr_svc )
BL do_irq ; goto C handler
LDMFD sp!, {r4} ; get cpsr_svc from stack
MSR spsr_cxsf, r4 ; prepare spsr_cxsf to return svc mode
LDMFD sp!, {r0-r12, pc}^ ; recover r0-r12 and pc from stack, cpsr also
-----------------------------------------------------------------------------------
/* main.c */
void (*device_irq_handler[IRQ_SOURCE_NUM])(int irq);
void do_irq( void )
{
void (* current_pc)();
int irq_source;
int i;
// get irq number from INTPND
irq_source = INTPND;
// get current device irq handler to current_pc
for( i = 0; i < IRQ_SOURCE_NUM; i++ )
{
if( irq_source & ( 1 << i ) )
{ // here is an interrupt at source i
if( device_irq_handler[i] )
{ // if this interrupt has an registered handler
// then get this handler address to current_pc
current_pc = device_irq_handler[i];
// call registered device irq handler to do_irq
((void (*)(void))(current_pc))(); /* thanks, STheobald */
}
}
}
return;
}
關(guān)于中斷處理函數(shù)的注冊和釋放,主要都是參考了 linux 的實(shí)現(xiàn)機(jī)制,尤其是對于設(shè)備驅(qū)動(dòng)
的處理,也是做了一些簡化,但基本思路上是類似的。在 int0_driver 這個(gè)文件里面,照例還是
實(shí)現(xiàn)了 open, read, write, ioctl, release 這 5 個(gè)底層接口。而中斷處理函數(shù)的實(shí)現(xiàn),中斷號(hào)
的申請和釋放,以及用戶安裝 hooker 函數(shù)的實(shí)現(xiàn)都放在了上層 int0_api 這個(gè)文件里面,這有點(diǎn)
類似于 linux 里面的內(nèi)核模塊。
/* main.c */
int request_irq( unsigned int irq, void (*handler)(int irq) )
{
if( device_irq_handler[irq] )
return -1; // fail to request, free this irq first
// fill the device_irq_handler vector
device_irq_handler[irq] = handler;
return 0;
}
int free_irq( unsigned int irq, void (*handler)(int irq) )
{
if( !device_irq_handler[irq] )
return -1; // fail to request, free this irq first
// free the device_irq_handler vector
device_irq_handler[irq] = 0;
return 0;
}
在 int0_api 這一層,主要是實(shí)現(xiàn) int0_irq_handler 的中斷處理程序,在該程序中,
必要的話,就調(diào)用用戶的 hooker 函數(shù)。同時(shí)為用戶安裝 hooker 函數(shù)提供接口。
/* int0_api.c */
extern int request_irq( unsigned int irq, void (*handler)(void) );
static void (*int0_irq_hooker)(void) = 0;
void int0_irq_handler( void )
{
// here we add some user code for int0_irq
if( int0_irq_hooker )
int0_irq_hooker();
// here we call low-level int0_irq_handler
int0_ioctl( INT0_CLEAR_INTERRUPT, 0 );
return;
}
void int0_install_irq_hooker( void (*handler)(void) )
{
int0_irq_hooker = handler;
}
在 int0_init 初始化流程中,主要是完成申請注冊一個(gè)中斷號(hào)以及和它對應(yīng)的
中斷處理函數(shù)。同時(shí)設(shè)置啟動(dòng)中斷的一些參數(shù),例如中斷觸發(fā)方式為上升沿觸發(fā)等。
/* set int0 related gpio */
int int0_init( void )
{
// External interrupt 0 source number is 0
request_irq( 0, int0_irq_handler );
int0_open();
// set int0 interrupt edge detect
int0_ioctl( INT0_RISING_EDGE_INTERRUPT, 0 );
// enable int0 interrupt
int0_ioctl( INT0_ENABLE_INTERRUPT, 0 );
// set active high
int0_ioctl( INT0_ACTIVE_HIGH, 0 );
return 0;
}
在 int0_driver 底層驅(qū)動(dòng)接口中,最重要的是實(shí)現(xiàn) int0_ioctl 控制接口。這個(gè) ioctl
在前面的幾個(gè)例子里,都沒有很實(shí)際的應(yīng)用,但對于 int0 這個(gè)設(shè)備,就體現(xiàn)出其很重要的
作用,可以對比以前的例子,這些設(shè)置參數(shù)是無法通過設(shè)備 讀/寫 的接口來實(shí)現(xiàn)的。
int int0_ioctl( unsigned int cmd, unsigned long arg )
{
switch( cmd )
{
// Enable interrupt request and Unmask PIO8 interrupt
case INT0_ENABLE_INTERRUPT:
IOPCON |= INT0_IO_ENABLE;
break;
// clear int 0 pending interrupts
case INT0_CLEAR_INTERRUPT:
INTPND |= INT0_MASK;
break;
// set rising edge interrupt
case INT0_RISING_EDGE_INTERRUPT:
IOPCON |= INT0_IO_RISING_EDGE;
break;
// set falling edge interrupt
case INT0_FALLING_EDGE_INTERRUPT:
IOPCON |= INT0_IO_FALLING_EDGE;
break;
// set both edge interrupt
case INT0_BOTH_EDGE_INTERRUPT:
IOPCON |= INT0_IO_BOTH_EDGE;
break;
// set as active high
case INT0_ACTIVE_HIGH:
IOPCON |= INT0_IO_ACTIVE_HIGH;
break;
default:
break;
}
return 0;
}
在學(xué)習(xí) linux 設(shè)備驅(qū)動(dòng)程序的過程中,我很想做的一件事情,就是基于 linux 的機(jī)制,
實(shí)現(xiàn)一個(gè)簡化的中斷處理和設(shè)備驅(qū)動(dòng)模型,應(yīng)用在一些相對簡單的領(lǐng)域。在 lumit4510 平臺(tái)
上做的這個(gè)例子,多少也體現(xiàn)了這樣一個(gè)思路。
┌-------------------------------------------------------------------------┐
│ │
│ Welcome to visit http://www.lumit.org & http://bbs.lumit.org │
│ │
│ [lumit] - let us make it together │
│ │
│ │
│ lumit-admin <admin@lumit.org> │
│ │
└-------------------------------------------------------------------------┘
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -