打印

LPC1343学习笔记(连载中)

[复制链接]
楼主: LPC300
手机看帖
扫描二维码
随时随地手机跟帖
21
LPC300|  楼主 | 2010-6-28 22:40 | 只看该作者 回帖奖励 |倒序浏览
笔者在此就不多阐述了,这个函数就是对GPIO寄存器中的IE寄存器操作,打开对应IO的中断功能(即取消中断屏蔽)。



最后两句是点灯程序,这个大家都知道了的。



我们继续看看中断的另外一半,中断服务函数:



/*****************************************************************************



** Function name:              PIOINT2_IRQHandler



**



** Descriptions:           Use one GPIO pin(port2 pin1) as interrupt source



**



** parameters:                    None



** Returned value:              None



**



*****************************************************************************/



void PIOINT2_IRQHandler(void)



{



  uint32_t regVal;



gpio2_counter++;



  regVal = GPIOIntStatus( PORT2, 1 );//读取IO中断状态,若触发返回1



  if ( regVal )



  {



       p2_1_counter++;



       GPIOIntClear( PORT2, 1 );//这里很重要,一定要软件清除IO的中断触发标志!!!



  }



// CodeRed - extend original interrupt handler so as to toggle the LED state



  if (LEDvalue == LED_OFF)



  {



         LEDvalue = LED_ON;



  }



  else



  {



         LEDvalue = LED_OFF;



  }



  GPIOSetValue( LED_PORT, LED_BIT, LEDvalue );



return;



}



这个服务函数的流程也很清晰,首先判断是否是我们想要的IO触发的中断,为什么要判断呢?那是因为每个PORT共用同1个中断,即共用同一个中断服务函数。判断完之后,清除对应中断标志,否则中断不会被清除,再三强调一下。之后就是程序员想要在中断里面处理的事情了。特别提一下注意中断服务函数的函数名的写法~



编译,运行,灯亮,用杜邦线将P21接到GND上,灯灭,拔出再接上,灯亮……



当然这个现象是理想状态,你的手不可能那么稳那么准~呵呵,Enjoy







下一节,我们来看看如何使用中断的嵌套功能~

使用特权

评论回复
22
LPC300|  楼主 | 2010-6-28 22:40 | 只看该作者
LPC1343中断嵌套程序实验设计
本节的内容是对LPC1343进行中断嵌套程序实验设计。

实验具体内容是,使用PORT2.0PORT3.0作为外部中断源。当PORT3.0触发,板子上的LED以较快频率闪烁,在此期间触发较高级的PORT2.0中断,此时PORT3.0中断被打断挂起,执行PORT2.0中断服务,LED以较慢速度闪烁,PORT2.0中断服务结束后,返回挂起的PORT3.0中断,LED继续以较快速度闪烁……



这个是一个很典型的中断嵌套的过程。当然LPC也可以做到中断嵌套。但是必须要说说基于Cortex-M3内核的LPC1343的中断系统。

为何要特地提到Cortex-M3内核呢,那是因为,凡是使用Cortex-M3内核的微控制器都使用一个叫NVIC

的中断控制系统,而不需要芯片设计厂商再额外设计一个中断控制器了。这样做的好处就是带来了程序上移植的方便。你可以在任意两个使用M3内核的控制器之间很轻松的移至中断服务程序,而不需要你再去学习一种新的中断控制系统。




以下是来自ARM公司对NVIC控制器的简要描述(原文翻译):

NVIC(Nested Vectored Interrupt Controller,即嵌套向量中断控制器):


更低的异常和中断处理延迟

●      可实现电源管理


使用系统控制寄存器

NVIC支持多达240个优先级可变化的中断源,每一个中断可以有多达256个优先级设置。NVIC和处理器核心的连接非常的接近,这样可以达到更低的中断处理延迟和对迟到的中断进行高效的处理。NVIC一直保持对堆栈中断进行应答,由此实现中断的尾链技术。

你可以使NVIC完全工作在特权模式下,但是如果你使能了设置与控制寄存器,你也可以在用户模式下对中断进行屏蔽。其他任何的用户模式将引起总线错误。

所有的NVIC寄存器都可以进行字节,半字,字操作,除非在指定状态下。

所有的NVIC寄存器和系统调试寄存器都工作在小端模式下。

关于NVIC的详细结构,工作方式以及带来的优点,大家可以参看《Cortex-M3 Technical Reference Manual》,学习使用M3内核控制器的朋友是一定要把这份文档读个通透的。




下面开始讲述如何在我们的LPC1343上设计中断嵌套实验。

上一节我们已经成功实现了使用外部中断介入一个LED的点亮状态。本节内容里,我们使用PORT2.0PORT3.0作为外部中断源。这个部分的内容和上一节是一样的。在此就不详细阐述其过程了,放出这部分的源程序:

/*初始化P20,P30以及连接LEDP07口的工作方式*/

使用特权

评论回复
23
LPC300|  楼主 | 2010-6-28 22:40 | 只看该作者
void GPIO_Configuration(void)



{



    GPIOInit();



    GPIOSetDir( PORT2, 0, 0 );         



    GPIOSetDir( PORT3, 0, 0 );



    GPIOSetDir( PORT0, 7, 1 );



    GPIOSetInterrupt( PORT2, 0, 0, 0, 0 );



    GPIOSetInterrupt( PORT3, 0, 0, 0, 0 );



    GPIOIntEnable( PORT2, 0 );



    GPIOIntEnable( PORT3, 0 );



}







接下来就是重点了,通过设置NVIC设定两个中断的优先级:



void NVIC_Configuration(void)



{



NVIC_SetPriorityGrouping(1);//使用中断优先组1



NVIC_SetPriority(EINT2_IRQn, 0);//设置外部中断2为0级中断(最高级)



NVIC_SetPriority(EINT3_IRQn, 1); //设置外部中断3为1级中断







}







大家肯定看到了一个新的名词:中断优先组。



中断优先组是M3内核引入的一种对中断优先级进行管理的机制。在Application Interrupt and Reset Control Register中的[10:8]这三个位设置。






  

16.jpg (20.23 KB)

2010-5-11 15:29


明显它的取值范围是0~7,正好表示2^8=256个中断优先级,具体含义如下:



