TQ2440中断系统

1.1   S3C2440系统中断

CPU和外设构成了计算机系统,CPU和外设之间通过总线进行连接,用于数据通信和控制,CPU管理监视计算机系统中所有硬件,通常以两种方式来对硬件进行管理监视:

  •   查询方式:CPU不停的去查询每一个硬件的当前状态,根据硬件的状态决定处理与否。好比是工厂里的检查员,不停的检查各个岗位工作状态,发现情况及时处 理。这种方式实现起来简单,通常用在只有少量外设硬件的系统中,如果一个计算机系统中有很多硬件,这种方式无疑是耗时,低效的,同时还大量占用CPU资 源,并且对多任务系统反应迟钝。
  •   中断方式:当某个硬件产生需要CPU处理的事件时,主动通过一根信号线“告知”CPU,同时设置某个寄存器里对应的位,CPU一旦发现这根信号线上的电平 有变化,就会中断当前程序,然后去处理发出该中断请求。这就像是医院重危病房,病房每张病床床头有一个应急按钮,该按钮连接到病房监控室里控制台一盏指示 灯,只要该张病床出现紧急情况病人按下按钮,病房监控室里电铃会响起,通知医护人员有紧急情况,医护人员这时查看控制台上的指示灯,找出具体病房,病床 号,直接过去处理紧急情况。中断处理方式相对查询方式要复杂的多,并且需要硬件的支持,但是它处理的实时性更高,嵌入式系统里基本上都使用这种方式来处 理。

系统中断是嵌入式硬件实时地处理内部或外部事件的一种机制。对于不同CPU而言,中断的处理只是细节不同,大体处理流程都一样,S3C2440A的中断控制器结构如下图所示:

图3-3 S3C2440中断控制器

  中断请求由硬件产生,根据中断源类型分别将中断信号送到SUBSRCPND(SubSourcePending)和 SRCPND(SourcePending)寄存器,SUBSRCPND是子中断源暂存寄存器,用来保存子中断源信号,SRCPND是中断源暂存寄存器, 用来保存中断源信号。中断信号可通过编程方式屏蔽掉,SUBMASK是子中断源屏蔽寄存器,可以屏蔽指定的子中断信号, MASK功能同SUBMASK用来屏蔽中断源信号。中断分为两种模式:一般中断的和快速中断,MODE是中断模式判断寄存器,用来判断当前中断是否为快速 中断,如果为快速中断直接将快速中断信号送给ARM内核,如果不是快速中断,还要将中断信号进行仲裁选择。S3C2440A支持多达60种中断,很有可能 多个硬件同时产生中断请求,这时要求中断控制器做出裁决,Priority是中断源优先级仲裁选择器,当多个中断产生时,选择出优先级最高的中断源进行处 理,INTPND是中断源结果寄存器,里面存放优先级仲裁出的唯一中断源。

1  中断的产生-中断源

  S3C2440A支持60种中断源,基本上满足了开发板内部,外部设备等对中断的需求。其中每一个中断源对应寄存器中的一位,显然要支持60种中断 至少需要二个32位寄存器,SUBSRCPND和SRCPND分别保存中断源信号。S3C2440A对60种中断源的管理是按层级分的。如图3-4所示:

 

图3-4中断源信号复合示意图

  S3C2440A将中断源分为两级:中断源和子中断源,中断源里包含单一中断源和复合中断源,复合中断源是子中断源的复合信号。如实时时钟中断,该 硬件只会产生一种中断,它是单一中断源,直接将其中断信号线连接到中断源寄存器上。对于复合中断源,以UART串口为例进行说明,S3C2440A可以支 持3个UART串口,每个串口对应一个复合中断源信号INT_UARTn,每个串口可以产生三种中断,也就是三个子中断:接收数据中断INT_RXDn, 发送数据中断INT_TXDn,数据错误中断INT_ERRn,这三个子中断信号在中断源寄存器复合为一个中断信号,三种中断任何一个产生都会将中断信号 传递给对应的中断源INT_UARTn,然后通过中断信号线传递给ARM内核。

 

图3-5 UART串口中断源信号复合示意图

总中断源详下面表中列出了S3C2440A部分中断源,它分别对应中断源寄存器里某个位:详细中断源请查看S3C2440A硬件手册。

表3-5 部分中断源信号

中断源

描述

优先级仲裁分组

INT_ADC

数模转换和触摸屏中断

ARB5

INT_RTC

实时时钟中断

ARB5

INT_UART0

UART0中断(包含子中断)

ARB5

INT_NFCON

NandFlash控制中断

ARB4

INT_WDT_AC97

看门狗中断

ARB1

EINT8-23

外部中断8~23(包含外部子中断)

ARB1

EINT4-7

外部中断4~7(包含外部子中断)

ARB1

EINT3

外部中断3

ARB0

EINT2

外部中断2

ARB0

EINT1

外部中断1

ARB0

EINT 0

外部中断0

ARB0

中断信号除上述分法之外,还可以按照硬件位置分为:外部中断源和内部中断源。

  •   内部中断源:它是嵌入式系统中常见硬件产生的中断信号,比如:UART串口中断源,时钟Timer中断源,看门狗中断源等
  •   外部中断源:有时嵌入式系统里要在外部接口上挂载一些外部设备,这些设备并不是一个通用嵌入式系统里必备硬件,比如:蓝牙模块,各种传感器,WIFI无线 通信模块,这些硬件也要产生中断让CPU来处理数据,因此这些外设硬件通过中断信号线连接到中断控制器上,它们产生的中断叫做外部中断信号。它们有着和内 部中断一样的处理机制,只不过,它没有一个固定的中断号与之对应,硬件与嵌入式系统的连接方式与中断处理完全由系统硬件与软件设计者实现。

  外设硬件通过输入输出接口I/O Ports挂接到嵌入式系统上,I/O Ports向外设提供外部中断信号线,输出电源,频率时钟和输入输出信号线,外部硬件根据自己需要连接到I/O Ports上,产生中断时向外部中断信号线上送出中断信号,通过外部中断信号线传递到中断控制器。

按键Key可以认为最为简单的一种硬件设备了,如下图所示:

 

