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 有 GPT1 和 GPT2 两个通用定时器

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

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

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

与GPIO的关系

GPIOGPT
控制"引脚输出什么"控制"什么时候输出"
配寄存器控制电平高低配寄存器控制计数和定时
基础: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 什么是时钟信号?

时钟信号 = 定时器的"心跳",一个固定频率的方波脉冲,每跳一次,计数器+1。

时钟信号:  ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
           └ ┘─└ ┘─└ ┘─└ ┘─└ ┘
           
每↑上升沿 → 计数器+1

为什么需要时钟?

没有时钟,定时器不知道"1秒"是多长。有了时钟,就知道:

延时时间 = 计数值 × 时钟周期

时钟周期 = 1 / 时钟频率

一句话:

时钟信号 = 定时器的节拍器,频率越精准,定时越准。

配好时钟源和分频,就知道"数几个数=多少时间",这就是定时的本质。

2.4 时钟源选择

CLKSRC值时钟源频率备注
000关闭-
001ipg_clk66MHz外设时钟,本笔记使用
010ipg_clk_highfreq~132MHz高频参考时钟
011GPT_CLK外部外部时钟,需接外部时钟引脚
100ipg_clk_32k32kHz低频参考时钟,低功耗场景
101ipg_clk_24M24MHz晶体振荡器,晶振直供,不受系统分频影响

注意:

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

2. 外部时钟(GPT_CLK)或者晶体振荡器时钟(ipg_clk_24M)只能选择一个。


三、寄存器地址

3.1 GPT基地址

定时器基地址
GPT10x02098000
GPT20x020E8000

3.2 寄存器偏移

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

寄存器偏移读/写作用
GPTx_CR0x00R/W控制寄存器:开关、时钟源、模式、复位
GPTx_PR0x04R/W预分频寄存器
GPTx_SR0x08R/W状态寄存器:标志位(写1清除)
GPTx_IR0x0CR/W中断使能寄存器
GPTx_OCR10x10R/W输出比较寄存器1
GPTx_OCR20x14R/W输出比较寄存器2
GPTx_OCR30x18R/W输出比较寄存器3
GPTx_ICR10x1CR输入捕获寄存器1
GPTx_ICR20x20R输入捕获寄存器2
GPTx_CNT0x24R当前计数值(只读)

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)
#define GPT2_CNT            (*(volatile unsigned int *)0x020E8024)

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


四、寄存器详解

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

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

位名称含义
bit0EN使能:1=启动计数,0=停止
bit1ENMOD使能模式:0=停止时保持计数值,1=停止时清零计数值
bit2DBGEN调试模式使能
bit3WAITEN等待模式使能
bit4DOZEN休眠模式使能
bit5STOPEN停止模式使能
bit8:6CLKSRC时钟源选择(001=ipg_clk, 101=24MHz)
bit9FRR模式:0=Restart,1=Free-Run
bit10EN_24M使能24MHz晶振输入(选24MHz时钟源时才需要)
bit15SWR软件复位:写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:0PRESCALER分频值(0~4095,实际分频系数 = 值+1)

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

PR寄存器值实际分频系数输入66MHz → 计数频率每计数时间
0166MHz15.26ns
65661MHz1μs ← 本笔记用这个
23999240002.75kHz363.6μs
GPT1_PR = 65;  // 66分频:66MHz ÷ 66 = 1MHz → 每计数1次 = 1μs

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

位名称含义清除方式
bit0OF1输出比较1标志写1清除
bit1OF2输出比较2标志写1清除
bit2OF3输出比较3标志写1清除
bit3IF1输入捕获1标志写1清除
bit4IF2输入捕获2标志写1清除
bit5ROV回滚(溢出)标志写1清除
// 清除所有标志位(写1清除!不是写0)
GPT1_SR = 0x03F;  // bit5~bit0 全部写1

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

位名称含义
bit0OF1IE输出比较1中断使能
bit1OF2IE输出比较2中断使能
bit2OF3IE输出比较3中断使能
bit5ROVIE回滚中断使能

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

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,算差值。



五、GPT设置流程(配置步骤详解)

使用GPT之前必须按顺序配置,下面是完整的设置流程:

步骤总览

第1步:关闭GPT(重启开发板,可取消这一步)
    ↓
第2步:软件复位(恢复默认值)
    ↓
第3步:选择时钟源(CLKSRC)
    ↓
第4步:选择运行模式(FRR)
    ↓
第5步:设置预分频(PR寄存器)
    ↓
第6步:设置比较值(OCR寄存器,可选)
    ↓
第7步:清除状态标志
    ↓
第8步:使能GPT,开始计数(EN=1)