[10:8] PRIGROUP Interrupt priority grouping field:



PRIGROUP         Split      of pre-emption priority from subpriority



0                   7.1       indicates seven bits of pre-emption priority, one bit of subpriority



1                   6.2       indicates six bits of pre-emption priority, two bits of subpriority



2                   5.3       indicates five bits of pre-emption priority, three bits of subpriority



3                   4.4       indicates four bits of pre-emption priority, four bits of subpriority



4                   3.5       indicates three bits of pre-emption priority, five bits of subpriority



5                   2.6       indicates two bits of pre-emption priority, six bits of subpriority



6                   1.7       indicates one bit of pre-emption priority, seven bits of subpriority



7                   0.8       indicates no pre-emption priority, eight bits of subpriority.







Split是分割的意思,分割什么呢,分割实际支配M3器件中表述中断优先级的那个8位字节,控制256个优先级。



上面字段表示,当PRIGROUP取0时,分割点位于控制字节右起第1位(7.1),表示有7位数据用于先占优先级,同时1位用于次占优先级。所以使用PRIGROUP=0,则一共有128个先占优先级以及2个次占优先级。



同样,当PRIGROUP取1时,分割点位于控制字节右起第2位(6.2),表示有6位数据用于先占优先级,同时2位用于次占优先级。所以使用PRIGROUP=1,则一共有64个先占优先级以及2个次占优先级。



同样,当PRIGROUP取2时,分割点位于控制字节右起第2位(5.3),表示有5位数据用于先占优先级,同时3位用于次占优先级。所以使用PRIGROUP=2,则一共有32个先占优先级以及8个次占优先级。



………………一直类推到PRIGROUP=7:



同样,当PRIGROUP取7时,分割点位于控制字节右起第8位(0.8),表示有0位数据用于先占优先级,同时8位用于次占优先级。所以使用PRIGROUP=7,则一共有0个先占优先级以及256个次占优先级。







这就是中断优先组的概念,先占优先组拥有比后占优先组更高的优先权限。需要提到的是,Cortex-M3并没有要求所有的控制器都要使用8位数据来管理优先级,毕竟不是什么场合都需要多达256个中断优先级,所以可以使用低于7个优先组的分级机制,比如LPC1343是7组,而STM32F103是4组……



所以两个中断的优先级关系可以如下判断:



先占优先级高的优先执行,先占优先级一样的按后占优先级执行,两个优先级一样的按照M3指定的默认优先级执行。



以上是笔者对中断优先组的一些见解,下面看回源程序:



NVIC_SetPriorityGrouping(1);这里使用优先级分组1,函数原型如下:



static __INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)



{



  uint32_t reg_value;



  uint32_t PriorityGroupTmp = (PriorityGroup & 0x07);          /* only values 0..7 are used */



  



  reg_value  =  SCB->AIRCR;                            /* read old register configuration*/



  reg_value &= ~(SCB_AIRCR_VECTKEY_Msk|SCB_AIRCR_PRIGROUP_Msk);/*clear bits to change   







reg_value  =  (reg_value|(0x5FA << SCB_AIRCR_VECTKEY_Pos)|



(PriorityGroupTmp << 8));              /* Insert write key and priorty group */



  SCB->AIRCR =  reg_value;



}







这个函数里,最重要的一个信息是“only values 0..7 are used”,只能填入0-7之间。说明LPC1343使用了7位数据来管理中断优先级,最多可以设置256个中断优先级(而广为流传的STM32是使用4个优先组,只能设置16个优先级)。这里设置优先级分组为1,即使用“64个先占优先级以及2个次占优先级”。



接着是设置具体中断的优先级:



NVIC_SetPriority(EINT2_IRQn, 0);//设置外部中断2为0级中断(最高级)



NVIC_SetPriority(EINT3_IRQn, 1); //设置外部中断3为1级中断

使用特权

评论回复
24
LPC300|  楼主 | 2010-6-28 22:40 | 只看该作者
也找到其原型如下:



/**



* @brief  Set the priority for an interrupt



* @param  IRQn      The number of the interrupt for set priority



* @param  priority  The priority to set



* Set the priority for the specified interrupt. The interrupt



* number can be positive to specify an external (device specific)



* interrupt, or negative to specify an internal (core) interrupt.



* Note: The priority cannot be set for every core interrupt.



*/



static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)



{



  if(IRQn < 0) {



SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); }



/* set Priority for Cortex-M3 System Interrupts */



  else {



NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);    }        



/* set Priority for device specific Interrupts  */



}



从函数头部的注释就可以知道很多内容:



●该函数的作用是设置中断优先级;



●第一个参数填入具体中断号,第二个参数填入优先级;



●优先级参数是负数的时候,表示指定Cortex-M3内部中断,是正数则指定外部中断;



●并不是每一个内核中断都可以被指定;



我设置外部中断0为最高级中断,而外部中断3为1级中断。



至此,中断设置部分结束,以下是具体的中断服务函数:



void PIOINT2_IRQHandler(void)



{



    if (GPIOIntStatus( PORT2, 0 )==1)



    {



        GPIOIntClear( PORT2, 0 );



        for(n=0;n<10;n++)



        {



            LPC_GPIO0->DATA|=0x0080;



            for(k=0;k<1000000;k++);



            LPC_GPIO0->DATA&=~0x0080;



            for(k=0;k<1000000;k++);



        }



    }







  return;



}



void PIOINT3_IRQHandler(void)



{



    if (GPIOIntStatus( PORT3, 0 )==1)



    {



        GPIOIntClear( PORT3, 0 );



        for(n=0;n<100;n++)



          {



                LPC_GPIO0->DATA|=0x0080;



                for(k=0;k<100000;k++);



                LPC_GPIO0->DATA&=~0x0080;



                for(k=0;k<100000;k++);



        }



    }



}



这个就没什么特别的了,上一节特别提示过,要注意中断函数的命名,在这里就用到了,如果是P1口的中断函数呢?对~ 举一反三,就是void PIOINT1_IRQHandler(void),P2口呢?void PIOINT2_IRQHandler(void)。



编译,Debug,F8全速运行,此时