图3-6按键硬件接线原理图

  它功能很简单,可以将电路接通,按键K1~K6一端接地为低电平,另外一端接电源正极为高电 平,EINT8,EINT11,EINT13,EINT14,EINT15,EINT19六根中断信号线分别和高电平端按键相连,当按键按下时电路接通, 整个电路变成低电平,中断信号线上电压产生变化,通过设置中断触发方式,产生外部中断请求,输入到CPU内部,从而实现按键中断控制。

  S3C2440A可以支持EINT0~EINT23共24种外部中断,完全可以满足小型嵌入式设备外设硬件的需求。

  外部中断源也分为外部中断源和外部子中断源,其处理方式和内部中断源基本一样。

1.1.1   中断优先级

  S3C2440A支持60种中断,多个硬件可能同时产生中断请求,由于CPU只能处理一个中断,中断控制器怎么选择出一个最佳的中断,交给ARM内 核进行处理呢? 中断控制器采用优先级仲裁比较的方式进行选择,找出优先级最高的中断源。中断控制器将60种中断源分成7组,如下图所示,它类似体育赛事里的比赛方式,所 有参赛选手在小组赛PK,选择出小组赛最优秀选手,然后进入决赛阶段和其它小组最优先选择再PK,最后优胜者就是总冠军。其中 ARBITER0~ARBITER5为“小组赛”阶段,中断源信号在各自小组里进行优先级仲裁,选择出最高优先级中断信号,每小组选出的中断信号送到 ARBITER6,也就是决赛阶段,选择出最高优先级中断信号,交给ARM内核。

 

图3-7 S3C2440优先级仲裁示意图

中断信号在7个分组里PK时的优先级是可编程的,通过PRIORITY寄存器进行优先级设置。如下表(只列出PRIORITY寄存器部分位):

表3-6 中断优先级控制寄存器(PRIORITY)

寄存器名

地址

是否读写

描述

复位默认值

PRIORITY

0x4A00000C

R/W

中断优先级控制寄存器

0x7F

 

PRIORITY

描述

初始值

ARB_SEL6

[20:19]

仲裁组6优先级排序方式

00 = REQ 0-1-2-3-4-5

01 = REQ 0-2-3-4-1-5

10 = REQ 0-3-4-1-2-5

11 = REQ 0-4-1-2-3-5

0x00

ARB_SEL5

[18:17]

仲裁组5优先级排序

00 = REQ 1-2-3-4

01 = REQ 2-3-4-1

10 = REQ 3-4-1-2

11 = REQ 4-1-2-3

00

ARB_MODE6

[6]

仲裁组6优先级是否轮转:

0 = 不轮转,     1 = 轮转

1

ARB_MODE5

[5]

仲裁组5优先级是否轮转:

0 = 不轮转,     1 = 轮转

1

  通过设置仲裁组n优先级排序方式位,设置每个仲裁组内中断信号的优先级顺序,比如:ARB_SEL5分组时包含四个中断信号:REQ1 INT_UART0, REQ2 INT_SPI1, REQ3 INT_RTC, REQ4 INT_ADC,ARB_SEL5位采用默认值:00,当INT_UART0和INT_RTC中断信号同时产生时,INT_UART0会被选出,通过可编 程方式改变优先级排序方式来改变中断信号优先级。

  ARB_MODE0~ ARB_MODE6为每个仲裁分组的优先级轮转设置位,采用默认值时,当前中断信号被选择处理之后,再次产生中断请求时,它的优先级自动轮转到该组最低, 这样可以保证优先级低的中断信号可以被及时处理,不至于出现优先级高且中断请求频繁的中断每次都被优先处理,而优先级低的被“饿死”的情况。显然,这种方 式更民主,实时性更佳。

2   中断控制器相关寄存器

(1)SUBSRCPND子中断源暂存寄存器

表3-7 子中断源暂存寄存器(SUBSRCPND)

寄存器名

地址

是否读写

描述

复位默认值

SUBSRCPND

0x4A000018

R/W

子中断源暂存寄存器,保存中断请求状态:

0:没有中断请求信号

1:中断请求信号产生

0x00000000

 

SUBSRCPND

对应SRCPND

描述

初始值

Reserved

[31:15]

未使用

0

INT_AC97

INT_WDT_AC97

[14]

0 = 未产生中断 1 = 产生中断

0

INT_RXD0

INT_UART0

[0]

0 = 未产生中断 1 = 产生中断

0

该寄存器用来标识保存子中断源信号,当某个子中断信号产生之后,SUBSRCPND对应位被自动置1,该位会一直保持被置位,只到中断处理程序将其清除为止,需要注意一下,清除中断是通过向对应位写入1来清除,而不是写入0,写入0无效。

(2)INTSUBMSK子中断源屏蔽寄存器

表3-8 子中断源屏蔽寄存器(INTSUBMSK)

寄存器名

地址

是否读写

描述

复位默认值

INTSUBMSK

0x4A00001C

R/W

子中断源信号屏蔽存寄存器,设置相应位来屏蔽中断信号:

0:未屏蔽,中断可用

1:屏蔽中断信号

0xFFFF

 

INTSUBMSK

描述

初始值

Reserved

[31:15]

未使用

0

INT_AC97

[14]

0 = 未屏蔽1 = 屏蔽中断

1

INT_RXD0

[0]

0 = 未屏蔽1 = 屏蔽中断

1

该寄存器用来屏蔽子中断源信号,默认值为全部子中断都被屏蔽掉,因此要想处理某个硬件中断,必须要打开中断屏蔽位,通过写入0来取消屏蔽中断。

(3)SRCPND中断源暂存寄存器

表3-9中断源暂存寄存器(SRCPND)

寄存器名

地址

是否读写

描述

复位默认值

SRCPND

0x4A000000

R/W

中断源暂存寄存器,保存中断请求状态:

0:没有中断请求信号

1:中断请求信号产生

0x00000000

 

SRCPND

描述

初始值

INT_ADC

[31]

0 = 未产生中断 1 = 产生中断

0

EINT0

[0]

0 = 未产生中断 1 = 产生中断

0

该寄存器用来保存中断源信号,当某个中断信号产生之后, SRCPND对应位被自动置1,该位会一直保持被置位,只到中断处理程序将其清除为止,需要注意一下,清除中断是通过向对应位写入1来清除,而不是写入0,写入0无效。

(4)INTMSK中断源屏蔽寄存器

表3-10中断源屏蔽寄存器(INTMSK)

寄存器名

地址

是否读写

描述

复位默认值

INTMSK

0x4A000008

R/W

中断源信号屏蔽存寄存器,设置相应位来屏蔽中断信号:

0:未屏蔽,中断可用

