自学内容网 自学内容网

在IMX6ul中,使用GPT定时器实现高精度延时

        在上一节讲解过了。IMX6UL中的EPIT定时器,这一节我们讲解通用寄存器
        在STM32中,我们使用过SYSTICK来实现高精度的延时。IMX6U当中没有SYSTICK定时器,但是IMX6U有其他的定时器,前面的EPIT以及这一节我们将要使用的GPT定时器来实现高精度的延时
        官方对于GPT的描述是通用定时器(GPT)模块接口。它同样作为软件驱动程序编程的参考。GPT具有一个32位向上计数器。定时器计数值可以在外部引脚上的事件发生时被捕获到一个寄存器中。捕获触发可以被编程为上升沿或/和下降沿。GPT还可以在输出比较引脚上生成事件,并在定时器达到编程值时产生中断。GPT具有一个12位预分频器,该预分频器可以从多个时钟源提供可编程的时钟频率。

        GPT(General Purpose Timer)定时器是一个32位向上定时器(也就是从0X00000000),GPT定时器也可以和一个比较值进行比较,当计数器和这个值相等的话就发生比较事件,产生比较中断。GPT定时器有一个12位的分频器,可以对GPT定时器的shizhongyuan进行分频,它的特性如下
        ①、一个可以选择时钟源的32位向上计数器
        ②、两个输入捕获通道,可以设置触发模式
        ③、三个输出比较通道,可以设置输出模式
        ④、可以生成捕获中断、比较中断和溢出中断
        ⑤、计数器可以运行在重新启动(restart)或(自由运行)free-run模式

        我们就按照上面一个一个介绍它的特性,首先它有五个可选择的时钟源分别如下图

        分别是ipg_clk_24M、GPT_CLK(外部时钟)、ipg_clk、ipg_clk_32k和ipg_clk_highfreq。

        GPT定时器结构如下图所示

        从左上角开始,此部分位GPT为定时器的时钟源,之后我们会ipg_clk作为GPT定时器时钟源,继续往右边走,有一个12位的分频器,也就是和EPIT一样可以设置0~4095的分频,也是分别对应1~4096分频
        分频完成后,进入到Timer Counter也就是GPT定时器内部的32位计数器,通过预分频器输出的时钟信号驱动计数器递增或递减。计数器可以与外部输入同步,并能够触发中断或输出事件,然后就是两天总线,分别是Counter Value Bus(计数器值总线),Processor Data Bus(处理器数据总线)
        首先Counter Value Bus主要用于传递计数器的当前计数值。定时器的计数器在时钟脉冲下递增或者递减,计数器的值通过Counte Value Bus被实时传输到其他模块中。如比较器,用于将当前的计数值与设定的输出寄存器中的值进行比较,或者其他模块例如中断模块,以便在计数器达到某个特定值时产生中断信号或者其他事件,与计数器之间的关系是,Counter Value Bus 从计数器输出,代表计数器的状态,并通过该总线发送给框图中的其他逻辑单元(如比较器、捕获单元等)。通过这条总线,外部模块可以实时获取计数器的当前值,用于比较和逻辑判断。
        那么Processor Data Bus,向定时器发送控制命令或配置数据,例如配置定时器的初始计数值(通过Timer Input Register),设置比较寄存器中的值,用于与计数值的值相比较,读取定时器当前的计数值,以便处理器能够获知定时器的状态。与定时器的关系即是Processor Data Bus 与定时器中的输入输出寄存器交互,它可以影响定时器的工作状态,比如通过Timer Input Reg 写入初始值,或者通过设置比较值来控制定时器的行为。同时,定时器的状态(比如当前计数值)也可以通过这条总线传递回处理器。
        通俗来讲两条总线的区别就是,Timer Counter的当前计数值通过 Counter Value Bus 被实时传递到定时器内部的其他模块(例如比较器),Processor Data Bus负责处理器与定时器之间的数据通信,处理器通过这条总线配置定时器并读取定时器的计数值。计数器既向 Counter Value Bus 提供实时的计数值,也通过 Processor Data Bus 与处理器进行数据交互,处理器可以通过读写寄存器来影响计数器的工作。

        

        那么我们接着看回来,GPT_CAPTURE1和GPT_CAPTURE2,是GPT中的两个输入捕获通道,这个模块用来捕获外部输入信号的时间戳。每当捕获信号触发时,计数器对的当前值会存入对应的捕获寄存器(Timer Input Reg1 以及2),用于测量脉冲或外部事件的时间。例如两个信号之间的间隔时间。通俗来讲就是捕获通道用于获捕外部信号的特定事件(如下降沿或者下降沿),并记录下此时定时器的计数值。输入捕获一般还常用于测量PWM波形的频率、占空比、脉冲间隔等参数或者速度测量、位置检测等场合

        

        此部分为输出比较寄存器,一共有三路输出比较,因此有三个输出比较寄存器,输出比较寄存器是32位的。这些寄存器存储比较值。定时器的计数值会在这些寄存器中的值中进行比较。当计数值达到比较寄存器当中的值的时候,会触发输出事件或者中断。一般应用场景有生产一定频率和占空比的PWM波形,以及点击控制、LED调光、音频信号生成等
        实际的比较就在cmp中进行,OF1/OF2/OF3表示比较操作的输出标志,当计数器与某个输出寄存器匹配时,会触发相应的输出标志信号
        OM1,OM2,OM3这些是定时器输出比较事件的标志,当计数器与比较寄存器值相等时,触发相应的输出模块,输出信号(GPT_COMPARE1,GPT_COMPARE2,GPT_COMPARE3)。这可以用于产生定时信号或控制外设设备。
        Processor Interrupt Bus (处理器中断总线):定时器的溢出、比较匹配或者输入捕获事件都会通过中断总线通知处理器,触发相应的中断服务程序。这部分与外部事件和处理器之间的交互相关。

        那么GPT定时器的结构框图这里我们就讲解完成了,接着下一步就介绍GPT定时器有两种工作模式

        重新启动模式(restart): 当GPTx_CR(x=1 ,2)的FRR位清零的时候GPT工作在此模式。此模式下,当计数值和比较寄存器中的值相等的话计数值就会清零,然后从0X00000000开始向上重新计数,切记只有比较通道1才有此模式,向比较寄存器写入任何数据都会直接复位GPT计数器。对于其他两路比较通道(通道2和3),当发生比较事件以后不会复位计数器

        自然运行(free-run)模式:当GPTx_CR(x=1、2)寄存器的FRR位,置1的时候GPT定时器工作在此模式下,此模式适用于三个所有的比较通道,当比较事件发生以后并不会复位计数器,而是继续计数,直到计数值来到0XFFFFFFFF,然后再重新回到0X00000000

        下面就开始看一下对于GPT定时器比较重要的定时器,首先最开始的都是一个外设的配置寄存器GPTx_CR,需要用到的重比较重要的位如下

        首先是SWR(bit15)位,复位GPT定时器,向此位写1就可以复位GPT定时器,当GPT复位完成以后此为会自动清零
        这里翻译结果就是,软件复位: 这是GPT模块的软件复位。它是一个自清除位。
        当模块处于复位状态时,SWR位被置位。
        当复位过程完成时,SWR位被清除。
        设置SWR位会将所有寄存器复位为默认复位值,除了GPT控制寄存器中的EN、ENMOD、STOPEN、WAITEN和DBGEN位(此控制寄存器)。 0:GPT不处于复位状态 1:GPT处于复位状态


        FRR(bit9),运行模式的选择,在前面我们也介绍了GPT定时器有两个工作模式,位为零时比较通道1工作在重新启动模式(restart)模式。当此位为1的时候所有的三个比较通道均工作在自由运行模式(free-run)


        CLKSRC(bit8:6):GPT定时器时钟源选择位,为0的时候关闭时钟源;为1的时候选择ipg_clk作为时钟源:为2的时候选择ipg_clk_highfreq为时钟源;为3的时候选择外部时钟为时钟源;为4的时候选择ipg_clk_32k为时钟源;为5的时候选择ip_clk_24M为时钟源。这里的话我们选择ipg_clk作为GPT定时器的时钟源,因此此为设置为1,在之后的时候


        ENMOD(bit1):GPT使能模式,此位为零的时候如果关闭GPT定时器,计数器寄存器保存定时器关闭时候的计数值。此位为1的时候如果关闭GPT定时器,计数器寄存器就会清零

        EN(bit0):GPT使能位,为1的时候使能GPT定时器,为零的时候关闭GPT定时器

        下一个是GPT定时器的分频寄存器GPTx_PR,寄存器的结构图如下

        寄存器GPTx_PR我们用到的重要位就只有一个:PRESCALED(bit11:0),这就是12位分频值,可以设置0~4095,还是分别1~4096分频

        还有GPT定时器的状态寄存器GPTx_SR,此寄存器结构也是如图

        他的31:6位都是保留位,从第五位ROV(bit5),也就是回滚标志位,当计数值从0XFFFFFFFF回滚到0X00000000的时候此位置位1,一共有两路输入捕获通道,如果使用输入捕获中断的话需要在中断处理函数中清除此位
        IF2~IF1(bit4:3):输入比较中断标志位,当输出比较事件发生以后此位置1,一共有两路输入捕获通道。如果使用输入捕获中断的话需要在中断处理函数中清除此位


         OF3~OF1(bit2:0):输出比较中断标志位,当输出比较事件发生以后此位置1,一共有三路输出比较通道。如果使用输出比较中断的话需要在中断处理函数中清除此位。

        下一个是GPT定时器的计数寄存器GPTx_CNT,这个寄存器保存着GPT定时器的当前计数值。

        以及另外一个GPT定时器的输出比较寄存器GPTx_OCR,每个输出比较通道对应一个输出比较寄存器,因此一个GPT定时器有三个OCR寄存器,它们的作用都是相同的。例如输出比较通道1为例,它的输出比较寄存器为GPTx_OCR1,这是一个32位寄存器,用于存放32位的比较值。当计数值和寄存器GPTx_OCR1中的值相等就会产生比较事件,如果使能了比较中断的话就会触发相应的中断

        那么如何通过定时器去实现高精度的延时,它背后的原理是什么是我们需要去分析一下的
