Learn lumit Step 15 : 中断按钮实验
Learn lumit Step 15 : 中断按钮实验++++++++++++++++++++++++++++++++++++++++++++++++++++++
所有前面例子中提到的实验,无论是输入设备或者是输出设备,都是采用顺序执行的
方式,没有涉及到程序的异步执行。但即使是单片机也有中断,导致程序需要对中断进行
处理,因此就不得不涉及到 ARM 处理器的一些体系结构方面的知识。
这一节我们以板上的 int0 中断按钮为例,介绍一下如何处理和实现系统设备的中断。
首先需要对 ARM 的中断处理流程做一个简单介绍。
ARM 分为多个处理器状态,其中最常用的是 SVC 和 IRQ 状态,每种状态的区别主要
在于有一些寄存器是只有自己可见,而其他状态不可见的。SVC 可以理解成就是系统态,
我们前面所介绍的程序都是在 SVC 态执行的,而 IRQ 就是系统发生中断后,自动跳转到
IRQ 状态,同时 CPU 会自动到 0x18 地址处执行这里的处理指令。
我们以 int0 例子中的代码为例,具体说明这个过程。相比以前的例子这里多了一个
startup.s 作为进入 main 之前需要首先执行的启动代码。它所要做的事情主要有如下的
这五个工作:
1) 建立 IRQ 态的堆栈指针 sp_irq
2) 建立 SVC 态的堆栈指针 sp_svc
3) 保存 __main 的入口到 lr 寄存器
4) CPU 进入 SVC 状态,同时开中断。
5) 跳转到 __main 入口,执行 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
对比上面的代码,这几个工作为系统支持 IRQ 中断状态做了必要的准备,其中最重要的
就是为 IRQ 状态下的 SP 堆栈寄存器做了赋值,建立了 IRQ 中断态的堆栈空间。
进入 main 函数后,系统主要执行了如下代码:
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;
}
可以看到,程序结构还是很清晰的,从函数命名上基本就能理解大致的执行流程。
1) 为系统注册一个中断处理函数 irq_handler ,是在汇编文件 startup.s 中实现的。
2) 为 int0 设备的中断处理安装一个用户来实现的钩子函数 int0_hooker 。
3) 初始化 led 设备,便于程序执行过程中有一个简单的输出结果显示。
4) 初始化 int0 设备,开始允许 int0 中断。
5) 进入主程序流程中,在一个无限循环中,依次点亮 led 0, led 1, led 2 三个灯。
在 main.c 主文件中,irq 中断的管理和注册是通过一个中断向量数组来实现的。
这个函数指针的数组,代表了 S3C4510 CPU 的 21 个中断源的中断处理函数的入口。
系统在中断发生后,首先进入到 irq_handler 中,对 cpu 寄存器上下文状态做必要的
保存(堆栈操作)后,调用 C 语言级的 do_irq 函数。在这个函数中,根据当前中断源
的中断号 irq_source,来查询系统维护的中断向量数组,看是否已经有了某个设备注册
了该中断号的中断处理函数。如果有则跳转过去执行该中断处理函数。
-----------------------------------------------------------------------------------
; startup.s
IMPORTdo_irq
EXPORTirq_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)(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 )
{ // if this interrupt has an registered handler
// then get this handler address to current_pc
current_pc = device_irq_handler;
// call registered device irq handler to do_irq
((void (*)(void))(current_pc))(); /* thanks, STheobald */
}
}
}
return;
}
关于中断处理函数的注册和释放,主要都是参考了 linux 的实现机制,尤其是对于设备驱动
的处理,也是做了一些简化,但基本思路上是类似的。在 int0_driver 这个文件里面,照例还是
实现了 open, read, write, ioctl, release 这 5 个底层接口。而中断处理函数的实现,中断号
的申请和释放,以及用户安装 hooker 函数的实现都放在了上层 int0_api 这个文件里面,这有点
类似于 linux 里面的内核模块。
/* main.c */
int request_irq( unsigned int irq, void (*handler)(int irq) )
{
if( device_irq_handler )
return -1; // fail to request, free this irq first
// fill the device_irq_handler vector
device_irq_handler = handler;
return 0;
}
int free_irq( unsigned int irq, void (*handler)(int irq) )
{
if( !device_irq_handler )
return -1; // fail to request, free this irq first
// free the device_irq_handler vector
device_irq_handler = 0;
return 0;
}
在 int0_api 这一层,主要是实现 int0_irq_handler 的中断处理程序,在该程序中,
必要的话,就调用用户的 hooker 函数。同时为用户安装 hooker 函数提供接口。
/* 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 初始化流程中,主要是完成申请注册一个中断号以及和它对应的
中断处理函数。同时设置启动中断的一些参数,例如中断触发方式为上升沿触发等。
/* 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 底层驱动接口中,最重要的是实现 int0_ioctl 控制接口。这个 ioctl
在前面的几个例子里,都没有很实际的应用,但对于 int0 这个设备,就体现出其很重要的
作用,可以对比以前的例子,这些设置参数是无法通过设备 读/写 的接口来实现的。
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;
}
在学习 linux 设备驱动程序的过程中,我很想做的一件事情,就是基于 linux 的机制,
实现一个简化的中断处理和设备驱动模型,应用在一些相对简单的领域。在 lumit4510 平台
上做的这个例子,多少也体现了这样一个思路。 这段例程非常好,感谢。 :lol: 实验思想非常好,但可否把源码下载的链接做上,以方便下载源码。 :?: 上面第一个帖子的后面跟着的 Learn-lumit-Step-15.中断按钮实验.zip链接不就是么?
每一个帖子后面都带有例子的全部源码和 readme ,看仔细哦 :twisted: 大侠请指导一下编译过程好吗?我直接make不行啊,不懂怎么编译,puke :oops: 别着急,先试试下载 Step 1 里面的例子看看 make 行不行? * this is isr entry address, could be another address like 0x3c, 0x58... */
unsigned int * isr_entry_addr = ( unsigned int * ) 0x38;
0x38的地址是怎么来的啊,could be another address ?
* make an instruction: it is machine-code for "ldrpc, "*/
instruction = ((unsigned int) isr_entry_addr- (unsigned int)irq_vec_addr - 0x08) | 0xe59ff000;
0xe59ff000的地址是怎么得出的啊? 0xe59ff000 不是一个地址,而是一条机器指令的某些二进位,具体这些位是什么含义,可以查看arm汇编指令相关资料。 :P
页:
[1]