jz2440裸机开发与分析:S3c2440ARM异常与中断体系详解8---定时器中断程序示例

这节课我们来写一个定时器的中断服务程序
使用定时器来实现点灯计数
查考资料就是第10章PWM TIMER

我们先把这个结构图展示出来在这里插入图片描述
这个图的结构很好

这里面肯定有一个clk(时钟),
1 、每来一个clk(时钟)这个TCNTn减去1
2、 当TCNTn == TCMPn时,可以产生中断,也可以让对应的SPWM引脚反转,(比如说原来是高电平,发生之后电平转换成低电平)
3、 TCNTn继续减1,当TCNTn == 0时,可以产生中断,pwm引脚再次反转 TCMPn 和 TCNTn的初始值来自TCMPBn,TCNTBn
4 、TCNTn == 0时,可自动加载初始

怎么使用定时器?

1、 设置时钟
2 、设置初值
3、 加载初始,启动Timer
4、 设置为自动加载
5 、中断相关
由于2440没有引出pwm引脚,所以pwm功能无法使用,也就无法做pwm相关实验,所谓pwm是指可调制脉冲
T1高脉冲和T2低脉冲它的时间T1, T2可调整,可以输出不同频率不同占控比的波形,在控制电机时特别有用

我们这个程序只做一个实验,当TCNTn这个计数器到0的时候,就产生中断,在这个中断服务程序里我们点灯

写代码
打开我们的main函数

int main(void)
{
    led_init();
    interrupt_init();  /* 初始化中断控制器 */
//我们初始化了中断源,同样的,我们初始化timer
    key_eint_init();   /* 初始化按键, 设为中断源 */
//初始化定时器
    timer_init();

我们需要实现定时器初始化函数
新建一个timer.c ,我们肯定需要操作一堆寄存器,添加头文件

#include "s3c2440_soc.h"

void timer_init(void)
  1. 设置TIMER0的时钟
  2. 设置TIMER0的初值
  3. 加载初值, 启动timer0
  4. 设置为自动加载并启动(值到0以后会自动加载)
  5. 设置中断,显然我们需要提供一个中断处理函数void timer_irq(void)在这里面我们需要点灯
打开芯片手册,我们想设置timer0的话
  • 首先设置8-Bit Prescaler
  • 设置5.1 MUX(选择一个时钟分频)
  • 设置TCMPB0和TCNTB0
  • 设置TCONn寄存器
    在这里插入图片描述
    看手册上写如何初始化timer

在这里插入图片描述

  1. 把初始值写到TCNTBn 和TCMPBn寄存器
  2. 设置手动更新位
  3. 设置启动位

往下看到时钟配置寄存器
在这里插入图片描述
有个计算公式
Timer clk = PCLK / {(预分频数)prescaler value+1} / {divider value(5.1MUX值)}
PCLK是50M
= 50000000/(99+1)/16
= 31250
也就是说我们得TCON是31250的话,从这个值一直减到0

   Prescaler0等于99
  TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */  

TCFG1 MUX多路复用器的意思,他有多路输入,我们可以通过MUX选择其中一路作为输出
在这里插入图片描述
根据上面mux的值,我们要把MUX0 设置成0011
只需要设置这4位即可,先清零
再或上 0011 就是3

TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */

再来看看初始值控制寄存器
在这里插入图片描述
一秒钟点灯太慢了 ,就让0.5秒
TCNTB0 = 15625; /* 0.5s中断一次 */

这个寄存器是用来观察里面的计数值的,不需要设置

现在可以设置TCON来设置这个寄存器
在这里插入图片描述
现在需要设置Timer0
在这里插入图片描述
开始需要手工更新
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
把这两个值放到TCNTB0 和 TCMPB0中
在这里插入图片描述
注意:这一位必须清楚才能写下一位

设置为自动加载并启动,先清掉手动更新位,再或上bit0 bit3

TCON &=~ (1<<1); 
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */

设置中断,显然我们需要提供一个中断处理函数void timer_irq(void)
在Timer里没有看到中断相关的控制器,我们需要回到中断章节去看看中断控制器,看看有没有定时器相关的中断
我们没有看到更加细致的Timer0寄存器

TCNTn=TCMPn时,他不会产生中断,会发生脉冲的反转,只有当TCNTn等于0的时候才可以产生中断,我们之前以为这个定时器可以产生两种中断,那么肯定有寄存器中断或者禁止两种寄存器其中之一,那现在只有一种中断的话,就相对简单些
设置中断的话,我们只需要设置中断控制器
设置interrupu.c中断控制器

*初始化中断控制器 void interrupt_init(void) 
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));

*把定时器相应的位清零就可以了,哪一位呢?

INTPND的哪一位? 
INT_TIMER0第10位即可 

