S3C2440中的异常与中断

发布者:RadiantExplorer最新更新时间:2021-09-03 来源: eefocus关键字:S3C2440  异常  中断 手机看文章 扫描二维码
随时随地手机看文章

1、异常与中断的简单介绍与实现

S3C2440中一共有7种模式(如图1),其中异常模式有svc(管理模式)、abt(中止模式)、und(未定义指令模式)、irq(中断模式)和 fiq(快中断模式)5种,中止模式又分为指令预取中止和数据访问中止;usr(用户模式)不可直接切换到其他模式,而其他6种模式都可以通过修改CPSR[4:0]与其他模式进行切换(参考图3和图4)。

之所以会有这么多的异常模式是为了能更好地处理程序遇到的各种异常,在ARM状态下(S3C2440的CPU有ARM状态和THUMB状态之分)这些异常模式的差别如图2所示。在supervisor、abortt、iqr和undefine中,R0-R12 和 CPSR是通用的寄存器,R13-R15 和 SPSR是各自私有的寄存器;而在fiq中 R0-R7和 CPSR是通用的寄存器,R8-R15和SPSR是自己私有的;user和system中的 R0-R15和CPSR都是通用的寄存器,无私有的SPSR。

中断是异常的一种情况,它是我们想要的,而其他的异常却是我们不太愿意见到的。

图 1

图 2

图 3

图 4

为了处理不同的异常(中断)CPU也是耗费了脑筋,那么CPU到底是怎么去处理异常(中断)的呢?下面进行简单地介绍(参考图7和图8):


每执行完一条指令CPU就会去检测有无异常(中断)产生;

CPU一检测到有异常(中断)产生就会针对不同的异常(中断)跳到不同的地址去处理,这些地址被称为中断向量表(如图5);

在进入到具体的异常(中断)处理程序之前,CPU会将被中断处的下一条指令的地址保存在相应异常(中断)自己的LR寄存器里,将被中断程序的CPSR保存到相应异常(中断)自己的SPSR中同时将CPSR的低5位修改成自己的值(如图4),最后CPU强制将PC设为相应异常(中断)的向量地址(如图5);

保护现场:先根据图6修改LR的值,然后将可能被修改的通用寄存器(fiq: R0-R7;其他:R0-R12)和LR保存在相应异常(中断)自己的栈中;处理异常:执行异常(中断)处理函数;恢复现场:将被保存的寄存器从栈中弹出并将LR的值赋给PC,以让程序从被中断处的下一条指令开始执行。

图 5

图 6

图 7

图 8

下面是未定义指令异常和软件中断的代码示例(start.S):



.text

.global _start


_start:

b reset /* vector 0 : reset */

ldr pc, und_addr /* vector 4 : und */

ldr pc, swi_addr /* vector 8 : swi */


und_addr: /* 存放跳转指令,以防前面部分超过4k从而导致不能NAND启动 */

.word do_und

swi_addr:

.word do_swi


do_und:

/* 执行到这里之前:

* 1. lr_und中保存了被中断模式中的下一条即将执行的指令的地址

* 2. SPSR_und保存有被中断模式的CPSR

* 3. CPSR中的M4-M0被设置为11011,进入到und模式

* 4. 跳到0x4的地方执行程序

*/

/* 设置sp_und */

ldr sp, =0x34000000 /* SDRAM为64M,将SP指向它的最顶端 */


/* 在und异常处理函数中可能会修改r0-r12,要先保存它们 

* lr是异常模式处理函数完后的返回地址,也要保存

*/

stmdb sp!, {r0-r12, lr}


/* 处理异常 */

mrs r0, cpsr

ldr r1, =und_string

bl printException


/* 恢复现场 */

ldmia sp!, {r0-r12,pc}^ /* ^会把spsr的值复制到cpsr */


und_string:

.string "undefine instruction exception!"


.align 4 /* 4字节对齐 */


do_swi:

/* 执行到这里之前:

* 1. lr_svc中保存了被中断模式中的下一条即将执行的指令的地址

* 2. SPSR_svc保存有被中断模式的CPSR

* 3. CPSR中的M4-M0被设置为10011,进入到swi模式

* 4. 跳到0x8的地方执行程序

*/

/* 设置sp_und */

ldr sp, =0x33e00000 /* 不能与前面设的SP相同 */


/* 在swi异常处理函数中可能会修改r0-r12,要先保存它们 

* lr是异常模式处理函数完后的返回地址,也要保存

*/

stmdb sp!, {r0-r12, lr}