给PORT3.0一个下降沿,LED开始快速闪烁,在LED快速闪烁期间给PORT2.0一个下降沿,LED的快速闪烁状态被打断,开始慢速闪烁,说明低级中断被高级中断打断了,正在执行高级中断PORT2.0的服务程序,慢速闪烁结束之后,返回快速闪烁状态,说明高级中断执行完毕,返回被打断的低级中断继续执行至完结……

使用特权

评论回复
25
LPC300|  楼主 | 2010-6-28 22:41 | 只看该作者
本节内容,我们来会会LPC1343内部的看门狗。

看门狗,又叫 watchdog timer,是一个定时器电路, 一般有一个输入,叫“喂狗”(kicking the dog or service the dog),一个输出到MCU的RST端,MCU正常工作的时候,每隔一端时间输出一个信号到喂狗端,给 WDT 清零,如果超过规定的时间不喂狗,(一般在程序跑飞时),WDT 定时超过,就会给出一个复位信号到MCU,使MCU复位. 防止MCU死机. 看门狗的作用就是防止程序发生死循环,或者说程序跑飞。



我们的LPC1343也 内部自带了看门狗,以下是它的特性,来自《user.manual.lpc13xx》:



●     在看门狗没有定期重载的情况下从内部复位芯片



●     支持调试模式



●     使用软件使能,但是只有在硬件复位或者发生看狗复位/中断时候才能失能



●     错误或者不完整的喂狗结果会导致看门狗/中断的发生



●     拥有表面发生看门狗复位的标志



●     拥有内部预分频器的可编程32位定时器



●     可选的定时时间,从(TWDCLKX256X4)到(TWDCLKX2^32X4)可选,它们都是TWCLKX4的倍数



●     看门狗的时钟源可以从系统控制模块从选择,包括内部RC振荡器,主时钟以及看门狗振荡器,这样就看门狗就有一个较为宽广的定时时间选择,在方便在不同的低功耗场合使用。为了提高可靠性,看门狗也可以只在独立的内部时钟源驱动下运行,这个内部时钟与外部晶振,与它连接的部件和线路都保持独立状态,既是说看门狗可以在不受干扰的内部时钟源驱动下运行。



如何使用看门狗呢?同样来自来自《user.manual.lpc13xx》:



1、 在WDTC寄存器中设置看门狗的固定重装值;



2、 在WDMOD寄存器中设置看门狗的运行模式;



3、 通过向WDFFD寄存器写0XAA接着写0X55启动看门狗;



4、 在看门狗计数器向下溢之前应该再次喂狗以阻止看门狗复位/中断;



凭借以上信息,我们来分析一下LPC1343自带的wdt例程



int main (void)



{



  WDT_CLK_Setup(WDTCLK_SRC_MAIN_CLK);



  init_timer16( 0, TIME_INTERVALmS * 10 );



  GPIO_Configuration();               //作者自添函数,作用在后面解释



  WDTInit();



  enable_timer16( 0 );



while( 1 )



  {



       if ( timer16_0_counter > 0 ) // Every 10ms



       {



         /* Feed the watchdog timer */



         feed_counter++;



         WDTFeed();



         timer16_0_counter = 0;



       }



  }



}



此主函数的执行功能是,开始进行看门狗和16位定时器0的初始化,使定时器0实现10ms间隔,并以此间隔进行喂狗操作,比较简单。下面就是比较重要的剖析部分了,首先看看

使用特权

评论回复
26
LPC300|  楼主 | 2010-6-28 22:41 | 只看该作者
WDT_CLK_Setup(WDTCLK_SRC_MAIN_CLK);这个函数,看门狗时钟源的选择,原型如下:

void WDT_CLK_Setup ( uint32_t clksrc )

{


/* Watchdog configuration. */


/* Freq = 0.5Mhz, div_sel is 0, divided by 2. WDT_OSC should be 250khz */


LPC_SYSCON->
WDTOSCCTRL = (0x1<<5)|0x00;


LPC_SYSCON->
WDTCLKSEL = clksrc;
/* Select clock source */


LPC_SYSCON->
WDTCLKUEN = 0x01;
/* Update clock */


LPC_SYSCON->
WDTCLKUEN = 0x00;
/* Toggle update register once */


LPC_SYSCON->
WDTCLKUEN = 0x01;


while ( !(LPC_SYSCON->WDTCLKUEN & 0x01) );
/* Wait until updated */


LPC_SYSCON->
WDTCLKDIV = 1;
/* Divided by 1 */


LPC_SYSCON->
PDRUNCFG &= ~(0x1<<6);
/* Let WDT clock run */


return;

}

这个函数的作用是,WDT_CLK_Setup,即看门狗时钟设置,其结果是Freq = 0.5Mhz, div_sel is 0, divided by 2. WDT_OSC should be 250khz,即看门狗振荡器的频率是250KHz,但是这句话带有相当的误导性,笔者一开始也以为这个函数的功能就是将看门狗时钟设置为250KHz频率。导致后面计算喂狗间隔时间错误!我们来看看这段来自user.manual.lpc13xx》的关键REMARK




Remark: The frequency of the watchdog oscillator is undefined after reset. The

watchdog oscillator frequency must be programmed by writing to the WDTOSCCTRL

register before using the watchdog oscillator for the WDT.



注意:在复位之后看门狗振荡器并未定义,在使用看门狗振荡器之前必须通过WDTOSCCTRL寄存器定义看门狗振荡器频率。

这句话包含了一个重要的信息,使用看门狗振荡器之前必须通过WDTOSCCTRL寄存器定义看门狗振荡器频率,但是在我们的主函数中使用的是如下语句:WDT_CLK_Setup(WDTCLK_SRC_MAIN_CLK)

选择主时钟作为看门狗时钟而不是看门狗振荡器。所以只有在使用看门狗振荡器的情况下

Freq = 0.5Mhz, div_sel is 0, divided by 2. WDT_OSC should be 250khz才是正确的。那么在使用主时钟的情况下,这个函数的结果是什么呢?

这样问题已经转变为Cortex-M3时钟系统的问题了,下面是LPC1343的内部时钟分支结构:


17.jpg (30.48 KB)
2010-5-17 11:27