1:屏蔽中断信号

0xFFFFFFFF

 

INTMSK

描述

初始值

INT_ADC

[31]

0 = 未屏蔽1 = 屏蔽中断

1

EINT0

[0]

0 = 未屏蔽1 = 屏蔽中断

1

该寄存器用来屏蔽中断源信号,默认值为全部中断都被屏蔽掉,因此要想处理某个硬件中断,必须要打开中断屏蔽位,通过写入0来取消屏蔽中断。

(5)INTPND最高优先级中断暂存寄存器

表3-11最高优先级中断暂存寄存器(INTPND)

寄存器名

地址

是否读写

描述

复位默认值

INTPND

0x4A000010

R/W

最高优先级中断暂存寄存器里面保存有经过优先级仲裁的结果:

0:没有中断请求信号

1:中断请求信号产生

0x00000000

 

INTPND

描述

初始值

INT_ADC

[31]

0 = 未产生中断 1 = 产生中断

0

EINT0

[0]

0 = 未产生中断 1 = 产生中断

0

该寄存器保存了经过优先级仲裁出的中断信号位,它是所有当前中断请求里优先级别最高的中断,因此该寄存器里的值最多有一位被置1,通常中断处理程序中会通过读取该寄存器的值来获得当前正在处理的中断请求。中断处理完成之后,通过写入1来清除中断。

(6)INTOFFSET中断号偏移量寄存器

表3-12中断号偏移量寄存器(INTOFFSET)

寄存器名

地址

是否读写

描述

复位默认值

INTOFFSET

0x4A000014

R

中断号偏移量寄存器,用来保存当前处理的中断号

0x0000000

该寄存器里存放的是经过优先级仲裁出的中断信号对应的中断号,是一个0~31之间的整数,其实它就是INTPND里对应的位号,比 如:INT_UART0产生了中断,INTPND里第28位置1,INTOFFSET里保存的整数就是28,多出来这个寄存器的目的主要是方便中断处理程 序查询中断源,清除中断源:

 1 #define TIMER0_IRQ_OFT       10               // 时钟0定时中断
 2 
 3 #define EINT0_IRQ_OFT       0                  // 开发板K1按键1对应外部中断EINT0
 4 
 5 void  handle_irq()
 6 
 7 {
 8 
 9     unsigned long irqOffSet = INTOFFSET;   // 取得中断号
10 
11     switch(irqOffSet)
12 
13     {
14 
15                   case TIMER0_IRQ_OFT:               // 当前中断为定时器0中断
16 
17                     do_timer();                             // 跳入定时器0处理程序
18 
19                         break;
20 
21                   case EINT0_IRQ_OFT:                  // 当前中断为K1按键触发
22 
23                        do_key1_pressed();               // 处理K1按下事件
24 
25                         break;
26 
27     }
28 
29   
30 
31     SRCPND &= (1<<irqOffSet);                   // 清除中断源
32 
33     INTPND = INTPND;                             // 清除最高优先级中断暂存寄存器中断
34 
35 }

 (7)INTMOD中断模式寄存器

表3-13中断模式寄存器(INTMOD)

寄存器名

地址

是否读写

描述

复位默认值

INTMOD

0x4A000004

R/W

中断模式寄存器,指定对应中断模式:

0 = IRQ一般中断模式

1 = FIQ快速中断模式

0x0000000

 

INTMOD

描述

初始值

INT_ADC

[31]

0 = IRQ 1 = FIQ

0

EINT0

[0]

0 = IRQ 1 = FIQ

0

通过设置INTMOD寄存器对应位,来指定对应中断模式,如果指定为一般中断,那么中断信号会进行优先级仲裁,如果指定为快速中断,那么中断信号直接送给ARM内核产生中断。需要注意的是,快速中断不存在优先级仲裁,只能有一位被设置为FIQ模式。

**********************************************************************************************************************************************************

*************************************************************************************************************************************************************

1.1   ARM处理器异常处理

所谓异常就是正常的用户程序被暂时中止,处理器就进入异常模式,例如响应一个来自外设的中断,或者当前程序非法访问内存地址都会进入相应异常模式。

1.1.1   异常分类

(1)复位异常

当CPU刚上电时或按下reset重启键之后进入该异常,该异常在管理模式下处理。

(2)一般/快速中断请求

CPU和外部设备是分别独立的硬件执行单元,CPU对全部设备进行管理和资源调度处理,CPU要想知道外部设备的运行状态,要么CPU定时的去查看 外部设备特定寄存器,要么让外部设备在出现需要CPU干涉处理时“打断”CPU,让它来处理外部设备的请求,毫无疑问第二种方式更合理,可以让CPU“专 心”去工作,这里的“打断”操作就叫做中断请求,根据请求的紧急情况,中断请求分一般中断和快速中断,快速中断具有最高中断优先级和最小的中断延迟,通常 用于处理高速数据传输及通道的中数据恢复处理,如DMA等,绝大部分外设使用一般中断请求。

(3)预取指令中止异常

该异常发生在CPU流水线取指阶段,如果目标指令地址是非法地址进入该异常,该异常在中止异常模式下处理。

(4)未定义指令异常

该异常发生在流水线技术里的译码阶段,如果当前指令不能被识别为有效指令,产生未定义指令异常,该异常在未定义异常模式下处理。

(5)软件中断指令(swi)异常

该异常是应用程序自己调用时产生的,用于用户程序申请访问硬件资源时,例如:printf()打印函数,要将用户数据打印到显示器上,用户程序要想 实现打印必须申请使用显示器,而用户程序又没有外设硬件的使用权,只能通过使用软件中断指令切换到内核态,通过操作系统内核代码来访问外设硬件,内核态是 工作在特权模式下,操作系统在特权模式下完成将用户数据打印到显示器上。这样做的目的无非是为了保护操作系统的安全和硬件资源的合理使用,该异常在管理模 式下处理。

(6)数据中止访问异常

该异常发生在要访问数据地址不存在或者为非法地址时,该异常在中止异常模式下处理。

1.1.2   异常发生的硬件操作

在异常发生后,ARM内核会自动做以下工作:

  •   保存执行状态:将CPSR复制到发生的异常模式下SPSR中;
  •   模式切换:将CPSR模式位强制设置为与异常类型相对应的值,同时处理器进入到ARM执行模式,禁止所有IRQ中断,当进入FIQ快速中断模式时禁止FIQ中断;
  •   保存返回地址:将下一条指令的地址(被打断程序)保存在LR(异常模式下LR_excep)中。
  •   跳入异常向量表:强制设置PC的值为相应异常向量地址,跳转到异常处理程序中。