mov r4, lr /* 为得到软件中断的值,r4根据ATPCS调用规则选取 */


/* 处理异常 */

mrs r0, cpsr

ldr r1, =swi_string

bl printException

sub r0, r4, #4

bl printSwiVal


/* 恢复现场 */

ldmia sp!, {r0-r12,pc}^ /* ^会把spsr的值复制到cpsr */


swi_string:

.string "swi instruction exception!"


.align 4

reset:

.../* 部分省略 */

/* 复位之后,CPU切换到svc模式

* 现在切换到usr模式

*/

mrs r0, cpsr

bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */

msr cpsr, r0


/* 设置sp_usr */

ldr sp, =0x33f00000

.../* 部分省略 */

und_code:

.word 0xdeadc0de /* 未定义指令 */


swi 0x123 /* 软件中断 */

.../* 部分省略 */


其中的printException和printSwiVal如下,


void printException(unsigned int cpsr, char *str)

{

puts("Excption! cpsr= ");

printHex(cpsr);

puts(" ");

puts(str);

puts("nr");

}


void printSwiVal(unsigned int *pSWI)

{

puts("swi val= ");

printHex(*pSWI & ~0xff000000);

puts("nr");

}


2、按键中断控制LED亮灭

在我之前的博文用LED实现流水灯和用按键控制LED灯的亮灭中设置过用按键控制LED亮灭的程序,但是前面我们是将按键设为的输入,并且是在主程序中通过不断地去检测按键所连引脚处输入的状态来决定灯该亮该灭,这极大的浪费了CPU的资源。为此我们即将让他变身为中断模式,当有按键按下时产生中断去执行点灯,无按键按下时CPU继续执行其他程序。

下面让我们先梳理用按键中断控制LED亮灭的过程:


初始化按键。将连接按键的引脚设为外部中断模式,设置对应引脚的中断触发方式(在图9中可以知到对应按键的外部中断号),根据图10设置EINTMASK的11bit和19bit为0,使能中断源。

EINT0-EINT3不需要单独设置。

图 9

图 10

初始化中断。通过阅读S3C2440的芯片手册知道在这里我们只需要设置中断控制器中INTMASK寄存器的0bit/2bit/5bit为0以启用对应的外部中断。

图 11

清除CPSR中的 I 位(如图3)以使能中断。

以下为代码部分(strat.S,在上文的基础上添加了部分代码)


.text

.global _start


_start:

b reset /* vector 0x0 : reset */

ldr pc, und_addr /* vector 0x4 : undefined instruction */

ldr pc, swi_addr /* vector 0x8 : software interrupt */

b halt /* vector 0x0c : prefetch abort */

b halt /* vector 0x10 : data abort */

b halt /* vector 0x14 : reserved */

ldr pc, irq_addr /* vector 0x18 : irq */

b halt

.../* 部分省略 */

irq_addr:

.word do_irq

.../* 部分省略 */

do_irq:

/* 执行到这里之前:

* 1. lr_irq中保存了被中断模式中的下一条即将执行的指令的地址

* 2. SPSR_irq保存有被中断模式的CPSR

* 3. CPSR中的M4-M0被设置为10010,进入到irq模式

* 4. 跳到0x18的地方执行程序

*/

 

/* 设置sp_irq */

ldr sp, =0x33d00000


/* 在irq异常处理函数中可能会修改r0-r12,要先保存它们 

* lr-4是异常模式处理函数完后的返回地址,也要保存

*/

sub lr, lr, #4

stmdb sp!, {r0-r12, lr}


/* 处理异常 */

bl handle_irq_c


/* 恢复现场 */

ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值复制到cpsr */


.reset:

.../* 部分省略 */

/* 复位之后,CPU切换到svc模式

* 现在切换到usr模式

*/

mrs r0, cpsr

bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */

bic r0, r0, #(1<<7) /* 清除 I 位,使能中断 */

msr cpsr, r0


/* 设置sp_usr */

ldr sp, =0x33f00000

.../* 部分省略 */


其中handle_irq_c函数在 interrupt.c 中,如下,



#include "s3c2440_soc.h"


/* SRCPND, 用来显示哪个中断产生了,通过向相应位写入数据来清除对应中断源

 * SRCPND[0] : eint0

 * SRCPND[2] : eint2

 * SRCPND[5] : eint8-eint23

 */


/* INTMSK, 用来屏蔽中断, 1 : masked

 * INTMSK[0] : eint0

 * INTMSK[2] : eint2

 * INTMSK[5] : eint8-eint23

 */