这个图很清晰地表现出LPC1343内部的时钟分配路线,而圈起来的部分就是提供给看门狗的部分。

从左边开始看起,可以看到前文已经提到的irc_osc_clk(内部RC振荡器)wdt_osc_clk(看门狗振荡器)sys_osc_clk(系统时钟,即外部晶振),从圈1处我们可以得知对于上文的信息:看门狗时钟可以从内部RC振荡器,主时钟以及看门狗振荡器,那么我们的程序中使用了主时钟,所以看门狗时钟要来自圈2。其中

使用特权

评论回复
27
LPC300|  楼主 | 2010-6-28 22:41 | 只看该作者
MAINCLKSEL:主时钟源选择;

SYSPLLCLKSEL:系统锁相环时钟源选择;

SYSPLL:系统锁相环,可以将输入以倍数放大,LPC1343所谓的最大72MHz工作频率就是12MHz从这里产生6倍放大得到的;

我们可以按下主时钟的来源有五条路径:

1、
irc_osc_clk-> SYSPLLCLKSE-> MAINCLKSEL,既使用内部RC振荡器经过系统锁相环时钟源选择作为主时钟;

2、
sys_osc_clk-> SYSPLLCLKSE-> MAINCLKSEL,既使用外部晶振经过系统锁相环时钟源选择作为主时钟;

3、
irc_osc_clk-> MAINCLKSEL,使用内部RC直接作为主时钟源;

4、
sys_osc_clk> MAINCLKSEL,使用外部晶振直接作为时钟源;

5、
sys_osc_clk/ irc_osc_clk-> SYSPLLCLKSE ->SYSPLL-> MAINCLKSEL ,既使用外部晶振/内部RC振荡器经过系统锁相环作为主时钟;

那么在本节看门狗程序中,我们的主时钟来自哪条路径呢?先在user.manual.lpc13xx》中找到MAINCLKSEL寄存器:


18.jpg (21.36 KB)
2010-5-17 11:27



得知这个寄存器的前两位值决定了当前主时钟的来源。现在查看这两个关键的位!

这里笔者通过LPCXpresso的寄存器查看功能尝试查看到这个信息,将来NXP自己解释这些例程的混乱原因。编译运行… …

查看到:

19.jpg (4.94 KB)
2010-5-17 11:27



所以确认主时钟源来自PLL倍频器的输出,即上述第5条路径,而PLL倍频的输入呢?

20.jpg (20.41 KB)
2010-5-17 11:27



查看到:

21.jpg (4.86 KB)
2010-5-17 11:27



所以PLL输入来自System oscillator。由此我们最终确定了看门狗的时钟来源:

sys_osc_clk-> SYSPLLCLKSE ->SYSPLL-> MAINCLKSEL ,既使用外部晶振过系统锁相环作为主时钟;

既然通过了倍频器,自然要来看看倍频器是个什么东西:

使用特权

评论回复
28
LPC300|  楼主 | 2010-6-28 22:42 | 只看该作者
这个是它的说明:

The block diagram of this PLL is shown in Figure 4. The input frequency range is 10 MHz to 25 MHz. The input clock is fed directly to the Phase-Frequency Detector (PFD). This block compares the phase and frequency of its inputs, and generates a control signal when phase and/ or frequency do not match. The loop filter filters these control signals and drives the current controlled oscillator (CCO), which generates the main clock and optionally two additional phases. The CCO frequency range is 156 MHz to320 MHz.These clocks are either divided by 2×P by the programmable post divider to create the output clock(s), or are sent directly to the output(s). The main output clock is then divided by M by the programmable feedback divider to generate the feedback clock. The output signal of the phase-frequency detector is also monitored by the lock detector, to signal when the PLL has locked on to the input clock.

在此笔者就不翻译啦,这段话里面最重要的红字部分:时钟在输出之前可以经过一个“post divider”进行“2XP”倍数的放大,或者不放大之间输出。所以我们还是得查看一下本次看门狗程序中的PLL放大了几倍:

先是寄存器:


23.jpg (30.65 KB)
2010-5-17 11:30




接着查看到:

24.jpg (8.37 KB)
2010-5-17 11:30



得知PSEL01,则P=2,则放大倍数为2XP=2X2=4倍。所以从SYS_PLL出来的时钟频率是12MHzX4=48MHz!!这个就是我们苦苦追寻的看门狗主时钟!

时钟选择好之后,来到看门狗初始化部分(定时器初始化部分属于定时器的内容,暂且跳过):

WDTInit()

原型(笔者已经添加详细注释):

void WDTInit( void )

{


LPC_SYSCON->SYSAHBCLKCTRL |= (1<<15);//
开启看门狗AHB时钟,即启用看门狗

wdt_counter = 0;


NVIC_EnableIRQ(WDT_IRQn);//
允许看门狗中断

LPC_WDT->TC = WDT_FEED_VALUE;/* 重装值,once WDEN is set, the WDT will start after*/ /*feeding
*/


LPC_WDT->MOD = WDEN;
/*
使能看门狗*/

LPC_WDT->FEED = 0xAA;
/*
喂狗*/


LPC_WDT->FEED = 0x55;



return;


}

这部分完全是按照前文提到的步骤来编写的:

1、在WDTC寄存器中设置看门狗的固定重装值;

2、在WDMOD寄存器中设置看门狗的运行模式;

3、通过向WDFFD寄存器写0XAA接着写0X55启动看门狗;

其中有一个很有用的信息,便是once WDEN is set, the WDT will start afte feeding,当WDEN设置后,执行完LPC_WDT->FEED = 0xAA; LPC_WDT->FEED = 0x55; 看门狗就开始启动计数了。

我们通过LPCXpresso的查找功能查找到:

#define WDT_FEED_VALUE
0x003FFFFF
//0x003fffff= 4194303
,备待会计算用

前文同样提到过,LPC1343的看门狗拥有内部预分频器的可编程32位定时器,同样翻阅到:

The Watchdog consists of a divide by 4 fixed pre-scaler and a 32-bit counter. The clock is

fed to the timer via a pre-scaler. The timer decrements when clocked. The minimum value

from which the counter decrements is 0xFF. Setting a value lower than 0xFF causes 0xFF

