S3C2440中断代码的深层次分析

发布者:量子心跳最新更新时间:2015-05-05 来源: 51hei关键字:S3C2440  中断代码  深层次分析 手机看文章 扫描二维码
随时随地手机看文章
在前一段时间分析了ARM异常处理机制的处理方式,分析了在异常产生以后CPU自动完成的相关处理以及程序员应该完成的基本操作。着重分析了异常代码的返回地址分析已经采用通用代码处理各种异常的可能性。
异常处理的基本过程如下:异常产生(在指令的临界中检测CPU的状态,一般实质在这条指令被执行完成,但是还没有执行下一条指令之前检测)——>保存状态寄存器,切换状态寄存器,保存LR=PC-4,强制PC跳转到对应异常向量(以上的过程都是CPU自动完成)——》调整返回地址,在栈中保存寄存器,便于恢复寄存器的值——》异常处理函数——》退出异常。
中断处理机制的两种形式:
1、 采用在中断向量中存储简单的跳转指令,跳转到异常处理函数中,但是这种方式存在的缺点就是跳转指令的范围是有局限性的。
2、 采用更新PC值的方法进行,具体的实现形式是在另一个固定地址处(handle_addr)保存对应异常处理函数的地址,然后采用LDR PC [PC, offset],其中offset = handle_addr – vect – 0x08;这种机制只要保证选择的地址恰当就能实现不同距离的跳转。
以上的分析和处理在上一次中已经分析,这次分析中断的处理过程,中断只是异常的一种特殊情况,对异常的处理得到了好的理解,那么对中断的处理也就比较方便了。
在ARM内核中只支持IRQ和IFQ两种类型的中断,但是不同的厂商提供不同类型的中断控制器实现对中断的扩展,使得实际的芯片更加适合我们的使用。但是中断控制器的差别也使得不同厂商的中断处理也有差别,但是基本的思想是一致的。
 
S3C2440的中断控制器一个支持60种中断源,基本的实现如上图所示。基本的寄存器包括SRCPND、INTPND(有且仅有1bit会被置位,可以通过这个寄存器判断中断源,找出那个IRQ源发生中断)、INTMOD、INTMSK、PRIORITY(用来改变中断的优先级顺序,但是其中还是存在一些固有的顺序,具体的参看手册)、INTOFFSET(用来表示IRQ中INTPND的那个bit被置位,这样每一类的中断源都存在一个固定的偏移量,这个寄存器可以用来用来计算偏移量以及通过这个偏移量找到对应的中断处理函数地址存储位置等),当然也存在一些关于多个中断源构成的子中断寄存器,SUBSRCPND、INTSUBMSK。
 
在S3C2440的启动代码中描述了关于中断处理过程的基本过程和原理。
首先需要搞清楚下面的一个宏定义:
       MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
     sub sp,sp,#4 ;decrement sp(to store jump address)
       stmfd     sp!,{r0}   ;PUSH the work register to stack(lr does not push because it return to original address)
       ldr     r0,=$HandleLabel;load the address of HandleXXX to r0
       ldr     r0,[r0]     ;load the contents(service routine start address) of HandleXXX
       str     r0,[sp,#4]      ;store the contents(ISR) of HandleXXX to stack
       ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)
       MEND
 
1、搞清楚ARM中的MACRO伪指令,这个伪指令就是我们在汇编中的宏定义,我们都知道宏的实现能够避免代码的重复型以及代码的可修复性。关于ARM汇编中的宏定义基本的形式如下:
       MACRO
              {$label} macroname {$parameter} {$parameter}…
              Code
       MEND
       其中$label 宏指令被展开时,label可被替换为相应的符号,一般为一个标号
       macroname 所定义的宏的名称
       $parameter 宏指令的参数,当宏指令被展开时被替换成对应的值。