每一步详解

第1步:关闭GPT

GPT1_CR = 0;  // EN=0,停止计数
  • 为什么要做:配置寄存器时不能让计数器在跑,否则状态不稳定

  • 做了什么:把控制寄存器清零,停止GPT

第2步:软件复位

GPT1_CR = (1 << 15);                // SWR=1,触发复位
while (GPT1_CR & (1 << 15));        // 等待复位完成
  • 为什么要做:把所有寄存器恢复到默认值,确保干净的初始状态

  • 做了什么:写1到SWR位,硬件自动复位所有寄存器,完成后SWR自动清0

  • 注意:必须等待SWR自动清0才能继续,用while循环检测

第3步:选择时钟源

GPT1_CR |= (1 << 6);  // CLKSRC=001,选择ipg_clk(66MHz)
  • 为什么要做:告诉GPT用什么频率的时钟来计数

  • 可选值:

    • 001 = ipg_clk(66MHz,系统时钟(外设时钟))

    • 101 = ipg_clk_24M(24MHz,晶振直供,更稳定)

    • 100 = ipg_clk_32k(32kHz,低功耗场景)

第4步:选择运行模式

GPT1_CR |= (1 << 9);  // FRR=1,Free-Run模式
  • 为什么要做:决定计数到顶后怎么办

  • 两种模式:

    • FRR=0(Restart):计数到OCR1值后自动清零,适合周期性中断

    • FRR=1(Free-Run):一直计数到0xFFFFFFFF才回滚,适合延时函数

第5步:设置预分频

GPT1_PR = 65;  // 66分频:66MHz ÷ 66 = 1MHz
  • 为什么要做:把高频时钟降到容易计算的低频

  • 计算方法:

    • 分频系数 = PR寄存器值 + 1(PR=65 → 66分频)

    • 计数频率 = 时钟频率 ÷ 分频系数

    • 66MHz ÷ 66 = 1MHz → 每计数1次 = 1μs(计算最直观)

第6步:设置比较值(可选)

GPT1_OCR1 = 0xFFFFFFFF;  // Free-Run模式设最大值
  • 为什么要做:Restart模式下,计数到OCR1值就复位;Free-Run模式下设最大值即可

  • 注意:做延时时通常不需要比较中断,这一步可以跳过

第7步:清除状态标志

GPT1_SR = 0x03F;  // bit5~bit0全部写1,清除所有标志
  • 为什么要做:确保没有残留的标志位干扰

  • 注意:状态寄存器是写1清除,不是写0

第8步:使能GPT

GPT1_CR |= (1 << 0);  // EN=1,启动计数
  • 为什么要做:前面都配好了,最后一步开始计数

  • 做了什么:设置EN位=1,计数器开始按照配置好的时钟频率计数

配置顺序为什么重要?

错误顺序:先配时钟 → 再复位 → 配置被清掉了!
正确顺序:先复位 → 再配置 → 最后使能

核心原则:

  1. 先关掉,再配置(避免配置时计数器乱跑)

  2. 先复位,再设参数(确保干净状态)

  3. 最后才使能(全部配好再启动)

完整配置示例(流程图)

┌─────────────────────────────────────┐
│  1. GPT1_CR = 0        (关闭GPT)    │
├─────────────────────────────────────┤
│  2. GPT1_CR = (1<<15)  (软件复位)    │
│     while等待SWR清0                  │
├─────────────────────────────────────┤
│  3. GPT1_CR |= (1<<6)  (选时钟源)    │
│     CLKSRC=001 → ipg_clk 66MHz      │
├─────────────────────────────────────┤
│  4. GPT1_CR |= (1<<9)  (选模式)      │
│     FRR=1 → Free-Run                │
├─────────────────────────────────────┤
│  5. GPT1_PR = 65       (设分频)      │
│     66分频 → 1MHz (1μs/计数)         │
├─────────────────────────────────────┤
│  6. GPT1_OCR1 = 0xFFFFFFFF (可选)   │
├─────────────────────────────────────┤
│  7. GPT1_SR = 0x03F    (清标志)      │
├─────────────────────────────────────┤
│  8. GPT1_CR |= (1<<0)  (启动GPT)    │
│     EN=1 → 开始计数                  │
└─────────────────────────────────────┘

六、C语言实现延时函数

6.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. 启动

6.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

6.3 毫秒延时函数

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

6.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 66MHz651MHz1μs~71.6分钟
ipg_clk 66MHz659910kHz100μs~119.3小时
24MHz 晶振231MHz1μs~71.6分钟
ipg_clk_32k311kHz1ms~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对比)

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

更多推荐