(1)保存执行状态

当前程序的执行状态是保存在CPSR里面的,异常发生时,要保存当前的CPSR里的执行状态到异常模式里的SPSR里,将来异常返回时,恢复回CPSR,恢复执行状态。

(2)模式切换

硬件自动根据当前的异常类型,将异常码写入CPSR里的M[4:0]模式位,这样CPU就进入了对应异常模式下。不管是在ARM状态下还是在 THUMB状态下发生异常,都会自动切换到ARM状态下进行异常的处理,这是由硬件自动完成的,将CPSR[5] 设置为 0。同时,CPU会关闭中断IRQ(设置CPSR 寄存器I位),防止中断进入,如果当前是快速中断FIQ异常,关闭快速中断(设置CPSR寄存器F位)。

(3)保存返回地址

当前程序被异常打断,切换到异常处理程序里,异常处理完之后,返回当前被打断模式继续执行,因此必须要保存当前执行指令的下一条指令的地址到 LR_excep(异常模式下LR,并不存在LR_excep寄存器,为方便读者理解加上_excep,以下道理相同),由于异常模式不同以及ARM内核 采用流水线技术,异常处理程序里要根据异常模式计算返回地址。

(4)跳入异常向量表

该操作是CPU硬件自动完成的,当异常发生时,CPU强制将PC的值修改为一个固定内存地址,这个固定地址叫做异常向量(详见3.2.4章节)。

1.1.3   异常返回地址

由1.1.3节可知,一条指令的执行分为:取指,译码,执行三个主要阶段, CPU由于使用流水线技术,造成当前执行指令的地址应该是PC – 8(32位机一条指令四个字节),那么执行指令的下条指令应该是PC – 4。在异常发生时,CPU自动会将将PC – 4 的值保存到LR里,但是该值是否正确还要看异常类型才能决定。

各模式的返回地址说明如下:

(a)一般/快速中断请求:

快速中断请求和一般中断请求返回处理是一样的。通常处理器执行完当前指令后,查询FIQ/IRQ中断引脚,并查看是否允许FIQ/IRQ中断,如果 某个中断引脚有效,并且系统允许该中断产生,处理器将产生FIQ/IRQ异常中断,当FIQ/IRQ异常中断产生时,程序计数器pc的值已经更新,它指向 当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当 FIQ/IRQ异常中断产生时,处理器将值(pc-4)保存到FIQ/IRQ异常模式下的寄存器lr_irq/lr_irq中,它指向当前指令之后的第2 条指令,因此正确返回地址可以通过下面指令算出:

1 SUBS    PC,LR_irq,#4        ; 一般中断
2 
3 SUBS    PC,LR_fiq,#4                   ; 快速中断

注:LR_irq/LR_fiq分别为一般中断和快速中断异常模式下LR,并不存在LR_xxx寄存器,为方便读者理解加上_xxx

(b)预取指中止异常:

  在指令预取时,如果目标地址是非法的,该指令被标记成有问题的指令,这时,流水线上该指令之前的指令继续执行,当执行到该被标记成有问题的指令时, 处理器产生指令预取中止异常中断。发生指令预取异常中断时,程序要返回到该有问题的指令处,重新读取并执行该指令,因此指令预取中止异常中断应该返回到产 生该指令预取中止异常中断的指令处,而不是当前指令的下一条指令。

  指令预取中止异常中断由当前执行的指令在ALU里执行时产生,当指令预取中止异常中断发生时,程序计数器pc的值还未更新,它指向当前指令后面第2 条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置)。此时处理器将值(pc-4)保存 到lr_abt中,它指向当前指令的下一条指令,所以返回操作可以通过下面指令实现:

SUBS  PC,LR_abt,#4

注:LR_abt为中止模式下LR,并不存在LR_abt寄存器,为方便读者理解加上_abt

(c)未定义指令异常:

  未定义指令异常中断由当前执行的指令在ALU里执行时产生,当未定义指令异常中断产生时,程序计数器pc的值还未更新,它指向当前指令后面第2条指 令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置),当未定义指令异常中断发生时,处理器 将值(pc-4)保存到lr_und中,此时(pc-4)指向当前指令的下一条指令,所以从未定义指令异常中断返回可以通过如下指令来实现:

 MOV PC, LR_und 

注:LR_und为未定义模式下LR,并不存在LR_und寄存器,为方便读者理解加上_und

(d)软中断指令(SWI)异常:

  SWI异常中断和未定义异常中断指令一样,也是由当前执行的指令在ALU里执行时产生,当SWI指令执行时,pc的值还未更新,它指向当前指令后面 第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置),当未定义指令异常中断发生 时,处理器将值(pc-4)保存到lr_svc中,此时(pc-4)指向当前指令的下一条指令,所以从SWI异常中断处理返回的实现方法与从未定义指令异 常中断处理返回一样:

 MOV PC, LR_svc 

注:LR_svc为管理模式下LR,并不存在LR_svc寄存器,为方便读者理解加上_svc

(e)数据中止异常:

  发生数据访问异常中断时,程序要返回到该有问题的指令处,重新访问该数据,因此数据访问异常中断应该返回到产生该数据访问中止异常中断的指令处,而不是当前指令的下一条指令。

  数据访问异常中断由当前执行的指令在ALU里执行时产生,当数据访问异常中断发生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令 (对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置)。此时处理器将值(pc-4)保存到 lr_abt中,它指向当前指令后面第2条指令,所以返回操作可以通过下面指令实现:

 SUBS PC, LR_abt, #8 

注:LR_abt为中止模式下LR,并不存在LR_abt寄存器,为方便读者理解加上_abt

上述每一种异常发生时,其返回地址都要根据具体异常类型进行重新修复返回地址,再次强调下,被打断程序的返回地址保存在对应异常模式下的LR_excep里。

1.1.4   异常向量表

  异常向量表是一段特定内存地址空间,每种ARM异常对应一个字长空间(4Bytes),正好是一条32位指令长度,当异常发生时,CPU强制将PC的值设置为当前异常对应的固定内存地址。如表3-4所示是S3C2440的异常向量表。

表3-4 异常向量表

 

