13.【NXP 号令者RT1052】开发——实战-QTMR 定时器中断
13.【NXP 号令者RT1052】开发——实战-QTMR 定时器中断
这一章,我们将向大家介绍如何使用 RT1052 的四定时器(Quad Timer,简称:QTMR),RT1052内部集成了 4 个 QTMR 定时器,每个 QTMR 定时器又有 4 个通道,每个通道都有独立的:1 个 16 位计数器、1 个预分频器、1 个加载值寄存器、1 个捕获寄存器、2 个比较寄存器、两个状态寄存器和 1 个控制寄存器等,完全就是一个独立定时器的功能。因此,我们可以将QTMR 看成是一个拥有 4*4 个定时器的集合,这样光 QTMR 就拥有 16 个定时器之多。
13.1 RT1052 通用定时器简介
RT1052 内部包含 4 个四定时器(QTMR1~4),每个 4 定时器又包含 4 个独立的通道,4 个通道完全独立,不共用任何资源

基本结构
- 单个通道由 输入时钟部分、计数器(CNTR)、加载寄存器(LOAD)、捕获寄存器(CAPTURE)、两个比较器 等组成。
- RT1052 内部共有 16 个通道(4 个定时器 × 每个 4 通道)。
功能特点
- 16 位计数器 CNTR,支持向上/下计数。
- 可级联,组成 32/48/64 位计数器。
- 独立分频器(支持 1/2/4/8/16/32/64/128 分频)。
- 支持 输出比较 和 输入捕获。
- 提供 14 种工作模式。
- 输入捕获支持 滤波器。
- 最大计数频率可达 150 MHz。
- 支持 单次计数 / 连续计数。
- 比较寄存器具有 预装载功能。
- 4 个通道可 同步启动。
QTMR 工作模式
- 共 14 种模式,详见《RT1050 参考手册》第 47.6.5 节。
- 本章使用 计数模式(CTRL[CM] = 001):
- 通道计数器在时钟上升沿计数(时钟源由
CTRL[PCS]设置)。 - 计数方向可选递增/递减(由
CTRL[DIR]设置)。 - 适合周期性中断、计时或外部脉冲计数。
- 通道计数器在时钟上升沿计数(时钟源由
应用示例:QTMR1 通道 0 周期性中断
- 设置计数方向:
CTRL[DIR] = 0→ 向上计数。 - 设置初始值:
LOAD。 - 设置匹配值:
COMP1(向上计数时的比较匹配值)。 - 设置计数模式:
CTRL[CM] = 001。 - 工作过程:
- CNTR 从
LOAD值开始递增。 - 当 CNTR = COMP1 时,产生比较匹配事件 → 触发中断。
- CNTR 重新加载
LOAD值,继续递增计数。 - 循环往复,实现周期性中断。

图中 t1、t2 和 t3 是 3 次比较匹配的时刻,会产生中断和重置 CNTR 寄存器(需要设置CTRL[LENTH]=1,否则 CNTR 将持续计数到 0XFFFF(溢出)),重置 CNTR 寄存器使用 LOAD寄存器的值来初始化,所以通道 0 的定时时间由 LOAD0 和 COMP1 以及输入时钟三个参数决定。特别的,当 LOAD0=0 的时候,定时时间就由 COMP1 和时钟频率决定了。
- CNTR 从
实验目标
使用 QTMR1 通道 0 产生周期性中断,在中断服务函数中翻转 DS1 电平,指示定时器中断的发生。
实现步骤
1. QTMR1 时钟使能
c
CLOCK_EnableClock(kCLOCK_Timer1);
实际上
QTMR_Init()内部已调用,无需重复。
2. 初始化 QTMR1
c
qtmr_config_t qtimer1_config;
QTMR_GetDefaultConfig(&qtimer1_config); // 默认配置
qtimer1_config.primarySource = kQTMR_ClockDivide_128;// 设置第一时钟源为 IPG_CLK_ROOT 128 分频
QTMR_Init(TMR1, kQTMR_Channel_0, &qtimer1_config); // 初始化 QTMR1 通道 0
3. 设置定时周期
c
QTMR_SetTimerPeriod(TMR1, kQTMR_Channel_0, ticks);
ticks为匹配比较值,决定溢出时间。- 当计数器 CNTR0 达到该值时产生中断。
- 默认计数方向为 向上计数。
4. 使能比较中断
c
QTMR_EnableInterrupts(TMR1, kQTMR_Channel_0, kQTMR_CompareInterruptEnable);
- 开启 比较中断,当 CNTR0 = COMP 值时触发。
5. 启动 QTMR
c
QTMR_StartTimer(TMR1, kQTMR_Channel_0, kQTMR_PriSrcRiseEdge);
- 设置计数模式为 第一时钟源上升沿计数。
6. 配置 NVIC 中断优先级并使能
c
RT1052_NVIC_SetPriority(TMR1_IRQn, 6, 0); // 抢占优先级 6,子优先级 0
EnableIRQ(TMR1_IRQn); // 使能 TMR1 中断
7. 编写中断服务函数
c
void TMR1_IRQHandler(void)
{if(QTMR_GetStatus(TMR1, kQTMR_Channel_0) & kQTMR_CompareFlag){LED1_Toggle(); // 翻转 DS1QTMR_ClearStatusFlags(TMR1, kQTMR_Channel_0, kQTMR_CompareFlag); // 清除比较标志位}
}
定时周期计算
- 时钟源:IPG_CLK_ROOT = 75 MHz
- 分频:128 → 有效频率 = 75 MHz / 128 ≈ 585.9 kHz
- 周期时间 =
ticks / 有效频率
实验结果
- DS1 在 QTMR1 通道 0 中断中翻转,实现周期性闪烁。
- 通过调整
ticks值可改变闪烁频率。
13.2硬件设计
本实验用到的硬件资源有:
- 指示灯 DS0 和 DS1
2.QTMR1 定时器
本章将通过 QTMR1 的中断来控制 DS1 的亮灭,DS1 是直接连接到 GPIO3_IO03(P303)上的,这个前面已经有介绍了。而 QTMR1 属于 RT1052 的内部资源,只需要软件设置即可正常工作。
13.3 软件设计
软件设计我们在之前的工程上面增加,首先,打开上次的工程,然后在 HARDWARE 下新建 QTIMER 文件夹,在 QTIMRE 下新建一个 qtimer.c 的文件和 qtimer.h 的头文件。
qtimer.c
#include "qtimer.h"
#include "led.h"//QTIMER1配置结构体
qtmr_config_t qtimer1_config;//初始化QTIMER,TMR定时器的时钟源为IPG_CLK_ROOT=150MHz
//prisrc : 第一时钟源选择
// 0000~0011,通道0~3的输入引脚.
// 0100~0111,通道0~3的输出.可用于级联.
// 1000~1111,IPG_CLK_ROOT时钟的:1,2,4,8,16,32,64,128分频.
//cmp1 : COMP1寄存器值,0~65535.
//定时时间=cmp1/QTMR1_CLK
//假设prisrc=1111,则QTMR1_CLK=1171875Hz.
//若cmp1=46875,则定时时间= 46875/1171875=0.04秒.
//当使用IPG_CLK_ROOT作为时钟源时,最大延时时间=65535*128/150M=55.9ms
void QTMR1_CH0_Int_Init(u8 prisrc,u16 cmp1)
{qtmr_primary_count_source_t qtimer1_source;qtimer1_source=(qtmr_primary_count_source_t)prisrc;QTMR_GetDefaultConfig(&qtimer1_config); //先设置为默认配置,后面在根据实际情况配置qtimer1_config.primarySource=qtimer1_source; //设置第一时钟源QTMR_Init(TMR1,kQTMR_Channel_0,&qtimer1_config); //初始化QTIMERQTMR_SetTimerPeriod(TMR1,kQTMR_Channel_0,cmp1); //设置COMP1匹配值(向上计数用COMP1,向下用COMP2)//配置中断QTMR_EnableInterrupts(TMR1,kQTMR_Channel_0,kQTMR_CompareInterruptEnable); //使能通道0的比较中断RT1052_NVIC_SetPriority(TMR1_IRQn,6,0); //抢占优先级6,子优先级0EnableIRQ(TMR1_IRQn); //是能TMR1中断//开始通道0QTMR_StartTimer(TMR1, kQTMR_Channel_0,kQTMR_PriSrcRiseEdge); //通道0在primary时钟源的上升沿计数
} //TMR1中断服务函数
void TMR1_IRQHandler(void)
{static u8 count=0; //计数器//判断是不是TMR1通道0的比较中断if((QTMR_GetStatus(TMR1,kQTMR_Channel_0)&kQTMR_CompareFlag)==kQTMR_CompareFlag){count++;if(count==25) //每进入25次中断服务函数,才执行一次.{ LED1_Toggle; //DS1翻转count=0; //清零计数器}QTMR_ClearStatusFlags(TMR1,kQTMR_Channel_0,kQTMR_CompareFlag); //清除中断标志位 }__DSB(); //数据同步屏蔽指令
}
该文件下包含一个中断服务函数和一个 QTMR1 通道 0 中断初始化函数,中断服务函数比较简单,在每次中断后,判断是否是 QTMR1 通道 0 的中断,如果是,则每进入 25 次,执行一次 LED1(DS1)的取反,并清除中断标志位(每次进入都要执行)。QTMR1_CH0_Int_Init 函数就是执行我们上面介绍的那 7 个步骤,设置 QTMR1 定时器通道0 的中断。该函数有 2 个参数:prisrc 和 cmp1。prisrc 用于选择第一时钟源;cmp1 用于设置COMP1 的值;这两个参数共同决定了定时器的溢出时间(中断时间)。
我们将 qtimer.c 文件保存,然后加入到 HARDWARE 组下。接下来,在 qtimer.h 文件里,我们输入如下代码:
#ifndef _QTIMER_H
#define _QTIMER_H
#include "sys.h"void QTMR1_CH0_Int_Init(u8 prisrc,u16 cmp1);
#endif
最后,我们在主程序里面输入如下代码:
#include "sys.h"
#include "lpuart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "wdog.h"
#include "rtwdog.h"
#include "gptimer.h"
#include "pitimer.h"
#include "qtimer.h"int main(void)
{u8 key=0;MPU_Memory_Protection(); //初始化MPURT1052_Clock_Init(); //配置系统时钟DELAY_Init(600); //延时函数初始化LPUART1_Init(115200); //初始化串口1RT1052_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//优先级分组4
#ifdef LED_DEBUGu8 led0sta=1,led1sta=1; //LED0,LED1的当前状态LED_Init(); //初始化LED
#endif
#ifdef KEY_DEBUGKEY_Init(); //初始化KEY
#endif
#ifdef LPUART_DEBUGu8 len; //接收数据长度u16 times=0; //延时计数器LED0(0); //先点亮红灯
#endif
#ifdef EXTIX_DEBUGEXTIX_Init(); //初始化外部中断
#endif
#ifdef WDOG_DEBUGWDOG1_Init(3,2); //初始化看门狗1,2秒溢出,提前1秒进入中断,方便喂狗LED0(0); //先点亮LED灯delay_ms(300); //延时300ms再初始化看门狗,LED0的变化"可见"
#endif
#ifdef RTWDOG_DEBUGLED_Init(); //初始化LED KEY_Init(); //初始化KEYdelay_ms(100); //延时100ms再初始化看门狗,LED0的变化"可见"MYRTWDOG_Init(1,0,32768,0); //初始化RT看门狗,1秒溢出,非窗口模式LED0(0); //先点亮LED灯
#endif
#ifdef GPT1_DEBUGLED_Init(); //初始化LED KEY_Init(); //初始化按键GPT1_Int_Init(3750-1,10000); //设置GPT1 0.5秒钟产生一次中断
#endif
#ifdef PIT_DEBUGLED_Init(); //初始化LED PIT_CH0_Int_Init(75000000/2); //设置PIT 0.5秒钟产生一次中断
#endifKEY_Init(); //初始化按键LED_Init(); //初始化LEDQTMR1_CH0_Int_Init(15,46875); //设置QTMR1 0.04秒钟产生一次中断while(1){LED0_Toggle;delay_ms(1000);
#ifdef PIT_DEBUGLED0_Toggle;delay_ms(1000);
#endif
#ifdef GPT1_DEBUGLED0_Toggle;delay_ms(1000);
#endif
#ifdef RTWDOG_DEBUGkey=KEY_Scan(0);if(key==WKUP_PRES) //如果按键按下,则喂狗.{RTWDOG_Feed();} delay_ms(10);
#endif
#ifdef WDOG_DEBUGLED0(1); //关闭DS0,如不复位,DS0将一直处于关闭状态.delay_ms(100);
#endif
#ifdef EXTIX_DEBUGprintf("Int example driver!\r\n");delay_ms(1000);
#endif
#ifdef KEY_DEBUGkey=KEY_Scan(0); //得到键值if(key){ switch(key){ case WKUP_PRES: //控制LED0,LED1互斥点亮led1sta=!led1sta;led0sta=!led1sta;break;case KEY2_PRES: //控制LED0翻转led0sta=!led0sta;break;case KEY1_PRES: //控制LED1翻转 led1sta=!led1sta;break;case KEY0_PRES: //同时控制LED0,LED1翻转 led0sta=!led0sta;led1sta=!led1sta;break;}LED0(led0sta); //控制LED0状态LED1(led1sta); //控制LED1状态}else delay_ms(10);
#endif // KEY_DEBUG
#ifdef LPUART_DEBUGif(LPUART_RX_STA&0x8000){ len=LPUART_RX_STA&0x3fff;//得到此次接收到的数据长度printf("\r\n发送的消息为:\r\n");LPUART_WriteBlocking(LPUART1,LPUART_RX_BUF,len);//发送接收到的数据printf("\r\n\r\n");//插入换行LPUART_RX_STA=0;}else{times++;if(times%5000==0){printf("\r\nALIENTEK RT1052开发板 串口实验\r\n");}if(times%200==0)printf("请输入数据,以回车键结束\r\n"); if(times%30==0)LED0_Toggle;//闪烁LED,提示系统正在运行.delay_ms(10); }
#endif // LPUART_DEBUG}
}

这里的代码和之前大同小异,此段代码通过 QTMR1_CH0_Int_Init 函数,初始化 QTMR1 的通道 0,设置其为 0.04 秒中断一次。在中断服务函数里面,每进入 25 次,执行一次 DS0 取反,这样就是 1 秒钟取反一次。同样,在死循环里面,我们对 DS0 也是 1 秒钟执行一次取反,因此,可以看到 DS0 和 DS1 几乎是同步亮灭。
总结
本章通过配置 RT1052 的 QTMR1 通道 0,实现了周期性中断控制 LED 翻转的功能:通过初始化时钟源、设置匹配值、使能比较中断并编写中断服务函数,使定时器每 0.04 秒触发一次中断,在中断中每累计 25 次翻转一次 DS1,从而实现 1 秒闪烁一次的效果;同时主循环中 DS0 也以 1 秒周期翻转,最终呈现 DS0 与 DS1 几乎同步的亮灭现象,验证了 QTMR 在周期性中断应用中的基本用法。
OK,谢谢大家