/* INTPND, 用来显示当前优先级最高、正在发生的中断,通过向相应位写入数据来清除

 * INTPND[0] : eint0

 * INTPND[2] : eint2

 * INTPND[5] : eint8-eint23

 */


/* 通过读取 INTOFFSET 的值可以确定 INTPND 中哪一位被设置为 1 了 */


/* 初始化中断控制器 */

void interrupt_init(void)

{

/* 配置GPIO为中断引脚 */

INTMSK &= ~((1<<0) | (1<<2) | (1<<5));

}


/* 初始化按键,设为中断源 

 * eint0--s2, eint2--s3, eint11--s4, eint19--s5

 * eint0--GPF0, eint2--GPF2, eint11--GPG3, eint19--GPG11

 */

void key_eint_init(void)

{

GPFCON &= ~((3<<0) | (3<<4));

GPFCON |=  ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */


GPGCON &= ~((3<<6) | (3<<22));

GPGCON |=  ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */


/* 设置中断触发方式,双边沿触发 */

EXTINT0 |= (7<<0) |  (7<<8); /* eint0, eint2 */

EXTINT1 |= (7<<12); /* eint11 */

EXTINT2 |= (7<<12); /* eint19 */


/* 使能eint11,eint19 */

EINTMASK &= ~((1<<11) | (1<<19));

}


/* 读EINTPEND分辨哪个EINT 产生(eint4-eint3) 

 * 清除中断时,写EINTPEND的相应位

 */


void key_eint_irq(int irq)

{

unsigned int val = EINTPEND;


if (irq == 0) /* eint0, s2(GPF0) 控制 D12(GPF6) */

{

if ((GPFDAT & (1<<0)) == 0 )

{

/* 按下,点亮D12 */

GPFDAT &= ~(1<<6);

}

else

{

/* 松开,熄灭D12 */

GPFDAT |= (1<<6);

}

}

else if (irq == 2) /* eint2, s3(GPF2) 控制 D11(GPF5) */

{

if ((GPFDAT & (1<<2)) == 0 )

{

/* 按下,点亮D11 */

GPFDAT &= ~(1<<5);

}

else

{

/* 松开,熄灭D11 */

GPFDAT |= (1<<5);

}

}

else if (irq == 5)

{

if (val & (1<<11) ) /* eint11, s4(GPG3) 控制 D10(GPF4) */

{

if ((GPGDAT & (1<<3)) == 0 )

{

/* 按下,点亮D10 */

GPFDAT &= ~(1<<4);

}

else

{

/* 松开,熄灭D10 */

GPFDAT |= (1<<4);

}

}

else if (val & (1<<19) ) /* eint19, s5(GPG11) 同时控制 D10/D11/D12 亮灭 */

{

if ((GPGDAT & (1<<11)) == 0 )

{

/* 按下,点亮所有灯 */

GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));

}

else

{

/* 松开,熄灭所有灯 */

GPFDAT |=  ((1<<4) | (1<<5) | (1<<6));

}

}

}


/* 清源中断 */

EINTPEND = val;

}


void handle_irq_c(void)