注:

  异常向量也可以出现在高地址0xFFFF0000处,当今操作系统为了控制内存访问权限,通常会开启虚拟内存,开启了虚拟内存之后,内存的开始空间通常为内核进程空间,和页表空间,异常向量表不能再安装在0地址处了

  ARM的例外优先级从高到低依次为Reset→Data abort→FIQ→IRQ→Prefetch abort→Undefined instruction/SWI。

  跳入异常向量表操作是异常发生时,硬件自动完成的,剩下的异常处理任务完全交给了程序员。由上表可知,异常向量是一个固定的内存地址,我们可以通过向该地址处写一条跳转指令,让它跳向我们自己定义的异常处理程序的入口,就可以完成异常处理了。

  正是由于异常向量表的存在,才让硬件异常处理和程序员自定义处理程序有机联系起来。异常向量表里0x00000000地址处是reset复位异常, 之所以它为0地址,是因为CPU在上电时自动从0地址处加载指令,由此可见将复位异常安装在此地址处也是前后接合起来设计的,不得不感叹CPU设计师的伟 大,其后面分别是其余7种异常向量,每种异常向量都占有四个字节,正好是一条指令的大小,最后一个异常是快速中断异常,将其安装在此也有它的意义,在 0x0000001C地址处可以直接存放快速中断的处理程序,不用设置跳转指令,这样可以节省一个时钟周期,加快快速中断处理时间。

我们可以通过简单的使用下面的指令来安装异常向量表:

b reset                                      ;跳入reset处理程序

b HandleUndef              ;跳入未定义处理程序

b HandSWI                    ;跳入软中断处理程序

b HandPrefetchAbt        ;跳入预取指令处理程序

b HandDataAbt              ;跳入数据访问中止处理程序

b HandNoUsed              ;跳入未使用程序

b HandleIRQ                 ;跳入中断处理程序

b HandleFIQ                  ;跳入快速中断处理程序

通常安装完异常向量表,跳到我们自己定义的处理程序入口,这时我们还没有保存被打断程序的现场,因此在异常处理程序的入口里先要保存打断程序现场。

保存执行现场:

异常处理程序最开始,要保存被打断程序的执行现场,程序的执行现场无非就是保存当前操作寄存器里的数据,可以通过下面的栈操作指令实现保存现场:

 STMFD SP_excep!, {R0 – R12, LR_excep} 

注:LR_abt,SP_excep分别为对应异常模式下LR和SP,为方便读者理解加上_abt

需要注意的是,在跳转到异常处理程序入口时,已经切换到对应异常模式下了,因此这里的SP是异常模式下的SP_excep了,所以被打断程序现场 (寄存器数据)是保存在异常模式下的栈里,上述指令将R0~R12全部都保存到了异常模式栈,最后将修改完的被打断程序返回地址入栈保存,之所以保存该返 回地址就是将来可以通过类似:MOV  PC,  LR的指令,返回用户程序继续执行。

异常发生后,要针对异常类型进行处理,因此,每种异常都有自己的异常处理程序,异常处理过程通过下节的系统中断处理来进行分析。

1.1.5   异常处理的返回

异常处理完成之后,返回被打断程序继续执行,具体操作如下:

  •   恢复被打断程序运行时寄存器数据
  •   恢复程序运行时状态CPSR
  •   通过进入异常时保存的返回地址,返回到被打断程序继续执行

异常发生后,进入异常处理程序时,将用户程序寄存器R0~R12里的数据保存在了异常模式下栈里面,异常处理完返回时,要将栈里保存的的数据再恢复 回原先R0~R12里,毫无疑问在异常处理过程中必须要保证异常处理入口和出口时栈指针SP_excep要一样,否则恢复到R0~R12里的数据不正确, 返回被打断程序时执行现场不一致,出现问题,虽然将执行现场恢复了,但是此时还是在异常模式下,CPSR里的状态是异常模式下状态,因此要恢复 SPSR_excep里的保存状态到CPSR里,SPSR_excep是被打断程序执行时的状态,在恢复SPSR_excep到CPSR的同时,CPU的 模式和状态从异常模式切换回了被打断程序执行时的模式和状态。此刻程序现场恢复了,状态也恢复了,但PC里的值仍然指向异常模式下的地址空间,我们要让 CPU继续执行被打断程序,因此要再手动改变PC的值为进入异常时的返回地址,该地址在异常处理入口时已经计算好,直接将PC = LR_excep即可。

上述操作可以一步一步实现,但是通常我们可以通过一条指令实现上述全部操作:

 LDMFD SP_excp!, {r0-r12, pc}^ 

注:SP_excep为对应异常模式下SP,^符号表示恢复SPSR_excep到CPSR

以上操作可以用下图来描述:

 

图3-2中断处理示意图

****************************************************************************************************************************************************************

****************************************************************************************************************************************************************

 

ARM系统中断产生流程

分类: ARM体系结构 2011-06-20 14:40 247人阅读评论(2) 收藏举报

 

中断源按照硬件位置分为外部中断源和内部中断源,外部中断源和内部中断源又包含子外部中断源和子内部中断源,如上图所示(画了一整天)。

1. 子内部中断源的产生

  以UART0接收数据产生INT_RXD0中断为例,INT_RXD0产生后进入SUBSRCPND子中断源暂存寄存器,设置INT_RXD0对应 的中断位,中断信号经过INTSUBMSK子中断屏蔽寄存器,如果INT_RXD0信号对应位没有被置位(屏蔽掉),中断信号继续向前传递,经过子内部中 断源聚合器,将INT_RXD0聚合成对应的中断源信号INT_UART0,设置SRCPND中断源暂存寄存器里INT_UART0位,经过INTMSK 中断屏蔽寄存器,如果INT_UART0信号没有被屏蔽掉,中断信号进入INTMOD中断模式寄存器判断是否为快速中断,如果被编程为快速中断,直接打断 ARM内核,进入中断处理,如果中断信号为一般中断,进入中断优先级仲裁器进入优先级仲裁,如果INT_UART0信号为最高优先级或只有 INT_UART0中断信号产生,则该中断信号被记录到INTPND最高优先级中断暂存寄存器,同时设置INTOFFSET的值为中断号28,最终将中断 信号打断ARM内核进行中断处理。如果同时产生多个中断且INT_UART0不是最高优先级,则该中断信号不会被处理,等最高优先级信号处理完后,再次进 行优先级仲裁,也就是说中断信号不消失,一直保存在SRCPND里,只到被处理为止。

