# GPT定时器 ## GPT定时器介绍 GPT的英文是General Purpose Timer,即通用目的定时器。IMX6ULL有2个GPT定时器。 1. 功能介绍 - GPT具有32位递增计数器,GPT具有一个12位预分频器,它可以对多个时钟源的时钟进行分频。 - 输入捕获:外部引脚上的事件捕获到GPT寄存器中,这些输入事件可以设置为上升沿或下降沿触发,甚至双边沿触发。 - 输出比较:当定时器达到设定的值时,可以让定时器的输出引脚输出指定电平,或是产生中断。 2. GPT框图如下: ![](media/wps6.jpg) 3. 特性: - 一个带有时钟源选择的32位递增计数器,时钟源包括外部时钟; - 2个具有可编程触发沿的输入捕捉通道; - 三个输出比较通道,还有一个force compare功能; - 可以通过编程,让它在低功耗(low power)和调试(debug)模式下运行; - 可以在捕获(capture),比较(compare)和翻转(rollover)事件时产生中断; - 两种计数模式:重新启动(restart)或自由运行(free-run)模式。 ### 时钟源选择 | Clock name | Clock Root | Description | | ---------------- | ------------------ | -------------------------------------- | | ipg_clk | ipg_clk_root | Peripheral clock | | ipg_clk_32k | ckil_sync_clk_root | Low-frequency reference clock (32 kHz) | | ipg_clk_highfreq | perclk_clk_root | High-frequency reference clock | | ipg_clk_s | ipg_clk_root | Peripheral access clock | 上表是GPT模块会使用到的时钟,ipg_clk提供了Peripheral的时钟,ipg_clk_32k提供低频参考时钟,ipg_clk_highfreq提供了高频参考时钟,ipg_clk_s提供了访问控制器寄存器所需的时钟。 ![](media/wps7.jpg) 从上图可以看出,可以从4个时钟源中选择输入到预分频器的时钟,分别为: - 高频参考时钟(ipg_clk_highfreq); - 低频参考时钟(ipg_clk_32k); - 外围时钟(ipg_clk); - 外部时钟(GPT_CLK)或者晶体振荡器时钟(ipg_clk_24M)只能选择一个。 本章暂时不关注lower power mode的内容,实验里将`ipg_clk`作为预分频器的时钟源。`ipg_clk`怎么设置呢?需要查看时钟树,下图中的红色箭头就是时钟路线: ![](media/wps8.jpg) 根据IMX6ULLRM.pdf第18章的内容,可知PPL2又被称作System PLL、SYS PLL。除PLL2的输出外,它还有4个分相器的输出:PFD0~3。 从上图可知:最终我们想要的PERCLK_CLK_ROOT来源如下: ```bash OSC(晶振)------ | | ——————-》PERCLK_CLK_ROOT IPG_CLK_ROOT---| 这里选择IPG_CLK_ROOT作为PERCLK_CLK_ROOT的输入,而IPG_CLK_ROOT的来源如下 SYS PLL PFD2-》CBCD[RAHB_PODF]实验设置2,即3分频-》AHB_CLK_ROOT-》CBCDR[IPG_PODF]实验设置为1,即2分频-》IPG_CLK_ROOT ``` 本实验中SYS PLL PFD2是396M,所以`PERCLK_CLK_ROOT = 396M / 3 / 2 = 66M`。 #### 时钟源选择的操作流程 GPT_CR寄存器中的CLKSRC字段用于选择时钟源。要修改CLKSRC字段,需要先禁用GPT(EN = 0),这是理所当然的:GPT正在运行着你不能去修改它的时钟源。 更改GPT时钟源时,应该按照以下顺序编写程序: 1. 通过在GPT_CR寄存器中设置EN = 0来禁用GPT; 2. 禁用GPT中断寄存器(GPT_IR); 3. 将输出模式配置为未连接/断开连接:往GPT_CR中的OM1,OM2,OM3写0; 4. 禁用输入捕获模式:往GPT_CR的IM1和IM2中写入零; 5. 在GPT_CR寄存器中将时钟源CLKSRC更改为所需的值; 6. 将GPT_CR寄存器中的SWR位置1; 7. 清除GPT状态寄存器(GPT_SR)(该寄存器是往相应位写1清0); 8. 在GPT_CR寄存器中设置ENMOD = 1,以使GPT计数器为0x00000000; 9. 在GPT_CR寄存器中启用GPT(EN = 1); 10. 启用GPT中断寄存器(GPT_IR)。 ### GPT的计数模式 GPT有两种计数模式: 1. 重新启动计数模式(Restart mode) 在重启模式下(可通过GPT控制寄存器GPT_CR选择),当计数器达到比较值时,计数器将复位并从0x00000000重新开始计数。仅与比较通道1相关联。对通道1的比较寄存器的任何写操作都将复位GPT计数器。这样做是为了避免在进行计数时将比较值从较高的值更改为较低的值时可能丢失比较事件。对于其他两个比较通道,当发生比较事件时,计数器不会复位。 2. 自由运行模式(free-run mode): 在自由运行模式下,三个通道发生比较事件时,计数器不会复位;而是,计数器继续计数直到0xffffffff,然后翻转(变为0x00000000)。 ### GPT的操作 通用定时器(GPT)具有一个计数器(GPT_CNT),该计数器是32位递增计数器,在由软件启用该计数器后(EN = 1)开始计数,下文称之为主计数器。 还有一个预分频器计数器,是用来给GPT提供分频时钟的。 ![](media/wps9.jpg) 1. GPT的禁止与使能 - 当GPT(EN = 0)被禁止时,则主计数器和预分频器计数器将冻结其当前计数值。 - 当EN位置1且计数器再次使能时,ENMOD位决定这两个计数器是否重置为0: - 如果将ENMOD位置1:则当启用GPT(EN = 1)时,主计数器和预分频器计数器值将重置为0。 - 如果将ENMOD位设置为0:则当启用GPT(EN = 1)时,主计数器和预分频器计数器将从其冻结值继续计数。 2. 低功耗模式(Low Power Down) - 当GPT进入低功耗模式(STOP / WAIT)时,主计数器和预分频器计数器将冻结在其当前计数值 - 当GPT退出低功耗模式时,无论ENMOD位值如何,主计数器和预分频器计数器都将从其冻结值开始计数。 - 处理器可以随时读取GPT_CNT,并且两个输入捕获通道都使用相同的计数器GPT_CNT 3. 硬件复位将所有GPT寄存器复位为各自的复位值 除输出比较寄存器(OCR1,OCR2,OCR3)以外的所有寄存器的值均为0x0,比较寄存器复位为0xFFFF_FFFF。 4. 软件复位(GPT_CR控制寄存器中的SWR位)将复位所有寄存器位 EN,ENMOD,STOPEN,WAITEN和DBGEN,这些位的状态不受软件复位的影响。 注意:禁用GPT时可以进行软件复位操作。 ### GPT的输入捕获 GPT有两个输入捕获通道,每个输入捕获通道都有:一个专用的捕获引脚,捕获寄存器和输入边沿检测/选择逻辑。每个输入捕获功能都有一个状态标记位,并且可以向处理器发出中断服务请求。 我们先设置好捕获引脚的边沿检测/选择逻辑,当该引脚上发生指定的边沿转换时,GPT_CNT的内容被捕捉到相应的捕捉寄存器中,并设置适当的中断状态标志。如果该中断被使能了,它就可以产生中断。 有哪些边沿检测/选择逻辑?上升沿,下降沿,双边沿,或者禁用捕获。 捕获事件与计数器的时钟同步。假设当前正在记录一个事件,如果紧接着立刻发生另一个事件,那么第2个事件可能会丢失。当前事件正在记录时,至少一个时钟周期之后发生的事件,才能保证不丢失。可以随时读取输入捕获寄存器,而不会影响它们的值。具体时序图如下所示: ![](media/wps10.jpg) ### GPT的输出比较 GPT中有三个输出比较通道,它们都使用同一个计数器(GPT_CNT),输入捕获通道也是使用这个计数器。 软件上先设置输出比较寄存器,当GPT_CNT的值与输出比较寄存器匹配时,GPT就会设置“输出比较状态标志”(output compare status flag)为1,并产生中断。 并且,根据设置的不同,输出比较定时器对应的引脚将被置位(set)、清除(clear)、翻转(toggle)或者不受影响,或输出一个低脉冲(脉冲持续时间为定时器的时钟源的周期)。 还有一个“强制比较(forced-compare)”功能,一旦设置,就会马上产生比较事件;不管当前计数器值是否等于比较值。强制比较的产生的事件,跟正常的输出事件相同,只是它不会设置状态标记位并且不会产生中断。一旦设置force-compare位,该事件会即刻产生,这个位是自动清除的,读的话一直零。 下图是输出比较时的时序图: ![](media/wps11.jpg) ### GPT的中断 GPT可以产生6种不同的中断: 1. 翻转中断(Rollover Interrupt) 当GPT计数器达到0xffffffff,然后重新设置为0x00000000并继续计数时,将产生翻转中断。翻转中断通过GPT_IR寄存器中的ROVIE位来使能。相关的状态位是GPT_SR寄存器中的ROV位。 2. 输入捕获中断1、2 捕获事件发生后,相应的输入捕获通道会产生一个中断。 “捕获事件中断”通过IF2IE和IF1IE位(在GPT_IR寄存器中)使能;相应的状态位是IF2和IF1(在GPT_SR寄存器中)。 发生捕获事件时会产生中断,但是该中断无论是否被处理,都不会影响下次捕获事件的发生。当再次发生捕获事件时,无论之前的中断是否被处理了,捕获寄存器都会被再次更新。 3. 输出比较中断1、2、3 当比较事件发生后,相应的输出比较通道会产生一个中断。“比较事件中断”由OF3IE,OF2IE和OF1IE位(在GPT_IR寄存器中)使能;相应的状态位是OF3,OF2和OF1(在GPT_SR寄存器中)。 “强制比较(Force compare)”不会产生中断。 ## GPT寄存器介绍 ### GPT Control Register (GPTx_CR) GPT控制寄存器,用来配置GPT。 ![](media/wps12.jpg) | 位域 | 名 | 读写 | 描述 | | ------- | ------- | ---- | ------------------------------------------------------------ | | [31:29] | FO3,2,1 | R/W | 对应3个输出通道,用于设置强制比较(Force compare),
0:没影响写
1:强制比较,会导致相应的输出引脚状态发生变化,但是OFn不受影响 | | [28:26] | OM3 | R/W | 用来设置输出通道3的工作模式,000:输出引脚跟GPT断开,即输出引脚不受影响;
001:输出引脚翻转;
010:输出引脚清0;
011:输出引脚置位;
1xx:输出引脚产生一个低脉冲 | | [25:23] | OM2 | R/W | 用来设置输出通道2的工作模式,跟OM3类似 | | [22:20] | OM1 | R/W | 用来设置输出通道1的工作模式,跟OM3类似 | | [19:18] | IM2 | R/W | 用来设置输入通道2的工作模式,
00:捕获功能关闭;
01:捕获上升沿;
10:捕获下降沿;
11:同时捕获上升和下降沿 | | [15] | SWR | R/W | 软件复位,会自动清零,
a. GPT在复位状态时,该位自动置1
b. 复位结束时,该位自动清0
c. 设置该位为1时,会把所有寄存器设置为它们的默认值,GTPx_CR中这些位不受影响:EN、ENMOD、STOPEN、WAITEN、DBGEN | | [10] | EN_24M | R/W | 是否使用24M晶振作为GPT时钟,
0:不使用1:使用硬件复位时,该位被设置为0;软件复位不影响该位 | | [9] | FRR | R/W | 用来选择“Restart”模式或“Free-Run”模式,
0:Restart模式(比较事件发生后,计数值清0,重新计数)
1:Free-Run模式(比较事件不影响计数值,计数值到达0xFFFFFFFF才清0) | | [8:6] | CLKSRC | R/W | 时钟源选择,
000:时钟源断开;
001:Peripheral Clock (ipg_clk);
010:High Frequency Reference Clock (ipg_clk_highfreq);
011:External Clock;100:Low Frequency Reference Clock (ipg_clk_32k);
101:Crystal oscillator as Reference Clock (ipg_clk_24M) | | [5] | STOPEN | R/W | stop mode时GPT是否使能,
0:在stop mode下,GPT禁止
1:在stop mode下,GPT仍然使能 | | [4] | DOZEEN | R/W | Doze mode时GPT是否使能,
0:在doze mode下,GPT禁止
1:在doze mode下,GPT仍然使能 | | [3] | WAITEN | R/W | Wait mode时GPT是否使能,
0:在wait mode下,GPT禁止
1:在wait mode下,GPT仍然使能 | | [2] | DBGEN | R/W | Debug mode时GPT是否使能,
0:在debug mode下,GPT禁止
1:在debug mode下,GPT仍然使能 | | [1] | ENMOD | R/W | 当EPIT重新使能后,主计数器和预分频器计数器从什么值开始计数,
0:从上次关闭时的计数值继续计数;
1:主计数器、预分频计数器都从0开始计数 | | [0] | EN | R/W | GPT使能位,
0:GPT禁止;
1:GPT使能 | ### GPT Prescaler Register (GPTx_PR) GPT预分频寄存器,用来决定GPT时钟的分频系数。 ![](media/wps13.jpg) | 位域 | 名 | 读写 | 描述 | | ------- | ------------ | ---- | ------------------------------------ | | [15:12] | PRESCALER24M | R/W | 时钟源在24M晶振时,GPT时钟的分频系数 | | [11:0] | PRESCALER | R/W | 选择其它时钟源时,GPT时钟的分频系数 | ### GPT Status Register (GPTx_SR) GPT状态寄存器,用来显示计数值是否翻转、输入事件、输出事件是否发生了。 ![](media/wps14.jpg) | 位域 | 名 | 读写 | 描述 | | ----- | ------------- | ---- | ------------------------------------------------------------ | | [5] | ROV | R/W | 计数器的值是否到达了0xFFFFFFFF(Rollover),
0:Rollover未发生;
1:Rollover已发生 | | [4:3] | IF2、IF1 | R/W | 输入捕获通道2、1的事件是否已经发生,
0:未发生;
1:已发生 | | [2:0] | OF3、OF2、OF1 | R/W | 输出比较通道3、2、1的事件是否已经发生,
0:未发生;
1:已发生 | ### GPT Interrupt Register (GPTx_IR) GPT中断寄存器,用来设置翻转,输入和输出通道事件的中断使能位,与状态寄存器的位对应。 ![](media/wps15.jpg) | 位域 | 名 | 读写 | 描述 | | ----- | ------------------- | ---- | ------------------------------------------------------------ | | [5] | ROVIE | R/W | Rollover中断使能位(计数值达到0xFFFFFFFF),
0:Rollover中断禁止;
1:Rollover中断使能 | | [4:3] | IF2IE、IF1IE | R/W | 输入捕获通道2、1的中断使能位,
0:中断禁止;
1:中断使能 | | [2:0] | OF3IE、OF2IE、OF1IE | R/W | 输出比较通道3、2、1的中断使能位,
0:中断禁止;
1:中断使能 | ### GPT Output Compare Register 1~3 (GPTx_OCR1~3) GPT输出比较寄存器,有GPTx_OCR1-3共3个输出比较寄存器。 当计数器达到输出比较寄存器的值时,将在相应通道上产生事件。 当GPT使用Restart mode时,写入比较寄存器的同时会复位GPT计数器。 写寄存器的值在一个时钟周期后生效,读寄存器的值会立即返回。 ![](media/wps16.jpg) | 位域 | 名 | 读写 | 描述 | | ------ | ---- | ---- | ------ | | [31:0] | COMP | R/W | 比较值 | ### GPT Input Capture Register 1~2 (GPTx_ICR1~2) GPT输入捕获寄存器,有GPTx_ICR1-2共两个输入捕获寄存器。它们是只读寄存器,用于保存相应输入捕获通道中,发生捕获事件发生时计数器的值。 ![](media/wps17.jpg) | 位域 | 名 | 读写 | 描述 | | ------ | ---- | ---- | ----------------------------- | | [31:0] | CAPT | R/W | 发生捕获事件时,GPT计数器的值 | ### GPT Counter Register (GPTx_CNT) GPT计数器寄存器,主计数值,它是只读寄存器;读取GPT计数器的值,不影响计数过程。 ![](media/wps18.jpg) | 位域 | 名 | 读写 | 描述 | | ------ | ----- | ---- | ------------- | | [31:0] | COUNT | R/W | GPT计数器的值 | ## 基本的定时器实验 参见代码【14.gpt_timer】的`bsp_gpt2_base_timer.c`和`bsp_gpt1_base_timer.c` ### 源码分析 #### bsp_gpt2_base_timer.c 初始化gpt2为1M时钟,即1个tick是1us,比较通道1值为0XFFFFFFFF,即0XFFFFFFFFus才会溢出实际就是0XFFFFFFFFus = 4294967296us = 4295s = 71.5min后溢出,在bsp_delay中利用GPT2->CNT值实现`delay_us`、`delay_ms`函数的精准延时。 ```c #include "bsp_gpt_timer.h" #include "bsp_int.h" /* * @description : 延时有关硬件初始化,主要是GPT定时器 GPT定时器时钟源选择ipg_clk=66Mhz * @param : 无 * @return : 无 */ void gpt2_base_timer_init(void) { GPT2->CR = 0; /* 清零,bit0也为0,即停止GPT */ GPT2->CR = 1 << 15; /* bit15置1进入软复位 */ while((GPT2->CR >> 15) & 0x01); /*等待复位完成 */ /* * GPT的CR寄存器,GPT通用设置 * bit22:20 000 输出比较1的输出功能关闭,也就是对应的引脚没反应 * bit9: 0 Restart模式,当CNT等于OCR1的时候就产生中断,且计数器从0开始继续计数 * bit8:6 001 GPT时钟源选择ipg_clk=66Mhz * bit */ GPT2->CR = (1<<6); /* * GPT的PR寄存器,GPT的分频设置 * bit11:0 设置分频值,设置为0表示1分频, * 以此类推,最大可以设置为0XFFF,也就是最大4096分频 */ GPT2->PR = 65; /* 设置为65,即66分频,因此GPT2时钟为66M/(65+1)=1MHz */ /* * GPT的OCR1寄存器,GPT的输出比较1比较计数值, * GPT的时钟为1Mz,那么计数器每计一个值就是就是1us。 * 为了实现较大的计数,我们将比较值设置为最大的0XFFFFFFFF, * 这样一次计满就是:0XFFFFFFFFus = 4294967296us = 4295s = 71.5min * 也就是说一次计满最多71.5分钟,存在溢出 */ GPT2->OCR[0] = 0XFFFFFFFF; GPT2->CR |= 1<<0; //使能GPT2 } uint32_t gpt2_base_timer_count(void) { return GPT2->CNT; } ``` ```c void delay_us(unsigned int usdelay) { unsigned long oldcnt,newcnt; unsigned long tcntvalue = 0; /* 走过的总时间 */ oldcnt = gpt2_base_timer_count(); while(1) { newcnt = gpt2_base_timer_count(); if(newcnt != oldcnt) { if(newcnt > oldcnt) /* GPT是向上计数器,并且没有溢出 */ tcntvalue += newcnt - oldcnt; else /* 发生溢出 */ tcntvalue += 0XFFFFFFFF-oldcnt + newcnt; oldcnt = newcnt; if(tcntvalue >= usdelay)/* 延时时间到了 */ break; /* 跳出 */ } } } /* * @description : 毫秒(ms)级延时 * @param - msdelay : 需要延时的ms数 * @return : 无 */ void delay_ms(unsigned int msdelay) { int i = 0; for(i=0; iSR & (1<<0)) { //中断处理函数 BEEP_TOG(); } GPT1->SR |= 1<<0; /* 清除中断标志位 */ } /* * @description : 延时有关硬件初始化,主要是GPT定时器 GPT定时器时钟源选择ipg_clk=66Mhz * @param : 无 * @return : 无 */ void gpt1_base_timer_init(void) { GPT1->CR = 0; /* 清零,bit0也为0,即停止GPT */ GPT1->CR = 1 << 15; /* bit15置1进入软复位 */ while((GPT1->CR >> 15) & 0x01); /*等待复位完成 */ /* * GPT的CR寄存器,GPT通用设置 * bit22:20 000 输出比较1的输出功能关闭,也就是对应的引脚没反应 * bit9: 0 Restart模式,当CNT等于OCR1的时候就产生中断,且计数器从0开始继续计数 * bit8:6 001 GPT时钟源选择ipg_clk=66Mhz * bit */ GPT1->CR = (1<<6); /* * GPT的PR寄存器,GPT的分频设置 * bit11:0 设置分频值,设置为0表示1分频, * 以此类推,最大可以设置为0XFFF,也就是最大4096分频 */ GPT1->PR = 65; /* 设置为65,即66分频,因此GPT1时钟为66M/(65+1)=1MHz */ /* * GPT1时钟为1Mhz 就是1个tick是1us * 当计数器和比较寄存器GPT1->OCR[0]相等时候产生 中断 这里设置OCR[0] =1000000 即1s中产生一个中断 */ GPT1->OCR[0] = 1000000; /* * GPT的IR寄存器,使能通道1的比较中断 * bit0: 0 使能比较通道1输出比较中断 */ GPT1->IR |= 1 << 0; /* * 使能GIC里面相应的中断,并且注册中断处理函数 */ GIC_EnableIRQ(GPT1_IRQn); //使能GIC中对应的中断 system_register_irqhandler(GPT1_IRQn, (system_irq_handler_t)_gpt1_base_timer_irqhandler, NULL); //注册中断服务函数 GPT1->CR |= 1<<0; //使能GPT1 } ``` #### main.c文件 ```c int main(void) { int_init(); /* 初始化中断(一定要最先调用!) */ imx6u_clkinit(); rgb_led_init(); /* 初始化led */ uart_init(); /* 初始化串口,波特率115200 */ beep_init(); key_int_init(); gpt1_base_timer_init(); // 1s中断定时器 gpt2_base_timer_init(); // 精准delay延时用 printf("gpt base timer demo start c=%d\r\n",c); while(1) { delay_ms(1000); LED_RGB_RED_TOG(); } return 0; } ``` #### 实验现象 蜂鸣器1s钟响一下,红灯1s钟闪烁一下 ![](media/image-20210822153409035.png) ## 输入捕获实验 代写 ## 输出比较实验 代写