{

/* 分辨中断源 */

int bit = INTOFFSET;


/* 调用对应的中断处理函数 */

if (bit ==0 || bit == 2 || bit == 5) /* eint0eint2eint8-eint23 */

{

key_eint_irq(bit); /* 处理中断,清源中断EINTPEND */

}


/* 清中断 */

SRCPND = (1< INTPND = (1<

}


3、定时器中断的实现以及对中断初始化函数的升级

在实现定时器中断前我们要先对定时器做如下初始化(这里我们选择TIMER0)


设置TIMER0的时钟,根据图12得知我们需要设置预分频器(Prescaler)和分频器(Clock Divider),他们的值是根据我们的需要进行设置的;他俩所在对应寄存器中的位数以及时钟的计算公式从图13和图14中可以得到;

图 12

图 13

图 14

对TIMER0设置计数初值,根据我们需要定的时长对TCNTB0设值(这里我们没有用到TCMPB0,故不进行设置),这里需要注意TCNTB0的位数。

图 15

装载初值,刚开始需要手动更新初值,先将TCON[1]置1。

先清除手动更新位,再设为自动装载并启动定时器。

图 16

具体示例代码如下(timer.c)



#include "s3c2440_soc.h"


void timer_irq(int irq)

{

static int statu = 7; /* 初始三盏灯全熄灭,0b111 */

/* 循环点亮LED012,对应GPF456 */

GPFDAT &= ~(7<<4);

GPFDAT |= (statu<<4);

switch (statu) /* 状态切换 */

{

case 7: statu = 6; break; /* 状态1: 6 = 0b110, 点亮D10 */

case 6: statu = 5; break; /* 状态2: 5 = 0b101, 点亮D11 */

case 5: statu = 3; break; /* 状态3: 3 = 0b011, 点亮D12 */

case 3: statu = 6; break; /* 返回状态 1 以达到循环的目的 */

default: statu = 7; /* 如果都不是则熄灭D10D11D12 */

}

}


void timer_init(void)

{

/* 设置 TIMER0 时钟 

* Timer clk = PCLK / {prescaler value+1} / {divider value}

* = 50000000 / (99 + 1) / 16

* = 31250

*/

TCFG0 = 99; /* prescaler0 = 99  */

TCFG1 = 3; /* MUX0 : 1/16 */

/* 设置 TIMER0 初值 */

TCNTB0 = 15625; /* 0.5s 中断一次 */


/* 加载初值,手动更新 TCONB0 */

TCON |= (1<<1);

/* 设为自动装载并启动定时器,先清除手动更新位 */

TCON &= ~(1<<1);

TCON |=  ((1<<0) | (1<<3));


/* 设置中断 */

register_irq(10, timer_irq);

}


(定时器中断服务子程序中这里我设置的是跑马灯,你也可以实现其他的功能)。

上面程序中的register_irq(10, timer_irq);就是我们对中断初始化函数做的升级了(它大大减少了我们每次执行新中断时对代码的修改),其具体实现如下(其中的handle_irq_c函数我们也做了修改,它们都是对上文interrupt.c的修改)


typedef void (*irq_func)(int); /* 定义函数指针 */

irq_func irq_array[32]; /* 指针数组 */


/* 初始化中断控制器 */

void register_irq(int irq, irq_func fp)

{

irq_array[irq] = fp;

INTMSK &= ~(1<}


void handle_irq_c(void)

{

/* 分辨中断源 */

int bit = INTOFFSET;


/* 调用对应的中断处理函数 */

irq_array[bit](bit);


/* 清中断 */

SRCPND = (1< INTPND = (1<}

关键字:S3C2440  异常  中断 引用地址:S3C2440中的异常与中断

上一篇:s3c2440中断控制器操作
下一篇:S3C2440 按键中断

推荐阅读最新更新时间:2024-11-11 11:14

STM32F103标准库开发---Uart串口通信实验---函数发送和中断接收
一、Uart串口通信----函数发送 1. Uart串口发送(标准库)函数—单字节发送 Uart串口发送函数在STM32F103标准库的 stm32f103x_usart.c 文件中,具体如下图所示: 具体函数如下: /** *@功能:通过USARTx外设传输单个字节数据 *@参数1:指定USART外设(USART1,USART2,USART3,USART4,USART5) *@参数2:要传输的数据(最多9位数据,由初始化配置决定) *@返回值:无 */ void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) { /* Check the parame
[单片机]
STM32F103标准库开发---Uart串口通信实验---函数发送和<font color='red'>中断</font>接收
工程师笔记|一个地址未对齐引起的 HardFault 异常
1.概述 客户在使用 STM32G070 的时候,KEIL MDK 为编译工具,当编译优化选项设置为Level0 的时候,程序会出现 Hard Fault 异常,而当编译优化选项设置为 Level1 的时候,则程序运行正常。 表面上看,这似乎是 KEIL MDK 的问题,通过分析,导致这个问题的本质原因是内存地址没有对齐引起的,下面章节将详细分析该问题的来龙去脉以及解决方法。 问题描述与分析 根据客户的反馈,引起问题的代码很简单,客户定义了几个全局数组,在主程序中访问这几个数组就会出现 Hard Fault 异常,参考代码如下。 把客户提供的代码片段移植到 NUCLEO-G070RB
[单片机]
工程师笔记|一个地址未对齐引起的 HardFault <font color='red'>异常</font>
STM32Cube HAL库中断处理机制 以及回调函数实现原理
最近有较多关于STM32Cube HAL的问题,侧面反应了使用STM32CubeMX的人不少。所以,最近可能会重点写这方面内容。 1写在前面 很多人都知道STM32CubeMX这套工具的一个目的:减少开发者对STM32底层驱动的开发时间,把重心放在应用代码上。 但是,STM32CubeMX只是生成了底层驱动的初始化代码。所以,我们还需要掌握:应用层代码如何调用HAL库函数接口,以及HAL库中断处理机制等相关知识。 HAL库牵涉的内容较多,本文拿HAL库中断处理来讲解,以及相关的回调函数。 2 HAL库中断处理机制 之前使用标准外设库开发时,中断程序(函数)由我们自己实现。 而HAL库的中断处理函数是按照HAL处理机制来实现
[单片机]
STM32Cube HAL库<font color='red'>中断</font>处理机制 以及回调函数实现原理
04 点亮LED C
1 C语言控制LED 前述汇编中,写地址0x56000050(GPFCON)和0x56000054(GPFDAT),相当于C中的指针操作。 void main(void) { unsigned int * pGPFCON = 0x56000050; unsigned int * pGPFDAT = 0x56000054; /* 配置GPF4为输出引脚 */ *pGPFCON = 0x100; /* 设置GPF4输出0 */ *pGPFDAT = 0; } 问题来了。1 我们写出了main函数,谁来调用它? 2 main函数中的变量,保存在内存中,内存地址是多少? 解决办法:我们还
[单片机]
04 点亮LED C
11-S3C2440驱动学习(五)嵌入式linux-网络设备驱动(二)移植DM9000C网卡驱动程序
我们实现了一个虚拟网卡驱动程序,现在我们针对真实的网卡芯片DM9000C,编写移植DM9000C网卡驱动程序。 一、移植分析 协议类的驱动,我们的主要工作往往是将现有的驱动和我们的硬件所匹配起来。协议类的函数往往已经成型不需要我们去修改和编写。比如发包函数:hard_start_xmit函数和netif_rx上报函数都不需要我们编写。网络驱动是针对很多硬件编写出来的,我们使用的是什么硬件CPU,比如ARM9,以及我们使用的系统版本。我们只需要修改驱动,告诉驱动现在的硬件情况是怎么样的,基地址是多少,中断引脚是哪个、设置下内存管理器以满足时序等等。这也是网络驱动移植的简单之处。 (1)DM9000C 一般一款网卡芯片,出
[单片机]
11-S3C2440驱动学习(五)嵌入式linux-网络设备驱动(二)移植DM9000C网卡驱动程序
S3C2440在MDK开发环境下的相关配置
我的TQ2440开发板是在大学时候买的,已经有两三年没碰了。现在翻出来重新开始学,主要是想学习ucos ii,然后进一步的linux。对于裸机,其实没必要花太多时间去琢磨。如果项目不上操作系统,我觉得cortex-m3的性能足以应付绝大部分项目。stm32裸机已经玩了有一段时间,都是直接操作寄存器,所以没必要做重复的事情了。 开发板光盘上的资料和工程都是基于ADS开发环境的,但本人从学51单片机一来,都是使用keil,所以我觉得利用keil开发S3C2440会比较熟手一点,但事实是我纠结了好长时间,可能是我太菜:    1)建立工程。类似stm32的工程,使用MDK提供的启动代码即可。 2)对于芯片存储地址的配置。如果是要
[单片机]
<font color='red'>S3C2440</font>在MDK开发环境下的相关配置
s3c2440 地址分配讲解
(一)s3c2440 地址分配讲解 (很难很纠结) mini2440的地址怎么分配。mini2440处理器的地址怎么分配。 S3C2440处理器可以使用的物理地址空间可以达到4GB,其中前1GB的地址(也就是0x0000 0000--0x4000 0000)为外设地址空间,外设地址究竟怎么确定的呢??好烦?? 还有一部分为CPU内部使用的特殊功能寄存器地址空间(地址范围为0x4800 0000--0x5FFF FFFF),其余的地址空间没有使用。 下面用两个表格说明外设地址空间好特殊功能寄存器地址空间 3FF FFFF 共 26根地址线,也就是 2^6=64 2^20=1M 那么就是 64M 内存概念: 内存是代码的
[单片机]
<font color='red'>s3c2440</font> 地址分配讲解
合泰单片机外部中断程序
;;内容:按下PA3接的按键进中断LED灯左移一位 ;LED接法PD口 ;源码下载:http://www.51hei.com/f/htwzd.rar ;1.开中断 ;2.中断中防抖 include HT66F50.inc ORG 0000H JMP A1 ORG 04H ;外部中断0入口地址 JMP ZD01 ; ORG 30H ; A1: MOV A,00000000B ; MOV PRM1,A ;设置外部中断0的管脚为PA.3 MOV A,00000010B ; MOV INTEG,A ;设置外部中断0为下降沿触发 CLR ACERL ;设置PA口不为AD输入口 SET PAPU.3 ;设置PA.3上拉 SET PAC.3
[单片机]
小广播
设计资源 培训 开发板 精华推荐

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

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

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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