to be loaded in the counter. Hence the minimum Watchdog interval is (TWDCLK × 256 × 4)

and the maximum Watchdog interval is (TWDCLK × 232 × 4) in multiples of (TWDCLK × 4).

由以上信息可知,该分频器是一个4分频分频器。同时还有一个重要信息是,看门狗的重装值不能低于0XFF,即便你写入的数小于0XFF还是会得到0XFF的写入结果。所以看门狗的最小溢出时间是(TWDCLK × 256 × 4),最大是(TWDCLK × 2^32 × 4)

现在我们要尝试计算一下WDT_FEED_VALUE=0x003FFFF的情况下看门狗的溢出时间:

Tunderflow = (TWDCLK ×WDT_FEED_VALUE

× 4)

=1/48MHz X 0x003FFFF X 4


=1/48MHz X 4194303 X 4=0.34952525s

所以溢出时间大概是350ms,而在该看门狗程序中,定时器设定了10ms的喂狗间隔,所以在程序不跑飞的情况下,看门狗永远都不会溢出,不会产生看门狗复位以及中断。

最后来看看看门狗的中断服务函数:

void WDT_IRQHandler(void)

{


NVIC_DisableIRQ(WDT_IRQn);
// Disable the watchdog- interrupt flag cannot




// be cleared except by reset


LPC_WDT->MOD &= ~WDTOF;
/* clear the time-out interrupt flag */



wdt_counter++;


}


使用特权

评论回复
29
LPC300|  楼主 | 2010-6-28 22:42 | 只看该作者
这个是它的说明:



The block diagram of this PLL is shown in Figure 4. The input frequency range is 10 MHz to 25 MHz. The input clock is fed directly to the Phase-Frequency Detector (PFD). This block compares the phase and frequency of its inputs, and generates a control signal when phase and/ or frequency do not match. The loop filter filters these control signals and drives the current controlled oscillator (CCO), which generates the main clock and optionally two additional phases. The CCO frequency range is 156 MHz to320 MHz.These clocks are either divided by 2×P by the programmable post divider to create the output clock(s), or are sent directly to the output(s). The main output clock is then divided by M by the programmable feedback divider to generate the feedback clock. The output signal of the phase-frequency detector is also monitored by the lock detector, to signal when the PLL has locked on to the input clock.



在此笔者就不翻译啦,这段话里面最重要的红字部分:时钟在输出之前可以经过一个“post divider”进行“2XP”倍数的放大,或者不放大之间输出。所以我们还是得查看一下本次看门狗程序中的PLL放大了几倍:



先是寄存器:



  

23.jpg (30.65 KB)

2010-5-17 11:30






接着查看到:



  

24.jpg (8.37 KB)

2010-5-17 11:30


得知PSEL为01,则P=2,则放大倍数为2XP=2X2=4倍。所以从SYS_PLL出来的时钟频率是12MHzX4=48MHz!!这个就是我们苦苦追寻的看门狗主时钟!



时钟选择好之后,来到看门狗初始化部分(定时器初始化部分属于定时器的内容,暂且跳过):



WDTInit()



原型(笔者已经添加详细注释):



void WDTInit( void )



{



  LPC_SYSCON->SYSAHBCLKCTRL |= (1<<15);//开启看门狗AHB时钟,即启用看门狗



wdt_counter = 0;



  NVIC_EnableIRQ(WDT_IRQn);//允许看门狗中断



LPC_WDT->TC = WDT_FEED_VALUE;/* 重装值,once WDEN is set, the WDT will start after*/ /*feeding                                  */  



LPC_WDT->MOD = WDEN;     /*使能看门狗*/



LPC_WDT->FEED = 0xAA;           /*喂狗*/



  LPC_WDT->FEED = 0x55;   



  return;



}



这部分完全是按照前文提到的步骤来编写的:



1、在WDTC寄存器中设置看门狗的固定重装值;



2、在WDMOD寄存器中设置看门狗的运行模式;



3、通过向WDFFD寄存器写0XAA接着写0X55启动看门狗;



其中有一个很有用的信息,便是once WDEN is set, the WDT will start afte feeding,当WDEN设置后,执行完LPC_WDT->FEED = 0xAA; LPC_WDT->FEED = 0x55; 看门狗就开始启动计数了。



我们通过LPCXpresso的查找功能查找到:



#define WDT_FEED_VALUE           0x003FFFFF        //0x003fffff= 4194303,备待会计算用



前文同样提到过,LPC1343的看门狗拥有内部预分频器的可编程32位定时器,同样翻阅到:



The Watchdog consists of a divide by 4 fixed pre-scaler and a 32-bit counter. The clock is



fed to the timer via a pre-scaler. The timer decrements when clocked. The minimum value



from which the counter decrements is 0xFF. Setting a value lower than 0xFF causes 0xFF



to be loaded in the counter. Hence the minimum Watchdog interval is (TWDCLK × 256 × 4)



and the maximum Watchdog interval is (TWDCLK × 232 × 4) in multiples of (TWDCLK × 4).



由以上信息可知,该分频器是一个4分频分频器。同时还有一个重要信息是,看门狗的重装值不能低于0XFF,即便你写入的数小于0XFF还是会得到0XFF的写入结果。所以看门狗的最小溢出时间是(TWDCLK × 256 × 4),最大是(TWDCLK × 2^32 × 4)。



现在我们要尝试计算一下WDT_FEED_VALUE=0x003FFFF的情况下看门狗的溢出时间:



Tunderflow = (TWDCLK ×WDT_FEED_VALUE  × 4)



=1/48MHz X 0x003FFFF X 4



          =1/48MHz X 4194303 X 4=0.34952525s



所以溢出时间大概是350ms,而在该看门狗程序中,定时器设定了10ms的喂狗间隔,所以在程序不跑飞的情况下,看门狗永远都不会溢出,不会产生看门狗复位以及中断。



最后来看看看门狗的中断服务函数:



void WDT_IRQHandler(void)



{



  NVIC_DisableIRQ(WDT_IRQn);   // Disable the watchdog- interrupt flag cannot



                                                       // be cleared except by reset



LPC_WDT->MOD &= ~WDTOF;          /* clear the time-out interrupt flag */



  wdt_counter++;



}