如果设置GPT定时器的时钟源为ipg_clk = 66Mhz,设置66分频那么进入GPT的最终频率就是66MHz / 66 = 1Mhz,周期为1us,也就是每进行一次计数就表示经过了1us,也就是计数十次就经过了10us。通过读取寄存器GPTx_CNT中的值就知道计时个数
        例如现在要延时100us,那么进入延时函数以后记录下寄存器GPTx_CNT中的值为200,当GPTx_CNT中的值为300的时候就表示100us过去了,也就是延时解释。GPTx_CNT是个32位寄存器,如果时钟为1MHz的话,GPTx_CNT最多可以实现0XFFFFFFFFus = 4294967295us约等于4294s约等于72min。也就是72分钟以后GPTx_CNT寄存器就会回滚到0X00000000,也就是溢出,所以需要在延时函数中处理溢出的情况,大概的原理就是如上   

        上面对原理进行了讲解,现在开始分析具体实现的措施
        
        1.设置GPT1定时器
         首先设置GPT1_CR寄存器的SWR(bit15)位来复位寄存器GPT1.复位完成以后设置寄存器GPT1_CR寄存器的CLKSRC(bit8:6),选择GPT1的时钟源为ipg_clk,设定定时器的工作模式

        2. 设置GPT1的分频值
         设置寄存器GPT1_PR寄存器的PRESCALAR(bit11:0)位,设置分频值

        3.设置GPT1的比较值
        如果要使用GPT1的输出比较中断,那么GPT1的输出比较寄存器GPT1_OCR1的值可以根据所需要的中断时间来设置。在教程中不使用比较输出中断,所以将GPT1_OCR1设置位最大值0XFFFFFFFF

        4. 使能GPT1定时器
        设置好GPT1定时器1以后就可以使能了,设置GPT1_CR的EN(bit0)位为1来使能GPT1定时器

        5. 编写延时函数
        GPT1定时器在使能后就开始运行了,可以根据前面介绍的原理来编写延时函数,针对us和ms延时分别编写两个延时函数

        思路大致如下,然后下面我会展示之前的延时函数,然后我们直接将原来的delay函数删除掉