2. 内部中断源的产生

该过程在子内部中断处理过程中已经包含,中断信号产生后直接进入SRCPND里,然后经历上述子内部中断后期处理过程。

3. 子外部中断的产生

外部中断源共有24个,其中EINT0~EINT3为外部中断源,EINT4_7,EINT8_23为复合中断源,他们包含有子外部中断源。

由于外部硬件直接挂接到I/O Ports(详见S3C2440A硬件手册第9章)上的,我们要想让外设硬件中断得到处理,要先从EINT0~EINT23里选择中断信号,我们以EINT11为例,介绍子外部中断处理过程。

通常CPU内部引出引脚都是复用的,也就是说一根CPU引脚可以有多种功能,可以设置其为输入信号线,输出信号线或中断信号线,要想让硬件产生中 断,首先要对可以产生中断的引脚进行编程,设置该引脚为中断信号线。EINT11中断信号对应CPU引脚为GPG3,通过设置GPGCON[7:6] = 0b10,可以设置该引脚为中断信号线。

表3-14 GPGCON寄存器

 

 

设置了CPU管脚为中断信号线之后,还要通过设置EXTINT0寄存器来指定中断信号的触发方式:高电平触发,低电平触发,电平上升沿,下除沿,双沿触发。

 

图3-9 电平信号触发示意图

由于按键按下时让它产生中断,也就是从高电平变为低电平时产生(上节按键中断原理),因此我们设置EINT11中断信号的触发方式为下降沿触发,EXTINT1[14:12] = 0b01x

表3-15 EXTINT1寄存器

 

  设置完触发方式之后,当外设中断信号线上的电平达到触发条件时,通过外部中断产生器产生中断信号,然后将子外部中断暂存寄存器EINTPND中对应 的EINT11位置1,中断信号再进入EINTMSK子外部中断屏蔽寄存器,如果EINT11中断源信号没有被屏蔽,则EINT11中断信号进入子外部中 断聚合器,复合成EINT8_23中断信号,然后再经历与前面子内部中断信号一样的处理机制。

(1)EINTPEND外部中断暂存寄存器

表3-16外部中断暂存寄存器(EINTPEND)

寄存器名

地址

是否读写

描述

复位默认值

EINTPEND

0x560000A8

R/W

外部中断信号暂存寄存器

0:没有中断请求信号

1:中断请求信号产生

0x0000000

 

EINTPEND

描述

初始值

EINT23

[23]

0 = 未产生中断 1 = 产生中断

0

EINT4

[4]

0 = 未产生中断 1 = 产生中断

0

保留位

[3:0]

0000

 

(2)EINTMASK外部中断屏蔽寄存器

表3-17外部中断屏蔽寄存器(EINTMASK)

寄存器名

地址

是否读写

描述

复位默认值

EINTMASK

0x560000A4

R/W

外部中断信号屏蔽寄存器

0:未屏蔽,中断可用

1:屏蔽中断信号

0x000FFFFF

 

EINTMASK

描述

初始值

EINT23

[23]

0 = 未屏蔽1 = 屏蔽中断

1

EINT4

[4]

0 = 未屏蔽1 = 屏蔽中断

1

保留位

[3:0]

1111

4. 外部中断源的产生

外部中断产生过程读者可以根据上面中断图自行分析。

按键控制LED灯实验

本实验分三个版本,分别针对三种开发板:友善之臂QQ2440,友善之臂MINI2440,天嵌TQ2440。每种开发板对应工程在:“sys_irq_开发板名”目录下。下面实验内容为针对MINI2440开发板。

head.s:

主要实现安装异常向量表,处理复位异常,初始化必要硬件,中断入口处理等功能。

  1 ;**********************************************************************
  2 
  3 ; 系统中断实验(MINI2440)
  4 
  5 ;**********************************************************************
  6 
  7 GPBCON   EQU      0x56000010
  8 
  9 GPBDAT    EQU      0x56000014       
 10 
 11          EXPORT SYS_IRQ
 12 
 13          AREA  SYS_IRQ,CODE,READONLY
 14 
 15          ENTRY
 16 
 17          ;**********************************************************************     
 18 
 19          ; 设置中断向量,除Reset和HandleIRQ外,其它异常都没有使用(如果不幸发生了,
 20 
 21          ; 将导致死机)
 22 
 23          ;**********************************************************************     
 24 
 25          ; 0x00: 复位Reset异常
 26 
 27          b       Reset
 28 
 29  
 30 
 31 ; 0x04: 未定义异常(未处理)
 32 
 33 HandleUndef
 34 
 35          b       HandleUndef
 36 
 37  
 38 
 39 ; 0x08: 软件中断异常(未处理)
 40 
 41 HandleSWI
 42 
 43          b       HandleSWI
 44 
 45  
 46 
 47 ; 0x0c: 指令预取异常(未处理)
 48 
 49 HandlePrefetchAbt
 50 
 51          b       HandlePrefetchAbt
 52 
 53  
 54 
 55 ; 0x10: 数据访问中止异常(未处理)
 56 
 57 HandleDataAbt
 58 
 59          b       HandleDataAbt
 60 
 61  
 62 
 63 ; 0x14: 未使用异常(未处理)
 64 
 65 HandleNotUsed
 66 
 67          b       HandleNotUsed
 68 
 69  
 70 
 71 ; 0x18: 一般中断异常,跳往HandleIRQ
 72 
 73          b       HandleIRQ
 74 
 75  
 76 
 77 ; 0x1c: 快速中断异常(未处理)
 78 
 79 HandleFIQ
 80 
 81            b       HandleFIQ
 82 
 83  
 84 
 85 Reset                                                                          ; 复位异常处理入口
 86 
 87          ; 关闭看门狗
 88 
 89            ldr  r0, = 0x53000000
 90 
 91            mov  r1, #0
 92 
 93            str  r1, [r0]
 94 
 95            bl  initmem
 96 
 97          ldr     sp,     =0x32000000                                  ; 设置管理模式栈指针
 98 
 99            IMPORT uart_init