使用特权

评论回复
30
LPC300|  楼主 | 2010-6-28 22:43 | 只看该作者
下一节来看看LPC1343的定时器~

LPCXpresso_WatchDog.rar

572.29 KB

使用特权

评论回复
31
yoyowodeai| | 2010-6-28 22:47 | 只看该作者
虽然现在用不了,不过还是要顶,好帖!

使用特权

评论回复
32
年轻不在| | 2010-6-28 22:47 | 只看该作者
楼主依然是那么的辛苦啊 谢谢了

使用特权

评论回复
33
想实习去| | 2010-6-28 22:48 | 只看该作者
善于写总结的人,不错,很详细的资料!应该向你好好学习!

使用特权

评论回复
34
LPC300|  楼主 | 2010-6-28 22:48 | 只看该作者
本节内容是利用LPC1343内部计数/定时器得到一个准确的时间间隔。比较简单,但仍求详细。
以下是一些官方描述:

1Description

每一个计数/定时器的设计是用以对设备时钟(PCLK)或者外部提供的时钟进行其时钟周期数的计量,同时基于四个匹配寄存器中存放着既定的时间值,可以有选择地产生中断或者履行其他既定的操作。每一个计数/定时器还包含一个输入捕获环节可以在输入信号转变的时候捕获计数器值,同样可以选择产生一个中断。

PWM模式中,三个匹配寄存器用以提供单边沿控制匹配输出引脚的PWM输出。另外一个寄存器控制PWM的占空比。

提醒:

两个32位定时器在功能上完全一样,它们只有设备基地址上的区别。

2Features

带有32位可编程预分频器的两个32位计数/定时器

计数/定时模式

各拥有一个捕获通道,当输入信号变化之时可以对时间值进行快照。一次捕获时间可以选择产生一次中断。

四个32位匹配寄存器可以做到:


当可选匹配中断产生时继续计数器


当可选匹配中断产生时停止计数器


当可选匹配中断产生时复位计数器


四个对应匹配寄存器的外部输出引脚可以做到:

匹配时置为低电平

匹配时置为高电平

匹配时产生跳变

匹配时无操作


每一个定时器,多达四个匹配寄存器可以设置为PWM模式,允许多达三个匹配寄存器输出单边沿控制的PWM输出。

3Applications

通过既定时间间隔执行既定时间

通过输入捕获实现脉宽解调

定时器自由运行

通过输出匹配实现脉宽解调

以上是关于LPC134332位定时器的简要描述,其实定时器本身就不应该是一个太复杂的东西。这在我们学习51单片机的时候就应该知道了。下面是定时器的一个结构图:


1.jpg (91.38 KB)
2010-5-20 07:37




从这个图,我们可以看出定时器的运行过程:

1、
时钟进入预分频器后供给TIMER COUNTER

2、
TIMER COUNTER计数

3、
TIMER COUNTER中数值与MATCH REGISTER中数值相等时……

4、
CONTROL单元进行相应操作,而CONTROL的反应取决于围绕在它周围的一系列寄存器;

我们就跟着这个思路,来亲自看看程序是怎么做的,NXP带给我们的第一个演示程序,就是32位定时器产生固定时间间隔的功能,下面是稍有改动之后的主函数:

使用特权

评论回复
35
LPC300|  楼主 | 2010-6-28 22:50 | 只看该作者
int main (void)

{

init_timer32(0, TIME_INTERVAL*10);
//
初始化定时器,并设置其时间间隔



enable_timer32(0);//
使能定时器



GPIOInit();



GPIOSetDir( LED_PORT, LED_BIT, 1 );

while (1)



{



}

}

很明显,第一句就是定时器设置的关键所在,找出它的原型并先把所有的预编译部分去掉:

void init_timer32(uint8_t timer_num, uint32_t TimerInterval)//参数分别为定时器标//号和计数值

{


if ( timer_num == 0 )


{

LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9);
//
开启设备时钟

/*以下语句是选择IO口的定时器功能*/


LPC_IOCON->;PIO1_5 &= ~0x07;
/*
Timer0_32 I/O config */


LPC_IOCON->;PIO1_5 |= 0x02;
/* Timer0_32 CAP0 */


LPC_IOCON->;PIO1_6 &= ~0x07;


LPC_IOCON->;PIO1_6 |= 0x02;
/* Timer0_32 MAT0 */


LPC_IOCON->;PIO1_7 &= ~0x07;


LPC_IOCON->;PIO1_7 |= 0x02;
/* Timer0_32 MAT1 */


LPC_IOCON->;PIO0_1 &= ~0x07;

LPC_IOCON->;PIO0_1 |= 0x02;
/* Timer0_32 MAT2 */

/*以上语句是选择IO口的定时器功能*/


LPC_TMR32B0->MR0 = TimerInterval;//
设置计数值


LPC_TMR32B0->MCR = 3;
/*
当匹配时触发匹配中断并复位TC寄存器*/


NVIC_EnableIRQ(TIMER_32_0_IRQn);//
使能32位定时器0中断


}


else if ( timer_num == 1 )


{


/*
这部分是对定时器1的设置,跟上面完全一样,故也省去*/


}


return;

}

以上程序中,

1、首先开启AHB时钟给TIMER32提供驱动时钟;

2、其次对把相关的IO设置为定时器功能,因为32位定时器0IOLPC1343JTAG端口IO存在复用情况,默认情况下IO执行JTAG IO功能;

3、接着设置计数值;

4、设置匹配时触发匹配中断,并且复位TC寄存器。这个地方比较关键,首先要知道TIMER是加1计数,即计数范围从0x00000000xffffffff,但是当其计数值和设置的计数值TimerInterval匹配(相等)时,便触发了匹配中断,同时复位TC寄存器即清0,相当于起到了一个初值重装的作用。

5、最后是开启32TIMER的中断;

回到主函数,enable_timer32(0),如此定时器就开始启动计数了。

最后总结一下定时器的运作,打开时钟,IO设置,写入匹配值,打开中断并开始运行。当TC中的计数值和匹配值相等时,触发中断,执行中断服务程序,并且清零TC计数值开始新一轮计数。

下面是另外一个重点,定时时间的计算。

