• 十一.GPT定时器使用


    今天我们来学习一种新的定时器——GPT(General Purpose Timer通用定时器)。I.MX6UL可以通过GPT来实现高精度的定时效果。

    GPT定时器原理

    GPT和前面讲的EPIT的基础有很多相似度地方:

    计数器都是32位;

    • 时钟源可选择,但是GPT除过关闭时钟源还有5组可选择
    • 12位分频器,可选1~4096分频
    • 可通过设置比较值触发中断
    • 32位计数器
    • 有两种运行模式,但是稍微有些区别:分别是restar模式和free-run模式

    当然还是有些区别的

    • GPT是向上计数,从0开始每个时钟周期增加1
    • 可以设置3个比较值进行比较中断
    • 计数器溢出后会产生溢出中断
    • 除了比较中断和溢出中断,还有两个输入捕获通道,可以产生捕获中断

    GPT定时器在《IMX6ULL参考手册.pdf》Chapter 30General Purpose Timer (GPT)有很详细的介绍,首先看一下系统框架图

    GPT两种运行模式

    GPT有两种运行模式:restart模式和free-run模式,两种模式选择通过寄存器GPTx_CR中的FRR位[bit9]控制

    restart模式下,当计数器的值和比较寄存器值相等时计数器直接清零,然后从0x00000000开始重新自增。所以这种模式下只有比较通道1有效,并且在向比较通道1寄存器写入数据时GPT计数器都会复位。

    free-run模式,该模式适用于3个比价通道,比较事件触发中断后计数器不会复位而是直接数到0xFFFFFFFF,触发溢出中断后回滚到0x00000000。

    GPT的输出模式

    GPT的输出模式和EPIT差不多,直接从手册上截图,能看出来大致的流程,对应的IO也是可以设置高、低电平或反转模式

    GPT的寄存器

    I.MX6UL有两套GPT,先看下内存映射表

    我们只对一套GPT寄存器分析,可以看出来,GPT跟EPIT的寄存器构成差不多, 一组控制,一组分频器(GPT把分频器单独定义了一个寄存器设置),3个输出比较寄存器用来存放对应的比较值,2个捕获输入通道,还有一个计数器寄存器。

    控制寄存器

    GPTx_CR状态寄存器ControlRegister,32位,寄存器结构如下表

     这个寄存器内容有些多,按位说明有些事重复的我就省略了

    下面我们挑几个比较重要的讲一下

    bit[31:16]高16位都是设置输出、捕获等功能,我们暂时用不到。

    SWR[bit15]SoftWareReset 软件复位,通过对该位写1即可复位定时器,复位后该位自动清零

    EN_24M([bit10]) 24MHz时钟源使能,这个24MHz时钟源的分频器和另外一组时钟源的分频器是一组

    FRR(bit[9]) 运行模式,是为Free-Run mode

    CLKSRC(bit[8:6]) 时钟源选择

    ENMOD(bit[1]) 启动初始值,1时从0开始计数,0时从上一次disable时的计数器值开始计数

    EN(bit[0]) GPT使能

    回顾一下EPIT,可以发现使用方法都差不多。

    分频寄存器

    分频寄存器GPTx_PR,Prescaler Register,32位,高16位无效,bit[15:12]对24MHz时钟源进行最高16分频,最低12位对时钟源进行分频

     状态寄存器

    状态寄存器GPTx_SR Status Register,只有5位可以用

    ROV是回滚标志位Rollover Flag,当计数器从0x00000000累加到0xFFFFFFFF后该位置1,在Restar和Free-Run模式下都有效。(Restar模式应该是到达最大值。)

    IF1、2对应两个捕获事件发生

    OF3/2/1是三个比较事件发生。

    注意5个位都是w1c的,也就是如果我们需要用中断处理事件时要对该bit写1清除。

    中断寄存器

    GPTx_IR,5位可以用,跟前面状态寄存器的结构一样

    需要哪个时间可以触发中断,对该位置1即可。

    还有比较值寄存器1、2、3,捕获寄存器1、2,计数器寄存器都是32位的,保存对应的值就行了。没什么可讲的。

    GPT定时器使用

    gpt定时器可以用作中断触发另外的服务,也可以定制成高精度的延时定时器。

    GPT定时中断

    GPT用作中断的时候跟EPIT的方法基本一致,先初始化,再注册服务函数,最后使能定时器。

    这里跟教程上不太一样,我做了;一个通用的GPT定时器使用,直接放代码

    /**
     * @file bsp_gpt.c
     * @author your name (you@domain.com)
     * @brief GPT定时器
     * @version 0.1
     * @date 2022-01-14
     * 
     * @copyright Copyright (c) 2022
     * 
     */
    
    #include "bsp_gpt.h"
    
    /**
     * @brief               通用GPT定时器初始化
     * 
     * @param num           定时器编号1/2
     * @param flac          分频器0~4095
     * @param value         终止值
     */
    void gpt_init(int num,unsigned int flac,unsigned int value)
    {   
        volatile GPT_Type *gpt;             
        volatile unsigned int iRn;    
        if(num ==1)                     //判定选择哪个GPT
        {
            gpt = GPT1;                 
            iRn = GPT1_IRQn;
        }
    
        else if(num ==2)
        {
            gpt = GPT2;
            iRn = GPT2_IRQn;
        }
        else{}
    
        gpt->CR = 0;                   //清除GPT
        gpt->CR |= (1<<15);            //复位GPT
        while((gpt->CR >>15) & 0x01);  //复位结束,SWR回0,跳出while循环
    
            
        gpt->CR |= (1<<1) | (1<<6);    //初始化
    
        gpt->PR = flac;                //分频器
    
        gpt->OCR[0] = value;           //计数器中值
    
        gpt->IR = 1<<0;                //使能中断
    
        GIC_EnableIRQ(iRn);            //GIC使能GPT
    
        system_register_irqHandler(iRn,gpt_irqhandler,NULL);    //注册中断服务
        gpt->CR |= 1<<0;               //使能GPT
    
    }
    
    
    /**
     * @brief GPT中断服务函数
     * 
     * @param gicciar 中断ID号
     * @param param   参数
     */
    void gpt_irqhandler(unsigned int gicciar,void *param)
    {       
        if(gicciar == GPT1_IRQn)
        {
            gpt1_irqhandler();      //GPT1中断调用实际服务函数
            GPT1->SR |= (1<<0);     //清除GPT1_SR中断标志位
        }
        else if(gicciar == GPT2_IRQn)
        {
            gpt2_irqhandler();     //GPT2中断调用实际服务函数
            GPT2->SR |= (1<<0);    //清除GPT2_SR中断标志位
        }
    }
    
    
    void gpt1_irqhandler(void)
    {
        static unsigned char status=OFF;
        status = !status;
        beep_switch(status);
    }
    
    void gpt2_irqhandler(void)
    {
        static unsigned char status=OFF;
        status = !status;
        led_switch(LED0,status);
    }

     这个确实也没什么可讲到,这是个低版本的定时中断功能,GPT还有很多其他的作用这里没写,就是那几个寄存器,直到怎么用就可以了。初始化函数在头文件里声明一下就可以用了。

    想要用哪个定时器,在main.c里直接初始化就可以

    gpt_init(1,65,500000);
    gpt_init(2,65,1000000);

    下面是头文件

    #ifndef __BSP_GPT_H
    #define __BSP_GPT_H
    
    #include "imx6ul.h"
    #include "bsp_int.h"
    
    /*调用底层硬件驱动*/
    #include "bsp_beep.h"
    #include "bsp_led.h"
    
    /*声明终端处理函数*/
    
    void gpt_irqhandler(unsigned int gicciar,void *param);
    
    
    
    void gpt1_irqhandler(void);
    void gpt2_irqhandler(void);
    #endif
    bsp_gpt.h

    其实还可以解藕一下,初始化的函数一被调用,定时器就启动了,其实应该把初始化、定时器启动、定时器停止分开模块化。这里不再细分了。

    高精度定时器

    我们最开始闪烁LED的时候写了一个延时函数,那个函数是通过一个变量自减实现的,也就是个软件延时,而软件延时跟CPU主频什么的关系比较大,再更改CPU主频后肯定就不准了。而GPT是个定时器,我们可以利用定时器的特性实现延时功能。

    /*
    * @description          :基于GPT1的延时功能初始化
    * @param                :None
    * @return               :None
    */
    void delay_init(void)
    {   
        /*先对通过对SWR标志位写1复位*/
        GPT1->CR = 0;
        GPT1->CR |= (1<<15);
        while((GPT1->CR >>15) & 0x01);  //复位结束,SWR回0,跳出while循环
    
        GPT1->CR |= 1<<1 | 1<<6;        //设置ENMOD=1,时钟源为IPG_CLK
         
        GPT1->PR = 65;                  //66分频,周期为1M
    
        GPT1->OCR[0] = 0xFFFFFFFF;      //输出比较1寄存器
    
        GPT1->CR |= 1<<0;               //使能GPT1定时器
    }
    
    /**
     * @brief           微秒精度定时器
     * 
     * @param us        定时微秒值
     */
    void delay_us(unsigned int us)
    {
        unsigned long oldcnt,newcnt;
        unsigned long tcntvalue = 0;
    
        oldcnt = GPT1->CNT;                                //函数启动是GPT1计数值 
    
        while(1)
        {
            newcnt = GPT1->CNT;                            //当前计数值             
            if(newcnt != oldcnt)
            {
                if(newcnt>oldcnt)                          //未溢出
                {
                    tcntvalue += newcnt-oldcnt;            //newcnt-oldcnt=1,tcntvalue加1           
                }
                else                                       //溢出
                {
                    // tcntvalue += 0xFFFFFFFF -oldcnt + newcnt;  //正点原子教程上的代码
                    tcntvalue += 0xFFFFFFFF -oldcnt + newcnt +1;
                }
    
                oldcnt = newcnt;                           
                if(tcntvalue >= us)                       //延时值超过指定微秒值
                {
                   break; 
                }
            } 
        }
    }
    
    /**
     * @brief                毫秒精度定时器  
     * 
     * @param ms             定时毫秒值
     */
    void delay_ms(unsigned int ms)
    {
        int i=0;
        for(i=0;i<ms;i++)
        {delay_us(1000);}
    }

    初始化过程中值定义了时钟源、分频器和OCR1的值(也可以不定义OCR1,设定工作模式为free-run模式),这样,GPT的计数器就可以从0跑到0xFFFFFFFF,分频器值为65,对应66分频,时钟源是外设时钟源(IPG_CLK),时钟周期66MHz,对其进行66分频刚好计数器加1时间为1us。我们只需要计算counter的值就行了。函数里定义了3个变量,分别表示这个指令周期counter的值,上个指令周期counter的值以及总数。这里有个点要注意,GPT的计数器在跑到0xFFFFFFFF是会溢出后回到0的,要注意溢出时那个周期,oldcnd值为0xFFFFFFFF,newcnt的值为0,那么这时候再用newcnt-oldcnt值就跑远了,这里要修正一下。根据正点原子的教程上提供的代码,是这样的

    tcntvalue += 0xFFFFFFFF -oldcnt + newcnt;

    我觉得这里应该是少加了个1,因为溢出后newcnt值应该是0,oldcnt值为0xFFFFFFFF,结果这个指令周期tcntvalue累加值为0。应该是不对的。

    我用示波器对这个延时函数进行了测量,这种延时的用法要比中断精度差一些,但是精确到ms还是可以的。还有一点,也是个延时函数,也就是说以旧需要占用CPU资源的,(us的是阻塞在while循环里,ms的是阻塞在for循环里)。

  • 相关阅读:
    转:神经网络入门
    转:Webkit Flex伸缩盒模型属性备忘
    css3 display:-webkit-box
    display:inline和display:block及html常用标签
    display
    weui flex 分布
    图片、字体、iconfont矢量图
    flex weui列表demo
    方法调用
    C#多线程之Task
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/15800245.html
Copyright © 2020-2023  润新知