zoukankan      html  css  js  c++  java
  • 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);
    }
  • 相关阅读:
    设计一个圆柱体类,计算表面积及体积。建立一个半径为3、高为3.5的圆柱体,输出其表面积及体积
    写一个方法完成如下功能,判断从文本框textbox1输入的一个字符,如果是数字则求该数字的阶乘,如果是小写字条,则转换为大写,大写字符不变,结果在文本框textbox2中显示
    写一方法用来计算1+2+3+...n,其中n作为参数输入,返回值可以由方法名返回,也可以由参数返回
    winform控件记录
    写4个同名方法,实现两个整数、两个实数,一个实数一个整数,一个整数一个实数之间的求和。在主调函数中调用这4个方法计算相关的值。(方法的重载)
    写一方法计算实现任意个整数之和.在主调函数中调用该函数,实现任意个数之和。(使用params参数)
    在主函数中提示用户输入用户名和密码。另写一方法来判断用户输入是否正确。该方法分别返回一个bool类型的登录结果和和一个string类型的登录信息。如登录成功,返回true及“登录成功”,若登录失败则返回false及“用户名错误”或“密码错误”(使用out参数)
    Linux下使用Kickstart自动化安装平台架构
    Day10 多线程理论 开启线程
    关闭ipv6的方法
  • 原文地址:https://www.cnblogs.com/cxl-93/p/11052425.html
Copyright © 2011-2022 走看看