2、依据上面的定义我们可以知道当前这段代码定义了一个宏指令,HANDLER,其中标号为$HandlerLabel,参数为$HandleLabel
基本的实现代码分析如下:
sub sp,sp,#4;               在栈中预留一个区域,用来保存PC的值
stmfd      sp!,{r0} ;      由于r0还需要被使用,因此需要被压栈
ldr     r0,=$HandleLabel ;这里的ldr是一个伪指令,主要是将标号$HandleLabel的地址加载到r0中,这也是压栈r0的原因。
ldr     r0,[r0]            ;这是ARM的ldr指令,主要是将$HandleLabel对应地址中的内容加载到r0中。如果在$HandleLabel中保存的是一个中断处理函数的地址,那么只需要将这个值加载到PC即可实现了中断任务跳转,实际上这个过程就是采用了异常处理的第二种方式:
即加载PC的方式,而不是简单的跳转方式。
 
str     r0,[sp,#4]      ;store the contents(ISR) of HandleXXX to stack
ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)
这两句代码正是这段代码的精髓。基本形式如下:
str     r0,[sp,#4],是指将r0的内容,也就是异常处理函数的地址保存到栈中的SP-4位置处,这个位置也恰好是之前sub       sp,sp,#4; 用来预留给保存PC值的位置,这时将异常处理函数的地址保存在这个地址处,接下来的ldmfd   sp!,{r0,pc}刚好就是将栈中的内容加载到R0和PC中,这样也就实现了将异常处理函数地址加载到PC.实现了跳转过程。
 
 
高地址
SP_0/SP_3
SP_1
Handle_addr
SP_2
R0
 
低地址
从上面的分析可以知道这种中断处理的方式,并不是中断处理中的简单跳转方式(因为跳转范围的局限性)而是采用更新PC值的形式实现的。
 
接下来分析IRQ,这种在我们实际开发中使用比较多的中断形式进行分析。
首先可以发现存在:
1、b       HandlerIRQ ;handler for IRQ interrupt
这种情况下发生在中断产生过程中,是在IRQ向量中执行的,也就是在0x18处执行,其中HandlerIRQ实质上是一个标号,对应一个具体的地址。其中保存的内容就是对应IRQ处理函数的地址。但是在代码中只有一个HandlerIRQ,形式如下:
HandlerIRQ HANDLER HandleIRQ
 
2、HandlerIRQ    HANDLER HandleIRQ
根据上面的宏定义,可以将这句代码进行扩展,得到如下的形式:
HandlerIRQ
     sub sp,sp,#4 
       stmfd     sp!,{r0}
ldr     r0,= HandleIRQ
       ldr     r0,[r0]
       str     r0,[sp,#4]
       ldmfd   sp!,{r0,pc}
 
3、关于HandleIRQ其中存放的内容可以从下面的代码中得到。
       ; Setup IRQ handler
       ldr   r0,=HandleIRQ    ;This routine is needed
       ldr   r1,=IsrIRQ    ;if there is not 'subs pc,lr,#4' at 0x18, 0x1c
       str   r1,[r0]
其中可以看到,在HandleIRQ中保存的内容是IsrIRQ的地址,而IsrIRQ我们可以知道是一个中断服务函数,因为在写代码的过程中进程会遇到这个特殊字符__ISR,这段代码是在启动代码中执行的。
4、IsrIRQ实现问题
IsrIRQ
       sub sp,sp,#4       ;reserved for PC
       stmfd     sp!,{r8-r9}
       ldr   r9,=INTOFFSET
       ldr   r9,[r9]
       ldr   r8,=HandleEINT0
       add r8,r8,r9,lsl #2
       ldr   r8,[r8]
       str   r8,[sp,#8]
       ldmfd     sp!,{r8-r9,pc}
还是一句一句的分析:
       sub sp,sp,#4        ;为保存PC值预留一个栈区域,这个区域与上面的处理过程是异曲同工的。
       stmfd      sp!,{r8-r9}     ;保存r8,r9中的值,因为接下来将使用这两个寄存器
ldr   r9,=INTOFFSET; 这是一个伪指令操作,实质上是将寄存器INTOFFSET的地址加载到r9中。
ldr   r9,[r9];得到寄存器中的值,这个寄存器中的值恰好保存了当前最高优先级中断的中断号(优先级是可以调节的,而中断号是一个固定值,因此选择中断号比较恰当),这样也就知道了具体是那个中断源产生了中断。
 
ldr   r8,=HandleEINT0;这句的ldr是伪指令,意思是将标号的地址加载到r8中
add r8,r8,r9,lsl #2;从指令的意义分析:r8 = r8 + r9>>2 = r8+r9*4;
其实这两句结合一下S3C2440的中断资料就不难分析得出,因为HandleEINT0实质上是指存储外部中断0处理函数地址的地方,那么我们可以将这一块内存地址看做是一个IRQISR中断向量表,而EINT0恰好是中断优先级最高的中断,那么可以将这个地址HandleEINT0作为IRQ中断向量表的入口地址,其他中断号的地址,只需要通过偏移地址就能得到,由于指针的大小恰好为4个字节,因此得到的相应中断号的入口地址是
HandleEINT0 = HandleEINT0 + INTOFFSET*4,
这些地址中都保存了对应中断处理函数的函数地址。
       ldr   r8,[r8]是指将r8的内容加载到r8中,也就是将对应中断处理函数的地址加载到r8中。
       str   r8,[sp,#8];这句代码的作用实质上就是和上面的分析一样,也就是将r8的值保存到之前为PC预留的区域中。
ldmfd      sp!,{r8-r9,pc};这句也恰好验证了上面的分析,PC中的值恰好就是之前的sp+8处的内容,这样中断处理函数的地址就到了PC中。[page]
小结:
我们可以将ARM中采用2级向量表的形式实现异常的中断处理,其中第一级是CPU中定义好的向量表,也就是异常向量表。在这一级的向量表中,实现跳转到对应的异常公共处理函数,另外每一种异常问题都存在自己的子问题,这时候采用第二级的向量表就可以解决各种子问题。第一级的向量表一般来说都是CPU定义好的,而第二级向量表则是我们在程序设计中人工实现的。
 
5、那么又是如何得到C语言中的函数呢,实质上已经很简单了,具体的分析如下:
//S3c2440init.s
       ^   _ISR_STARTADDRESS             ; _ISR_STARTADDRESS=0x33FF_FF00
HandleReset  #   4
HandleUndef #   4
HandleSWI            #   4
HandlePabort    #   4
HandleDabort    #   4
HandleReserved #   4
HandleIRQ             #   4
HandleFIQ             #   4
 
这边就可以看做第二级中断向量表
       ;@0x33FF_FF20
HandleEINT0         #   4
HandleEINT1         #   4
HandleUART0       #   4
….
HandleSPI1           #   4
HandleRTC           #   4
HandleADC           #   4
       _ISR_STARTADDRESS在s3c2440中是一个具体的地址值,这个地址值可以在option.h中找到。因此依据这个值我们就可以知道我们的二级向量表的实际位置,这种处理的方式存在一定的巧妙性,同时中断地址的选择也需要我们恰当的设置。这里的“^” 其实就是 MAP ,这段程序的意思是,从 _ISR_STARTADDRESS 开始,预留一个变量,每个变量一个标号,预留的空间为 4个字节,也就是 32BIT,其实这里放的是真正的C写的处理函数的地址,说白了,就是函数指针,这样做就很灵活了。
//option.h
#define _ISR_STARTADDRESS               0x33ffff00
同时在s3c2440addr.h中又可以找到下面的定义:
//s3c2440addr.h
// Exception vector(异常向量,不是CPU的异常向量)
#define pISR_RESET           (*(unsigned *)(_ISR_STARTADDRESS+0x0))
#define pISR_UNDEF           (*(unsigned *)(_ISR_STARTADDRESS+0x4))
#define pISR_SWI        (*(unsigned *)(_ISR_STARTADDRESS+0x8))
#define pISR_PABORT         (*(unsigned *)(_ISR_STARTADDRESS+0xc))
#define pISR_DABORT        (*(unsigned *)(_ISR_STARTADDRESS+0x10))
#define pISR_RESERVED     (*(unsigned *)(_ISR_STARTADDRESS+0x14))
#define pISR_IRQ         (*(unsigned *)(_ISR_STARTADDRESS+0x18))
#define pISR_FIQ         (*(unsigned *)(_ISR_STARTADDRESS+0x1c))
// Interrupt vector(中断向量)
#define pISR_EINT0            (*(unsigned *)(_ISR_STARTADDRESS+0x20))
#define pISR_EINT1            (*(unsigned *)(_ISR_STARTADDRESS+0x24))
#define pISR_EINT2            (*(unsigned *)(_ISR_STARTADDRESS+0x28))
#define pISR_EINT3            (*(unsigned *)(_ISR_STARTADDRESS+0x2c))
#define pISR_EINT4_7 (*(unsigned *)(_ISR_STARTADDRESS+0x30))
#define pISR_SPI1        (*(unsigned *)(_ISR_STARTADDRESS+0x94))
#define pISR_RTC        (*(unsigned *)(_ISR_STARTADDRESS+0x98))
#define pISR_ADC        (*(unsigned *)(_ISR_STARTADDRESS+0x9c))
从上面的代码中我们可以知道pISR_EINT0之类的实质上就是一个地址,如果我们在这个地址中填充处理函数的地址值也就形成了函数指针,实际上只需要将函数名赋值给对应的中断向量即可。这样也就找到了适当的处理方式.基本的形式如下所示:
       Void main()
{
       …
pISR_EINT0 = (U32)Button_ISR;
While(1)
{
}
}
/*中断服务函数*/
static void _irq Button_ISR(void)
{
      …
}
几个分析的比较清晰的网址可以去看看:
 
总结:
问题?在其中的代码中,我并没有看到返回地址的操作问题,我找了很多的代码,但是好像都不是特别的准确。也就是没有找到对应的 subs pc, lr, =0x04操作。
 
代码中经典的片段就是如何实现了代码的跳转问题:
sub sp,sp, #0x04;为保存PC值预留空间
stmfd     sp!,{r0}; 保存需要使用到的寄存器值,需要使用多少,就压多少的堆栈
…//使用r0进行相关的操作
ldr r0,[r0];
str r0, [sp,#0x04]; //这个操作类似于函数调用中的问题
ldmfd sp!,{r0, pc};//出栈操作,实现了对PC值的赋值
关键字:S3C2440  中断代码  深层次分析 引用地址:S3C2440中断代码的深层次分析

上一篇:关于对ARM处理器中“8位位图”的理解
下一篇:基于stm32f103zet6的内存管理的学习

推荐阅读最新更新时间:2024-03-16 14:00

S3C2440-裸机篇-10 | 使用S3C2440操作Nand Flash
1. Nor Flash与Nand Flash Nor Nand 接口 引脚多,类似于RAM 引脚较少 容量 小(1-32MB) 大(128-512MB) 读操作 简单,和RAM相同 简单,和RAM相同 写操作 发出特定命令才能写入 发出特定命令才能写入 价格 贵 便宜 比较 无坏块 有坏块 XIP 可以 不可以 应用场合 存储关键性代码(比如uboot,kernel) 存储海量数据(允许错误) 2. JZ2440开发板上的Nand Flash JZ2440开发板上板载了一个Nand Flash,型号为K9F2G08U0C,大小为256MB,连接在S3C2440的nand控制器上,原理图如下:
[单片机]
S3C2440-裸机篇-10 | 使用<font color='red'>S3C2440</font>操作Nand Flash
基于ARM板s3c2440---SPI协议
SPI简介 SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,比如AT91RM9200。 简单的说,就是SPI是一种告诉的协议,相当于IIC,只是IIC是两根线(时钟线SCL,数据地址线SDA)而SPI是四根线(SCK时钟信号,DO输出线,DI输入线,CSn片选信号),同样可以挂在多个SPI高速设备。 数据传输 数据传输由CPOL,CPHA两个控制器决定,CPOL决定电平启示状态是低电平还
[单片机]
基于ARM板s3c2440---SPI协议
S3C2440-LCD字符显示
LCD字符显示有两种方式,一个是通过字模提取软件,将字符转化成一个字节型的数组,另一个是使用字库。如果字符较多的时候,直接使用字库比较方便。现在说一下中英文字符的存储结构和编码方式。中英文的字符点阵结构有4*8,8*16,16*16,24*24,32*32,48*48等结构形势,不同点阵汉字的字体又有宋体,仿宋体,黑体,楷体等之分。在计算机中,相同点阵结构和相同字体的字符存放在同一字库中。本文使用的是16*16的宋体。16*16的点阵字库中,字符的信息结构采用以行排列的形式,共有16行,每行有16个点,分别存放在两个字节内。因此每个字符共占用16*16/2=32个字节。字节的存放顺序为从左到右,从上到下。将汉字变成字符模式,使用16
[单片机]
S3C2440-LCD字符显示
S3C2440地址空间的分配与启动
一、S3C2440地址空间的分配 1. s3c2440A 的存储器控制器有以下特性: 大小端(通过软件选择) 地址空间:每个bank有128M 的字节(总共1G字节/8个banks) 可编程的访问位宽,bank0(16/32 位),其他bank(8/16/32 位) 共8个存储器banks 6个是ROM,SRAM 等类型存储器bank 2个是可以作为ROM、SRAM、SDRAM 等存储器bank 7个固定的存储器bank起始地址 最后一个bank 的起始地址可调整 最后两个bank 大小可编程 所有存储器bank的访问周期可编程 总线访问周期可通过插入外部wait来延长 支持SDRAM 的自刷新和掉电模式 2. 寻
[单片机]
总结C51、STM32和S3C2440的时钟体系和定时器
/* 名称:总结C51、STM32和S3C2440的时钟体系和定时器 说明:对于C51单片机来说,其谈不上什么时钟体系。片上运行的所有东西都靠一个时钟脉冲提供–外部晶振。 对于STM32来说,其时钟体系比较复杂。为了达到降低能耗的目的,再设计STM32时,其厂商特地设计了一个STM32的时钟树。就连最简单的点亮一个LED灯,都必须配置其时钟树,再打开对应的时钟开关。 在这里简单介绍一些STM32的时钟树的主要部分: 对于这个复杂的时钟树,它的来源可以为外部高速时钟、外部低速时钟、内部低速时钟、内部高速时钟(HSE、HSI、LSI、LSE)。当然,具体选择哪个需要根据需要。在这里选择一个主要的来讲:就是外部高速时钟(HSE)。然
[单片机]
u-boot-2016.11移植到S3C2440之新建一个单板(1)
1、确定单板 下载并解压u-boot-2016.11,查看一下board目录下的这一u-boot版本支持的单板,寻找你的单板或者和你的单板相近的单板。对于S3C2440来说,u-boot没有提供与之相对应的单板支持,但是S3C2410与S3C2440是十分相似的,在u-boot-2016.11boardsamsung目录下有smdk2410的目录,即支持smdk2410开发板,那我们的u-boot也就基于这一开发板来移植修改。 2、配置、编译u-boot 首先就以smdk2410为目标来配置编译一下u-boot-2016.11,建议使用较高版本的交叉工具链,我使用的是arm-linux-gcc-4.3.3。 mak
[单片机]
u-boot-2016.11移植到<font color='red'>S3C2440</font>之新建一个单板(1)
S3C2440定时器0 的初始化程序
定时器比较值、计数值比较缓存寄存器TCMPBn、TCNTBn,这两个寄存器用于存储定时器的比较值和计数初始值。 定时器比较值、计数值比较寄存器TCMPn、TCNTn,这两个寄存器是定时器的内部寄存器,用户无需进行操作。 1. 需要配置成定时器模式时使用以下的初始化函数,并且计数值到0时产生中断。 //****************定时器中断服务函数***************** void __irq Timer0_isr() { //send_str((unsigned char *) Timer0 interrupt is started!\r\n ); Led_on(0);Led_off(1);Led_off
[单片机]
基于嵌入式Linux与S3C2440双USB接口的视频存储
  针对长途客车“三超”(中途超载上人,超时,超速)以及在非正常停靠站私自停车上人导致的客车超载,同时目前市面上视频监控设备由于体积大、成本高等,使其难以推广应用在车辆中等问题,整合USB摄像头低成本、USB存储介质较SD(Secure Digital Memory Card)卡存储速度更快、支持更大容量等优点,提出了基于嵌入式。Linux及S3C2440平台的双USB接口车载视频存储方案。本方案主要利用单片机判别模块采集车辆行驶过程中运动状态信息(是否超速,存在急刹车、中途停车超时等),并结合车门在车速为零时的开闭对长途客车在非正常停靠点超载上人进行主动视频监控,便于以后的责任追究和认定,为监控中心的管理提供辅助。    1
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved