GPT | i.MX6ULL 芯片 | 基础学习
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的关系
| GPIO | GPT |
|---|---|
| 控制"引脚输出什么" | 控制"什么时候输出" |
| 配寄存器控制电平高低 | 配寄存器控制计数和定时 |
| 基础:IO方向+数据寄存器 | 进阶:时钟源+分频+计数 |
两者合起来就是嵌入式最基础的"定时控制"。
二、核心概念
2.1 延时公式(最重要,先记住)
延时时间 = 计数值 × 时钟周期 其中:时钟周期 = 1 / 时钟频率
实际配置思路:
-
选时钟源 → 得到输入频率
-
设预分频 → 得到计数器频率
-
计数器频率确定后,每个计数值对应的时间就固定了
常用配置(本笔记采用):
时钟源: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
}
初始化流程总结:
-
关闭 → 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清) |
更多推荐


所有评论(0)