zoukankan      html  css  js  c++  java
  • s3c2440裸机-异常中断(五. irq之定时器中断)

    之前讲过s3c2440时钟体系,看了时钟体系再来看定时器中断会更好的结合运用所学知识点。

    S3c2440共有2种定时器:

    1.Watchdog看门狗定时器
    2.PWM脉冲可调制定时器
    

    下面详细介绍2种定时器的原理,来了解定时器是如何产生定时器中断的。

    1. Watchdog看门狗定时器

    1)Watchdog看门狗定时器原理

    Watchdog定时器的原理很简单,寄存器很少,框图如下:

    1.定时器,定时器那肯定是需要用到时钟的,从框图中可以看到Watchdog定时器采用的时钟源是PCLK,从s3c2440时钟体系中也可以体现出来,接的是APB总线。

    2.然后到达一个8bit的分频器,可以通过配置WTCON[15:8]来设置分频器的预设值。

    3.再设置WTCON[4:3]来设置除数因子来进一步分频。
    所以最终的Watchdog定时器的时钟周期t_watchdog = 1/[ PCLK / (Prescaler value + 1) / Division_factor ]

    4.到达WTCNT:看门狗递减寄存器。WTCNT里的数据就开始在输入时钟频率下递减。WTCNT的值由WTDAT寄存器提供。

    5.WTDAT:WTDAT寄存器用于指定计数器的初始值,也就是它的超时时间,系统上电之后硬件自动的将0x8000的初始值载入到WTCNT里,在发生了第一次超时操作时,WTDAT的值才会载入到WTCNT寄存器

    当WTCNT的值减到0时,就会触发看门狗定时器中断,进而产生复位。中断框图中可以看到可以设置WTCON[2]来设置是否产生中断信号,可以设置WTCON[0]来设置是否产生复位信号。

    WTCON寄存器如下图:

    WTCNT、WTDAT寄存器如下图:

    2)看门狗定时器中断编程实现

    在之前的章节中,我们在start.s启动代码中首先做的就是关闭看门狗,把WTCON[5]=0,也就是把Watchdog timer给disable。那么Watchdog Timer就不再工作了,这样做是为了防止在启动代码进行硬件初始化的时候出现超时,发出复位信号又去重启硬件,这样就陷入了不断重启过程中。因为s3c2440芯片默认WTCON[5]是1,也就是Watchdog Timer默认是处于使能状态。

    我们之前s3c2440时钟体系中配置了PCLK=50M Hz, 那么让WTDAT取默认值0x8000,那么根据公式算出从开机到触发复位重启的时间t=WTDAT*( 1/[ PCLK / (Prescaler value + 1) / Division_factor ])。

    根据WTCON寄存器配置Prescaler value=255,配置Division_factor=128,这样最终定时器分得的频率更低,那么减数器递减的更慢,也就代表从开机到触发复位重启的时间T=0x8000 * (1/[50*10^6/(255+1)/128]) = 21474836.48us = 21s。

    之前的start.s中把看门狗已经关闭了,那么我们在跳转到main函数中调用wtd_timer_init函数实现如下:

    void wtd_timer_init(void)
    {
    	WTCON |= (1<<0) | (1<<5);//使能定时器,开启reset复位
    	WTCON |= (3<<3) | (255<<8);
    }
    

    我们查看测试结果:

    xxx.png
    

    果然初始化wtd_timer_init后,过21s后板子重启了,说明我们watchdog定时器功能已经OK了。

    现在修改代码如下:

    void wtd_timer_init2(void)
    {
    	WTCON |= (1<<0) | (1<<2);//使能定时器,开启watchdog定时器中断
    	WTCON |= (3<<3) | (255<<8);
    
    	WTDAT = 0x4000;
    }
    

    我们看到我们现在定时器的初值被修改成了0x4000, 相对于默认值少了一半,那么触发wtd_timer中断的时间应该减半,也就是约等于10s。

    那么需要写一个wtd_timer的中断服务程序,同样需要先在do_irq中去保护现场、调用handle_irq_c、恢复现场,可以参考上一节irq之外部中断。查看INTOFFSET寄存器:

    得知:
    handle_irq_c代码修改如下:

    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) */
        }
    

    else if(bit == 9)
    {
    这里还需区分子中断源
    }

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

    查看芯片手册查找“INT_WDT_AC97”如下图:

    从上图可以看到SRCPND和SUBSRCPND的映射关系。
    SUBSRCPND寄存器如下图:

    我们可以读取SUBSRCPND来区分到底是哪一个子中断源产生了中断,当SUBSRCPND中哪一位被置1,表示对应的中断源发生了中断。

    前面做完wtd_timer_init,还要进行中断控制器的初始化,查看INTMSK寄存器如下图:

    查看INTSUBMSK寄存器如下图:

    在interrupt_init中添加:

    INTMSK &= ~(1<<9);
    INTSUBMSK &= ~(1<<14);
    

    修改handle_irq_c:

    ...
    else if (bit == 9)
    {
    	if (SUBSRCPND & 1<<14)
    	{
    		printf("watchdog timer interrupt occured.
    ");
    	}
    }
    ...
    

    测试结果如下:

    2. PWM脉冲宽度调制定时器

    PWM(Pulse Width Modulation),字面上是脉冲可调制的意思,就是可以调节占空比。

    s3c2440有5个定时器,其中定时器0、1、2和3具有脉宽调制(PWM)功能。定时器4是一个无输出引脚
    的内部定时器。

    先认识下s3c2440的pwm timer的框架:

    从框架图中得知:

    1.时钟源为PCLK
    2.pclk经过8 bit的预分频系数(Prescaler),和4 bit的时钟除数因子(clock divider),进行分频
    3.经过MUX选择器选择用哪个定时器(5选1)
    4.设置TCMPB0和TCNTB0和TCONn寄存器
    

    1)pwm定时器原理

    pwm定时器的逻辑控制单元结构如下:

    1 TCMPBn和TCNTBn寄存器中的值分别加载到TCMPn和TCNTn寄存器
    2 每来一个clk(时钟)这个TCNTn减去1
    3 当TCNTn == TCMPn时,可以产生中断,pwm输出引脚反转
    4 TCNTn继续减1,当TCNTn == 0时,又产生一次中断,pwm引脚再次反转
    5 重复1-4过程
    

    从上述过程我们得知可以设置TCNTBn寄存器来设置加载初值,设置后TCNTn中的值就会按照时钟周期递减。
    从上述过程我们得知可以设置TCMPBn寄存器来设置占空比,从而控制高低电平持续时间的比例。

    2) pwm定时器编程实现

    要开始一个PWM定时器功能的步骤如下:(假设使用的是timer0)

    ① 初始化pwm定时器

    先进行初始化工作,定义一个pwm_timer_init()
    1)设置时钟:


    分别设置定时器0的预分频器值(prescaler)和时钟分频值(clock divider),从而控制TCNT0减数器的频率。
    

    根据公式:

    pwm Timer clk = PCLK / {(预分频数)prescaler value+1} / {divider value(5.1MUX值)} 
    

    PCLK是50M,设置prescaler value=99, divider value=16,所以pwm Timer clk= 50000000/(99+1)/16 = 31250 Hz

    TCFG0 = 99; 
    TCFG1 &= ~0xf;
    TCFG1 |= 3;
    

    2)设置初值:

    /* 设置比较缓存寄存器TCMPB0和计数缓存寄存器TCNTB0的初始值*/
    TCNTB0 = 31250 << 1;  /* 2s中断一次 */
    TCMPB0 = 31250 >> 1;  /* 设置占空比*/
    

    3)开启定时器0的手动更新TCNTB0&TCMPB0功能(设置TCON的第1位):

    TCON |= (1<<1); //开始需要手工更新,这样才能将TCNTB0&TCMPB0同步到TCNT0&TCMP0
    

    4)开启定时器0的自动加载:

    TCON &= ~(1<<1); //开启自动加载要先清除手动更新
    TCON |= (1<<3);
    

    5)启动定时器0(设置TCON的第0位);

    TCON |= (1<<0);
    

    ② 初始化中断控制器


    interrupt_init(){
    	...
    	INTMSK &= ~(1<<10);  /* enable timer0 int */		
    	...
    }
    

    做完这些初始化工作,就可以产生定时器中断了,同样我们需要在handle_irq_c函数中区分中断源:
    我们还可以通过查看TCNTO0寄存器来查看当前TCNT的值。

    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) */
        }
    
    	else if(bit == 9) //INT_WDT_AC97
    	{
    		...
    	}
    	
    	else if(bit == 10) //timer0
    	{
    		printf("timer0 interrupt occured.
    ");
    		print_hex(TCNTO0);
    	}
    
        /* 清中断 : 从源头开始清 */
        SRCPND = (1<<bit);
        INTPND = (1<<bit);  
    }
    

    测试结果如下:
    xxx.ong

    irq的优化改进:

    我们对比irq外部中断irq定时器中断发现每增加一个中断源,又要去修改中断控制器的初始化interrupt_init()和handle_irq_c(),要在handle_irq_c()中去添加分支去执行不同的中断服务。

    那么我们现在不去改变interrupt文件,在timer.c、key_eint.c中去注册自己的中断服务程序即可,这里我们使用函数指针数组,建立一个中断号和中断服务程序的映射关系。这样就可以根据中断号来执行对应的中断服务程序,即在handle_irq_c()中去回调不同类型的中断源注册下来的函数即可。

    /* 定义函数指针数组 */
    #define IRQ_NUM	32
    typedef void(*irq_func)(int);
    irq_func irq_array[IRQ_NUM];
    

    然后实现一个register_irq(...)如下:

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

    handle_irq_c()修改实现如下:

    void handle_irq_c(void)
    {
    	/* 分辨中断源 */
    	int bit = INTOFFSET;
    
    	irq_array[bit](bit); //根据中断号回调不同的中断处理函数
    	
    	/* 清中断 */
    	SRCPND = (1<<bit);
    	INTPND = (1<<bit);	
    }
    

    这样子我们的irq中断就被统一管理了起来,只要在其他各中断模块初始化的时候调用register_irq(...)注册即可.
    优化后的代码详见:

  • 相关阅读:
    [转] 计算机网络中的服务原语
    VMWare的三种网络连接方式
    Vim常用操作
    [转] 图解单片机下载程序电路原理之USB转串口线、CH340、PL2303、MAX232芯片的使用
    [转] MMU 配置
    [转] C++项目中的extern "C" {}
    数据结构62:表插入排序算法
    数据结构61:2-路插入排序算法
    数据结构60:折半插入排序算法(折半排序算法)
    数据结构59:插入排序算法
  • 原文地址:https://www.cnblogs.com/fuzidage/p/12464079.html
Copyright © 2011-2022 走看看