在LPC824中,除了系统定时器SysTick以外,还拥有4个其他定时器,分别是多速率定时器MRT,自唤醒定时器WKT,SCTimer/PWM和窗口看门狗定时器。这4个定时器都位于APB设备区,与SysTick不在同一区域。虽然都是定时器,但它们却有各自不同的特点及用途。下面就先来讨论一下多速率定时器MRT。
多速率定时器MRT是一个31位的中断定时器,拥有4个通道独立,具有重复、总线停止和单次三种中断模式,具有从单独设置的值开始递减计数的功能。MRT不具备可配置的引脚。
多速率定时器MRT提供四通道重复中断定时器,每个通道均可通过独立的时间间隔编程,每个通道均独立于其他通道工作,各定时器的模式分别在定时器控制寄存器中设置。MRT结构框图如下图所示。
在上图中,CHANNEL0就是TIMER0,INTVAL是定时器的初始值,MUX为重复、总线停止和单次三种中断模式选择,-1DEC为减1操作,TIMER为定时器当前值,可见实际的TIMER值为INTVAL-1。CONTROL为控制寄存器,STAT为状态寄存器,IRQ0为CHANNEL0的定时器中断。其余的CHANNEL1~CHANNEL3的结构与CHANNEL0完全一样。
多速率定时器MRT用到的寄存器共有4个,它们分别是:时间间隔值寄存器INTVAL、定时器寄存器TIMER、控制寄存器CTRL及状态寄存器STAT。 全部4个MRT定时器就有16个寄存器,再加上1个空闲通道寄存器IDLE_CH和1个全局中断标志寄存器IRQ_FLAG,一共是18个寄存器,如下表所示。
在MDK环境中,针对以上18个MRT寄存器,定义了如下的结构体。
typedef struct {
__IO uint32_t INTVAL0;
__I uint32_t TIMER0;
__IO uint32_t CTRL0;
__IO uint32_t STAT0;
__IO uint32_t INTVAL1;
__I uint32_t TIMER1;
__IO uint32_t CTRL1;
__IO uint32_t STAT1;
__IO uint32_t INTVAL2;
__I uint32_t TIMER2;
__IO uint32_t CTRL2;
__IO uint32_t STAT2;
__IO uint32_t INTVAL3;
__I uint32_t TIMER3;
__IO uint32_t CTRL3;
__IO uint32_t STAT3;
__I uint32_t RESERVED0[45];
__I uint32_t IDLE_CH;
__IO uint32_t IRQ_FLAG;
} LPC_MRT_Type;
MRT定时器组的基址为0x40004000,所以要将基址指针强制转换为上述结构体,还要加上下面的定义。
#define LPC_MRT_BASE 0x40004000UL
#define LPC_MRT ((LPC_MRT_Type *) LPC_MRT_BASE)
由于4组MRT寄存器的功能完全一样,所以下面就以第0组(MRT0)来进行讨论。先看时间间隔值寄存器INTVAL0,下表给出了它的全部位结构,共字节地址为0x40004000。
(1)第0~30位(IVALUE)为时间间隔载入值,该值载入TIMER0寄存器,MRT0开始从IVALUE-1递减计数。
(2)第31位(LOAD)用于决定定时器间隔值IVALUE-1如何载入TIMER0寄存器,置1时为强制加载,置0时为非强制加载,非强制加载仅当TIMER0递减为0时才加载,强制加载为立刻加载。该位为只写位,读取值始终为0。
接下来看定时器寄存器TIMER0,下表给出了它的全部位结构,其字节地址为0x40004004。
(1)第0~30位(VALUE)为递减计数器的当前定时器值。TIMER0寄存器的初始值从INTVAL0寄存器以IVALUE-1载入。定时器处于空闲状态时,读取该位字段返回-1(即0x00FFFFFF)。
(2)第31位为保留位。
接下来看控制寄存器CTRL0,下表给出了它的全部位结构,其字节地址为0x40004008。
(1)第0位(INTEN)为TIMER0中断使能位,置1时使能,置0时禁止,默认为禁止。
(2)第1、2两位(MODE)为定时器模式选择位,值从0x0到0x2分别对应选择重复中断模式、单次中断模式、单次总线停止模式,默认为重复模式。
(3)第3~31位为保留位。
接下来看状态寄存器STAT0,下表给出了它的全部位结构,其字节地址为0x4000400C。
(1)第0位(INTFLAG)为监控中断标志位,读取为0时表明无中断挂起,写入0无影响,读取为1时表明有中断挂起,写1可清除中断请求。
(2)第1位(RUN)为TIMER0状态表示位,读取为0时表明TIMER0为空闲状态,读取为1时表明TIMER0在运行状态。该位为只读属性。
(3)第2~31位为保留位。
接下来看空闲通道寄存器IDLE_CH,下表给出它的全部位结构,其字节地址为0x400040F4。
(1)第0~3位为保留位。
(2)第4~7位(CHAN)为空闲通道值,读取CHAN位,返回最低空闲定时器通道。如果所有定时器通道都在运行中,则CHAN = 4。
(3)第8~31位为保留位。
最后来看全局中断标志寄存器IRQ_FLAG,下表给出它的全部位结构,其字节地址为0x400040F8。
(1)第0~3位分别为TIMER0~TIMER3的中断标志位,读取为0时表明无中断挂起,写入0无影响,读取为1时表明有中断挂起,写1可清除中断请求。
(2)第4~31位为保留位。
全局中断寄存器其实就是把单个定时器通道的中断标志集合在了一个寄存器中,置位和清零每个标志的方式与置位和清零每个STATUS寄存器中的INTFLAG位的效果完全相同。
除了上述讨论的寄存器外,其实还有一个寄存器与MRT也有关系,但它不是属于MRT部分,它就是外设复位控制寄存器PRESETCTRL,下面给出它的全部位结构,其字节地址为0x40048004。
(1)第0位(SPI0_RST_N)为SPI0复位控制位,置0时外设SPI0复位,置1时复位清零,默认值为清零。
(2)第1位(SPI1_RST_N)为SPI1复位控制位,置0时外设SPI1复位,置1时复位清零,默认值为清零。
(3)第2位(UARTFRG_RST_N)为USART小数波特率生成器(UARTFRG)复位控制位,置0时外设UARTFRG复位,置1时复位清零,默认值为清零。
(4)第3位(UART0_RST_N)为USART0复位控制位,置0时外设USART0复位,置1时复位清零,默认值为清零。
(5)第4位(UART1_RST_N)为USART1复位控制位,置0时外设USART1复位,置1时复位清零,默认值为清零。
(6)第5位(UART2_RST_N)为USART2复位控制位,置0时外设USART2复位,置1时复位清零,默认值为清零。
(7)第6位(I2C0_RST_N)为I2C0复位控制位,置0时外设I2C0复位,置1时复位清零,默认值为清零。
(8)第7位(MRT_RST_N)为多速率定时器(MRT)复位控制位,置0时外设MRT复位,置1时复位清零,默认值为清零。
(9)第8位(SCT_RST_N)为SCT复位控制位,置0时外设SCT复位,置1时复位清零,默认值为清零。
(10)第9(WKT_RST_N)为自唤醒定时器(WKT)复位控制位,置0时外设WKT复位,置1时复位清零,默认值为清零。
(11)第10位(GPIO_RST_N)为GPIO和GPIO引脚中断复位控制位,置0时外设GPIO复位,置1时复位清零,默认值为清零。
(12)第11位(FLASH_RST_N)为闪存控制器复位控制位,置0时闪存控制器复位,置1时复位清零,默认值为清零。
(13)第12位(ACMP_RST_N)为模拟比较器复位控制位,置0时模拟比较器复位,置1时复位清零,默认值为清零。
(14)第13位为保留位。
(15)第14位(I2C1_RST_N)为I2C1复位控制位,置0时外设I2C1复位,置1时复位清零,默认值为清零。
(16)第15位(I2C2_RST_N)为I2C2复位控制位,置0时外设I2C2复位,置1时复位清零,默认值为清零。
(17)第16位(I2C3_RST_N)为I2C3复位控制位,置0时外设I2C3复位,置1时复位清零,默认值为清零。
(17)第17到31位为保留位0。
PRESETCTRL寄存器允许软件复位特定的外设模块,只要在寄存器中分配位为0,便可复位特定外设,分配为1则可清零复位并允许外设运行。
讨论完了寄存器,接下来就看一下MRT的具体用法。
MRT一共有3种工作模式,即重复、总线停止和单次。先来讨论重复中断模式。
重复中断模式在选定的时间间隔之后生成重复中断。当定时器n处于空闲状态时,将非零值IVALUE写入INTVALn寄存器可立即载入时间间隔值IVALUE-1,然后定时器开始从该值递减计数。当定时器计数至零时,生成一个中断,INTVALn寄存器中的数值IVALUE-1会自动重新加载,定时器再次开始递减计数。
当MRT定时器工作在重复中断模式时,可执行下列操作:
1、在下一次定时器周期中改变间隔值,方法是将新的值(>0)写入INTVALn寄存器,然后将LOAD位置0。当定时器计数至零时,生成中断。在下一个周期中,定时器从这个新的数值开始递减计数。
2、立即动态改变间隔值,方法是将新的值(>0)写入INTVALn寄存器中,然后将LOAD 置1。定时器立刻从新的定时器间隔值开始递减计数。当定时器计数至0时,生成中断。
3、在时间间隔结束时停止定时器,方法是将0写入INTVALn寄存器,然后将LOAD位置0。当定时器计数至零时,生成中断。
4、立即停止定时器,方法是将0写入INTVALn寄存器,然后将LOAD位置1。写入INTVALn寄存器时,不生成中断。
下面来看一个使用重复中断模式的例子。
例子:电路原理图和前面“外部引脚中断”一章中的一样,要求实现一个8位流水灯效果,时间间隔为100ms,使用MRT0。完整代码如下:
#include
//************************端口初始化***********************************
void Port_init(void)
{
LPC_GPIO_PORT->DIR0 = 0x1FFFFFFF; //设置端口为输出方向
LPC_GPIO_PORT->PIN0 = 0x1FFFFFFF; //关闭所有LED
LPC_GPIO_PORT->CLR0 |= (1<<7); //点亮第1个LED
}
//************************定时器初始化*********************************
void MRT_init(void)
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<10); //使能MRT时钟
LPC_SYSCON->PRESETCTRL |= (1<<7); //清零MRT复位
LPC_MRT->INTVAL0 = 0x00249F01; //初始值
LPC_MRT->CTRL0 |= (1<<0); //使能中断,重复模式
}
//***************************主函数************************************
int main(void)
{
Port_init(); //端口初始化
MRT_init(); //MRT初始化
NVIC_EnableIRQ(MRT_IRQn); //使能MRT的NVIC中断
while(1)
{
;
}
}
//************************定时器中断***********************************
void MRT_IRQHandler(void)
{
static uint8_t i=0;
// if(LPC_MRT->STAT0 & 0x1) //判断是否为MRT0中断
if(LPC_MRT->IRQ_FLAG & 0x1) //判断是否为MRT0中断
{
switch(i++)
{
case 0:
LPC_GPIO_PORT->SET0 = (1<<7); //熄灭第1个LED
LPC_GPIO_PORT->CLR0 = (1<<13); //点亮第2个LED
break;
case 1:
LPC_GPIO_PORT->SET0 = (1<<13); //熄灭第2个LED
LPC_GPIO_PORT->CLR0 = (1<<16); //点亮第3个LED
break;
case 2:
LPC_GPIO_PORT->SET0 = (1<<16); //熄灭第3个LED
LPC_GPIO_PORT->CLR0 = (1<<17); //点亮第4个LED
break;
case 3:
LPC_GPIO_PORT->SET0 = (1<<17); //熄灭第4个LED
LPC_GPIO_PORT->CLR0 = (1<<19); //点亮第5个LED
break;
case 4:
LPC_GPIO_PORT->SET0 = (1<<19); //熄灭第5个LED
LPC_GPIO_PORT->CLR0 = (1<<27); //点亮第6个LED
break;
case 5:
LPC_GPIO_PORT->SET0 = (1<<27); //熄灭第6个LED
LPC_GPIO_PORT->CLR0 = (1<<28); //点亮第7个LED
break;
case 6:
LPC_GPIO_PORT->SET0 = (1<<28); //熄灭第7个LED
LPC_GPIO_PORT->CLR0 = (1<<18); //点亮第8个LED
break;
case 7:
LPC_GPIO_PORT->SET0 = (1<<18); //熄灭第8个LED
LPC_GPIO_PORT->CLR0 = (1<<7); //点亮第1个LED
i = 0; //计数清零
break;
default:
break;
}
}
LPC_MRT->STAT0 |= 0x1; //清除MRT0中断标志
// LPC_MRT->IRQ_FLAG |= 0x1; //清除MRT0中断标志
}
把上述程序编译后下载到LPC824中,可观察到8个LED被依次点亮,实现了流水灯的效果。在程序中,由于LPC824使用的是12MHz的晶振,倍频后是24MHz,即记录24个脉冲用时1us,100ms需要记录2400000个脉冲,十六进制值为0x249F00,但由于该值是IVALUE-1后的值,所以初始值应该为0x249F00+1=0x249F01。另外,由于在NVIC中断中只有一个MRT中断项,所以在MRT中断被触发后,应该在中断服务程序中先判断是MRT0~MRT3中的具体哪个产生的中断,这可以通过STAT0寄存器或IRQ_FLAG寄存器来进行判断,完成后还要记得对其中断标志进行清除。
接下来看单次中断模式。
单次中断可在一次计数后生成一个中断。这种模式下,可以在任意时刻生成单次中断,该模式可用来在软件堆栈中产生一个特定延迟。当定时器处于空闲状态时,将非零值IVALUE写入INTVALn寄存器可立即载入时间间隔值IVALUE-1,然后定时器开始递减计数。定时器计数至0时,生成中断,定时器停止并进入空闲状态。
当定时器工作在单次中断模式时,可执行下列操作:
1、以新的时间间隔值(>0)更新INTVALn寄存器,然后将LOAD位置1。定时器立即重新载入新的时间间隔,并从新的数值开始递减计数。TIME_INTVALn寄存器更新时不生成中断。
2、将0写入INTVALn寄存器并将LOAD位置1。定时器立即停止计数,转入空闲状态。INTVALn寄存器更新时,不生成中断。
单次中断模式可以认为是重复中断模式的单次版,也就是说如果在单次中断模式的中断服务程序中手动对初始值时行重载,那它就和重复中断模式没有区别了。 大家可以试一下,这里就不给具体的例子了。
最后来看单次总线停止模式。
单次总线停止模式使总线接口停止IVALUE+3个系统时钟周期。对于Cortex-M0+而言,该模式有效地停止所有CPU活动,直至MRT完成递减计数至零。计数结束时,不生成中断,总线恢复其事务。总线停止模式允许应用暂停一段预定义时间然后恢复,不用建立软件循环或轮询定时器。由于在总线停止模式下MRT递减计数时没有总线事务,因此CPU在此期间功耗极低。通常,该模式可用于应用必须短时间空闲时(us数量级或10至50个时钟周期)。对于较长的等待时间,可使用单次中断模式,该模式允许对其他已使能的中断提供服务。
接下来把刚才的流水灯例子使用单次总线停止模式来改写一下,效果不变,完整代码如下:
#include
//************************端口初始化***********************************
void Port_init(void)
{
LPC_GPIO_PORT->DIR0 = 0x1FFFFFFF; //设置端口为输出方向
LPC_GPIO_PORT->PIN0 = 0x1FFFFFFF; //关闭所有LED
LPC_GPIO_PORT->CLR0 |= (1<<7); //点亮第1个LED
}
//************************定时器初始化*********************************
void MRT_init(void)
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<10); //使能MRT时钟
LPC_SYSCON->PRESETCTRL |= (1<<7); //清零MRT复位
}
//*************************n*1us延时***********************************
void delay(uint32_t us)
{
LPC_MRT->INTVAL0 = 24*us;
LPC_MRT->CTRL0 |= (1<<2); //设置MRT为单次总线停止模式
}
//***************************主函数************************************
int main(void)
{
uint8_t i=0;
Port_init(); //端口初始化
MRT_init(); //MRT初始化
while(1)
{
switch(i++)
{
case 0:
LPC_GPIO_PORT->SET0 = (1<<7); //熄灭第1个LED
LPC_GPIO_PORT->CLR0 = (1<<13); //点亮第2个LED
break;
case 1:
LPC_GPIO_PORT->SET0 = (1<<13); //熄灭第2个LED
LPC_GPIO_PORT->CLR0 = (1<<16); //点亮第3个LED
break;
case 2:
LPC_GPIO_PORT->SET0 = (1<<16); //熄灭第3个LED
LPC_GPIO_PORT->CLR0 = (1<<17); //点亮第4个LED
break;
case 3:
LPC_GPIO_PORT->SET0 = (1<<17); //熄灭第4个LED
LPC_GPIO_PORT->CLR0 = (1<<19); //点亮第5个LED
break;
case 4:
LPC_GPIO_PORT->SET0 = (1<<19); //熄灭第5个LED
LPC_GPIO_PORT->CLR0 = (1<<27); //点亮第6个LED
break;
case 5:
LPC_GPIO_PORT->SET0 = (1<<27); //熄灭第6个LED
LPC_GPIO_PORT->CLR0 = (1<<28); //点亮第7个LED
break;
case 6:
LPC_GPIO_PORT->SET0 = (1<<28); //熄灭第7个LED
LPC_GPIO_PORT->CLR0 = (1<<18); //点亮第8个LED
break;
case 7:
LPC_GPIO_PORT->SET0 = (1<<18); //熄灭第8个LED
LPC_GPIO_PORT->CLR0 = (1<<7); //点亮第1个LED
i = 0; //计数清零
break;
default:
break;
}
delay(100000); //延时100ms
}
}
把上述程序编译后下载到LPC824中,可以看到运行的效果和前面是一样的。但是,再次下载程序时就会发现不能下载了,这是由于在延时期间CPU是不工作的,所以SWD功能也不正常了。解决的方法是使用ISP模式进行下载,即先把PIO0_12引脚接高电平,然后复位LPC824即可进入ISP模式下载程序了。从这点也可以认识到,单次总线停止模式并不适合作长时间(毫秒级以上)的定时。