从定时器的内部结构图和初始化函数都可以看出,定时器时钟来自PCLKPeripheral CLOCK),而定时器属于APB设备,所以它的时钟来自AHB->APB


2.jpg (115.05 KB)
2010-5-26 07:25



所以,为了看看AHB总线上传递过来的时钟是多少,这是一个顺藤摸瓜的过程。

使用特权

评论回复
36
LPC300|  楼主 | 2010-6-28 22:50 | 只看该作者






3.jpg (67.54 KB)
2010-5-26 07:25



上图展示了AHB时钟的来源:main clock经过CLOCK DIVIDER提供。

main clock来自哪里呢,这是一个逆势而上的过程。

在此笔者挖出LPC1343带的函数库里面所做的系统设置函数,在此之列出和本次试验有关的时钟设置语句,节省篇幅,来自system_LPC13xx.c




void SystemInit (void)

{


…… ……

LPC_SYSCON->SYSPLLCLKSEL
= SYSPLLCLKSEL_Val;
/*选择PLL输入★★★★*/


LPC_SYSCON->
SYSPLLCLKUEN
= 0x01;
/* 更新时钟源*/


LPC_SYSCON->
SYSPLLCLKUEN
= 0x00;
/* 更寄存器*/


LPC_SYSCON->
SYSPLLCLKUEN
= 0x01;


while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01));/*等待时钟更新完毕 */

#if (SYSPLL_SETUP)
/* PLL设置*/


LPC_SYSCON->
SYSPLLCTRL
= SYSPLLCTRL_Val;//
★★★★★


LPC_SYSCON->
PDRUNCFG
&= ~(1 << 7);
/* 打开PLL电源 */


while (!(LPC_SYSCON->SYSPLLSTAT & 0x01));/* 等到PLL锁定完毕 */


…… ……


LPC_SYSCON->
MAINCLKSEL
= MAINCLKSEL_Val;
/*选择主时钟源★★★★ */


LPC_SYSCON->
MAINCLKUEN
= 0x01;
/* 更新主时钟 */


LPC_SYSCON->
MAINCLKUEN
= 0x00;
/*更新寄存器
*/


LPC_SYSCON->
MAINCLKUEN
= 0x01;


while (!(LPC_SYSCON->MAINCLKUEN & 0x01));/* 等待更新完毕
*/


…… ……

LPC_SYSCON->SYSAHBCLKDIV
= SYSAHBCLKDIV_Val;//
选择AHB分频数★★★


LPC_SYSCON->
SYSAHBCLKCTRL = AHBCLKCTRL_Val;//打开AHB时钟


…… ……

}

星号注释为关键语句。从上述注释中,我们发现主时钟源的选择语句是

LPC_SYSCON->MAINCLKSEL
= MAINCLKSEL_Val


而可以查找出MAINCLKSEL_Val的值是:

#define MAINCLKSEL_Val
0x00000003




使用特权

评论回复
37
LPC300|  楼主 | 2010-6-28 22:51 | 只看该作者
翻阅到寄存器:


4.jpg (33.93 KB)
2010-5-26 07:31








所以发现,主时钟源来自System PLL clock out,所以要找出PLL的输出是多少。

PLL内部结构:



5.jpg (39.36 KB)
2010-5-26 07:31






这个结构里面,最为关键的是FCCOPSELMSEL三个环节,user.manual.lpc13xx给出了计算范例:





6.jpg (107.77 KB)
2010-5-26 07:31

使用特权

评论回复
38
LPC300|  楼主 | 2010-6-28 22:51 | 只看该作者
至此我们知道Fclkout = M× Fclkin = (FCCO) (2 × P),我们要找出这个公式的两个未知量,首先是M(MSEL),存放于以下寄存器:





7.jpg (49.52 KB)
2010-5-26 07:31



以及以下语句:

LPC_SYSCON->SYSPLLCTRL
= SYSPLLCTRL_Val;//
★★★★★

再查找出:

#define SYSPLLCTRL_Val
0x00000025

所以MESL=0x101,同时PSEL=0x01

然后再找出Fclkin,对应以下语句:

LPC_SYSCON->SYSPLLCLKSEL
= SYSPLLCLKSEL_Val;
/*选择PLL输入★★★★*/

依旧查找到:

#define SYSPLLCLKSEL_Val
0x00000001

以及寄存器:


8.jpg (32.03 KB)
2010-5-26 07:31



可以看出,Fclkin来自系统外部振荡器,也就是外部12MHz晶振。

所以最终:


Fclkout = M× Fclkin = (FCCO)
(2 × P)=72MHz


这个就是主时钟频率,也是LPC1343可以达到的最大工作频率。前文亦提到,APB设备时钟来自主时钟经过分频器提供,所以AHB分频数来自:

LPC_SYSCON->SYSAHBCLKDIV
= SYSAHBCLKDIV_Val;//
选择AHB分频数★★★

还有:

#define SYSAHBCLKDIV_Val
0x00000001

结论是1分频,所以到达定时器时钟入口处的时钟就是72MHz/1=72MHz

接下来就是定时器内部的事情了,接下来要得到定时器内部的预分频值,但是在定时器初始化语句里面并没有看到相应的设置,于是翻阅user.manual.lpc13xx,找到如下描述



9.jpg (16.92 KB)
2010-5-26 07:31



This causes the TC to increment on every PCLK when PR = 0, every 2 PCLKs when PR = 1, etc

所以我们知道两点,第一,预分频值PR默认为0,第二,当预分配值PR=0时,causes the TC to increment on every PCLK,即分频数为1

所以我们最终得到32位定时器的计数时钟为72MHz。计数一次所需要的时间是(1/72000000)s

回到主函数中的初始化语句

init_timer32(0, TIME_INTERVAL*10);

查到:

#define TIME_INTERVAL
(720000-1)//
1是因为计数值为0~719999而不是1~720000

PS在此原宏定义为:

#define TIME_INTERVAL
(SystemCoreClock/100 - 1)

笔者其实在此将SystemCoreClock/100计算出来了,因为SystemCoreClock72MHz

所以得到定时器的匹配间隔时间为:


T=1 x 10 x 720000 / 72000000=0.01=100ms


