s3c2440裸机-异常中断4-irq外部中断

发布时间:2024-07-05  

我们回顾下中断产生前后的处理流程:详见异常、中断的原理与流程

中断前:



中断产生后:



问题案例: 我们想实现一个按键点灯程序,我们知道有以下两种方案:

1.轮询方案:轮询检测按键的电平状态,当检测到被按下后,对应的gpio会拉低,点亮对应的led;(略)

2.中断方案:将按键配置成外部中断源,当有按键按下,触发中断,在中断服务程序(isr)中去完成点灯。下面开始写代码:


一.中断初始化

1)中断源设置

我们用按键作为外部中断源,我们把按键对应的gpio配置成中断引脚,当按键按下,相应的gpio产生了电平跳变,就会触发外部中断。

我们想达到按下按键灯亮,松开按键灯灭这种效果(配成双边沿触发,按下的时候产生下降沿中断,进行点亮,松开产生上升沿中断,进行熄灭)。当然也可做成按一下点亮,再按一下熄灭的效果(设成单边沿触发,每来一次中断,对led电平进行一次取反)。 查看原理图如下:



我们从按键的原理图中得知,当按键没有按下时,接上拉电阻,那么按键为高电平状态。当按键按下时,电位被拉低,按键处于低电平状态。s2-s5分别对应GPF0,GPF2,GPG3,GPG11; D10-D12这3盏led所对应的gpio分别是GPF4,GPF5,GPF6.

那么我们让s2,s3,s4分别控制D10,D11,D12;s5对D10-D12同时控制(按下s5,同时点亮3个led)。

我们需要配置D10-D12的gpio为输出模式,s2-s4的gpio为外部中断模式。

打开芯片手册找到第九章 IO ports,找到对应的gpio控制寄存器,将对应的gpio配置成中断模式。

代码如下:

  1. 配置GPIO为中断引脚:

GPG的一样,我就不放图片了。
GPFCON &= ~((3<<0) | (3<<4)); //先把eint0和eint2这两个引脚清零 GPFCON |= ((2<<0) | (2<<4)); //S2,S3被配置为中断引脚 GPGCON &= ~((3<<6) | (3<<22)); GPGCON |= ((2<<6) | (2<<22)); //S4,S5被配置为中断引脚

  1. 设置中断触发方式:

当电平从高变低时,此时表示按键按下,当电平由低变高,表示松开按键。不妨设置中断方式为双边沿触发,按下按键,触发下降沿中断,中断服务程序就可以去点亮led,反之,松开触发上升沿中断,就可以去熄灭led。


EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */
    EXTINT1 |= (7<<12);             /* S4 */
    EXTINT2 |= (7<<12);             /* S5 */

3.设置外部中断屏蔽寄存器EINTMASK:


从上图我们知道外部中断0-3是直接连接到中断控制器,而外部中断4-7、外部中断8-23还要经过EINTMASK,那么我们需要配置EINTMASK来打开中断的通道:


EINTMASK &= ~((1<<11) | (1<<19));    //打开外部中断通道

4.外部中断挂起寄存器EINTPEND:

当一个外部中断(EINT4-EINT23)发生后,那么相应的位会被置1, 所以中断结束后需要清除对应位。这个寄存器可以用来区分外部中断4-23的哪一个中断源。



2)中断控制器设置

我们先来看下中断控制器的总框图:


1.首先是SRCPND:用来表示哪个中断源发出了中断请求。

我们先看下中断源:


从上图我们发现外部中断有24个外部中断,除了外部中断EINT,还有定时器中断,ADC中断,UART中断等…。

我们来认识下SRCPND寄存器:(用来表示哪个(哪些)中断源已产生中断请求,中断结束后要清中断)


从上图中我们发现EINT4-7共用1bit,EINT8-23共用1bit,那么肯定有其他寄存器来区分它们,那就是EINTPEND寄存器(后面5会讲)。

2.然后到达INTMSK:(中断屏蔽寄存器)

我们需要把INTMSK寄存器配置成非屏蔽状态,默认是中断源时屏蔽的,见下图:


3.INTMOD(中断模式,是fiq还是irq)


4.Priroty:

5.INTPND: INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位。

中断发生后,SRCPND中会有bit置1,可能好几个(因为同时可能发生几个中断),这些中断会由优先级仲裁器选出一个最紧迫的,然后把INTPND中相应位置1。所以只有INTPND置1,CPU才会处理。



我们知道有可能同时出现多个中断请求,那么INTPND就挑选出当前优先级最高的、正在发生的中断。

当产生irq后,要去分辨是哪个中断源,根据不同的中断源去中断服务程序isr中做不同的事情,那么如何得知当前产生的中断是哪一个外部中断源产生的呢?那么就可以访问这个INTPND寄存器。

