60、嵌入式定时器深度解析:EPIT与GPT

嵌入式定时器深度解析:EPIT与GPT

一、前置基础:定时器的"心跳"------时钟与分频倍频

定时器的本质是"对已知频率的时钟计数",因此稳定的时钟源灵活的频率调节机制(倍频/分频)是定时器精准工作的前提。我们先理清这些核心概念:

1.1 时钟源:晶体振荡器(晶振)

晶振是整个系统时钟的"源头",其工作原理是:将石英晶体切割成音叉状结构,施加电压后会产生稳定的机械振荡,进而输出频率精准的电信号(如8MHz、24MHz、12MHz)。

  • 特点:频率稳定、误差小,是嵌入式系统最核心的时钟来源;
  • 举例:51单片机常用11.0592MHz晶振,i.MX6ULL开发板常用24MHz晶振(osc_clk)。

1.2 锁相环(PLL):低频时钟倍频核心

晶振输出的低频信号无法满足CPU/外设的高频需求,此时需要PLL(锁相环)进行倍频:

  • 原理:通过相位锁定机制,将输入的低频时钟信号放大为高频信号(如i.MX6ULL的PLL1可将24MHz的step_clk倍频至1056MHz);
  • 关键注意点:配置PLL倍频因子前,必须先设置PLL后级的分频(如二分频),否则会导致ARM内核因超频而故障!

1.3 分频器(Prescale/PODF):高频时钟降频适配

PLL输出的高频时钟需要分频后,才能适配不同外设的工作频率:

  • 原理:将高频时钟信号按固定比例降低(如除法运算);
  • i.MX6ULL实战配置举例:
    • AHB_CLK_ROOT(132MHz):通过CBCMR[PRE_PERIPH_CLK_SEL]选择时钟源、CBCDR[PERIPH_CLK_SEL]切换路径、CBCDR[AHB_PODF]设置分频;
    • IPG_CLK_ROOT(66MHz):通过CBCDR[IPG_PODF]分频得到;
    • PERCLK_CLK_ROOT(66MHz):通过CSCMR1[PERCLK_CLK_SEL]选源、CSCMR1[PERCLK_PODF]分频。

1.4 相位分数分频器(PFD):灵活升降频

PFD是比普通分频器更灵活的频率调节模块,支持输出频率"升频"或"降频",主要用于i.MX6ULL的528PLL(CCM_ANALOG_PFD_528n)和480PLL(CCM_ANALOG_PFD_480n),适配不同外设的时钟需求。

易混淆单位说明

  • 频率计算:1MHz = 1000×1000 Hz(定时器计数、时钟频率常用);
  • 存储计算:1MByte = 1024×1024 Byte(内存/Flash容量计算用)。

二、51单片机定时器:基础8/16位定时器实战

51单片机的Timer1、Timer2是入门级定时器,核心分为"8位自动重装"和"16位手动重装"两种模式,我们以最常用的16位定时器为例,讲解原理与实战。

2.1 51定时器核心原理

  • 计数对象:对"机器周期"计数(机器周期 = 12 / 晶振频率,如11.0592MHz晶振的机器周期≈1.085μs);
  • 8位自动重装:计数器溢出后,自动从预设的重装寄存器加载初值,无需手动干预;
  • 16位手动重装:计数器溢出后,需在中断服务函数中手动重置THx/TLx初值,否则下次计数从0开始。

2.2 实战:1s中断反转LED(Timer0为例)

需求

基于51单片机Timer0(16位模式)实现1s定时中断,中断服务函数中反转LED灯状态。

硬件环境
  • 晶振:11.0592MHz;
  • LED:接P1.0引脚,低电平点亮。
代码实现
c 复制代码
#include <reg51.h>

// 定义LED引脚
sbit LED = P1^0;
// 定义中断计数变量(50ms×20=1000ms)
unsigned char cnt = 0;

/**
 * @brief 定时器0初始化:配置16位模式,定时50ms
 */
