--- title: 定时器与看门狗 tags: [定时器, 看门狗, EPIT, GPT, IMX6ULL, 裸机开发, 按键消抖] created: 2026-09-16 updated: 2026-09-17 pdf_ref: - "正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 第18章 EPIT定时器实验" - "正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 第19章 定时器按键消抖实验" - "正点原子 I.MX6U嵌入式Linux驱动开发指南V2.0.1 第20章 高精度延时实验" --- # 定时器与看门狗 > 💡 **关联知识**: [[FreeRTOS学习笔记/08-时间片调度与延时管理]] | [[STM32学习笔记/08-SysTick与通用定时器TIM]] --- ## Part 1: 定时器 ### 1.1 IMX6ULL 定时器系统总览 | 定时器 | 数量 | 分辨率 | 用途 | | ------ | ---- | ------ | ---------------- | | EPIT | 2 | 32位 | 周期性中断定时 | | GPT | 2 | 32位 | PWM输出/输入捕获 | | SNVS | 1 | 32位 | 低功耗定时器 | | WDOG | 2 | 16位 | 看门狗 | --- ### 1.2 EPIT 定时器概述 EPIT(Enhanced Periodic Interrupt Timer)增强的周期中断定时器,I.MX6U 中最常用的定时器。与 STM32 不同,EPIT **仅完成周期性中断定时**,输入捕获、PWM 输出等功能由 GPT 等其他外设完成。 **核心特点:** - 时钟源可选的 **32 位向下计数器** - **12 位分频值**(0~4095,对应 1~4096 分频) - 当**计数值与比较值相等**时产生中断 **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重载,周而复始。 --- ### 1.3 EPIT 寄存器详解 **基地址:** `EPIT1_BASE = 0x020D0000`、`EPIT2_BASE = 0x020D4000`(寄存器偏移见表,基地址取自官方头文件 `MCIMX6Y2.h`)。 #### EPITx_CR — 控制寄存器(偏移 0x000) | 位域 | 名称 | 功能 | | -------- | --------- | ---------------------------------------------------------------- | | 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=使能 | ```c #define EPIT_CR_EN (1 << 0) #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_SWR (1 << 16) /* 软件复位 */ #define EPIT_CR_OM (3 << 22) /* 输出模式 */ #define EPIT_CR_CLKSRC(x) (((x) & 0x3) << 24) /* 时钟源: 01=ipg_clk */ ``` #### EPITx_SR — 状态寄存器(偏移 0x004) | 位域 | 名称 | 功能 | | ---- | ---- | --------------------------------------------------- | | bit0 | OCIF | 比较中断标志位:0=无事件,1=有比较事件。**写1清零** | #### EPITx_LR — 加载寄存器(偏移 0x008) 32位加载值,计数器每次减到0后从该寄存器重新加载。 #### EPITx_CMPR — 比较寄存器(偏移 0x00C) 32位比较值,与计数寄存器比较,相等时产生中断。**设为0时,计数器减到0即触发中断**。 #### EPITx_CNR — 计数寄存器(偏移 0x010) 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 ``` --- ### 1.4 GPT 定时器概述 GPT(General Purpose Timer)通用定时器,比 EPIT 功能更丰富。教材**第20章「高精度延时实验」正是用 GPT1 实现高精度延时**,本节按第20章 PDF 原文核对。 **GPT 特点(PDF 20.1.1):** - 可选时钟源的 32 位**向上**计数器(从 0x00000000 开始递增) - **12 位分频器**(PRESCALER 0~4095 → 1~4096 分频) - 两个输入捕获通道(可设置触发方式) - 三个输出比较通道(可设置输出模式) - 可产生捕获中断、比较中断和溢出中断 - 计数器可运行在重新启动(restart)或自由运行(free-run)模式 **五个可选时钟源(PDF 图 20.1.1.1):** | CLKSRC(bit8:6) | 时钟源 | | -------------- | --------------------------------- | | 0 | 关闭时钟源 | | **1** | **ipg_clk(=66MHz,本例程选择)** | | 2 | ipg_clk_highfreq | | 3 | 外部时钟 GPT_CLK | | 4 | ipg_clk_32k | | 5 | ipg_clk_24M | **两种工作模式(FRR 位):** | 模式 | FRR位 | 行为 | | ------------------------ | ----- | ----------------------------------------------- | | **restart(重新启动)** | FRR=0 | 计数值等于比较值时计数器清零,仅比较通道 1 有效 | | **free-run(自由运行)** | FRR=1 | 比较事件不清零计数器,直到 0xFFFFFFFF 回滚到 0 | **GPTx_CR 重要位域(PDF 20.1.1):** | 位域 | 名称 | 功能 | | ------ | ------ | ------------------------------------------ | | bit15 | SWR | 复位 GPT 定时器,写 1 复位,完成后自动清零 | | bit9 | FRR | 0=restart 模式,1=free-run 模式 | | bit8:6 | CLKSRC | 时钟源选择(见上表) | | bit1 | ENMOD | 0=关闭时保留计数值,1=关闭时计数器清零 | | bit0 | EN | GPT 使能:1=使能,0=关闭 | **GPTx_PR:** PRESCALER(bit11:0),0~4095 → 1~4096 分频。 **GPTx_SR:** ROV(bit5) 回滚标志、IF2~IF1(bit4:3) 输入捕获标志、OF3~OF1(bit2:0) 输出比较标志。 **GPTx_CNT:** 当前计数值;**GPTx_OCR1~3:** 三路 32 位输出比较值。 #### GPT 核心寄存器 **基地址:** `GPT1_BASE = 0x02098000`、`GPT2_BASE = 0x020E8000`。 ```c #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 /* 计数器 */ ``` #### GPT 初始化示例(高精度延时用,教材第20章例程) ```c void delay_init(void) { GPT1->CR = 0; GPT1->CR = 1 << 15; /* bit15 SWR 置1,进入软复位 */ while ((GPT1->CR >> 15) & 0x01); /* 等待复位完成 */ /* CLKSRC(bit8:6)=001,选择 ipg_clk=66MHz;FRR(bit9)=0,restart 模式 */ GPT1->CR = (1 << 6); /* 66 分频,GPT1 时钟为 66M/(65+1)=1MHz,计数一次即 1us */ GPT1->PR = 65; /* 不使用比较中断,OCR1 设为最大值 */ GPT1->OCR[0] = 0xFFFFFFFF; GPT1->CR |= 1 << 0; /* 使能 GPT1 */ } ``` #### EPIT vs GPT 对比 | 特性 | EPIT | GPT | | ------------ | ----------------------------- | ----------------------------- | | 计数方向 | **向下** | 向上 | | 核心功能 | 周期性中断 | 高精度延时/PWM/捕获/比较 | | 输出比较通道 | 无(仅CMPR) | 3个(OCR1/OCR2/OCR3) | | 输入捕获 | 无 | 2个(ICR1/ICR2) | | 工作模式 | set-and-forget / free-running | restart / free-run | | 典型用途 | 周期中断定时、定时器消抖 | 高精度延时、PWM输出、输入捕获 | | 配置复杂度 | **简单** | 较复杂 | > **选型建议:** 简单定时/周期中断用 EPIT,需要 PWM 或输入捕获用 GPT。 --- ### 1.5 定时器实验源码 #### 头文件 bsp_epittimer.h ```c #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 ``` #### 驱动文件 bsp_epittimer.c ```c #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清除中断标志位 */ } ``` #### main.c ```c #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; } ``` **关键分析:** - 第22行:`epit1_init(0, 66000000/2)` → 分频值0(1分频),LR=33000000 - 时钟源 ipg_clk=66MHz,中断周期 = 33000000 / 66000000 = 500ms - 中断处理函数 `epit1_irqhandler` 中翻转 LED0,实现 500ms 闪烁 --- ### 1.6 定时器消抖 **教材实现步骤(PDF 19.1):** 1. **配置按键 IO 中断**:配置按键所用 IO 为中断模式(下降沿触发)。 2. **初始化消抖用的定时器**:使用 EPIT1,定时周期 10ms(可按需调整)。 3. **编写中断处理函数**:按键 GPIO 中断里只重启 EPIT1;真正的按键处理放在 EPIT1 中断里完成(如开关蜂鸣器)。 #### 消抖原理 按键机械结构在按下/松开瞬间会产生 5~20ms 的电平抖动。传统做法是用软件延时等待抖动结束,但存在两个问题: 1. **延时函数空跑 CPU**,浪费处理器性能 2. **中断中不能使用延时函数**,违反中断快进快出原则 **定时器消抖流程:** ``` 按键按下(下降沿) │ ▼ 触发GPIO中断 ──→ 重启定时器(10ms) │ ┌───────────────┘ │ ▼ 定时器计时中... ←── 抖动期间每次GPIO中断都重启定时器 │ ▼(10ms内无新GPIO中断) 触发定时器中断 │ ▼ 读取GPIO电平 ──→ 仍为低?──→ 有效按键,执行处理 │ └──→ 高电平?→ 无效,忽略 ``` **消抖时序图:** ``` 电平: __|--|__|--|__|____________________ t1 t2 t3 t4(稳定按下) ↑ ↑ ↑ GPIO中断触发定时器 每次都重启定时器 ↑ t3启动的定时器完整运行10ms → t4时刻触发定时器中断 → 读取按键 ``` t1~t3 为抖动期(<10ms),每次 GPIO 中断都重启定时器。只有 t3 时刻启动的定时器能完整运行 10ms 并触发中断,此时按键已稳定。 #### 消抖实验源码 **bsp_keyfilter.h:** ```c #ifndef _BSP_KEYFILTER_H #define _BSP_KEYFILTER_H /* 函数声明 */ void filterkey_init(void); void filtertimer_init(unsigned int value); void filtertimer_stop(void); void filtertimer_restart(unsigned int value); void filtertimer_irqhandler(void); void gpio1_16_31_irqhandler(void); #endif ``` **bsp_keyfilter.c:** ```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中断标志 */ } ``` **main.c:** ```c #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_keyfilter.h" int main(void) { unsigned char state = OFF; int_init(); /* 初始化中断(一定要最先调用!) */ imx6u_clkinit(); /* 初始化系统时钟 */ clk_enable(); /* 使能所有的时钟 */ led_init(); /* 初始化LED */ beep_init(); /* 初始化BEEP */ filterkey_init(); /* 带有消抖功能的按键 */ while (1) { state = !state; led_switch(LED0, state); /* LED0 约 500ms 闪烁,提示系统运行 */ delay(500); } return 0; } ``` > 编译时需把 Makefile 的 `TARGET` 改为 `keyfilter`,并在 `INCDIRS` 与 `SRCDIRS` 中加入 `bsp/keyfilter`。 **消抖关键点总结:** | 要素 | 说明 | | ---------- | ------------------------------------------ | | 消抖时间 | 10ms(66000000/100 = 660000个时钟周期) | | GPIO中断 | 每次按键边沿都重启定时器 | | 定时器中断 | 10ms内无新按键时触发,确认有效按键 | | 重启机制 | `filtertimer_restart` 先关再开,重置计数器 | --- ## Part 2: 看门狗 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 > (教材裸机部分(第8~27章)未安排看门狗实验,仅在第8章引脚复用表中出现过 `WDOG3_WDOG_B`;此处寄存器信息依据《I.MX6ULL参考手册》与官方头文件 `MCIMX6Y2.h` 整理。) ### 2.1 看门狗概述 看门狗(Watchdog Timer)是一种硬件定时器,用于在软件跑飞或死锁时自动复位系统。 **工作原理:** ``` 程序正常运行: 喂狗 → 喂狗 → 喂狗 → 喂狗 → ... (周期性重置计数器) 程序跑飞/死锁: 喂狗 → (无喂狗)→ (无喂狗)→ 超时 → 系统复位 ``` **应用场景:** - 工业控制系统防死机 - 嵌入式设备无人值守运行 - 汽车电子、医疗设备等安全关键系统 - Linux 系统中由内核或应用层定期喂狗 --- ### 2.2 看门狗寄存器 **基地址:** `WDOG1_BASE = 0x020BC000`、`WDOG2_BASE = 0x020C0000`(另有 WDOG3,本表未列出)。 #### WDOG_WCR — 看门狗控制寄存器(偏移 0x000) | 位域 | 名称 | 功能 | | ------- | ----- | ------------------------------------- | | bit0 | WDZST | 停止计数:1=在调试/低功耗模式停止计数 | | bit1 | WDBG | 调试模式:1=调试模式停止计数 | | bit2 | WDE | **看门狗使能**:0=禁止,1=使能 | | bit3 | WDT | 定时器复位使能:1=超时产生复位 | | bit4 | SRS | 软件复位:写 0 产生复位 | | bit5 | WDA | 软件看门狗:写 1 暂停看门狗 | | bit15:8 | WT | **超时时间配置位**(16 个档位) | #### WDOG_WSR — 服务寄存器(偏移 0x002) 喂狗寄存器。必须依次写入 **0x5555** 和 **0xAAAA** 才能完成一次喂狗操作。 #### WDOG_WRSR — 复位状态寄存器(偏移 0x004) 记录复位原因:`SFTW(bit0)` 软件复位、`TOUT(bit1)` 看门狗超时复位、`POR(bit4)` 上电复位等。 #### WDOG_WICR — 中断控制寄存器(偏移 0x006) 看门狗中断控制,用于配置超时中断(而非直接复位)相关行为。 #### WDOG_WMCR — 杂项控制寄存器(偏移 0x008) 看门狗杂项控制寄存器。 --- ### 2.3 看门狗实验源码 ```c #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)) #define WDOG1_WICR (*(volatile unsigned short *)(WDOG1_BASE + 0x006)) #define WDOG1_WMCR (*(volatile unsigned short *)(WDOG1_BASE + 0x008)) /* 看门狗初始化(超时时间由 WT[15:8] 与 WDOG 时钟源共同决定,公式见参考手册) */ void wdog_init(void) { /* SRS(bit4)/WDA(bit5) 应保持为 1,避免写 0 误触发软件复位/暂停看门狗 */ WDOG1_WCR = (1 << 4) | (1 << 5); /* 2、配置超时时间与使能 * WT(bit15:8) 超时档位(示例取 5),具体超时值与 WDOG 时钟源有关 * WDT(bit3)=1 超时产生复位;WDE(bit2)=1 使能看门狗 */ WDOG1_WCR |= (5 << 8); WDOG1_WCR |= (1 << 3); WDOG1_WCR |= (1 << 2); } /* 喂狗函数:依次写入0x5555和0xAAAA */ void wdog_feed(void) { WDOG1_WSR = 0x5555; /* 第一次写入 */ WDOG1_WSR = 0xAAAA; /* 第二次写入 */ } ``` **看门狗配置要点:** | 操作 | 说明 | | -------- | ------------------------------------------------ | | 初始化 | 配置时保持 `SRS/WDA=1`,再设置超时档位与使能位 | | 喂狗 | 必须先写0x5555再写0xAAAA,顺序不能错 | | 超时时间 | 由 WT[15:8] 与看门狗时钟源共同决定 | | 注意 | 使能后**必须在主循环中定期喂狗**,否则系统会复位 | --- ### 2.4 跨平台定时器对比 | 特性 | 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向上 | 多数向上 | 依外设而异 | --- ### 2.5 面试精选 #### Q1: EPIT 和 GPT 有什么区别?如何选择? **考察点:** 定时器架构理解 **答案:** | 维度 | EPIT | GPT | | ---------- | ------------- | --------------------- | | 核心设计 | 周期中断专用 | 通用(PWM/捕获/比较) | | 计数方向 | 向下 | 向上 | | 比较通道 | 仅1个(CMPR) | 3个(OCR1/2/3) | | 输入捕获 | 无 | 2个(ICR1/2) | | 配置复杂度 | 简单 | 复杂 | **选择原则:** 只需要周期中断/精确延时 → EPIT;需要 PWM 输出或输入捕获 → GPT。Linux 驱动开发中,EPIT 常用于实现 `udelay`/`mdelay` 等延时函数。 --- #### Q2: 如何实现精确的微秒级延时? **考察点:** 定时器应用 **答案(教材第20章使用 GPT1 实现,非 EPIT):** 1. 选用 **GPT1**,时钟源设为 ipg_clk=66MHz;分频寄存器 `GPT1_PR = 65`(66 分频),进入 GPT1 的时钟为 66MHz/(65+1)=**1MHz**,计数一次即 1μs。 2. 输出比较寄存器 `GPT1->OCR[0] = 0xFFFFFFFF`(不使用比较中断)。 3. 使能 GPT1(`GPT1->CR |= 1<<0`),计数器从 0 向上递增。 4. 延时函数读取 `GPT1->CNT` 并累加走过的计数值,达到目标 us 数即结束;**必须处理 0xFFFFFFFF→0 的溢出回滚**。 ```c /* 教材第20章 delayus 实现 */ void delayus(unsigned int usdelay) { unsigned long oldcnt, newcnt; unsigned long tcntvalue = 0; /* 走过的总时间 */ oldcnt = GPT1->CNT; while (1) { newcnt = GPT1->CNT; if (newcnt != oldcnt) { if (newcnt > oldcnt) /* 向上计数且未溢出 */ tcntvalue += newcnt - oldcnt; else /* 发生溢出回滚 */ tcntvalue += 0xFFFFFFFF - oldcnt + newcnt; oldcnt = newcnt; if (tcntvalue >= usdelay) /* 延时时间到 */ break; } } } ``` **关键注意事项:** - 教材的高精度延时用的是 **GPT**(向上计数),EPIT 是向下计数器、只提供周期中断,不用于本延时实现。 - GPTx_CNT 为 32 位,1MHz 下最多约 0xFFFFFFFFμs≈4294s≈72min 后回滚,**必须处理溢出**。 - 主循环中除延时外还有别的代码开销(如 `led_switch`),示波器实测周期会略大于理论值,属正常现象。 --- #### Q3: 看门狗的喂狗操作为什么需要写两次特定值? **考察点:** 看门狗安全机制 **答案:** 写入 **0x5555 → 0xAAAA** 的序列是 IMX6ULL 看门狗的**安全握手协议**。设计目的: 1. **防止误操作**:程序跑飞时偶然写入错误值不会意外喂狗 2. **确认软件可控**:必须按特定顺序写入两个不同值,证明软件处于正常控制状态 3. **防止竞态**:多任务系统中避免多次喂狗操作交叉 如果只写入一个值或写入顺序错误,看门狗不会被重置,超时后系统将复位。这比简单的"写任意值"方案更安全。 --- #### Q4: 定时器消抖和软件延时消抖有什么本质区别? **考察点:** 消抖方案对比 **答案:** | 维度 | 软件延时消抖 | 定时器消抖 | | ---------- | -------------------- | -------------------------- | | CPU利用率 | **100%空跑**(浪费) | CPU可做其他任务 | | 中断兼容性 | **不能在中断中使用** | 可在中断驱动架构中使用 | | 精度 | 受编译器优化影响 | 硬件定时器,精度高 | | 实现复杂度 | 简单 | 较复杂(需管理定时器状态) | | Linux驱动 | 不使用 | **Linux标准做法** | Linux 内核的按键驱动正是使用定时器消抖方案。核心思想:GPIO 中断负责触发,定时器中断负责延时确认,两者解耦。 --- #### Q5: IMX6ULL 有两个 EPIT 和两个 GPT,裸机开发中如何合理分配? **考察点:** 资源规划 **答案:** 典型分配方案: | 定时器 | 推荐用途 | 理由 | | ------ | --------------------- | -------------------------- | | EPIT1 | 系统延时基准 | 提供 `delay_us`/`delay_ms` | | EPIT2 | 应用定时(如消抖) | 独立于延时基准,互不干扰 | | GPT1 | PWM输出(如舵机控制) | GPT支持PWM功能 | | GPT2 | 输入捕获/脉冲计数 | GPT支持捕获功能 | **关键原则:** - EPIT 用于纯定时场景,简单高效 - GPT 用于需要 PWM/捕获的场景 - 每个定时器独立使用,避免资源共享冲突 - 中断号不同(EPIT1_IRQn vs EPIT2_IRQn),可独立配置优先级 --- **代码来源**: 裸机例程 `10_epit_timer` (EPIT定时器), `11_key_filter` (定时器消抖), `12_highpreci_delay` (GPT高精度延时) **内容来源**: 《I.MX6U嵌入式Linux驱动开发指南》第18章 EPIT定时器实验、第19章 定时器按键消抖实验、第20章 高精度延时实验(看门狗部分为扩展知识) **最后更新**: 2026-09-17