title: 定时器与看门狗 tags: [定时器, 看门狗, EPIT, GPT, IMX6ULL, 裸机开发, 按键消抖] created: 2026-09-16 updated: 2026-09-17 pdf_ref:
💡 关联知识: [[FreeRTOS学习笔记/08-时间片调度与延时管理]] | [[STM32学习笔记/08-SysTick与通用定时器TIM]]
| 定时器 | 数量 | 分辨率 | 用途 |
|---|---|---|---|
| EPIT | 2 | 32位 | 周期性中断定时 |
| GPT | 2 | 32位 | PWM输出/输入捕获 |
| SNVS | 1 | 32位 | 低功耗定时器 |
| WDOG | 2 | 16位 | 看门狗 |
EPIT(Enhanced Periodic Interrupt Timer)增强的周期中断定时器,I.MX6U 中最常用的定时器。与 STM32 不同,EPIT 仅完成周期性中断定时,输入捕获、PWM 输出等功能由 GPT 等其他外设完成。
核心特点:
EPIT 工作原理框图:
时钟源选择(多路选择器)
│
▼
┌─────────────┐
│ 12位分频器 │ PRESCALAR (0~4095 → 1~4096分频)
└──────┬──────┘
│
▼
┌──────────────────────────────────────────┐
│ EPIT 内部 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ LR │ │ CNR │ │ CMPR │ │
│ │ 加载寄存器│→ │ 计数寄存器│→ │ 比较器 │──┼──→ 比较中断(定时中断)
│ │ (初值) │ │ (当前值) │ │ │ │
│ └──────────┘ └──────────┘ └────────┘ │
│ ↑ ↑ │
│ └──计数到0时重载──┘ │
└──────────────────────────────────────────┘
三种可选时钟源:
| 时钟源 | 频率 | 说明 |
|---|---|---|
| ipg_clk | 66MHz | 外设总线时钟(最常用) |
| ipg_clk_32k | 32.768kHz | 低频参考时钟 |
| ipg_clk_highfreq | 高频 | 高频参考时钟 |
两种工作模式:
| 模式 | RLD位 | 行为 |
|---|---|---|
| set-and-forget | RLD=1 | 计数到0后从LR重新加载,循环递减 |
| free-running | RLD=0 | 计数到0后从0xFFFFFFFF重新开始 |
set-and-forget 是最常用的模式。配置为1后,计数器从LR获取初值,每次减到0自动从LR重载,周而复始。
| 位域 | 名称 | 功能 |
|---|---|---|
| bit25:24 | CLKSRC | 时钟源选择:0=关闭,1=ipg_clk,2=ipg_clk_highfreq,3=ipg_clk_32k |
| bit15:4 | PRESCALAR | 分频值:0~4095 → 1~4096分频 |
| bit3 | RLD | 工作模式:0=free-running,1=set-and-forget |
| bit2 | OCIEN | 比较中断使能:0=关闭,1=使能 |
| bit1 | ENMOD | 计数器初始值来源:0=上次关闭时的值,1=从LR加载 |
| bit0 | EN | EPIT使能:0=关闭,1=使能 |
#define EPIT_CR_EN (1 << 0)
#define EPIT_CR_SWR (1 << 1) /* 软件复位 */
#define EPIT_CR_ENMOD (1 << 1)
#define EPIT_CR_OCIEN (1 << 2)
#define EPIT_CR_RLD (1 << 3)
#define EPIT_CR_PRESCALAR(x)(((x) & 0xFFF) << 4)
#define EPIT_CR_OM (3 << 22) /* 输出模式 */
#define EPIT_CR_CLKSRC (1 << 24) /* 时钟源: 01=ipg_clk */
| 位域 | 名称 | 功能 |
|---|---|---|
| bit0 | OCIF | 比较中断标志位:0=无事件,1=有比较事件。写1清零 |
32位加载值,计数器每次减到0后从该寄存器重新加载。
32位比较值,与计数寄存器比较,相等时产生中断。设为0时,计数器减到0即触发中断。
32位当前计数值,只读。
Tout = ((frac + 1) × value) / Tclk
frac:PRESCALAR 分频值value:LR 加载值Tclk:输入时钟频率(Hz)Tout:溢出时间(秒)示例: 500ms 中断周期
frac = 0 (1分频), Tclk = 66MHz
value = 66000000 / 2 = 33000000
Tout = ((0+1) × 33000000) / 66000000 = 0.5s
GPT(General Purpose Timer)通用定时器,比 EPIT 功能更丰富,支持输出比较和输入捕获,适合 PWM 输出场景。
GPT 特点:
#define GPT_CR 0x000 /* 控制寄存器 */
#define GPT_PR 0x004 /* 预分频寄存器 */
#define GPT_SR 0x008 /* 状态寄存器 */
#define GPT_IR 0x00C /* 中断使能寄存器 */
#define GPT_OCR1 0x010 /* 输出比较1 */
#define GPT_OCR2 0x014 /* 输出比较2 */
#define GPT_OCR3 0x018 /* 输出比较3 */
#define GPT_ICR1 0x01C /* 输入捕获1 */
#define GPT_ICR2 0x020 /* 输入捕获2 */
#define GPT_CNT 0x024 /* 计数器 */
void gpt1_init(void)
{
/* 复位GPT1 */
GPT1_CR |= GPT_CR_SWR;
while (GPT1_CR & GPT_CR_SWR) {}
/* 配置GPT1: 选择ipg_clk, 使能GPT1 */
GPT1_CR = 0;
GPT1_CR |= (1 << 16); /* 选择ipg_clk */
GPT1_CR |= GPT_CR_EN;
/* 预分频: 66MHz/66 = 1MHz */
GPT1_PR = 65;
/* 使能OC1比较中断 */
GPT1_IR |= (1 << 0);
/* 比较值: 1MHz下1000000=1秒中断 */
GPT1_OCR1 = 1000000;
}
| 特性 | EPIT | GPT |
|---|---|---|
| 计数方向 | 向下 | 向上 |
| 核心功能 | 周期性中断 | PWM/捕获/比较 |
| 输出比较通道 | 无(仅CMPR) | 3个(OCR1/OCR2/OCR3) |
| 输入捕获 | 无 | 2个(ICR1/ICR2) |
| 工作模式 | set-and-forget / free-running | 多种模式 |
| 典型用途 | 精确延时、周期中断 | PWM输出、脉冲计数 |
| 配置复杂度 | 简单 | 较复杂 |
选型建议: 简单定时/周期中断用 EPIT,需要 PWM 或输入捕获用 GPT。
#ifndef _BSP_EPITTIMER_H
#define _BSP_EPITTIMER_H
#include "imx6ul.h"
void epit1_init(unsigned int frac, unsigned int value);
void epit1_irqhandler(void);
#endif
#include "bsp_epittimer.h"
#include "bsp_int.h"
#include "bsp_led.h"
/*
* @description : 初始化EPIT定时器
* EPIT定时器是32位向下计数器,时钟源使用ipg=66MHz
* @param - frac : 分频值,范围为0~4095,分别对应1~4096分频
* @param - value : 倒计数值(加载寄存器值)
*/
void epit1_init(unsigned int frac, unsigned int value)
{
if (frac > 0xFFF)
frac = 0xFFF;
EPIT1->CR = 0; /* 先清零CR寄存器 */
/*
* CR寄存器配置:
* bit25:24 = 01 时钟源选择Peripheral clock=66MHz
* bit15:4 = frac 分频值
* bit3 = 1 set-and-forget模式,计数器到0从LR重载
* bit2 = 1 比较中断使能
* bit1 = 1 初始计数值来源于LR寄存器值
* bit0 = 0 先关闭EPIT1
*/
EPIT1->CR = (1 << 24) | (frac << 4) | (1 << 3) | (1 << 2) | (1 << 1);
EPIT1->LR = value; /* 加载寄存器值 */
EPIT1->CMPR = 0; /* 比较寄存器为0,减到0触发中断 */
/* 使能GIC中对应的中断 */
GIC_EnableIRQ(EPIT1_IRQn);
/* 注册中断服务函数 */
system_register_irqhandler(EPIT1_IRQn,
(system_irq_handler_t)epit1_irqhandler,
NULL);
EPIT1->CR |= (1 << 0); /* 使能EPIT1 */
}
/*
* @description : EPIT中断处理函数
*/
void epit1_irqhandler(void)
{
static unsigned char state = 0;
state = !state;
if (EPIT1->SR & (1 << 0)) /* 判断比较事件发生 */
{
led_switch(LED0, state); /* 定时器周期到,反转LED */
}
EPIT1->SR |= (1 << 0); /* 写1清除中断标志位 */
}
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_epittimer.h"
int main(void)
{
int_init(); /* 初始化中断(一定要最先调用!) */
imx6u_clkinit(); /* 初始化系统时钟 */
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化LED */
beep_init(); /* 初始化BEEP */
key_init(); /* 初始化KEY */
/* 初始化EPIT1: 1分频, 计数值66000000/2, 定时周期500ms */
epit1_init(0, 66000000 / 2);
while (1)
{
delay(500);
}
return 0;
}
关键分析:
epit1_init(0, 66000000/2) → 分频值0(1分频),LR=33000000epit1_irqhandler 中翻转 LED0,实现 500ms 闪烁按键机械结构在按下/松开瞬间会产生 5~20ms 的电平抖动。传统做法是用软件延时等待抖动结束,但存在两个问题:
定时器消抖流程:
按键按下(下降沿)
│
▼
触发GPIO中断 ──→ 重启定时器(10ms)
│
┌───────────────┘
│
▼
定时器计时中... ←── 抖动期间每次GPIO中断都重启定时器
│
▼(10ms内无新GPIO中断)
触发定时器中断
│
▼
读取GPIO电平 ──→ 仍为低?──→ 有效按键,执行处理
│
└──→ 高电平?→ 无效,忽略
消抖时序图:
电平: __|--|__|--|__|____________________
t1 t2 t3 t4(稳定按下)
↑ ↑ ↑
GPIO中断触发定时器
每次都重启定时器
↑
t3启动的定时器完整运行10ms
→ t4时刻触发定时器中断 → 读取按键
t1~t3 为抖动期(<10ms),每次 GPIO 中断都重启定时器。只有 t3 时刻启动的定时器能完整运行 10ms 并触发中断,此时按键已稳定。
bsp_keyfilter.c:
#include "bsp_key.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_beep.h"
#include "bsp_keyfilter.h"
/*
* @description : 按键初始化(带消抖)
*/
void filterkey_init(void)
{
gpio_pin_config_t key_config;
/* 1、初始化IO:UART1_CTS -> GPIO1_IO18 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
/* 2、初始化GPIO为中断模式:下降沿触发 */
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge;
key_config.outputLogic = 1;
gpio_init(GPIO1, 18, &key_config);
/* 3、使能GPIO中断,注册中断处理函数 */
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
(system_irq_handler_t)gpio1_16_31_irqhandler, NULL);
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18中断 */
filtertimer_init(66000000 / 100); /* 初始化定时器, 10ms */
}
/*
* @description : 初始化消抖定时器,默认关闭
* @param - value: EPIT计数值
*/
void filtertimer_init(unsigned int value)
{
EPIT1->CR = 0;
EPIT1->CR = (1 << 24) | (1 << 3) | (1 << 2) | (1 << 1);
EPIT1->LR = value;
EPIT1->CMPR = 0;
GIC_EnableIRQ(EPIT1_IRQn);
system_register_irqhandler(EPIT1_IRQn,
(system_irq_handler_t)filtertimer_irqhandler, NULL);
}
/*
* @description : 关闭定时器
*/
void filtertimer_stop(void)
{
EPIT1->CR &= ~(1 << 0); /* 清除EN位,关闭定时器 */
}
/*
* @description : 重启定时器
* @param - value: EPIT计数值
*/
void filtertimer_restart(unsigned int value)
{
EPIT1->CR &= ~(1 << 0); /* 先关闭定时器 */
EPIT1->LR = value; /* 设置新的计数值 */
EPIT1->CR |= (1 << 0); /* 重新打开定时器 */
}
/*
* @description : 定时器中断处理函数(消抖完成后的处理)
*/
void filtertimer_irqhandler(void)
{
static unsigned char state = OFF;
if (EPIT1->SR & (1 << 0)) /* 比较事件发生 */
{
filtertimer_stop(); /* 关闭定时器 */
if (gpio_pinread(GPIO1, 18) == 0) /* 读取按键值,仍按下 */
{
state = !state;
beep_switch(state); /* 反转蜂鸣器 */
}
}
EPIT1->SR |= (1 << 0); /* 清除中断标志位 */
}
/*
* @description : GPIO中断处理函数(按键触发)
*/
void gpio1_16_31_irqhandler(void)
{
filtertimer_restart(66000000 / 100); /* 重启定时器10ms */
gpio_clearintflags(GPIO1, 18); /* 清除GPIO中断标志 */
}
消抖关键点总结:
| 要素 | 说明 |
|---|---|
| 消抖时间 | 10ms(66000000/100 = 660000个时钟周期) |
| GPIO中断 | 每次按键边沿都重启定时器 |
| 定时器中断 | 10ms内无新按键时触发,确认有效按键 |
| 重启机制 | filtertimer_restart 先关再开,重置计数器 |
看门狗(Watchdog Timer)是一种硬件定时器,用于在软件跑飞或死锁时自动复位系统。
工作原理:
程序正常运行:
喂狗 → 喂狗 → 喂狗 → 喂狗 → ... (周期性重置计数器)
程序跑飞/死锁:
喂狗 → (无喂狗)→ (无喂狗)→ 超时 → 系统复位
应用场景:
| 位域 | 名称 | 功能 |
|---|---|---|
| bit0 | WDZST | 停止计数:1=在调试/低功耗模式停止 |
| bit1 | WDBG | 调试模式:1=调试模式停止计数 |
| bit2 | WDE | 看门狗使能:0=禁止,1=使能 |
| bit3 | WDT | 定时器复位使能:1=超时产生复位 |
| bit4 | SRS | 软件复位:写0产生复位 |
| bit5 | WDA | 软件看门狗:写1暂停看门狗 |
| bit8 | WDHO | 超时输出高 |
| bit11 | WDTOP | 超时周期选择 |
| bit15:8 | WCR[15:8] | 超时时间配置位 |
喂狗寄存器。必须依次写入 0x5555 和 0xAAAA 才能完成一次喂狗操作。
记录复位原因:看门狗超时复位、软件复位等。
#define WDOG1_BASE 0x020BC000
#define WDOG1_WCR (*(volatile unsigned short *)(WDOG1_BASE + 0x000))
#define WDOG1_WSR (*(volatile unsigned short *)(WDOG1_BASE + 0x002))
#define WDOG1_WRSR (*(volatile unsigned short *)(WDOG1_BASE + 0x004))
/* 看门狗初始化 */
void wdog_init(void)
{
/* 1、关闭看门狗(先配置再使能) */
WDOG1_WCR = 0;
/* 2、配置超时时间
* bit15:8 = WCR[15:8],超时时间 = 2^14 / 66MHz ≈ 0.25秒
* bit2 WDE = 1 使能看门狗
* bit3 WDT = 1 超时产生复位
*/
WDOG1_WCR |= (5 << 8); /* 超时配置 */
WDOG1_WCR |= (1 << 2); /* 使能看门狗 */
WDOG1_WCR |= (1 << 3); /* 超时复位使能 */
}
/* 喂狗函数:依次写入0x5555和0xAAAA */
void wdog_feed(void)
{
WDOG1_WSR = 0x5555; /* 第一次写入 */
WDOG1_WSR = 0xAAAA; /* 第二次写入 */
}
看门狗配置要点:
| 操作 | 说明 |
|---|---|
| 初始化 | 先清零WCR,配置超时时间和使能位 |
| 喂狗 | 必须先写0x5555再写0xAAAA,顺序不能错 |
| 超时时间 | 由WCR[15:8]位控制,与看门狗时钟源频率相关 |
| 注意 | 使能后必须在主循环中定期喂狗,否则系统会复位 |
| 特性 | IMX6ULL | STM32F4 | RK3568 |
|---|---|---|---|
| 基本定时器 | EPIT (32位) | TIM6/TIM7 (16位) | 系统定时器 |
| 通用定时器 | GPT (32位) | TIM2~TIM5 (32位) | 多种 |
| 高级定时器 | 无 | TIM1/TIM8 (16位) | 无 |
| PWM输出 | GPT | TIM1~TIM14 | PWM子系统 |
| 输入捕获 | GPT | TIM1~TIM14 | PWM子系统 |
| 看门狗 | WDOG1/WDOG2 | IWDG(独立)/WWDG(窗口) | WDOG |
| 看门狗喂狗 | 0x5555+0xAAAA | IWDG: 写KR=0xAAAA | 写服务寄存器 |
| 时钟频率 | ipg_clk=66MHz | APB1=42MHz/APB2=84MHz | 多时钟域 |
| 中断控制器 | GIC | NVIC | GIC |
| 计数方向 | EPIT向下/GPT向上 | 多数向上 | 依外设而异 |
考察点: 定时器架构理解
答案:
| 维度 | EPIT | GPT |
|---|---|---|
| 核心设计 | 周期中断专用 | 通用(PWM/捕获/比较) |
| 计数方向 | 向下 | 向上 |
| 比较通道 | 仅1个(CMPR) | 3个(OCR1/2/3) |
| 输入捕获 | 无 | 2个(ICR1/2) |
| 配置复杂度 | 简单 | 复杂 |
选择原则: 只需要周期中断/精确延时 → EPIT;需要 PWM 输出或输入捕获 → GPT。Linux 驱动开发中,EPIT 常用于实现 udelay/mdelay 等延时函数。
考察点: 定时器应用
答案:
EPIT1_CNR 获取当前计数值循环读取计数值差值,直到达到目标延时
/* 伪代码 */
void delay_us(unsigned int us) {
unsigned int start = EPIT1_CNR;
while ((start - EPIT1_CNR) < us) {} /* 向下计数,差值即μs数 */
}
关键注意事项:
考察点: 看门狗安全机制
答案:
写入 0x5555 → 0xAAAA 的序列是 IMX6ULL 看门狗的安全握手协议。设计目的:
如果只写入一个值或写入顺序错误,看门狗不会被重置,超时后系统将复位。这比简单的"写任意值"方案更安全。
考察点: 消抖方案对比
答案:
| 维度 | 软件延时消抖 | 定时器消抖 |
|---|---|---|
| CPU利用率 | 100%空跑(浪费) | CPU可做其他任务 |
| 中断兼容性 | 不能在中断中使用 | 可在中断驱动架构中使用 |
| 精度 | 受编译器优化影响 | 硬件定时器,精度高 |
| 实现复杂度 | 简单 | 较复杂(需管理定时器状态) |
| Linux驱动 | 不使用 | Linux标准做法 |
Linux 内核的按键驱动正是使用定时器消抖方案。核心思想:GPIO 中断负责触发,定时器中断负责延时确认,两者解耦。
考察点: 资源规划
答案:
典型分配方案:
| 定时器 | 推荐用途 | 理由 |
|---|---|---|
| EPIT1 | 系统延时基准 | 提供 delay_us/delay_ms |
| EPIT2 | 应用定时(如消抖) | 独立于延时基准,互不干扰 |
| GPT1 | PWM输出(如舵机控制) | GPT支持PWM功能 |
| GPT2 | 输入捕获/脉冲计数 | GPT支持捕获功能 |
关键原则:
代码来源: 裸机例程 10_epit_timer (EPIT定时器), 11_key_filter (定时器消抖)
最后更新: 2026-09-17