可是我们要去手工去解析INTPND里面的位,才能知道是哪个中断源产生了中断请求。那么有没有什么比较快捷的方式自动帮我们解析INTPND呢,直接返回中断号给我们?

当然有啦,有一个INTOFFSET寄存器的值就是代表哪个中断请求产生了,如果INTOFFSET=0表示EINT0产生了中断请求,INTOFFSET=2表示EINT2产生了中断请求。具体见下图:



我们从上图看到ENIT4-7共用一个offset, EINT8-23也共用一个offset,那么要通过访问EINTPEND寄存器来区分它们。

中断控制器设置代码入下:

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

void interrupt_init(void)

{

    //1是屏蔽我们需要清零,外部中断0 外部中断2 外部中8_23里面还有外部中断11到19

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

    //INTMOD默认是irq,可以不设置

}


3)中断总开关



CPSR有I位,是irq的总开关,我们需要把CPSR寄存器 bit7给清零,这是中断的总开关,如果bit7设置为1,CPU无法响应任何中断。

/* 把bit7这一位清零 */
bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
msr cpsr, r0

二. 中断服务程序设计

到这里中断前的初始化工作知识点就已经讲完了,当然要提前准备好led初始化工作(就是将led对应的gpio配置成输出模式,这个不讲解)。

那么中断产生后,我们之前讲过,会跳转到0x18异常向量,执行跳转指令ldr pc, =_irq,和之前的swi异常,und异常框架一样。 代码框架如下:


展开代码


.text
.global _start

_start:
    b reset  /* vector 0 : reset */

    ldr pc, und_addr /* vector 4 : und(绝对跳转) */
    ldr pc, swi_addr /* vector 8 : swi */
    b halt           /* vector 0x0c : prefetch aboot */
    b halt           /* vector 0x10 : data abort */
    b halt           /* vector 0x14 : reserved */
    ldr pc, irq_addr /* vector 0x18 : irq */
    b halt           /* vector 0x1c : fiq */

und_addr:
    .word do_und
swi_addr:
    .word do_swi
irq_addr:
    .word do_irq

reset:
    /* 关闭看门狗 */
    /*初始化时钟*/
    /*初始化sdram,设置栈*/
    /*代码重定位,清bss*/

    /* 把bit7这一位清零(打开中断总开关) */
    bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
    msr cpsr, r0

    ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

halt:
    b halt

1.我们在start.s中用汇编代码设置cpsr的I位,开启中断开关;

2.在main函数中初始化中断源key_eint_init,初始化中断控制器interrupt_init;

3.然后继续执行main主函数。

4.当中断产生,触发irq异常,进入0x18异常向量,执行do_irq。

do_irq实现如下(和之前的do_und, do_swi类似):


展开代码

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}


/* 处理irq异常 */

bl handle_irq_c


/* 恢复现场 */

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

handle_irq_c函数实现如下:


void key_eint_irq(int irq)

{

    unsigned int val = EINTPEND;

    unsigned int val1 = GPFDAT;

    unsigned int val2 = GPGDAT;


    if (irq == 0) /* eint0 : s2 控制 D12 */

    {

        if (val1 & (1<<0)) /* s2 --> gpf6 */

        {

            /* 松开 */

            GPFDAT |= (1<<6);

        }

        else

        {

            /* 按下 */

            GPFDAT &= ~(1<<6);

        }

    }

    else if (irq == 2) /* eint2 : s3 控制 D11 */

    {

        if (val1 & (1<<2)) /* s3 --> gpf5 */

        {

            /* 松开 */

            GPFDAT |= (1<<5);

        }

        else

        {

            /* 按下 */

            GPFDAT &= ~(1<<5);

        }


    }

    else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */

    {

        if (val & (1<<11)) /* eint11 */

        {

            if (val2 & (1<<3)) /* s4 --> gpf4 */

            {

                /* 松开 */

                GPFDAT |= (1<<4);

            }

            else

            {

                /* 按下 */

                GPFDAT &= ~(1<<4);

            }

        }

        else if (val & (1<<19)) /* eint19 */

        {

            if (val2 & (1<<11))

            {

                /* 松开 */

                /* 熄灭所有LED */

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

            }

            else

            {

                /* 按下: 点亮所有LED */

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

            }

        }

    }


    EINTPEND = val;     /* 清中断 : 源头*/

}


/*INTOFFSET中哪一位被设置成1,就表示哪一个 中断源*/

void handle_irq_c(void)

{

    /* 分辨中断源 */

    int bit = INTOFFSET;


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

    if (bit == 0 || bit == 2 || bit == 5)  /* eint0,2,bit==5还需细分eint8_23 */

    {

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

    }


    /* 清中断 : 从源头开始清 */

    SRCPND = (1<    INTPND = (1<}


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>