void Timer0_Init(void)
{
    // 1. 配置定时器模式:TMOD=0x01(Timer0,16位定时器,仅对机器周期计数)
    TMOD &= 0xF0;  // 清空Timer0模式位
    TMOD |= 0x01;  

    // 2. 设置计数初值:11.0592MHz晶振,机器周期≈1.085μs,50ms需要计数46080次
    // 16位计数器最大值65536,初值=65536 - 46080 = 19456 = 0x4C00
    TH0 = 0x4C;    // 高8位
    TL0 = 0x00;    // 低8位

    // 3. 使能定时器0中断、总中断
    ET0 = 1;       // 使能Timer0中断
    EA  = 1;       // 使能总中断

    // 4. 启动定时器0
    TR0 = 1;
}

/**
 * @brief 定时器0中断服务函数
 */
void Timer0_ISR(void) interrupt 1
{
    // 手动重装初值(16位模式无自动重装,溢出后需重置)
    TH0 = 0x4C;
    TL0 = 0x00;

    // 计数20次 = 50ms×20 = 1000ms
    cnt++;
    if(cnt >= 20)
    {
        cnt = 0;
        LED = ~LED;  // 反转LED状态
    }
}

void main(void)
{
    Timer0_Init();  // 初始化定时器
    while(1);       // 主循环空等,依赖中断处理
}
代码解析
  1. 模式配置:TMOD=0x01 设定Timer0为16位定时器,仅对机器周期计数(非外部脉冲计数);
  2. 初值计算:11.0592MHz晶振下,50ms需要计数50ms / 1.085μs ≈ 46080次,因此初值=65536-46080=0x4C00;
  3. 中断机制:ET0=1开启Timer0中断,EA=1开启总中断,溢出后进入interrupt 1(Timer0中断向量);
  4. 1s实现:单次定时50ms,通过cnt计数20次叠加为1s,达到后反转LED。

三、i.MX6ULL定时器:EPIT与GPT实战

i.MX6ULL作为工业级ARM Cortex-A7芯片,提供了功能更强的EPIT(增强型周期中断定时器)和GPT(通用目的定时器),远超51单片机的基础定时器。

3.1 EPIT(Enhanced Periodic Interrupt Timer):增强型周期中断定时器

EPIT专为"周期中断"设计,核心优势是自动重装计数初值精准周期定时,无需像51 16位定时器那样手动重装初值,适用于LED翻转、定时数据采集等场景。

EPIT核心原理
  • 时钟源:默认使用IPG_CLK_ROOT(66MHz),可分频后使用;
  • 计数模式:向下计数,从预设的加载值(LR寄存器)递减到0,触发中断后自动重装LR值,循环计数;
  • 1s中断计算:若EPIT输入时钟为1MHz(66MHz分频66倍),则计数1000000次(1MHz)可实现1s定时。
实战:1s中断反转LED
硬件环境
  • i.MX6ULL开发板,LED接GPIO1_IO03;
  • EPIT时钟源:IPG_CLK_ROOT(66MHz)。
代码实现(基于裸机驱动)
c 复制代码
#include "imx6ull.h"

// LED初始化:GPIO1_IO03输出
void LED_Init(void)
{
    // 1. 使能GPIO1时钟
    CCM->CCGR1 |= (3 << 26);  // CG13 (GPIO1) = 11

    // 2. 设置GPIO1_IO03为通用输出
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);

    // 3. 设置GPIO方向为输出,初始熄灭
    GPIO1->GDIR |= (1 << 3);
    GPIO1->DR |= (1 << 3);
}

// LED状态反转
void LED_Toggle(void)
{
    GPIO1->DR ^= (1 << 3);
}

/**
 * @brief EPIT初始化:1s周期中断
 */
void EPIT_Init(void)
{
    // 1. 使能EPIT1时钟(IPG_CLK_ROOT=66MHz)
    CCM->CCGR1 |= (3 << 20);  // CG10 (EPIT1) = 11

    // 2. 复位EPIT1
    EPIT1->CR = (1 << 1);     // SWR=1,复位
    while(EPIT1->CR & (1 << 1));  // 等待复位完成

    // 3. 配置EPIT1_CR寄存器
    EPIT1->CR = 0;
    EPIT1->CR |= (1 << 24);   // CLKSRC=1:选择IPG_CLK_ROOT(66MHz)
    EPIT1->CR |= (65 << 4);   // PRESCALAR=65:分频66倍(65+1),输入时钟=66MHz/66=1MHz
    EPIT1->CR |= (1 << 3);    // RLDPD=1:休眠时继续工作
    EPIT1->CR |= (1 << 2);    // IOVW=1:覆盖计数器值
    EPIT1->CR |= (1 << 1);    // ENMOD=1:计数器重载LR值
    EPIT1->CR |= (1 << 0);    // EN=0(先关闭,配置完LR再开启)

    // 4. 设置加载值LR:1MHz时钟,1s需要计数1000000次
    EPIT1->LR = 1000000;

    // 5. 设置比较值CMPR:0(计数到0触发中断)
    EPIT1->CMPR = 0;

    // 6. 配置中断:使能EPIT1中断,设置优先级
    GIC_EnableIRQ(EPIT1_IRQn);                    // 使能GIC中EPIT1中断
    system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)EPIT1_IRQHandler, NULL);  // 注册中断服务函数

    // 7. 开启EPIT1
    EPIT1->CR |= (1 << 0);
}

