历史上的今天

今天是:2024年09月10日(星期二)

正在发生

2021年09月10日 | S3c2440ARM异常与中断体系详解8---定时器中断程序示例

发布者:RadiantGlow 来源: eefocus关键字:S3c2440  ARM异常  中断体系  定时器中断 手机看文章 扫描二维码
随时随地手机看文章

这节课我们来写一个定时器的中断服务程序

使用定时器来实现点灯计数

查考资料就是第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)


设置TIMER0的时钟

设置TIMER0的初值

加载初值, 启动timer0

设置为自动加载并启动(值到0以后会自动加载)

设置中断,显然我们需要提供一个中断处理函数void timer_irq(void)在这里面我们需要点灯

打开芯片手册,我们想设置timer0的话

首先设置8-Bit Prescaler

设置5.1 MUX(选择一个时钟分频)

设置TCMPB0和TCNTB0

设置TCONn寄存器

在这里插入图片描述

看手册上写如何初始化timer

在这里插入图片描述

把初始值写到TCNTBn 和TCMPBn寄存器

设置手动更新位

设置启动位

往下看到时钟配置寄存器

在这里插入图片描述

有个计算公式

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<    INTPND = (1<}


回到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<    INTPND = (1<}


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


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

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

//我们的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

[1] [2]
关键字:S3c2440  ARM异常  中断体系  定时器中断 引用地址:S3c2440ARM异常与中断体系详解8---定时器中断程序示例

上一篇:S3c2440ARM异常与中断体系详解2---CPU模式(Mode)状态(State)
下一篇:S3c2440ARM异常与中断体系详解3---Thumb指令集程序示例

推荐阅读

   近日,财政部、税务总局发布《关于提高机电 文化等产品出口退税率的通知》,决定自2018年9月15日起,提高机电、文化等397项产品的出口退税率。将多元件集成电路、非电磁干扰滤波器等产品出口退税率提高至16%。如下为部分产品清单: 
从2018年下半年开始,就有机构预测全球半导体产业将进入下行周期。受到中美贸易战、日韩战争,汽车、消费电子等产品需求下滑,加上新 iPhone 缺乏创新、民众换机意愿减少等多重影响,2019年全球半导体市场需求不振,多数机构均看淡2019年半导体的增长率,预计将下降至个位数。专家估计有机会在今年下半年,看到半导体产业复苏的喜讯。目前 AI、5G、高速...
楞次定律是以1834年物理学家埃米尔·楞次(Emil Lenz)的名字命名的,他在1834年提出了这一定律指出,在导体中,由变化的磁场感应的电流的方向是,由感应电流产生的磁场与初始变化的磁场相反。这是一个定性定律,它规定了感应电流的方向,但对其大小却只字不提。Lenz定律解释了电磁学中许多效应的方向,如电流变化在电感器或导线回路中感应的电压方向,或...
广告摘要声明广告ABB机器人在瑞典延雪平设立了全新的随机抓取技术测试中心,旨在使用最新的3D视觉随机抓取技术开发自动化拾取与放置解决方案。该测试中心是ABB助力中小企业提升柔性自动化战略的一部分,中心位于ABB全球解决方案中心内,测试团队将与全球客户及合作伙伴共同开发创新解决方案,并将这些解决方案整合到应用场景中。在ABB全新的随机抓取技术测...

史海拾趣

问答坊 | AI 解惑

DEK新型印刷机提供10秒周期高速配置

本帖最后由 jameswangsynnex 于 2015-3-3 20:00 编辑 …

查看全部问答∨

安防的设计开发

维客科技发展有限公司是成立于2000年的股分制民营高科技企业,是东北嵌入式应用技术推广发展中心,研究中心和见习基地与地区大学合作开设嵌入式技术应用课程,并常年对嵌入式有志趣者,定期提供免费入门课程和收费的高级技能课程,为东北嵌入式技术 ...…

查看全部问答∨

ARM嵌入式系统基础教程1-8章

ARM嵌入式系统基础教程1-8章 [ 本帖最后由 yinfeng067 于 2009-8-29 16:33 编辑 ]…

查看全部问答∨

LM3S6916上电复位问题

有的时候上电,就是启动不起来,不过过一会儿就好了。 我现在做了个最简单的程序,让他运行一会儿就软件复位。这样反复4-5次之后,死机了。一直启动不起来。 我又编了个看门狗复位,也是一样的情况。 死机之后,等待一分钟之后就能启动来了, ...…

查看全部问答∨

to yashi--DNW v0.50L版本中UBOOT下载的问题

yashi大哥,不知道怎么上传附件,只好重新开帖…

查看全部问答∨

服务器出租/托管1005928844

服务器托管 规格 1U(厦门电信、网通机房) 2U(厦门电信、网通机房) 月付(元) 500       800 季付(元) 450*3     750*3 半年付(元) 430*6   700*6 年付(元) 400*12    65 ...…

查看全部问答∨

quartus ii 8.0

在quartus ii 8.0进行完全编译时提示不支持EPM3128_ATC_144,请指点...…

查看全部问答∨

置换2440开发板,可以几个模块搭配起来换

本帖最后由 ddllxxrr 于 2016-1-7 17:12 编辑 我想学习学习2440,有没有哪位兄弟愿意和我置换啊 stm的的开发板我也在考虑的我的宝贝有 : (1)400以上的板子 avr dragon一个 ZLG coretex M0评估板 (2)级别不够,可以 ...…

查看全部问答∨

我的stm32ISP软件发布新版本V0.950了。

加了个option bytes设置界面也可以预览一下手持式程序下载机的操作界面,呵呵。希望帮助各位更好的使用stm32f芯片。…

查看全部问答∨

请教一个2407调试的问题

当我在调试DSP2407程序时,出经这样一个错误提示 Can\\\'t Set Breakpoint: Error 0x00000008/-1076 Error during: Break Point,  Cannot set Verify breakpoint at 0x00000477 Breakpoint Manager: An error was encountered attempti ...…

查看全部问答∨
小广播
设计资源 培训 开发板 精华推荐

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

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

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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