100 
101          bl  uart_init                                                       ; UART串口初始化
102 
103            IMPORT irq_init
104 
105          bl      irq_init                                                      ; 系统中断初始化
106 
107            IMPORT key_init
108 
109          bl  key_init                                                       ; 按键初始化
110 
111            IMPORT led_init
112 
113          bl  led_init                                                        ; LED灯初始化
114 
115          msr   cpsr_cxsf, #0xd2                                      ; 切换到中断模式下
116 
117          ldr     sp,     =0x31000000                                  ; 设置中断模式栈指针
118 
119  
120 
121          msr   cpsr_cxsf, #0x13                                      ; 返回管理模式
122 
123          
124 
125          ldr     lr,      =halt_loop                                       ; 设置管理模式下返回地址
126 
127            IMPORT main
128 
129          ldr     pc,     =main                                              ; 跳入主函数main里执行
130 
131            ;***********************************************************************
132 
133            ; 中断处理
134 
135            ;***********************************************************************
136 
137 HandleIRQ
138 
139          sub  lr,lr,#4                                                                ; 修正返回地址
140 
141          stmdb  sp!,{r0-r12,lr}                                               ; 保存程序执行现场
142 
143          ldr  lr,=int_return                                                       ; 设置中断处理程序返回地址
144 
145            IMPORT handle_irq
146 
147          ldr  pc,=handle_irq                                           ; 跳入中断处理程序
148 
149  
150 
151 int_return                                                                      ; 中断处理返回标签
152 
153            ldmia  sp!,{r0-r12,pc}^                                             恢复程序执行现场,返回继续执行
154 
155          
156 
157 halt_loop
158 
159          b  halt_loop
160 
161 initmem
162 
163          ldr  r0, =0x48000000
164 
165            ldr  r1, =0x48000034
166 
167          ;ldr  r2, =memdata
168 
169          adr  r2, memdata
170 
171 initmemloop
172 
173            ldr  r3, [r2], #4
174 
175          str  r3, [r0], #4
176 
177          teq  r0, r1
178 
179          bne  initmemloop
180 
181          mov  pc,lr
182 
183  
184 
185 memdata
186 
187            DCD 0x22000000                ;BWSCON
188 
189          DCD 0x00000700                 ;BANKCON0   
190 
191          DCD 0x00000700                 ;BANKCON1   
192 
193          DCD 0x00000700                 ;BANKCON2   
194 
195          DCD 0x00000700               ;BANKCON3            
196 
197          DCD 0x00000700                 ;BANKCON4   
198 
199            DCD 0x00000700                 ;BANKCON5   
200 
201          DCD 0x00018005                 ;BANKCON6   
202 
203          DCD 0x00018005                 ;BANKCON7   
204 
205            DCD 0x008e07a3                  ;REFRESH       
206 
207            DCD 0x000000b1                 ;BANKSIZE     
208 
209          DCD 0x00000030                 ;MRSRB6
210 
211          DCD 0x00000030                 ;MRSRB7
212 
213          END                                                                                     ; 代码结束

 该程序主要设置异常向量表,除了Reset异常和中断处理被处理以外,其它异常都未被处理,如果发生时,会产生死循环,Reset异常里主要实现了 硬件的基本初始化,如:按键,LED灯等,设置栈指针,用于执行C程序,最后跳入C程序的main函数。在中断处理异常处理中首先修正返回地址,保存用户 执行现场,跳入到中断处理例程中执行。

sys_init.c:

硬件初始化文件,里面包含LED,KEY的初始化函数。

  1 #include "register.h"
  2 
  3 #include "comm_fun.h"
  4 
  5  
  6 
  7 #define        TXD0READY    (1<<2)      //发送数据状态OK
  8 
  9 #define        RXD0READY   (1)            //接收数据状态OK
 10 
 11  
 12 
 13 /* UART串口初始化 */
 14 
 15 void uart_init()
 16 
 17 {
 18 
 19          GPHCON |= 0xa0;                //GPH2,GPH3 used as TXD0,RXD0
 20 
 21                   GPHUP     = 0x0;                //GPH2,GPH3内部上拉
 22 
 23          ULCON0   = 0x03;             //8N1        
 24 
 25          UCON0     = 0x05;             //查询方式为轮询或中断;时钟选择为PCLK
 26 
 27          UFCON0 = 0x00;                 //不使用FIFO
 28 
 29                   UMCON0 = 0x00;                //不使用流控
 30 
 31          UBRDIV0 = 12;                   //波特率为57600,PCLK=12Mhz
 32 
 33 }
 34 
 35  
 36 
 37 /* UART串口单个字符打印函数 */
 38 
 39 extern void putc(unsigned char c)
 40 
 41 {
 42 
 43          while( ! (UTRSTAT0 & TXD0READY) );
 44 
 45          UTXH0 = c;
 46 
 47 }
 48 
 49  
 50 
 51 /* UART串口接受单个字符函数 */
 52 
 53 extern unsigned char getc(void)
 54 
 55 {
 56 
 57          while( ! (UTRSTAT0 & RXD0READY) );
 58 
 59          return URXH0;
 60 
 61 }
 62 
 63  
 64 
 65 /* UART串口字符串打印函数 */
 66 
 67 extern int printk(const char* str)
 68 
 69 {
 70 
 71          int i = 0;
 72 
 73          while( str[i] ){
 74 
 75                     utc( (unsigned char) str[i++] );
 76 
 77          }
 78 
 79          return i;
 80 
 81 }
 82 
 83  
 84 
 85 /* 按键初始化 */
 86 
 87 int key_init()
 88 
 89 {
 90 
 91 // 设置K1,K2,K3,K4,K5,K6对应控制寄存器为中断模式
 92 
 93          GPGCON = (2<<0) | (2<<6) | (2<<10) | (2<<12) | (2<<14) | (2<<22);                 
 94 
 95          /*
 96 
 97         01x falling edge triggered下降沿触发
 98 
 99         10x Rising edge triggered上升沿触发
100 
101         11x Both edge triggered双沿触发
102 
103          */
104 
105            //  设置K1,K2,K3,K4,K5按键中断触发方式为上升沿触发
106 
107          EXTINT1 = (3<<0) | (3<<12) | (3<<20) | (3<<24) | (3<<28);    
108 
109            EXTINT2 = (3<<12);                              //  设置K6按键中断触发方式为上升沿触
110 
111            printk("按键初始化OK/r/n");               
112 
113          return 0;
114 
115 }
116 
117  
118 
119 /* Led1~Led4初始化*/
120 
121 #define LED1       (1<<5)                                   //LED1 GPBDAT[5]
122 
123 #define LED2       (1<<6)                                   //LED2 GPBDAT[6]
124 
125 #define LED3       (1<<7)                                   //LED3 GPBDAT[7]
126 
127 #define LED4       (1<<8)                                   //LED4 GPBDAT[8]
128 
129  
130 
131 /* 点亮对应num号led灯 */
132 
133 extern int led_on(int num)
134 
135 {
136 
137          switch(num)
138 
139          {
140 
141                     case 1:
142 
143                              GPBDAT = GPBDAT & ~LED1; break;
144 
145                     case 2:
146 
147                              GPBDAT = GPBDAT & ~LED2; break;
148 
149                     case 3:
150 
151                              GPBDAT = GPBDAT & ~LED3; break;
152 
153                     case 4:
154 
155                              GPBDAT = GPBDAT & ~LED4; break;
156 
157                     default:
158 
159                              return 0;
160 
161          }
162 
163          return num;
164 
165 }
166 
167  
168 
169 /* 关闭num号led灯 */
170 
171 extern int led_off(int num)
172 
173 {
174 
175          switch(num)
176 
177          {
178 
179                     case 1:
180 
181                              GPBDAT = GPBDAT | LED1; break;
182 
183                     case 2:
184 
185                              GPBDAT = GPBDAT | LED2; break;
186 
187                     case 3:
188 
189                              GPBDAT = GPBDAT | LED3; break;
190 
191                     case 4:
192 
193                              GPBDAT = GPBDAT | LED4; break;
194 
195                     default:
196 
197                              return 0;
198 
199          }
200 
201          return num;
202 
203 }
204 
205  
206 
207 /* 关闭全部led灯 */
208 
209 extern int all_led_off(void)
210 
211 {
212 
213          GPBDAT = GPBDAT | LED1 | LED2 | LED3 | LED4;
214 
215          return 0;
216 
217 }
218 
219  
220 
221 /* led灯初始化 */
222 
223 int led_init(void)
224 
225 {
226 
227          GPBCON = 0x15400;                             //设置GPB7为输出口
228 
229          all_led_off();
230 
231          printk("led初始化OK/r/n");
232 
233          return 0;
234 
235 }
236 
237  
238 
239 /* 中断初始化 */
240 
241 void irq_init(void)
242 
243 {
244 
245            // 打开KEY1~KEY6的屏蔽位
246 
247          INTMSK &= ~(1<<5);
248 
249          EINTMASK &= ~((1<<8) | (1<<11) | (1<<13) | (1<<14) | (1<<15) | (1<<19));
250 
251            printk("中断初始化OK/r/n");
252 
253 }