#include "bsp_delay.h" //原delay.c延时函数

/*
 * @description: 短时间延时函数
 * @param - n: 要延时循环次数(空操作循环次数,模式延时)
 * @return : 无
 */
void delay_short(volatile unsigned int n)
{
while(n--){}
}

/*
 * @description: 延时函数,在396Mhz的主频下
 *     延时时间大约为1ms
 * @param - n: 要延时的ms数
 * @return : 无
 */
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7ff);
}
}

#ifndef __BSP_DELAY_H //原delay.h
#define __BSP_DELAY_H

#include "imx6ul.h"


/* 函数声明 */
void delay(volatile unsigned int n);

#endif

        先开始编写bsp_delay.c文件,第一步首先就是要初始化GPT,那么编写初始化函数

/*对GPT定时器进行初始化,并且选择时钟源*/
void delay_init()
{
GPT1->CR = 0;/*CR寄存器清零 */
GPT1->CR = 1<<15;/*定时器软复位*/
while((GPT1->CR >> 15) & 0x01);/*等待复位完成*/

/*
*GPT通用设置,bit22:bit20,输出比较1的输出功能关闭
*bit9:  设置定时器模式为Restart模式,当CNT等于OCR1时产生中断
*bit8:6 选择定时器时钟源ipg_clk = 66Mhz
*/
GPT1->CR = (1<<6);

/*
*GPTx_P寄存器,设置定时器分频
*bit11:0 设置为零表示为1分频,如果要设置为最大即0XFFFF,4096分频
*/
GPT1->PR = 65; /*这里设置66分频*/

/*
*GPTx_OCR寄存器
*GPT时钟为1Mz,计数器每次加1,就过去了1us
*将比较值设置为最大0XFFFFFFFF
*计时器会存在溢出的情况,0XFFFFFFFF=4294967296us=4295s=71.5min
*/
GPT1->CR[0] = 0XFFFFFFFF;
GPT1->CR |= 1<<0;

}

        首先设置GPTx_CR寄存器,这也是配置寄存器首先将此定时器进行软复位,在上面也对15位的软复位进行了一段解释,设置SWR位(15位)会将所有寄存器复位为默认复位值,除了GPT控制寄存器中的EN、ENMOD、STOPEN、WAITEN和DBGEN位

        然后第二段是一个等待循坏,直到复为完成,通过右移GPT控制寄存器(CR)15位,读取SWR位的值,然后与0X01进行按位与运算,只要SWR位仍为1(即模块处于复位状态),循环将持续执行。当复位完成,SWR位被自动清除为0,循环结束,程序继续执行。
        那么为什么这里需要专门去判断复位是否完成呢,例如硬件模块可能不仅仅是简单的寄存器清零,还包括整个模块的初始化,时钟同步等,都需要一定的时间完成,这么判断可以确保所有内部逻辑都完成复位。

        分频直接控制对应的GPTx_PR寄存器然后设置65,也就是对应的66分频,这个没有上什么好讲的,在下方我们将比较值设置到最大,因为如上图OCR寄存器为32位寄存器,所以最多对应0XFFFFFFFF,最后将GPT定时器进行使能,基本的初始化配置就已经完成了   