/**
 * @brief EPIT1中断服务函数
 */
void EPIT1_IRQHandler(void)
{
    if(EPIT1->SR & (1 << 0))  // 判断中断标志位(IFLAG=1)
    {
        LED_Toggle();         // 反转LED
        EPIT1->SR |= (1 << 0); // 清除中断标志位
    }
}

int main(void)
{
    LED_Init();   // LED初始化
    EPIT_Init();  // EPIT初始化

    while(1);     // 主循环空等
    return 0;
}
代码解析
  1. 时钟配置:CCM->CCGR1使能EPIT1时钟,CR寄存器选择IPG_CLK_ROOT(66MHz)并分频66倍,得到1MHz的计数时钟;
  2. 计数配置:LR=1000000设置1s计数阈值,ENMOD=1开启自动重装,计数到0后自动重新加载LR值;
  3. 中断处理:中断标志位SR[IFLAG]触发后,反转LED并清除标志位,保证下一次中断正常触发。

3.2 GPT(General Purpose Timer):通用目的定时器

GPT是功能更丰富的定时器,支持自由运行模式输入捕获比较输出,其中"自由运行模式"最适合编写精准延时函数。

GPT核心原理(自由运行模式)
  • 自由运行模式:计数器从0开始递增,溢出后自动从0重新开始,无停止;
  • 延时实现逻辑:记录延时开始时的计数值start,循环读取当前计数值current,当current - start达到"延时所需计数值"时,结束延时。
实战:自由运行模式编写精准延时函数
需求

基于GPT自由运行模式,实现GPT_DelayUs(uint32_t us)(微秒级延时)和GPT_DelayMs(uint32_t ms)(毫秒级延时)。

代码实现
c 复制代码
#include "imx6ull.h"

/**
 * @brief GPT初始化:自由运行模式,时钟源为IPG_CLK_ROOT(66MHz)
 */
void GPT_Init(void)
{
    // 1. 使能GPT1时钟
    CCM->CCGR1 |= (3 << 18);  // CG9 (GPT1) = 11

    // 2. 复位GPT1
    GPT1->CR = (1 << 15);     // SWR=1,复位
    while(GPT1->CR & (1 << 15));  // 等待复位完成

    // 3. 配置GPT1_CR寄存器
    GPT1->CR = 0;
    GPT1->CR |= (1 << 1);     // CLKSRC=1:选择IPG_CLK_ROOT(66MHz)
    GPT1->CR &= ~(1 << 0);    // FRR=0:自由运行模式(计数器递增,溢出后重置为0)
    GPT1->CR &= ~(1 << 2);    // CLKEN=0:先关闭,配置完再开启

    // 4. 配置GPT1_PR:分频系数(66分频,得到1MHz时钟,1us=1次计数)
    GPT1->PR = 65;  // 分频66倍(65+1),66MHz/66=1MHz

    // 5. 开启GPT1
    GPT1->CR |= (1 << 2);
}

/**
 * @brief GPT微秒级延时
 * @param us 延时微秒数(范围:0~4294967295)
 */
void GPT_DelayUs(uint32_t us)
{
    uint64_t start = GPT1->CNT;  // 记录开始计数值
    uint64_t target = start + us; // 目标计数值(1MHz=1us/次)

    // 处理计数器溢出(GPT1是32位计数器,最大值0xFFFFFFFF)
    if(target > 0xFFFFFFFF)
    {
        // 先等待计数器溢出到0
        while(GPT1->CNT < start);
        // 再等待计数器达到 target - 0xFFFFFFFF - 1
        while(GPT1->CNT < (target - 0xFFFFFFFF - 1));
    }
    else
    {
        // 直接等待计数器达到目标值
        while(GPT1->CNT < target);
    }
}

