S3C2440中断

Posted cxl-93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了S3C2440中断相关的知识,希望对你有一定的参考价值。

韦东山老师一期中断课程学习:

总结:

程序启动后工作流程,程序从0地址开始执行Reset  --》 重定位  --》ldr pc,=main [绝对跳转到SDRAM中执行main()函数],main函数中调用各种函数(初始化函数)。

根据S3C2440的Exception Vectors可以知道,  当发生中断时,CPU运行程序跳转到0X18的地方执行指令,该处我们存放中断处理相关内容,CPU运行相应中断内容{保存现场、处理异常(中断)【分辨中断源、调用相应函数】恢复现场}。

Exception Vectors 如下:

技术图片

启动文件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 */
    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 */

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里 */




reset:
    /* 关闭看门狗 */
    ldr r0, =0x53000000
    ldr r1, =0
    str r1, [r0]


    /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]


    /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    ldr r0, =0x4C000014
    ldr r1, =0x5
    str r1, [r0]


    /* 设置CPU工作于异步模式 */
    mrc p15,0,r0,c1,c0,0
    orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
    mcr p15,0,r0,c1,c0,0


    /* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
     *  m = MDIV+8 = 92+8=100
     *  p = PDIV+2 = 1+2 = 3
     *  s = SDIV = 1
     *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
     */
    ldr r0, =0x4C000004
    ldr r1, =(92<<12)|(1<<4)|(1<<0)
    str r1, [r0]


    /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
     * 然后CPU工作于新的频率FCLK
     */
    
    


    /* 设置内存: sp 栈 */
    /* 分辨是nor/nand启动
     * 写0到0地址, 再读出来
     * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
     * 否则就是nor启动
     */
    mov r1, #0
    ldr r0, [r1] /* 读出原来的值备份 */
    str r1, [r1] /* 0->[0] */ 
    ldr r2, [r1] /* r2=[0] */
    cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
    ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
    moveq sp, #4096  /* nand启动 */
    streq r0, [r1]   /* 恢复原来的值 */


    bl sdram_init
    //bl sdram_init2     /* 用到有初始值的数组, 不是位置无关码 */


    /* 重定位text, rodata, data段整个程序 */
    bl copy2sdram


    /* 清除BSS段 */
    bl clean_bss


    /* 复位之后, cpu处于svc模式
     * 现在, 切换到usr模式
     */
    mrs r0, cpsr         /* 读出cpsr */
    bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
    bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
    msr cpsr, r0


    /* 设置 sp_usr */
    ldr sp, =0x33f00000


    ldr pc, =sdram
sdram:
    bl uart0_init


    bl print1
    /* 故意加入一条未定义指令 */
und_code:
    .word 0xdeadc0de  /* 未定义指令 */
    bl print2


    swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */


    //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    ldr pc, =main  /* 绝对跳转, 跳到SDRAM */


halt:
    b halt

在分析中断处理函数handle_irq_c()时,我们发现如果中断源过多发生的时候,中断处理函数每次都要重新添加,而且显得不简洁。
正常我们在定义中断处理函数时候代码如下:

void handle_irq_c(void)

    /* 分辨中断源 */
    int bit = INTOFFSET;


    /* 调用对应的处理函数 */
    if (bit == 0 || bit == 2 || bit == 5)  /* eint0,2,eint8_23 */
    
        key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
    
    else if (bit == 10)
    
        timer_irq();
    


    /* 清中断 : 从源头开始清 */
    SRCPND = (1<<bit);
    INTPND = (1<<bit);    

通过学习可以知道一种思想:定义一个函数指针数组,将所有中断函数存在一个数组中,在写某个初始化中断函数的时候,我们将相应的中断函数存到我们定义的函数指针数组中,然后当处理中断函数工作时,即运行数组中相应的中断函数。

以按键中断点亮LED及定时器中断循环点亮LED为例:

相应代码如下:

[interrupt.c]

#include "s3c2440_soc.h"


typedef void(*irq_func)(int);
irq_func irq_array[32];




/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */


/* INTMSK 用来屏蔽中断, 1-masked
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */


/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */


/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
 */

#if 0
/* 初始化中断控制器 */
void interrupt_init(void)

    INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
    INTMSK &= ~(1<<10);  /* enable timer0 int */

#endif
/* 读EINTPEND分辨率哪个EINT产生(eint4~23) * 清除中断时, 写EINTPEND的相应位 */ 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; void handle_irq_c(void) /* 分辨中断源 */ int bit = INTOFFSET; /* 调用对应的处理函数 */ irq_array[bit](bit); /* 清中断 : 从源头开始清 */ SRCPND = (1<<bit); INTPND = (1<<bit); void register_irq(int irq, irq_func fp) irq_array[irq] = fp; INTMSK &= ~(1<<irq); /* 初始化按键, 设为中断源 */ void key_eint_init(void) /* 配置GPIO为中断引脚 */ 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); /* S2,S3 */ EXTINT1 |= (7<<12); /* S4 */ EXTINT2 |= (7<<12); /* S5 */ /* 设置EINTMASK使能eint11,19 */ EINTMASK &= ~((1<<11) | (1<<19)); register_irq(0, key_eint_irq); register_irq(2, key_eint_irq); register_irq(5, key_eint_irq);

[timer.c]

#include "s3c2440_soc.h"


void timer_irq(void)

    /* 点灯计数 */
    static int cnt = 0;
    int tmp;


    cnt++;


    tmp = ~cnt;
    tmp &= 7;
    GPFDAT &= ~(7<<4);
    GPFDAT |= (tmp<<4);



void timer_init(void)

    /* 设置TIMER0的时钟 */
    /* Timer clk = PCLK / prescaler value+1 / divider value 
                 = 50000000/(99+1)/16
                 = 31250
     */
    TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */
    TCFG1 &= ~0xf;
    TCFG1 |= 3;  /* MUX0 : 1/16 */


    /* 设置TIMER0的初值 */
    TCNTB0 = 15625;  /* 0.5s中断一次 */


    /* 加载初值, 启动timer0 */
    TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */


    /* 设置为自动加载并启动 */
    TCON &= ~(1<<1);
    TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */


    /* 设置中断 */
    register_irq(10, timer_irq);

 

以上是关于S3C2440中断的主要内容,如果未能解决你的问题,请参考以下文章

S3c2440的中断体系结构

s3c2440裸机-异常中断(四. irq之外部中断)

s3c2440裸机-异常中断(二. und未定义指令异常)

S3C2440 初始化时钟

s3c2440——swi异常

s3c2440裸机编程-时钟编程(2440时钟体系介绍)