该文件是相关硬件初始化程序,主要包含了看门狗驱动,按键驱动,系统中断驱动,LED驱动。

    handle_irq.c:

中断处理函数,查出中断源,中断处理,清除中断源。

 1 #include "register.h"
 2 
 3 #include "comm_fun.h"
 4 
 5  
 6 
 7 #define EINT_Key_REQUEST       5                // Key中断源中断号(6个按键全部使用外部子中断)
 8 
 9 #define K1_EINT_BIT                   (1<<8)       // K1外部子中断位
10 
11 #define K2_EINT_BIT                   (1<<11)     // K2外部子中断位
12 
13 #define K3_EINT_BIT                   (1<<13)     // K3外部子中断位
14 
15 #define K4_EINT_BIT                   (1<<14)     // K4外部子中断位
16 
17 #define K5_EINT_BIT                   (1<<15)     // K5外部子中断位
18 
19 #define K6_EINT_BIT                   (1<<19)     // K6外部子中断位
20 
21 /* 系统中断处理函数 */
22 
23 void handle_irq()
24 
25 {
26 
27          unsigned long irqOffSet = INTOFFSET;          // 取得中断号
28 
29            all_led_off();                                                               // 关闭全部Led灯
30 
31            if(EINT_Key_REQUEST==irqOffSet){                    // Key中断产生(6个按键使用一个总中断号)
32 
33                     if(K1_EINT_BIT & EINTPEND){
34 
35                              led_on(1);                                                 // 点亮Led1
36 
37                            printk("Key1 pressed/r/n");
38 
39                              EINTPEND &= K1_EINT_BIT;             // 清除外部子中断源
40 
41                     }else if(K2_EINT_BIT & EINTPEND){
42 
43                              led_on(2);                                                 // 点亮Led2
44 
45                            printk("Key2 pressed/r/n");
46 
47                              EINTPEND &= K2_EINT_BIT;             // 清除外部子中断源
48 
49                     }else if(K3_EINT_BIT & EINTPEND){
50 
51                              led_on(3);                                                 // 点亮Led3
52 
53                            printk("Key3 pressed/r/n");
54 
55                              EINTPEND &= K3_EINT_BIT;             // 清除外部子中断源
56 
57                     }else if(K4_EINT_BIT & EINTPEND){
58 
59                              led_on(4);                                                 // 点亮Led4
60 
61                            printk("Key4 pressed/r/n");
62 
63                              EINTPEND &= K4_EINT_BIT;             // 清除外部子中断源
64 
65                     }else if(K5_EINT_BIT & EINTPEND){
66 
67                              all_led_off(1);                                           // 熄灭全部Led
68 
69                            printk("Key5 pressed/r/n");
70 
71                              EINTPEND &= K5_EINT_BIT;             // 清除外部子中断源
72 
73                   }else if(K6_EINT_BIT & EINTPEND){
74 
75                              all_led_on();                                             // 点亮全部Led
76 
77                            printk("Key6 pressed/r/n");
78 
79                              EINTPEND &= K6_EINT_BIT;             // 清除外部子中断源
80 
81                     }
82 
83          }
84 
85            SRCPND &= (1<<irqOffSet);                                   // 清除中断源
86 
87            INTPND = INTPND;                                                 // 清除中断结果
88 
89 }

main.c:

包含主函数和延时函数,主要实现字符串的循环打印。

 1 #include "register.h"
 2 
 3 #include "comm_fun.h"
 4 
 5  
 6 
 7 /* 延时 */
 8 
 9 void delay(int msec)
10 
11 {
12 
13          int i, j;
14 
15          for(i = 1000; i > 0; i--)
16 
17                     for(j = msec*10; j > 0; j--)
18 
19                     /* do nothing */;
20 
21 }
22 
23  
24 
25 /* 主函数 */
26 
27 int main()
28 
29 {
30 
31          while(1)
32 
33          {
34 
35               printk("main函数在运行.../r/n");
36 
37              delay(5);              //delay
38 
39        }
40 
41        return 0;
42 
43 }

 


完。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值