zoukankan      html  css  js  c++  java
  • GPT定时器实现高精度延时—基于I.MX6UL嵌入式SoC

    1、前言

    在前面的文章《EPIT定时器中断实现—基于I.MX6UL嵌入式SoC》中,链接如下:

    https://www.cnblogs.com/Cqlismy/p/12977491.html

    介绍了EPIT定时器的基本概念以及使用思路,EPIT的全称为Enhanced Periodic Interrupt Timer,也就是增强型的周期中断定时器,它是一个32bit的定时器,主要用来完成周期性定时功能的,在嵌入式开发中,定时器是比较常用的外设,本文将继续了解I.MX6UL嵌入式SoC中功能更强大的GPT定时器。

    2、GPT定时器基本概述

    GPT的全称为General Purpose Timer,也就是通用定时器,该类定时器具有一个32bit递增的计数器,通过使用外部引脚的事件,定时器的计数值可以在相关的寄存器中被捕获,捕获触发的条件可以设置为上升沿或者下降沿,GPT定时器的计数值还能和指定的编程值进行比较,当值相等时,能发生比较事件,产生比较中断,GPT定时器还具有1个12位的预分频器,多路时钟源选择,通过12bit的分频器,能够选择合适的定时器计数频率,GPT定时器的特性如下:

    • 一个带有多路时钟源选择的32bit递增计数器;
    • 具有两路可编程触发沿的输入捕获通道;
    • 具有三路可编程输出模式的输出比较通道;
    • 在低功耗以及调试模式下可以编程为活动状态;
    • 捕获、比较以及溢出状态时都能产生中断;
    • 计数器能设置为Restart或者free-run模式运行。

     GPT定时器的多路时钟源选择如下:

    可以看到,总共有5路时钟源选择,分别为ipg_clk_24M、ipp_ind_clkin、ipg_clk、ipg_clk_32k以及ipg_clk_highfreq,常用的GPT定时器时钟源为ipg_clk(外设时钟)。

    GPT定时器的内部结构框图如下所示:

    图中各部分标号的含义为:

    • 为GPT定时器的多路选择时钟源,可以选择ipg_clk为定时器的时钟源输入;
    • 时钟源的12bit预分频器,通过相关寄存器可以设置为0~4095,对应着时钟1~4096分频;
    • 32bit的定时器递增计数器;
    • 定时器输入捕获通道1;
    • 定时器输入捕获通道2;
    • 输出比较寄存器,总共有3路输出比较通道;
    • 3路输出比较中断,当计数器的值达到比较值后,能触发输出比较中断。

     GPT定时器能够被编程运行在两种工作模式,分别是Restart和Free-Run模式,这两种工作模式的区别如下:

    • Restart模式:该工作模式可以通过GPTx_CR寄存器进行配置,在该模式下,当计数值达到比较值后,计数器将复位并从0x0000_0000重新开始计数,重新启动功能仅仅与比较通道1相关联,对通道1的比较寄存器进行任何写访问都将复位计数器;
    • Free-Run模式:当计数器工作在Free-Run模式时,当3个比较通道发生比较事件后,计数器将不会进行重启,相反,计数器将会继续计数直到0xFFFF_FFFF,然后进行翻转变为0x0000_0000。

    GPT定时器具有10个用户可以访问的32bit寄存器,这些寄存器能用来配置、操作以及监测GPT定时器的状态,接下来,对一些常用的寄存器进行讲解:

    首先是GPTx_CR寄存器,也叫做GPT控制寄存器,该寄存器的相关位描述如下:

    GPTx_CR寄存器中重要的bit如下:

    SWR(bit15):软件复位GPT定时器,向该bit写1能够复位定时器,定时器复位完成后,该bit自动清0;

    FRR(bit9):GPT定时器的运行模式选择,该bit设置为0时,GPT运行在Restart模式,该bit设置为1时,GPT运行在Free-Run模式;

    CLKSRC(bit[8:6]):GPT定时器时钟源选择bit,能够配置的选项如下:

    CLKSRC(bit[8:6]) 时钟源
    000 时钟关闭
    001 Peripheral Clock
    010 High Frequency Reference Clock
    011 External Clock
    100 Low Frequency Reference Clock
    others Reserved

    ENMOD(bit1):GPT定时器使能模式bit,该bit设置为0时,当GPT定时器关闭时,将会保存计数器中的值,该bit设置为1时,当GPT定时器关闭时,计时器中的值将会被清0;

    EN(bit0):GPT定时器使能bit,该bit设置为0时,定时器关闭,该bit设置为1时,定时器开启。

    接下来,则是GPTx_PR寄存器,也叫做GPT分频定时器,用来配置定时器时钟源分频系数的,该寄存器的描述如下:

    PRESCALER(bit[11:0]):定时器时钟源分频系数,能配置为0x000~0xFFF,对应着分频系数为1~4096分频。

    接下来,则是GPTx_SR寄存器,该寄存器也叫做状态寄存器,描述如下所示:

    ROV(bit5):计数器值回滚标志位,当计数器的值从0xFFFF_FFFF回滚到0x0000_0000时,该标志位置1,通过写1进行清0操作;

    IFx(bit[4:3]):输入捕获中断标志位,当输入捕获事件发生后,该标志位置1,如果使用了输入捕获中断的话,需要在中断函数中将该标志位进行清0操作;

    OFx(bit[2:0]):输出比较中断标志位,当输出比较事件发生后,该标志位置1,如果使用了输出比较中断的话,需要在中断函数中将该标志位进行清0操作。

    接下来,则是GPTx_IR寄存器,该寄存器也叫做中断寄存器,用来控制计数器值翻转、输入捕获、输出比较事件是否产生中断的,寄存器描述如下:

    ROVIE(bit5):该bit用于控制计数器值翻转是否产生中断,设置为0表示不产生中断,设置为1表示产生中断事件;

    IFxIE(bit[4:3]):用于控制输入捕获事件发生后是否产生中断,设置为0表示不产生中断,设置为1表示产生中断事件;

    OFxIE(bit[2:0]):用于控制输出比较事件发生后是否产生中断,设置为0表示不产生中断,设置为1表示产生中断事件。

    接下来,则是GPTx_OCRy(y=1~3)寄存器,也叫做输出比较寄存器,因为有3个输出比较通道,因此对应了3个类似的输出比较寄存器,该寄存器的值决定了什么时候产生输出比较事件,寄存器描述如下:

    COMP(bit[31:0]):比较值。

    接下来,就是GPTx_ICRy(y=1~2),也叫做输入捕获寄存器,因为有2个输入捕获通道,因此对应了2个类似的输入捕获寄存器,该寄存器是32bit的只读寄存器,用于保存输入捕获通道上一次捕获事件期间计数器中的值,寄存器描述如下:

    CAPT(bit[31:0]):捕获值。

    接下来,则是GPTx_CNT寄存器,也叫做计数寄存器,该寄存器就保存了当前定时器的计数值,描述如下:

    COUNT(bit[31:0]):定时器的当前计数值。

    3、高精度延时实现

    要想实现高精度的延时,可以借助于硬件定时器来实现,例如GPT定时器,如果我们设置定时器的时钟源为ipg_clk=66MHz,设置分频值为66,那么进入到定时器的最终频率为1MHz,也就是1us计数器递增1,每当递增1就表示过去了1us,递增了100就表示过去了100us,通过读取定时器的GPTx_CNT寄存器可以知道当前的计数值,例如,如果想要延时100us,假设进入到延时函数后读取到GPTx_CNT寄存器的值为300,那么当GPTx_CNT的值递增到400,就表示100us已经过去了,延时结束,由于GPTx_CNT是一个32bit的寄存器,当定时器的时钟为1MHz的话,那么最大计数为0xFFFF_FFFF,最大延时为4294967295us,在编写程序时需要考虑计数器溢出的情况,这时将会回滚到0x0000_0000,然后重新计数。

    GPT定时器实现高精度延时的编程步骤如下:

    • 配置GPT定时器时钟源和工作模式,首先需要设置GPTx_CR寄存器的SWR来复位定时器,复位完成后,需要配置寄存器的CLKSRC,选择GPT定时器的时钟源为ipg_clk,另外,还需要设置定时器的工作模式;
    • 配置GPT定时器分频值,通过设置GPTx_PR寄存器的PRESCALAR来配置定时器的分频值;
    • 配置GPT定时器的比较值,如果需要使用GPT定时器的输出比较中断功能的话,需要设置GPTx_OCRy寄存器的值,该值将决定输出比较事件什么时候产生;
    • 开启GPT定时器,通过配置GPTx_CR寄存器的EN来开启定时器;
    • 实现相应的延时函数。

    在NXP提供的SDK包的MCIMX6G2.h文件中,包含了GPT定时器的相关寄存器的封装,如下:

    /** GPT - Register Layout Typedef */
    typedef struct {
      __IO uint32_t CR;         /**< GPT Control Register, offset: 0x0 */
      __IO uint32_t PR;         /**< GPT Prescaler Register, offset: 0x4 */
      __IO uint32_t SR;         /**< GPT Status Register, offset: 0x8 */
      __IO uint32_t IR;         /**< GPT Interrupt Register, offset: 0xC */
      __IO uint32_t OCR[3];     /**< GPT Output Compare Register 1..GPT Output Compare Register 3, array offset: 0x10, array step: 0x4 */
      __I  uint32_t ICR[2];     /**< GPT Input Capture Register 1..GPT Input Capture Register 2, array offset: 0x1C, array step: 0x4 */
      __I  uint32_t CNT;        /**< GPT Counter Register, offset: 0x24 */
    } GPT_Type;
    
    /* GPT - Peripheral instance base addresses */
    /** Peripheral GPT1 base address */
    #define GPT1_BASE                                (0x2098000u)
    /** Peripheral GPT1 base pointer */
    #define GPT1                                     ((GPT_Type *)GPT1_BASE)
    /** Peripheral GPT2 base address */
    #define GPT2_BASE                                (0x20E8000u)
    /** Peripheral GPT2 base pointer */
    #define GPT2                                     ((GPT_Type *)GPT2_BASE)
    /** Array initializer of GPT peripheral base addresses */
    #define GPT_BASE_ADDRS                           { 0u, GPT1_BASE, GPT2_BASE }
    /** Array initializer of GPT peripheral base pointers */
    #define GPT_BASE_PTRS                            { (GPT_Type *)0u, GPT1, GPT2 }
    /** Interrupt vectors for the GPT peripheral type */
    #define GPT_IRQS                                 { NotAvail_IRQn, GPT1_IRQn, GPT2_IRQn }

    在编写GPT定时器实现高精度延时的裸机程序时,配置GPT定时器的相关寄存器时,可以直接使用GPT_Type*结构体指针,接下来,在前面的裸机基础上进行开发:

    进入到工程的bsp目录,新创建gptdelay目录:

    $ cd bsp/bsp
    $ mkdir gptdelay
    $ cd gptdelay
    $ touch bsp_gptdelay.h
    $ touch bsp_gptdelay.c

    新创建的bsp_gptdelay.h文件内容如下所示:

    #ifndef __BSP_GTPDELAY_H
    #define __BSP_GPTDELAY_H
    
    #include "imx6ul.h"
    
    void delay_init(void);
    void delay_us(unsigned int us);
    void delay_ms(unsigned int ms);
    
    #endif

    该文件主要是一些函数的声明,新创建的bsp_gptdelay.c文件内容如下:

    #include "bsp_gptdelay.h"
    
    /**
     * delay_init() - 延时初始化函数(GPT1定时器)
     * 
     * @param: 无
     * @return: 无
     */
    void delay_init(void)
    {
        GPT1->CR = 0;           /* GPT1_CR寄存器清0 */
        GPT1->CR |= 1 << 15;    /* GPT1定时器进入软复位状态 */
        while ((GPT1->CR >> 15) & 0x01);    /* 等待软复位完成 */
    
        /**
         * 配置GPT1_CR寄存器相关位
         * bit[22:20]: 000 输出比较功能关闭
         * bit[9]: 0 GPT1定时器工作于Restart模式
         * bit[8:6]: 001 GPT1时钟源选择ipg_clk=66MHz
         */
        GPT1->CR |= (1 << 6);
    
        /**
         * 配置GPT1_PR寄存器,设置时钟分频系数
         * bit[11:0]: 分频值,0x000~0xFFF代表1~4096分频
         */
        GPT1->PR = 65;      /* GPT1时钟频率为66M/(65+1)=1MHz */
    
        /**
         * 配置GPT1_OCR1寄存器,输出比较1的计数值
         */
        GPT1->OCR[0] = 0xFFFFFFFF;
    
        GPT1->CR |= (1 << 0);   /* 使能GPT1定时器开始计数 */
    }
    
    /**
     * delay_us() - us级别的延时函数
     * 
     * @us: 要延时的us数,最大延时为0xFFFFFFFFus
     * @return: 无
     */
    void delay_us(unsigned int us)
    {
        unsigned int oldcnt = 0;
        unsigned int newcnt = 0;
        unsigned int tcntvalue = 0; /* 过去的总时间 */
    
        oldcnt = GPT1->CNT;
        while (1) {
            newcnt = GPT1->CNT;
    
            if (newcnt != oldcnt) {
                if (newcnt > oldcnt)    /* 向上计数(计数值没有溢出) */
                    tcntvalue = tcntvalue + newcnt - oldcnt;
                else                    /* 计数值发生溢出 */
                    tcntvalue = tcntvalue + 0xFFFFFFFF - oldcnt + newcnt;
    
                oldcnt = newcnt;
    
                if (tcntvalue >= us)
                    break;
            }
        }
    }
    
    /**
     * delay_ms() - ms级别的延时函数
     * 
     * @ms: 要延时的ms数
     * @return: 无
     */
    void delay_ms(unsigned int ms)
    {
        unsigned int i;
        
        for (i = 0; i < ms; i++)
            delay_us(1000);
    }

    该文件中的delay_init()函数是用来初始化GPT1定时器的,delay_us()函数则是用来实现us级别的延时。

    app.c文件内容如下所示:

    #include "bsp_clk.h"
    #include "bsp_delay.h"
    #include "bsp_gpio.h"
    #include "bsp_led.h"
    #include "bsp_key.h"
    #include "bsp_int.h"
    #include "bsp_epit.h"
    #include "bsp_gptdelay.h"
    
    /**
     * main() - 主函数
     */
    int main(void)
    {
        unsigned char led2_state = ON;
    
        interrupt_init();           /* 中断初始化 */
        imx6ul_clk_init();          /* 初始化相关时钟 */
        system_clk_enable();        /* 系统外设时钟使能 */
        delay_init();               /* 硬件定时器延时初始化 */
        led_init();                 /* LED灯初始化 */
        epit1_init(66, 1000000);    /* EPIT1定时器初始化 */
    
        while (1) {
            led2_state = !led2_state;
            led_switch(LED2, led2_state);
            delay_ms(1000);
        }
    
        return 0;
    }

    main()函数比较简单,需要调用delay_init()函数进行GPT定时器的初始化,对于ms级别的延时,调用delay_ms()即可,最后,编译相应的文件生成可执行的.imx文件,烧写到I.MX6UL目标板中验证即可。

    4、小结

    本文主要简单介绍了I.MX6UL嵌入式SoC中的GPT定时器原理以及使用GPT定时器实现高精度延时简单实例。

  • 相关阅读:
    flutter项目目录介绍
    flutter真机调试出现flutter Launching 'app' on No Devices.
    flutter run出现 Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
    小程序uni-app中uview中select选择器第二次无法选中设置的默认值
    android studio编译flutter项目
    如何解决:Android Studio (version 4.1); Flutter plugin not installed and Dart plugin not installed errors
    CF Round 87
    CF #643(div.2)
    CF #642(div.3)
    CF #638(div.2)
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/13234708.html
Copyright © 2011-2022 走看看