至此,定时器定时间隔就计算出来了!

将定时器中断服务作如下修改:
void TIMER32_0_IRQHandler(void)

{


static
int k=1;


if ( LPC_TMR32B0->IR & 0x01 )


{


LPC_TMR32B0->
IR = 1;
/* clear interrupt flag */


timer32_0_counter++;


GPIOSetValue( 0, 7, k );


k=~k;


}


if ( LPC_TMR32B0->IR & (0x1<<4) )


{


LPC_TMR32B0->
IR = 0x1<<4;
/* clear interrupt flag */


timer32_0_capture++;


}


return;

}

编译,运行,每隔100ms便进入一次中断服务函数,LED100ms间隔闪烁。




使用特权

评论回复
39
LPC300|  楼主 | 2010-6-28 22:51 | 只看该作者
上一节里,我们了解了如何使用LPC1343内部的32位定时器来产生准确的时间间隔。本节,我们来实现32位定时器的另一个重要的功能,PWM信号的产生。

我们都知道PWM的全称是Pulse Width Modulation,即脉宽调制的意思。多用于电机控制领域,也可以用来实现DA转换。在此不需要累赘它的特点,我们直接切入主题。




user.manual.lpc13xx中对PWM的描述很简单也足够充分:

In PWM mode, three match registers can be used to provide a single-edge controlled

PWM output on the match output pins. One match register is used to control the PWM cycle length.

在定时器的PWM模式里,四个匹配寄存器中的三个可以用来提供单边沿控制的PWM信号在相应引脚输出。同时剩下的一个用来控制PWM波形的周期。

For each timer, a maximum of three-single edge controlled PWM outputs can be selected on the MATn[2:0] outputs. One additional match register determines the PWM cycle length. When a match occurs in any of the other match registers, the PWM output is set to HIGH. The timer is reset by the match register that is configured to set the PWM cycle length. When the timer is reset to zero, all currently HIGH match outputs configured as PWM outputs are cleared.



翻译一下:

每一个定时器可以产生最多三路的单边沿控制的PWM输出,可以在MATn[2:0]里选择输出引脚。另外一个寄存器用以决定PWM周期长度。当匹配事件发生在其PWM控制寄存器中,PWM输出引脚会置高电平。而当计数值和PWM周期长度寄存器匹配时,计数值复位清0,同时PWM输出引脚清零。

此外还需要看到一个寄存器:




1.jpg (73.81 KB)
2010-5-29 23:47

使用特权

评论回复
40
LPC300|  楼主 | 2010-6-28 22:52 | 只看该作者
上图中唯一的一个Note:



It is recommended to use match channel 3 to set the PWM cycle



意为:推荐使用第三个匹配通道设置PWM的周期。



如此,整体思路就有了:



在MATCH3寄存器设置PWM周期,同时在另外三个寄存器MAT0,1,2设置占空比。当计数值与MAT0,1,2中的数值匹配时,PWM输出引脚输出高电平;当计数值与MATCH3寄存器中的值匹配时,PWM引脚清零。所以PWM的高电平周期是VALUE[MAT0,1,2]-VALUE[MAT3],低电平周期就是0- VALUE[MAT0,1,2]。







我们来看了PWM带的PWM范例:



int main (void)



{



LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);//打开IO时钟



init_timer32PWM(1, period, MATCH0); //设置32位定时器为PWM模式,定义周期,输出通道



enable_timer32(1);//使能32位定时器1



setMatch_timer32PWM (1, 0, period/4);//设置占空比



    while (1)                 



    {



    }



}



PS:在此做了不小的删改,原本NXP带的例程里,前面做的许多的定时器设置之后,下面居然匪夷所思地来了个模拟PWM。



此程序里,首先是打开AHB中对于GPIO的时钟,这个前面章节提到多次了。



看看32位定时器PWM输出初始化程序:



void init_timer32PWM(uint8_t timer_num, uint32_t period, uint8_t match_enable)



{



   



    disable_timer32(timer_num);//首先要使能定时器,才能对定时器进行设置



    if (timer_num == 1)



    {



    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<10);//打开定时器时钟



LPC_TMR32B1->EMR = (1<<EMC3)|(1<<EMC2)|(2<<EMC1)



|(1<<EMC0)|(1<<3)|(match_enable);//使能各个匹配寄存器



/*以下四个if函数的作用是切换各个MATCH通道对于的输出IO到普通输出IO状态,因为LPC1343的JTAG接口与32位定时器1的匹配IO存在复用情况,而默认情况下这些IO行使JTAG的IO功能,所以必须切换*/   



if (match_enable & 0x01)



    {



LPC_IOCON->JTAG_TDO_PIO1_1  &= ~0x07;   



    LPC_IOCON->JTAG_TDO_PIO1_1  |= 0x03;



    }      



if (match_enable & 0x02)



    {



    LPC_IOCON->JTAG_nTRST_PIO1_2 &= ~0x07;



LPC_IOCON->JTAG_nTRST_PIO1_2 |= 0x03;   



    }



    if (match_enable & 0x04)



    {



    LPC_IOCON->ARM_SWDIO_PIO1_3&= ~0x07;



LPC_IOCON->ARM_SWDIO_PIO1_3|= 0x03;



    }



    if (match_enable & 0x08)



    {



    LPC_IOCON->PIO1_4&= ~0x07;



    LPC_IOCON->PIO1_4|= 0x02;   



    }



LPC_TMR32B1->PWMC = (1<<3)|(match_enable);//使用匹配寄存器MATCH3设置PWM周//期,与用户手册推荐的一致



timer32_1_period = period;



LPC_TMR32B1->MR3 = timer32_1_period; //写入周期



LPC_TMR32B1->MR0    = timer32_1_period/2;//设置MATCH0通道占空比为50%



LPC_TMR32B1->MR1    = timer32_1_period/2; //设置MATCH1通道占空比为50%



LPC_TMR32B1->MR2    = timer32_1_period/2; //设置MATCH2通道占空比为50%



LPC_TMR32B1->MCR = 1<<10;//允许当计数值和MAT3寄存器值匹配时请0计数值



}



else



{



    /*以下是对定时器0的设置,与上半段程序类同*/



}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则