在这里插入图片描述

INTMSK &= ~(1<<10); /* enable timer0 int */

当定时器减到0的时候就会产生中断,就会进到start.s这里一路执行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}  

    /* 处理irq异常 */
    bl handle_irq_c

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


让后进入irq处理函数中处理,处理这个irq
void handle_irq_c(void)
{
    /* 分辨中断源 */
    int bit = INTOFFSET;

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


if(bit ==0 || bit == 2 || bit == 5)/*eint0,2,rint8_23*/
{
    key_eint_irq(bit);/*处理中断,清中断源EINTPEND*/
}else if(bit == 10)//如果等于10的话说明发生的是定时器中断,这时候就调用我们得timer_irq
{
    timer_irq();
}

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


回到timer.c文件中,在这个定时器处理函数中我们需要点灯
void timer_irq(void)
{
    /* 点灯计数 循环点灯*/
    static int cnt = 0;
    int tmp;

    cnt++;

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

代码写完我们实验一下,上传代码,在Makefile中添加timer.o,进行编译
编译后进行烧写
发现灯已经开始闪
对程序进行改进

进入main函数中执行 timer_init();
还需要修改interrupt.c
初始化函数

void interrupt_init(void) 

还需要调用中断处理函数

void handle_irq_c(void) 

每次添加一个中断我都需要修改handle_irq这个函数,这样太麻烦,我能不能保证这个interrupt文件不变,只需要在timer.c中引用即可,这里我们使用指针数组
interrupt.c中定义函数指针数组

typedef void(*irq_func)(int); 

定义一个数组,我们来卡看下这里有多少项,一共32位,我们想把每一个中断的处理函数都放在这个数组里面来,当发生中断时,我们可以得到这个中断号,让后我从数组里面调用对应的中断号就可以了

irq_func irq_array[32];

那么我们得提供一个注册函数


void register_irq (int irq, irq_func fp)
{
    irq_array[irq] = fp;
    INTMASK &= ~(1 << irq)
}

以后我直接调用对应的处理函数

void handle_irq_c(void)
{
    /* 分辨中断源 */
    int bit = INTOFFSET;


/* 调用对应的处理函数 */
irq_array[bit](bit);

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

//按键中断初始化函数需要注册

    /* 初始化按键, 设为中断源 */
    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中也需要设置中断

    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);
    }

把interrupt.c中按键的初始化放在最后面

我们来看看我们做了什么事情,
<1>我们定义了一个指针数组
typedef void(*irq_func)(int);
注:这里看不懂请参考typedef函数指针用法
这个指针数组里面放有各个指针的处理函数
irq_func irq_array[32];

当我们去初始化按键中断时,我们给这按键注册中断函数
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);

这个注册函数会做什么事情,他会把这个数组放在注册函数里面,同时使能中断

void register_irq(int irq, irq_func fp)
{
    irq_array[irq] = fp;

    INTMSK &= ~(1<<irq);
}
//我们的timer.c中

timer_init();
//也会注册这个函数
    /* 设置中断 */
    register_irq(10, timer_irq);

把这个中断irq放在第10项里同时使能中断,以后我们只需要添加中断号,和处理函数即可,再也不需要修改函数
烧写执行

我们从start.s开始看,
一上电从 b reset运行做一列初始化

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


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执行 */

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

halt:
    b halt

进入main.c做一系列初始化


int main(void)
{
    led_init();
    //interrupt_init();  /* 初始化中断控制器 */
    key_eint_init();   /* 初始化按键, 设为中断源 */
    timer_init();

    puts("\n\rg_A = ");
    printHex(g_A);
    puts("\n\r");
}

进入按键初始化程序 interrupt.c
初始化按键, 设为中断源

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);
}


时钟初始化程序 <code>timer_init();</code>

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);
}

让后main.c函数一直循环执行
输出串口信息

 while (1)
 {
    putchar(g_Char);
    g_Char++;

    putchar(g_Char3);
    g_Char3++;
    delay(1000000);
    //printHex(TCNTO0);
 }

定时器减到0的时候就会产生中断,start.S
跳到 0x18的地方执行

    ldr pc, irq_addr /* vector 0x18 : irq */
    b halt           /* vector 0x1c : fiq */
.align 4

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

ldr pc, irq_addr /* vector 0x18 : irq */
b halt           /* vector 0x1c : fiq */

.align 4

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

看看怎么处理irq

void handle_irq_c(void)
{
    /* 分辨中断源 */
    int bit = INTOFFSET;

    /* 调用对应的处理函数执行 */
    irq_array[bit](bit);

    /* 清中断 : 从源头开始清 */
    SRCPND = (1<<bit);
    INTPND = (1<<bit);  
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值