⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 learn-lumit-step-15-readme.txt

📁 嵌入式开发试验:中断按钮实验
💻 TXT
字号:

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 
	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;	
}

    关于中断处理函数的注册和释放,主要都是参考了 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[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 这一层,主要是实现 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 平台
上做的这个例子,多少也体现了这样一个思路。



┌-------------------------------------------------------------------------┐
│                                                                         │
│    Welcome to visit http://www.lumit.org & http://bbs.lumit.org         │
│                                                                         │
│                  [lumit] - let us make it together                      │
│                                                                         │
│                                                                         │
│                                         lumit-admin <admin@lumit.org>   │
│                                                                         │
└-------------------------------------------------------------------------┘

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -