i.MX6ULL GPT 通用定时器 中文笔记

整理日期:2026-06-26

参考:NXP i.MX6ULL Applications Processor Reference Manual (Chapter 30: General Purpose Timer)

前置知识:GPIO基础(GPIO | i.MX6ULL芯片 | 基础学习

核心目标:用GPT实现高精度延时(us级/ms级)


一、GPT是什么?

一句话:GPT = 一个计数器。给时钟脉冲就 +1,数到某个值就触发事件。

  • i.MX6ULL 有 GPT1GPT2 两个通用定时器

  • 每个 GPT 是 32位向上计数器(0 → 0xFFFFFFFF → 0 → ...)

  • 12位预分频器(分频系数 1~4096)

  • 3路输出比较通道2路输入捕获通道(本笔记先聚焦延时,不展开捕获/比较输出)

与GPIO的关系

GPIO GPT
控制"引脚输出什么" 控制"什么时候输出"
配寄存器控制电平高低 配寄存器控制计数和定时
基础:IO方向+数据寄存器 进阶:时钟源+分频+计数

两者合起来就是嵌入式最基础的"定时控制"。


二、核心概念

2.1 延时公式(最重要,先记住)

延时时间 = 计数值 × 时钟周期
​
其中:时钟周期 = 1 / 时钟频率

实际配置思路

  1. 选时钟源 → 得到输入频率

  2. 设预分频 → 得到计数器频率

  3. 计数器频率确定后,每个计数值对应的时间就固定了

常用配置(本笔记采用)

时钟源:ipg_clk = 66MHz
预分频:66分频(PR寄存器写65)
→ 计数器频率 = 66MHz / 66 = 1MHz
→ 每计数1次 = 1μs
→ 延时1000μs → 计数值 = 1000

为什么这样配? 因为 1MHz 下每个计数正好是 1μs,计算最直观。

2.2 两种运行模式

模式 设置 行为 适用场景
Restart(重启) FRR=0 计数到 OCR1 值后自动清零重计 周期性定时中断
Free-Run(自由运行) FRR=1 一直计数到 0xFFFFFFFF 才回滚 延时函数(本笔记用这个)

做延时时选 Free-Run:计数器一直在跑,随时可以读当前值来算经过了多少时间。

2.3 时钟源选择

CLKSRC值 时钟源 频率 备注
000 关闭 -
001 ipg_clk 66MHz 本笔记使用
010 ipg_clk_highfreq ~132MHz
011 外部时钟 GPT_CLK 外部 需接外部时钟引脚
100 ipg_clk_32k 32kHz 低功耗场景
101 ipg_clk_24M 24MHz 晶振直供,不受系统分频影响

注意:24MHz 时钟(CLKSRC=101)适合需要精确时基的场景,因为它不受系统时钟配置影响。但本笔记先用 ipg_clk=66MHz 配合 66分频得到 1MHz,计算最简单。


三、寄存器地址

3.1 GPT基地址

定时器 基地址
GPT1 0x02098000
GPT2 0x020E8000

3.2 寄存器偏移

所有寄存器都是32位,相邻寄存器间隔4字节:

寄存器 偏移 读/写 作用
GPTx_CR 0x00 R/W 控制寄存器:开关、时钟源、模式、复位
GPTx_PR 0x04 R/W 预分频寄存器
GPTx_SR 0x08 R/W 状态寄存器:标志位(写1清除)
GPTx_IR 0x0C R/W 中断使能寄存器
GPTx_OCR1 0x10 R/W 输出比较寄存器1
GPTx_OCR2 0x14 R/W 输出比较寄存器2
GPTx_OCR3 0x18 R/W 输出比较寄存器3
GPTx_ICR1 0x1C R 输入捕获寄存器1
GPTx_ICR2 0x20 R 输入捕获寄存器2
GPTx_CNT 0x24 R 当前计数值(只读)

3.3 C语言地址宏定义

/* ====== GPT1 寄存器地址定义 ====== */
#define GPT1_BASE           0x02098000
​
#define GPT1_CR             (*(volatile unsigned int *)0x02098000)
#define GPT1_PR             (*(volatile unsigned int *)0x02098004)
#define GPT1_SR             (*(volatile unsigned int *)0x02098008)
#define GPT1_IR             (*(volatile unsigned int *)0x0209800C)
#define GPT1_OCR1           (*(volatile unsigned int *)0x02098010)
#define GPT1_OCR2           (*(volatile unsigned int *)0x02098014)
#define GPT1_OCR3           (*(volatile unsigned int *)0x02098018)
#define GPT1_ICR1           (*(volatile unsigned int *)0x0209801C)
#define GPT1_ICR2           (*(volatile unsigned int *)0x02098020)
#define GPT1_CNT            (*(volatile unsigned int *)0x02098024)
​
/* ====== GPT2 寄存器地址定义 ====== */
#define GPT2_BASE           0x020E8000
​
#define GPT2_CR             (*(volatile unsigned int *)0x020E8000)
#define GPT2_PR             (*(volatile unsigned int *)0x020E8004)
#define GPT2_SR             (*(volatile unsigned int *)0x020E8008)
#define GPT2_IR             (*(volatile unsigned int *)0x020E800C)
#define GPT2_OCR1           (*(volatile unsigned int *)0x020E8010)
#define GPT2_OCR2           (*(volatile unsigned int *)0x020E8014)
#define GPT2_OCR3           (*(volatile unsigned int *)0x020E8018)
#define GPT2_ICR1           (*(volatile unsigned int *)0x020E801C)
#define GPT2_ICR2           (*(volatile unsigned int *)0x020E8020)

如果用了 NXP 官方 MCIMX6Y2.h 头文件,可以直接用 GPT1->CR 这种结构体写法,上面这些宏就不需要了。


四、寄存器详解

4.1 控制寄存器 GPTx_CR(偏移 0x00)

这是最核心的寄存器,配 GPT 就是配它。

名称 含义
bit0 EN 使能:1=启动计数,0=停止
bit1 ENMOD 使能模式:0=停止时保持计数值,1=停止时清零计数值
bit2 DBGEN 调试模式使能
bit3 WAITEN 等待模式使能
bit4 DOZEN 休眠模式使能
bit5 STOPEN 停止模式使能
bit8:6 CLKSRC 时钟源选择(001=ipg_clk, 101=24MHz)
bit9 FRR 模式:0=Restart,1=Free-Run
bit10 EN_24M 使能24MHz晶振输入(选24MHz时钟源时才需要)
bit15 SWR 软件复位:写1复位GPT,完成后自动清0

做延时只需关注加粗的 5 个位。

配置示例(Free-Run + ipg_clk 66MHz):

// CLKSRC=001(0b001), FRR=1, EN=1
// 二进制:0 0000 0000 0010 0000 0101 = 0x0205
GPT1_CR = (1 << 6) | (1 << 9) | (1 << 0);
//        CLKSRC=001    FRR=1         EN=1

4.2 预分频寄存器 GPTx_PR(偏移 0x04)

名称 含义
bit11:0 PRESCALER 分频值(0~4095,实际分频系数 = 值+1)

关键:分频系数 = 寄存器值 + 1

PR寄存器值 实际分频系数 输入66MHz → 计数频率 每计数时间
0 1 66MHz 15.26ns
65 66 1MHz 1μs ← 本笔记用这个
23999 24000 2.75kHz 363.6μs
GPT1_PR = 65;  // 66分频:66MHz ÷ 66 = 1MHz → 每计数1次 = 1μs

4.3 状态寄存器 GPTx_SR(偏移 0x08)

名称 含义 清除方式
bit0 OF1 输出比较1标志 写1清除
bit1 OF2 输出比较2标志 写1清除
bit2 OF3 输出比较3标志 写1清除
bit3 IF1 输入捕获1标志 写1清除
bit4 IF2 输入捕获2标志 写1清除
bit5 ROV 回滚(溢出)标志 写1清除
​​​​​​​// 清除所有标志位(写1清除!不是写0)
GPT1_SR = 0x03F;  // bit5~bit0 全部写1

4.4 中断使能寄存器 GPTx_IR(偏移 0x0C)

名称 含义
bit0 OF1IE 输出比较1中断使能
bit1 OF2IE 输出比较2中断使能
bit2 OF3IE 输出比较3中断使能
bit5 ROVIE 回滚中断使能

做轮询延时不需要配中断,用中断方式时再设置对应位。

4.5 输出比较寄存器 GPTx_OCR1/2/3(偏移 0x10/0x14/0x18)

32位寄存器,存放比较值。计数值 = OCR值时触发比较事件。

做 Free-Run 延时时:把 OCR1 设为最大值,让计数器一直跑到溢出,不影响延时计算。

GPT1_OCR1 = 0xFFFFFFFF;  // 最大比较值,Free-Run模式不会因此复位

4.6 计数寄存器 GPTx_CNT(偏移 0x24)

只读。读它就能知道当前计数器的值。

延时函数的核心就是:读两次 CNT,算差值。


五、C语言实现延时函数

5.1 初始化

/*
 * GPT1初始化
 * 配置为 Free-Run 模式,ipg_clk(66MHz) 时钟源,66分频 → 1MHz(1μs/计数)
 */
void gpt1_init(void)
{
    /* 第1步:关闭GPT */
    GPT1_CR = 0;
​
    /* 第2步:软件复位 */
    GPT1_CR = (1 << 15);                      // SWR=1,触发复位
    while (GPT1_CR & (1 << 15));               // 等待复位完成(SWR自动清0)
​
    /* 第3步:配置时钟源和模式 */
    GPT1_CR = (1 << 6)     // CLKSRC=001 → ipg_clk(66MHz)
            | (1 << 9);     // FRR=1 → Free-Run模式
​
    /* 第4步:配置预分频 */
    GPT1_PR = 65;           // 66分频:66MHz/66 = 1MHz → 每计数1次=1μs
​
    /* 第5步:设置OCR1为最大值(Free-Run模式不需要比较中断) */
    GPT1_OCR1 = 0xFFFFFFFF;
​
    /* 第6步:清除所有状态标志 */
    GPT1_SR = 0x03F;
​
    /* 第7步:使能GPT,开始计数 */
    GPT1_CR |= (1 << 0);    // EN=1
}

初始化流程总结

  1. 关闭 → 2. 复位 → 3. 选时钟+模式 → 4. 设分频 → 5. 设OCR → 6. 清标志 → 7. 启动

5.2 微秒延时函数

/*
 * 微秒(μs)级延时
 * 原理:读两次CNT,算差值。处理溢出情况。
 */
void delay_us(unsigned int us)
{
    unsigned int old_cnt, new_cnt, elapsed = 0;
​
    old_cnt = GPT1_CNT;  // 记录起始计数值
​
    while (1) {
        new_cnt = GPT1_CNT;  // 读当前计数值
​
        if (new_cnt >= old_cnt) {
            // 未溢出:直接累加差值
            elapsed += (new_cnt - old_cnt);
        } else {
            // 溢出了(计数器从0xFFFFFFFF回滚到0)
            elapsed += (0xFFFFFFFF - old_cnt + new_cnt);
        }
​
        if (elapsed >= us)
            return;  // 已达到目标延时
​
        old_cnt = new_cnt;  // 更新基准值
    }
}

溢出处理是难点,画个图就清楚了

正常情况(未溢出):
old_cnt ──────→ new_cnt
elapsed = new_cnt - old_cnt
​
溢出情况:
old_cnt ──→ 0xFFFFFFFF ──→ 0 ──→ new_cnt
elapsed = (0xFFFFFFFF - old_cnt) + new_cnt

5.3 毫秒延时函数

/*
 * 毫秒(ms)级延时
 * 简单实现:循环调用delay_us(1000)
 */
void delay_ms(unsigned int ms)
{
    while (ms--) {
        delay_us(1000);
    }
}

5.4 使用裸地址的完整版本(不依赖MCIMX6Y2.h)

/* ====== 寄存器地址宏 ====== */
#define GPT1_CR    (*(volatile unsigned int *)0x02098000)
#define GPT1_PR    (*(volatile unsigned int *)0x02098004)
#define GPT1_SR    (*(volatile unsigned int *)0x02098008)
#define GPT1_OCR1  (*(volatile unsigned int *)0x02098010)
#define GPT1_CNT   (*(volatile unsigned int *)0x02098024)
​
/* 初始化 */
void gpt1_init(void)
{
    GPT1_CR = 0;
    GPT1_CR = (1 << 15);                    // 复位
    while (GPT1_CR & (1 << 15));            // 等待
    GPT1_CR = (1 << 6) | (1 << 9);         // ipg_clk + Free-Run
    GPT1_PR = 65;                           // 66分频→1MHz
    GPT1_OCR1 = 0xFFFFFFFF;
    GPT1_SR = 0x03F;                        // 清标志
    GPT1_CR |= (1 << 0);                   // 启动
}
​
/* us延时 */
void delay_us(unsigned int us)
{
    unsigned int old_cnt, new_cnt, elapsed = 0;
    old_cnt = GPT1_CNT;
    while (1) {
        new_cnt = GPT1_CNT;
        if (new_cnt >= old_cnt)
            elapsed += (new_cnt - old_cnt);
        else
            elapsed += (0xFFFFFFFF - old_cnt + new_cnt);
        if (elapsed >= us) return;
        old_cnt = new_cnt;
    }
}
​
/* ms延时 */
void delay_ms(unsigned int ms)
{
    while (ms--) delay_us(1000);
}

六、延时能力计算

单次最大延时

计数器32位,最大值 = 0xFFFFFFFF = 4,294,967,295
计数频率 = 1MHz(每计数1次 = 1μs)
​
单次最大延时 = 0xFFFFFFFF μs
            = 4,294,967,295 μs
            = 4,294,967 ms
            = 4,294.967 秒
            ≈ 71.58 分钟

也就是说,一次 delay_us 最多能延时约 71分钟,完全够用。

不同分频配置对比

时钟源 PR值 计数频率 每计数时间 单次最大延时
ipg_clk 66MHz 65 1MHz 1μs ~71.6分钟
ipg_clk 66MHz 6599 10kHz 100μs ~119.3小时
24MHz 晶振 23 1MHz 1μs ~71.6分钟
ipg_clk_32k 31 1kHz 1ms ~49.7天

七、常见问题

Q1:为什么要先复位GPT?

确保所有寄存器回到默认值,避免之前的配置干扰。就像GPIO初始化前先复位一样。

Q2:PR写65为什么是66分频?

因为实际分频系数 = 寄存器值 + 1。和STM32的预分频器一样。

  • 写0 = 1分频(不分频)

  • 写65 = 66分频

Q3:delay_us里为什么每次循环都要更新old_cnt?

因为每次循环只计算一小段经过的时间,累加起来。如果不更新,就只能算一次差值,精度差。

Q4:Free-Run和Restart有什么区别?

  • Restart:计数到OCR1值后自动清零,适合"每隔固定时间触发一次中断"

  • Free-Run:一直计数到最大值才回滚,适合"随时读取当前时间来计算延时"

Q5:选ipg_clk还是24MHz晶振?

  • ipg_clk(66MHz):配66分频得1MHz,计算简单,但依赖系统时钟配置

  • 24MHz晶振:配23分频得1MHz,更稳定(不受系统时钟影响),但需要设CLKSRC=101

初学建议用ipg_clk,等做精确时序(如1-Wire协议)时再换24MHz晶振。

Q6:用结构体写法怎么做?

// 如果用NXP官方MCIMX6Y2.h
GPT1->CR = 0;
GPT1->CR = (1 << 15);
while (GPT1->CR & (1 << 15));
GPT1->CR = (1 << 6) | (1 << 9);
GPT1->PR = 65;
GPT1->OCR[0] = 0xFFFFFFFF;  // 注意:数组写法 OCR[0]=OCR1
GPT1->SR = 0x03F;
GPT1->CR |= (1 << 0);

八、知识地图(和GPIO对比)

概念 GPIO GPT
核心功能 控制IO电平 精确计时
最核心寄存器 DR(数据)、GDIR(方向) CR(控制)、CNT(计数)
配置关键 复用+PAD+方向+数据 时钟源+分频+模式+启动
基地址 GPIO1=0x0209C000 GPT1=0x02098000
寄存器数量 8个 10个
复位方式 无专门复位 CR的SWR位(bit15)
状态查询 读PSR 读SR(标志位写1清)
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