目录
[输入捕获(Input Capture):](#输入捕获(Input Capture):)
一、定时器选择
本篇将用到EPIT(增强型周期中断定时器) 和GPT(通用目的定时器)
简要介绍一下它俩的区别:
| 特性对比 | EPIT (增强型周期中断定时器) | GPT (通用目的定时器) |
|---|---|---|
| 核心定位 | 专注于高精度周期中断/延时 | 通用的多功能定时器,适用于多种复杂场景 |
| 计数方向 | 向下计数(从设定值减到0) | 向上计数(从0加到设定值或最大值) |
| 输入捕获 | 不支持 | 支持。可以捕获外部输入信号的跳变,用于精确测量PWM波的频率、占空比或时间差 |
| 输出比较 | 1个通道,比较匹配时可触发中断或翻转IO | 3个输出比较通道,可以同时控制3个不同的定时任务或PWM输出 |
| 工作模式 | Set-and-Forget(自动重载)和Free-Running | Restart(单次)和Free-Running(自由运行) |
| 其他功能 | 无 | 溢出中断 、输入捕获中断等 |
简单来说,如果你只需要一个精准的、周期性的中断定时器,EPIT就足够了,它专精于此。但如果你需要测量外部信号的脉宽、生成复杂的PWM波形,或者同时处理多个定时任务,那么GPT是唯一的选择。
二、EPIT(增强型周期中断定时器)
1.EPIT介绍:
从参考手册我们可以看到EPIT的内部结构

| 模式 | 行为描述 |
|---|---|
| Set-and-Forget 模式 | 计数器减到 0 后,自动从 LR(加载寄存器) 重新加载初始值,循环往复,产生周期性中断。这是最常用的模式。 |
| Free-Running 模式 | 计数器减到 0 后,重新从 0xFFFFFFFF 开始向下计数,不从 LR 加载值。 |
2.EPIT的配置:
我们用EPIT设计一个1s计时为例子,整体流程为:
-
使能EPIT模块的时钟(通过CCGR,这里我们已经打开了全部时钟)。
-
配置EPIT_CR:选择时钟源(PERCLK,即ipg_clk),设置分频因子(这里设置66分频,即1MHz),设置模式(set-and-forget模式,RLD=1),使能中断(OCIEN),ENMOD,设置计数值加载(IOVW)。
-
设置EPIT_LR加载值为计数值(1000 000)设置EPIT_CMPR比较值(0)
-
使能EPIT中断(GIC配置)。
-
编写中断服务程序(在中断中翻转蜂鸣器GPIO)。
-
启动定时器(EN)。
-
清除中断标志位EPIT_SR
查看EPITx_CR寄存器、LR寄存器和CMPR寄存器手册,配置对应的bit位,代码为:
oid epit1_init(irq_handler_t handler)
{
unsigned int tmp = EPIT1->CR;
tmp &= ~(0x3 << 24);
tmp |= (0x1 << 24); //选择时钟 66MHZ
tmp |= (1 << 17); //设置计数值加载
tmp &= ~(0xfff << 4);
tmp |= (65 << 4); //66分频
tmp |= (1 << 3); //设置模式
tmp |= (1 << 2); //使能中断
tmp |= (1 << 1); //ENMOD置为1则从0xffffffff开始计数,置为0则时从当前值开始计数
EPIT1->CR = tmp;
EPIT1->LR = 1000000; //1s
// EPIT1->CNR = 1000000; //CNT初值的配置,手册中为只读就不配置了,如果不是则需要配置
EPIT1->CMPR = 0;
request_irq(EPIT1_IRQn, handler);
EPIT1->CR |= (1 << 0);
}
注意:
1.当ENMOD为1时,启动定时器,那么就从加载寄存器或者0xFFFFFFFF开始计数;如果ENMOD=0,启动定时器后就从当前值开始计数。
2.EPIT1->CNR 寄存器时CNT初值的配置,参考手册中为只读就不配置了,如果是其他开发板不是只读则需要配置
最后到我们之前文章写的总中断函数中加上EPIT的中断(这里中断号为:EPIT1_IRQn):

注意:SR中断标志位清除是往里面写1
然后写一下EPIT的中断函数,这里写的是反转蜂鸣器:
最后main.c中调用我们写好的epit1_init函数,编译一下上电即可听到蜂鸣器按1s的周期工作
三、GPT(通用目的定时器)
1.GPT介绍
简单来说就是比EPIT多了两个输入捕获,以及两个输出比较

输出比较的用法和EPIT差不多,这里介绍一下输入捕获:
输入捕获(Input Capture):
GPT_CAPTURE1 / GPT_CAPTURE2:两个捕获输入引脚(图中 IM1、IM2 为输入中断标志)。
当外部信号(如边沿)到来时,当前计数值 会被锁存到 Timer Input Reg 1/2,同时产生相应的中断标志(IF1、IF2)。
通过选择同步或非同步采样,可以滤除毛刺。
常用于测量脉冲宽度、频率或作为外部事件的时间戳。
| 模式 | 行为描述 |
|---|---|
| Restart Mode | 向上计数器从 0 开始递增,当计数值与比较通道 1 的比较值匹配时,计数器立即清零并重新从 0 开始计数,同时触发相应中断或输出事件。此模式适用于生成固定周期的定时中断或 PWM。 |
| Free-run Mode | 向上计数器从 0 一直递增到 0xFFFFFFFF,溢出后自动回绕到 0 并继续计数,循环往复。比较匹配事件不会复位计数器。此模式适用于测量时间间隔(如高精度延时、输入捕获)。 |
注意:
Restart模式,只有与比较通道 1 的比较值匹配时,才会清零,所以这个模式下一般COMPARE2 或 COMPARE3 的值都是小于COMPARE1的,这样就可以产生复杂的PWM波了
2.GPT配置
我们用GPT设计一个延时函数1us/1ms为例子,整体流程为:
-
使能GPT模块的时钟(通过CCGR,这里我们已经打开了全部时钟)。
-
复位GPT定时器(CR_SWR)
-
配置GPTx_CR:选择时钟源(PERCLK),设置模式(Free-run Mode,FRR=1),ENMOD,设置输入捕获(IM1-3)、设置输出比较(OM1-3)。
-
设置GPT_PR分频寄存器(66分频)
-
启动定时器(EN)。
查看参考手册上对应的寄存器配置即可:
static inline void gpt1_reset(void)
{
GPT1->CR |= (1 << 15); //写1复位
while ((GPT1->CR & (1 << 15))) //复位好后硬件会自动清零,我们一直持续判断是否为0
;
}
void gpt1_init(void)
{
gpt1_reset();
unsigned int tmp = GPT1->CR;
tmp &= ~(0x1ff << 20);
tmp &= ~(0x1ff << 20);
tmp |= (1 << 9);
tmp &= ~(0x7 << 6);
tmp |= (1 << 6);
tmp &= ~(1 << 1);
GPT1->CR = tmp;
tmp = GPT1->PR;
tmp &= ~(0xfff << 0);
tmp |= (65 << 0);
GPT1->PR = tmp;
GPT1->CR |= (1 << 0); // start
}
注意:写延时函数没有用到中断。
延时函数:
void inline delay_us(unsigned int num)
{
unsigned int counter = 0;
unsigned int cur_couter = 0;
unsigned int old_couter = GPT1->CNT; //读取计数值
while (1)
{
cur_couter = GPT1->CNT; //读取当前计数的值
if (cur_couter == old_couter) //如果读取的值和之前一样,就重新读取当前值
continue;
if (cur_couter > old_couter)
counter += cur_couter - old_couter;//当前值大于之前的值就相减加到counter总计时中
else if (cur_couter < old_couter)
counter += cur_couter + 0xffffffff - old_couter;//如果当前值已经计数了一个周期小于了之前的值
if (counter >= num)
return; //计时到与设置的值相匹配,即到达指定时间
old_couter = cur_couter; //这样赋值是为了counter一直累加1us(GPT定时器的频率就是1us一计数)
}
}
void delay_ms(unsigned int num)
{
while (num--)
{
delay_us(1000);
}
}
注意:
1. 为什么要处理当前读取的值等于之前的值是因为CPU工作在528MHz下,也就是 说它可以1us执行528条这样的语句,有很大可能会一直读到相同的值,所以这里判断一下使用countine回到循环开头可以提高效率
inline关键字:用于建议编译器 将函数的代码在调用处直接展开 ,替换掉调用指令。这样,最终生成的机器码中不再有独立的函数体,而是多处展开的代码副本。适用于频繁调用的寄存器访问、简单数学运算、延时函数等
四、总结
GPT和EPIT定时器配置并不难,只要了解他们的结构,会看手册配置对应的寄存器就好