RT1064通用定时器简介
RT1064内部包含4个四定时器(QTMR1~4),每个4四定时器又包含4个独立的通道,4个通道完全独立,不共用任何资源,单通道的框图如图所示
TMR模块设计包括这些独特的功能 :
- 4个16位计数器/定时器
- 向上/向下计数
- 计数器是可串联的
- 可编程计算模块
- 对于外部时钟,最大计数速率等于外部时钟/2
- 对于内部时钟,最大计数速率等于外部时钟
- 单次计数或者重复计数
- 计数器是可预加载的
- 比较寄存器是可预加载的(可通过比较加载特性获得)
- 计数器可以共享可用的输入引脚
- 为每个计数器单独的预分频器
- 每个计数器都具有捕获和比较功能
- 调试模式下可编程操作
- 输入可以作为错误输入
- 可编程输入滤波器
- 计数开始可以跨计数器同步
QTMR系统框图
定时器模式简介
我们使用计数模式(14 种模式中的 1 种)来实现 QTMR 的周期性中断应用。在计数模式(CTRL[CM]=001)下,QTMR 通道计数器在时钟(由 CTRL[PCS]设置)的上升沿计数,计数方向可以是递增/递减(由 CTRL[DIR]设置),非常适合做周期性中断、计时或者统计外部脉冲个数。
以 QTMR1 的通道 0 为例,我们设置好计数方向(CTRL[DIR]=0,向上计数),初始值(LOAD),匹配值(向上计数的匹配值一般为 COMP1)和计数模式(CTRL[CM]=001),通道 0 的 CNTR就会从 LOAD 值开始递增,当 CNTR 和 COMP1 相等时,发生比较匹配事件,产生中断,然后重新加载 LOAD 的值,再次开始递增计数,依次循环。
图中 t1、t2 和 t3 是 3 次比较匹配的时刻,会产生中断和重置 CNTR 寄存器(需要设置CTRL[LENTH]=1,否则 CNTR 将持续计数到 0XFFFF(溢出)),重置 CNTR 寄存器使用 LOAD寄存器的值来初始化,所以通道 0 的定时时间由 LOAD0 和 COMP1 以及输入时钟三个参数决定。特别的,当 LOAD0=0 的时候,定时时间就由 COMP1 和时钟频率决定了。
寄存器简介
Timer Channel Counter Register (TMRx_CNTRn)
QTMR 通道计数寄存器
x为QMTR编号,n为通道编号,x=0-3,n=0-3
该寄存器用于存储通道计数器值,在时钟的驱动下执行递增/递减计数,与 COMP1 比较匹配后,可以产生比较匹配事件。通过读取该寄存器,可以获取当前该通道的计数值。
Timer Channel Load Register (TMRx_LOADn)
QTMR 通道加载值寄存器
该寄存器用于存储 CNTR 的重载值,如果设置 CTRL[LENGTH]位为 1,当 CNTR 与 COMPx(x=1/2),会产生比较匹配事件,然后,则 CNTR 的值会从 LOAD 加载(CNTR=LOAD),后继续进行计数,直到下次比较匹配事件。
Timer Channel Control Register (TMRx_CTRLn)
QTMR 通道控制寄存器
CM[15:13]位:用于设置计数模式。只要这 3 个位非零,CNTR 就可以进行某种计数(7 种模式可选)
当CM[15:13]=000时,表示当前通道禁止计数
PCS[12:9]位:用于选择第一时钟源的来源。
0000 ~ 0011,选择外部输入引脚 0 ~ 3 作为第一时钟源,可用于外部脉冲计数;
0100 ~ 00111,选择内部其他通道(0 ~ 3)计数器输出作为第一时钟源,可以用于计数器级联;
1000 ~ 1111,用于选择时钟源来自 IP_CLK_ROOT 的 1~128 分频;IP_CLK_ROOT =150MHz
LENGTH 位:用于设置 CNTR 的计数长度。
当 LENGTH=0 时,忽略 LOAD 的设置,CNTR总是从 0 计数到 0XFFFF;
当 LENGTH=1 时,CNTR 的计数范围分 2 个情况:
1,递增计数(DIR=0)时:LOAD≤CNTR≤COMP1;
2,递减计数时 COMP2≤CNTR≤LOAD。
DIR 位:用于控制计数方向。
当 DIR=0 时,CNTR 向上计数;
当 DIR=1 时,CNTR 向下计数。
OUTMODE[2:0]位:用于控制 OFLAG 输出信号的状态(输出模式)。
设置为 000 时,不会对输出产生影响。
如果设置为其他值,则会对 OFLAG 执行不同的操作,这个在输出 PWM 的时候,非常有用。
本章我们设置 OUTMODE[2:0]=000 即可。
Timer Channel Comparator Status and Control Register(TMRx_CSCTRLn)
QTMR 通道比较状态&控制寄存器
DBG_EN[15:14]位:用于设置调试模式下,QTMR 的工作状态。
我们一般设置这两个位为 0, 即 DBG_EN[1:0]=00,Debug 模式下,QTMR 正常工作。
TCF1EN 位:用于设置是否使能比较匹配 1 的中断。本章,我们设置该位为 1,当发生比较匹配 1 事件时,产生中断。
TCF1 位:表示是否产生了比较匹配 1 中断。如果该位为 1,则表示发生了比较匹配 1 中断。该位为写 0 清除。
CL1[1:0]位:用于设置 COMP1 的值,在发生比较匹配事件 1 时,是否从 CMPLD1/CMPLD2加载。
这个在精确控制波形的时候,非常有用,本章我们用不到,所以设置 CL1[1:0]=00 即可。
Timer Channel Compare Register 1 (TMRx_COMP1n)
QTMR 通道比较寄存器 1
该寄存器存储了通道 n 的比较匹配值 1。
当 CNTR 工作在向上计数模式时,如果 CNTR 与 COMP1 相等,则会产生比较匹配事件,还可触发中断(TCF1EN=1)。
当 QTMR 通道 y工作在向上计数( CTRLy[DIR]=1=0 ),且 CTRLy[LENGTH]=1 ,LOADy=0 ,
假设我们选择IPG_CLK_ROOT 作为时钟源(CTRLy[PCS]=1000),则通道 y 的定时溢出时间为:
T o u t = C O M P 1 n I P G C L K R O O T Tout =\frac{COMP1n}{IPG_CLK_ROOT } Tout=IPGCLKROOTCOMP1n
COMP1y:通道 y 的比较匹配值 1。
IPG_CLK_ROOT:IPG 根时钟,一般为 150Mhz。
通过控制 COMP1n 的值,就可以改变通道 n 的定时时间(中断时间)。
Timer Channel Enable Register (TMRx_ENBL)
QTMR 通道使能寄存器
该寄存器用于使能 QTMRx 的某个通道,共 4 个位,表示 4 个通道。
当对应通道的 ENBL位设置为 1 时,表示使能该通道;
设置 ENBL=0 时,表示禁止该通道。
默认四个通道都是开启的,所以一般我们不设置这个寄存器,也是可以的(默认就开启了)。
但是,如果我们在某些特殊场景,需要同时使能几个通道的时候,这个寄存器就非常有用了,它可以用于同步触发这些通道,达到 0 相位差。
注意:该寄存器只有通道 0 的有效,对通道 1~3 的 ENBL 寄存器设置是
无效的!!
fsl库函数及配置过程
QTMR 相关的库函数在 fsl_qtmr.c 和 fsl_qtmr.h 这两个文件中。
以四定时器 QTMR1 通道 0 为实例
1.QTMR1时钟使能
使用函数 CLOCK_EnableClock 使能 QTMR1 时钟,使用方法如下:
CLOCK_EnableClock(kCLOCK_Timer1)
此函数会被 QTMR 定时器初始化函数 QTMR_Init 调用,所以不需要我们显示的调用。
2.初始化QTMR1
使用函数 QTMR_Init 来初始化 QTMR,此函数原型如下:
void QTMR_Init(TMR_Type *base, qtmr_channel_selection_t channel, const qtmr_config_t *config)
第一个参数TMR_Type *base
:指定初始化哪个 QTMR,可以选择:TMR1、TMR2、TMR3 和 TMR4
在MIMXRT1064.h中40148行
第二个参数qtmr_channel_selection_t channel
:选择使用哪个通道,可选择的通道如下:
/*! @brief List of channel selection */
typedef enum _qtmr_channel_selection
{
kQTMR_Channel_0 = 0U, /*!< TMR Channel 0 */
kQTMR_Channel_1, /*!< TMR Channel 1 */
kQTMR_Channel_2, /*!< TMR Channel 2 */
kQTMR_Channel_3, /*!< TMR Channel 3 */
} qtmr_channel_selection_t;
第三个参数const qtmr_config_t *config
:为指向结构体 qtmr_config_t 的指针
此结构体为 QTMR 的配置结构体,结构体定义如下:
typedef struct _qtmr_config
{
qtmr_primary_count_source_t primarySource; /*!< Specify the primary count source 指定第一时钟源*/
qtmr_input_source_t secondarySource; /*!< Specify the secondary count source 指定第二时钟源*/
bool enableMasterMode; /*!< true: Broadcast compare function output to other counters;
false no broadcast */
bool enableExternalForce; /*!< true: Compare from another counter force state of OFLAG signal
false: OFLAG controlled by local counter */
uint8_t faultFilterCount; /*!< Fault filter count */
uint8_t faultFilterPeriod; /*!< Fault filter period;value of 0 will bypass the filter */
qtmr_debug_action_t debugMode; /*!< Operation in Debug mode */
} qtmr_config_t;
这个结构体最常用的就是前两个成员变量,即设置定时器的时钟源
第一时钟源qtmr_primary_count_source_t primarySource;
:可选参数如下
/*! @brief Quad Timer primary clock source selection*/
typedef enum _qtmr_primary_count_source
{
kQTMR_ClockCounter0InputPin = 0, /*!< Use counter 0 input pin */
kQTMR_ClockCounter1InputPin, /*!< Use counter 1 input pin */
kQTMR_ClockCounter2InputPin, /*!< Use counter 2 input pin */
kQTMR_ClockCounter3InputPin, /*!< Use counter 3 input pin */
kQTMR_ClockCounter0Output, /*!< Use counter 0 output */
kQTMR_ClockCounter1Output, /*!< Use counter 1 output */
kQTMR_ClockCounter2Output, /*!< Use counter 2 output */
kQTMR_ClockCounter3Output, /*!< Use counter 3 output */
kQTMR_ClockDivide_1, /*!< IP bus clock divide by 1 prescaler */
kQTMR_ClockDivide_2, /*!< IP bus clock divide by 2 prescaler */
kQTMR_ClockDivide_4, /*!< IP bus clock divide by 4 prescaler */
kQTMR_ClockDivide_8, /*!< IP bus clock divide by 8 prescaler */
kQTMR_ClockDivide_16, /*!< IP bus clock divide by 16 prescaler */
kQTMR_ClockDivide_32, /*!< IP bus clock divide by 32 prescaler */
kQTMR_ClockDivide_64, /*!< IP bus clock divide by 64 prescaler */
kQTMR_ClockDivide_128 /*!< IP bus clock divide by 128 prescaler */
} qtmr_primary_count_source_t;
第二时钟源qtmr_input_source_t secondarySource;
:可选参数如下
/*! @brief Quad Timer input sources selection*/
typedef enum _qtmr_input_source
{
kQTMR_Counter0InputPin = 0, /*!< Use counter 0 input pin */
kQTMR_Counter1InputPin, /*!< Use counter 1 input pin */
kQTMR_Counter2InputPin, /*!< Use counter 2 input pin */
kQTMR_Counter3InputPin /*!< Use counter 3 input pin */
} qtmr_input_source_t;
函数QTMR_GetDefaultConfig的具体内容如下:
void QTMR_GetDefaultConfig(qtmr_config_t *config)
{
assert(NULL != config);
/* Initializes the configure structure to zero. */
(void)memset(config, 0, sizeof(*config));
/* Halt counter during debug mode */
config->debugMode = kQTMR_RunNormalInDebug;
/* Another counter cannot force state of OFLAG signal */
config->enableExternalForce = false;
/* Compare function's output from this counter is not broadcast to other counters */
config->enableMasterMode = false;
/* Fault filter count is set to 0 */
config->faultFilterCount = 0;
/* Fault filter period is set to 0 which disables the fault filter */
config->faultFilterPeriod = 0;
/* Primary count source is IP bus clock divide by 2 */
config->primarySource = kQTMR_ClockDivide_2;
/* Secondary count source is counter 0 input pin */
config->secondarySource = kQTMR_Counter0InputPin;
}
函数 QTMR_Init 的一般使用方法如下:
qtmr_config_t qtimer1_config;
QTMR_GetDefaultConfig(&qtimer1_config); //先设置为默认配置
qtimer1_config.primarySource= kQTMR_ClockDivide_128; //设置第一时钟源
QTMR_Init(TMR1, kQTMR_Channel_0, &qtimer1_config); //初始化 QTIMER
3.设置QTMR1通道0的定时周期
QTMR1 通道 0 的定时周期通过函数 QTMR_SetTimerPeriod 来设置,此函数原型如下:
void QTMR_SetTimerPeriod(TMR_Type *base, qtmr_channel_selection_t channel, uint16_t ticks)
{
/* Set the length bit to reinitialize the counters on a match */
base->CHANNEL[channel].CTRL |= TMR_CTRL_LENGTH_MASK;
if ((base->CHANNEL[channel].CTRL & TMR_CTRL_DIR_MASK) != 0U)
{
/* Counting down */
base->CHANNEL[channel].COMP2 = ticks;
}
else
{
/* Counting up */
base->CHANNEL[channel].COMP1 = ticks;
}
}
第一个参数TMR_Type *base
:指定要设置的 QTMR,这里为 TMR1。
第二个参数qtmr_channel_selection_t channel
:指定要设置的通道,这里是通道 0 所以应该设置为 kQTMR_Channel_0。
第三个参数uint16_t ticks
:设置匹配比较值,此值就是用来决定定时器溢出时间的。
QTMR 的计数方向是可以修改的,当 CTRL 寄存器的 DIR 位为 0 的时候就是向上计数器,当 DIR 位为 1 的时候就是向下计数器,默认是向上计数器。
因此当定时器的计数值(CNTR0)达到匹配比较值的时候就会产生定时中断(如果开启了相应中断的话)。
4.使能QTMR1通道0的比较中断
使用函数 QTMR_EnableInterrupts 来使能 QTMR1 通道 0 的比较中断,这样当定时器计数值(CNTR0)达到我们设置的匹配比较值的时候就会产生相应中断,我们可以在中断中做具体的处理,此函数原型如下:
void QTMR_EnableInterrupts(TMR_Type *base, qtmr_channel_selection_t channel, uint32_t mask)
第一个参数TMR_Type *base
:指定初始化哪个 QTMR,可以选择:TMR1、TMR2、TMR3 和 TMR4
在MIMXRT1064.h中40148行
第二个参数qtmr_channel_selection_t channel
:选择使用哪个通道,可选择的通道如下:
/*! @brief List of channel selection */
typedef enum _qtmr_channel_selection
{
kQTMR_Channel_0 = 0U, /*!< TMR Channel 0 */
kQTMR_Channel_1, /*!< TMR Channel 1 */
kQTMR_Channel_2, /*!< TMR Channel 2 */
kQTMR_Channel_3, /*!< TMR Channel 3 */
} qtmr_channel_selection_t;
第三个参数uint32_t mask
:要开启的中断类型,可选中断类型如下:
在fsl_qtmr.h中
/*! @brief List of Quad Timer interrupts */
// typedef enum _qtmr_interrupt_enable
typedef enum _qtmr_interrupt_enable
{
kQTMR_CompareInterruptEnable = (1U << 0), /*!< Compare interrupt.比较中断*/
kQTMR_Compare1InterruptEnable = (1U << 1), /*!< Compare 1 interrupt.比较 1 中断*/
kQTMR_Compare2InterruptEnable = (1U << 2), /*!< Compare 2 interrupt.比较 2 中断*/
kQTMR_OverflowInterruptEnable = (1U << 3), /*!< Timer overflow interrupt.溢出中断*/
kQTMR_EdgeInterruptEnable = (1U << 4) /*!< Input edge interrupt.输入边沿检测中断*/
} qtmr_interrupt_enable_t;
这里我们要使能的是匹配比较中断,因此选择 kQTMR_CompareInterruptEnable。
5.开启QTMR
在相关的配置完成以后,就可以使能 QTMR 了,使用函数 QTMR_StartTimer 来开启 QTMR定时器,此函数原型如下:
static inline void QTMR_StartTimer(TMR_Type *base, qtmr_channel_selection_t channel, qtmr_counting_mode_t clockSource)
{
uint16_t reg = base->CHANNEL[channel].CTRL;
reg &= (uint16_t)(~(TMR_CTRL_CM_MASK));
reg |= TMR_CTRL_CM(clockSource);
base->CHANNEL[channel].CTRL = reg;
}
第一个参数TMR_Type *base
:指定初始化哪个 QTMR,可以选择:TMR1、TMR2、TMR3 和 TMR4
在MIMXRT1064.h中40148行
第二个参数qtmr_channel_selection_t channel
:选择使用哪个通道
第三个参数qtmr_counting_mode_t clockSource
:用来设置定时器的计数模式,可选模式如下
/*! @brief Quad Timer counting mode selection */
typedef enum _qtmr_counting_mode
{
kQTMR_NoOperation = 0, /*!< No operation */
kQTMR_PriSrcRiseEdge, /*!< Count rising edges or primary source */
kQTMR_PriSrcRiseAndFallEdge, /*!< Count rising and falling edges of primary source */
kQTMR_PriSrcRiseEdgeSecInpHigh, /*!< Count rise edges of pri SRC while sec inp high active */
kQTMR_QuadCountMode, /*!< Quadrature count mode, uses pri and sec sources */
kQTMR_PriSrcRiseEdgeSecDir, /*!< Count rising edges of pri SRC; sec SRC specifies dir */
kQTMR_SecSrcTrigPriCnt, /*!< Edge of sec SRC trigger primary count until compare*/
kQTMR_CascadeCount /*!< Cascaded count mode (up/down) */
} qtmr_counting_mode_t;
6.使能QTMR1中断并设置优先级
在定时器配置完了之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,以使能QTMR1 的相关中断,设置方法如下:
NVIC_SetPriority(TMR1_IRQHandler,1);
7.编写中断服务函数
在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。在中断产生后,通过函数 QTMR_GetStatus 来获取中断状态,判断此次产生的中断来自哪个通道然后执行相关的操作,我们这里使用的是通道 0 的输出比较 1 中断。在处理完中断之后调用函数 QTMR_ClearStatusFlags 来清除相应的中断标志位。
中断状态获取函数 QTMR_GetStatus 原型如下:
/*!
* @brief Gets the Quad Timer status flags
*
* @param base Quad Timer peripheral base address
* @param channel Quad Timer channel number
*
* @return The status flags. This is the logical OR of members of the
* enumeration ::qtmr_status_flags_t
*/
uint32_t QTMR_GetStatus(TMR_Type *base, qtmr_channel_selection_t channel);
第一个参数TMR_Type *base
:要获取的定时器
第二个参数qtmr_channel_selection_t channel
:要获取的通道
此函数其实就是读取寄存器 SCTRL0 的值,然后做简单的处理并将处理后的值返回给调用者,通过这个返回值就可以知道中断发生的状态,也就是中断类型。
中断状态(标志位)清除函数 QTMR_ClearStatusFlags 原型如下:
/*!
* @brief Clears the Quad Timer status flags.
*
* @param base Quad Timer peripheral base address
* @param channel Quad Timer channel number
* @param mask The status flags to clear. This is a logical OR of members of the
* enumeration ::qtmr_status_flags_t
*/
void QTMR_ClearStatusFlags(TMR_Type *base, qtmr_channel_selection_t channel, uint32_t mask);
前两个参数与其他的API函数一样,都是写入对应的定时器和对应的通道值
最后一个参数指定要清除的中断类型,可选参数如下:
/*! @brief List of Quad Timer flags */
typedef enum _qtmr_status_flags
{
kQTMR_CompareFlag = (1U << 0), /*!< Compare flag 比较标志位*/
kQTMR_Compare1Flag = (1U << 1), /*!< Compare 1 flag 比较 1 标志位*/
kQTMR_Compare2Flag = (1U << 2), /*!< Compare 2 flag 比较 2 标志位*/
kQTMR_OverflowFlag = (1U << 3), /*!< Timer overflow flag 溢出标志位*/
kQTMR_EdgeFlag = (1U << 4) /*!< Input edge flag 输入边沿检测标志位*/
} qtmr_status_flags_t;
示例
#include "qtmr.h"
void QTMR1_CH0_Int_Init(uint8_t prisrc,uint16_t cmp1)
{
qtmr_config_t qtmr1_config;
qtimer1_source = (qtmr_primary_count_source_t)prisrc;
//配置为默认设置
QTMR_GetDefaultConfig(&qtmr1_config);
//设置第一时钟源
qtmr1_config.primarySource = qtimer1_source;
//初始化QTMR1
QTMR_Init(TMR1, kQTMR_Channel_0,&qtmr1_config);
//设置QTMR1的定时周期
QTMR_SetTimerPeriod(TMR1, kQTMR_Channel_0, cmp1);
//使能中断
QTMR_EnableInterrupts(TMR1, kQTMR_Channel_0, kQTMR_CompareInterruptEnable);
//设置中断优先级
NVIC_SetPriority(TMR1_IRQHandler,1);
//开启QTMR
QTMR_StartTimer(TMR1, kQTMR_Channel_0, kQTMR_PriSrcRiseEdge);
}
//中断服务函数
void TMR1_IRQHandler(void)
{
if((QTMR_GetStatus(TMR1, kQTMR_Channel_0)&kQTMR_CompareFlag) == kQTMR_CompareFlag)
{
//do something
QTMR_ClearStatusFlags(TMR1, kQTMR_Channel_0,kQTMR_CompareFlag);
}
__DSB();
}
本文参照正点原子RT1052 开发指南修改编辑。
作者的软件基于逐飞部分库和fsl库开发