/**
 * @brief GPT毫秒级延时
 * @param ms 延时毫秒数(范围:0~4294967295)
 */
void GPT_DelayMs(uint32_t ms)
{
    for(uint32_t i=0; i<ms; i++)
    {
        GPT_DelayUs(1000);  // 1ms=1000us
    }
}

// 测试:LED闪烁(延时1s)
void LED_Init(void)
{
    CCM->CCGR1 |= (3 << 26);
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);
    GPIO1->GDIR |= (1 << 3);
    GPIO1->DR |= (1 << 3);
}

int main(void)
{
    LED_Init();   // LED初始化
    GPT_Init();   // GPT初始化

    while(1)
    {
        GPIO1->DR &= ~(1 << 3);  // LED点亮
        GPT_DelayMs(1000);       // 延时1s
        GPIO1->DR |= (1 << 3);   // LED熄灭
        GPT_DelayMs(1000);       // 延时1s
    }
    return 0;
}
代码解析
  1. 时钟配置:GPT1选择IPG_CLK_ROOT(66MHz),分频66倍后得到1MHz时钟(1us/次计数),保证微秒级延时的精准性;
  2. 自由运行模式:FRR=0开启自由运行,计数器从0递增,溢出后自动重置为0;
  3. 延时逻辑:
    • 微秒延时:记录起始计数值,计算目标计数值(起始值+延时微秒数),等待计数器达到目标值;
    • 溢出处理:32位计数器最大值为0xFFFFFFFF(约4294秒),若目标值超过该值,先等待溢出,再继续计数;
    • 毫秒延时:循环调用微秒延时函数,1ms=1000us。

四、总结与拓展

4.1 不同平台定时器对比

特性 51单片机定时器 i.MX6ULL EPIT i.MX6ULL GPT
核心用途 基础定时/中断 周期中断(如LED翻转) 精准延时、输入捕获、比较输出
计数模式 8/16位手动/自动重装 向下计数+自动重装 自由运行(向上计数)
时钟灵活性 仅机器周期/外部脉冲 系统时钟分频 多时钟源+分频
功能丰富度 简单 专注中断 全功能(捕获/输出/延时)

4.2 实战注意事项

  1. 时钟配置是前提:PLL/分频器配置错误会导致定时器计数不准,甚至系统崩溃;
  2. 中断处理要简洁:EPIT中断服务函数中避免耗时操作,否则会影响定时精度;
  3. 初值计算要精准:根据时钟频率和定时需求,准确计算分频系数和计数初值。

4.3 拓展方向

  1. GPT输入捕获:测量外部脉冲的宽度/周期(如按键防抖、超声波测距);
  2. GPT比较输出:生成PWM波(如电机调速、LED呼吸灯);
  3. EPIT多任务调度:基于EPIT中断实现简单的任务调度器,管理多个定时任务。
相关推荐
乐亦_Lee2 小时前
在Ubuntu下如何提升下载速度
linux·嵌入式硬件·ubuntu
俊俊谢2 小时前
HC32F460如何配置GPIO中断
单片机·嵌入式硬件·hc32f460
三佛科技-187366133972 小时前
SLD50N06T美浦森60V,50A N沟道MOS场效应管解析
单片机·嵌入式硬件
wangjialelele2 小时前
二刷C语言后,一万字整理细碎知识点
c语言·开发语言·数据结构·c++·算法·cpp
草丛中的蝈蝈2 小时前
单片机烧写新程序后,ST-LINK无法发现设备
单片机·嵌入式硬件
StandbyTime2 小时前
C语言学习-菜鸟教程C经典100例-练习40
c语言
edisao2 小时前
【开源】轻量级 LLM 文本质检工具:精准识别核心概念缺失,支持动态别名 + 反馈闭环
大数据·开发语言·人工智能·经验分享·gpt·架构·开源
不做无法实现的梦~2 小时前
PX4怎么使用使用PlotJuggler分析PX4日志
linux·嵌入式硬件·机器人·自动驾驶
ae_zr2 小时前
STM32H743+DMA+串口空闲中断接收不定长数据,并使用DMA发送数据
stm32·单片机·嵌入式硬件·dma