/*us级延时,最多延时0XFFFFFFFFus*/
void delay_us(unsigned int usdealy)
{
unsigned long oldcnt,newcnt;
unsigned long tcntvalue = 0;/*定义一个变量,存放经过总时间*/

oldcnt = GPT1->CNT;
while(1)
{
newcnt = GPT1->CNT;
if(newcnt > oldcnt)/*计时器向上计数,并且没有溢出*/
{
tcntvalue +=  newcnt - oldcnt;
}
else
{
tcntvalue += 0XFFFFFFFF-oldcnt+newcnt;
}
oldcnt = newcnt;
if(tcntvalue >= usdealy)
{
break;
}
}
}

        接下来就是编写延时函数,这段代码的主要作用就是实现微妙延时级别的一个函数,使用通过不断比较定时器的当前值和之前记录的值来判断经过的时间,并且在满足延时需求后退出循环
        先定义了两个变量,oldcnt用来记录计数器之前的值,newcnt用来记录计数器当前的值,以及tcnvalue用于累加计数器经过的时间(总时间),初始值为0

        然后在循环之前我们将GPT定时器的值传入到oldcnt中,作为延时过程的初始参考点,下一步编写一个无限循环不断读取当前计数器的值(newcnt),newcnt用来获取最新的计数器状态,然后需要判断定时器是否发生溢出,如果newcnt大于oldcnt,说明计数器正常递增且没有溢出。这时两者的差值就是经过的时间,累计添加到tcnvalue这个值当中
        如果newcnt小于oldcnt说明计数器已经发生了溢出,已经从最大值返回到0,继续计数。这个时候经过的时间就是(0xFFFFFFFF-oldcnt+newcnt),即从oldcnt到最大值0XFFFFFFFF的时间,再加上从0到newcnt的时间。同样累加到tcnvalue
        oldcnt = newcnt目的是更新旧计数器值,将当前newcnt值赋给oldcnt,以便下一次循环时,能够继续计算新的时间差
        最后见擦汗是否达到目标延时。如果累计的时间tcntvalue >= usdelay,达到了目标延时usdelay突出循环,延时结束

        这个函数还需要多讲解一下,为什么这么写,和定时器的的频率有什么关系,这些都是需要思考的问题,因为我们定时器得频率被设置为了1us,每次计数器得值增加1代表经过了1us。假设usdelay为15微妙,在while循环中,初始oldcnt为某个值,比如0经过一定时间后,假设newcnt变为15,在这种情况下,newcnt - oldcnt得差值为15-0=15,这说明在这次循环中已经经过了15微妙,也就是完成15次周期,直接将15加入tcntvalue,此时tcntvalue变为15,检查tcntvalue >=usdelay,条件成立就退出循环
        定时器频率如果为2us的话,我们这里依然假设usdelay为15微妙,在while循环中,初始oldcnt为某个值,比如0经过一定时间后,假设newcnt变为7,在这种情况下,newcnt - oldcnt的差值为7 -0 =7。由于每个计数代表2us,所以实际经过的时间为7*2 =14us,再次进入循环的话,假设定时器继续递增,newcnt更新为8。此时newcnt - oldcnt = 8 - 7 =1,这代表再增加2微秒。将2加入tcnvalue,此时tcnvalue变为16,价差tcnvalue >= usdelay,条件成立,所以从这里可以看出在频率为2us的情况下,计数器的增量只能表示2us,因此实际情况中无法实现任意的微妙级别延时,只能实现2us的倍数延时(如2、4、6)

        那么再举一个具体的例子就是,假设定时器频率分别为1us和2us,先说1us,通过每次计数得到的值可以精确表示 1us 的延迟。经过 15 次循环,每次增量 1us,总计 15us,符合目标延时。
        而对于2us,每次计数值增量为 1,实际表示 2us 的时间。目标 15us 不能被 2 整除,因此需要在 8 次计数时累加 16us(8*2us),这使得实际延迟为 16us,而不是 15us

        因此,定时器的频率直接影响到代码循环中每次读取 GPT1->CNT 的值变化与实际延迟时间之间的关系。定时器频率越高,能够实现的延迟时间越精确;而频率较低时,可能会限制可实现的延迟时间为某些特定值的倍数。在设计系统时,选择合适的定时器频率是确保系统能够执行精确延迟的重要因素。

        

#include "bsp_clk.h"

#include "bsp_delay.h"

#include "bsp_led.h"

#include "bsp_beep.h"

#include "bsp_key.h"

#include "bsp_int.h"

#include "bsp_exit.h"

#include "bsp_epittimer.h"

#include "bsp_keyfilter.h"



int main(void)

{

unsigned char state =OFF;



int_init();

imx6u_clkinit();

clk_enable();

led_init();

beep_init();

filterkey_init();



while(1)

{

state = !state;

led_switch(LED0,state);

delay(500);

}

return 0;

}

        main.c文件如上,然后Makefile文件就除了修改一下最后生成的.bin文件名字就可以了,然后最后将文件烧写进去,此次文件就完成了


原文地址:https://blog.csdn.net/Hetertopia/article/details/142854912

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!