NXP (I.MX6ULL) GPT高精度延时定时器

延时函数是很常用的API 函数,在前面的实验中我们使用循环来实现延时函数,但是使用
循环来实现的延时函数不准确,误差会很大。虽然使用到延时函数的地方精度要求都不会很严
格(要求严格的话就使用硬件定时器了),但是延时函数肯定是越精确越好,这样延时函数就可
以使用在某些对时序要求严格的场合。本章我们就来学习一下如何使用硬件定时器来实现高精
度延时。

高精度延时简介

这里是引用
在这里插入图片描述
在这里插入图片描述

GPT 定时器简介

学过STM32 的同学应该知道,在使用STM32 的时候可以使用SYSTICK 来实现高精度延
时。I.MX6U 没有SYSTICK 定时器,但是I.MX6U 有其他定时器啊,比如第十八章讲解的EPIT
定时器。本章我们使用I.MX6U 的GPT 定时器来实现高精度延时,顺便学习一下GPT 定时器,
GPT 定时器全称为General Purpose Timer。
GPT 定时器是一个32 位向上定时器(也就是从0X00000000 开始向上递增计数),GPT 定时
器也可以跟一个值进行比较,当计数器值和这个值相等的话就发生比较事件,产生比较中断。
GPT 定时器有一个12 位的分频器,可以对GPT 定时器的时钟源进行分频,GPT 定时器特性如
下:

  • ①、一个可选时钟源的 32 位向上计数器。

  • ②、三个输出比较通道,可以设置输出模式。

  • ③、两个输入捕获(配置引脚复用)通道,可以设置触发方式。EPIT定时器只能定时,不能捕获。

  • ④、可以生成捕获中断、比较中断和溢出中断。

  • ⑤、计数器可以运行在重新启动(restart)或(自由运行)free-run 模式。

GPT 定时器的可选时钟源如图20.1.1.1 所示:

在这里插入图片描述
图20.1.1.1 GPT 时钟源

在这里插入图片描述

在这里插入图片描述

从图20.1.1.1 可以看出一共有五个时钟源,分别为:ipg_clk_24M、GPT_CLK(外部时钟)、
ipg_clk、ipg_clk_32k 和ipg_clk_highfreq。本例程选择ipg_clk 为GPT 的时钟源,ipg_clk=66MHz。
GPT 定时器结构如图20.1.1.2 所示:

在这里插入图片描述
图20.1.1.2 GPT 定时器结构图

GPT 定时器结构中各部分意义如下:

①、此部分为 GPT 定时器的时钟源,前面已经说过了,本章例程选择 ipg_clk 作为 GPT 定时器时钟源。

②、此部分为 12 位分频器,对时钟源进行分频处理,可设置 0~ 4095,分别对应 1~ 4096 分频。

③、经过分频的时钟源进入到 GPT 定时器内部 32 位计数器

④和⑤、这两部分是 GPT 的两路输入捕获通道(配置引脚复用),本章不讲解 GPT 定时器的输入捕获。

⑥、此部分为输出比较寄存器,一共有三路输出比较,因此有三个输出比较寄存器,输出比较寄存器是 32 位的。

⑦、此部分位输出比较中断,三路输出比较中断,当计数器里面的值和输出比较寄存器里面的比较值相等就会触发输出比较中断。

GPT 定时器有两种工作模式:重新启动(restart)模式和自由运行(free-run)模式,这两个工作
模式的区别如下:

重新启动(restart)模式和自由运行(free-run)模式,这两个工作模式的区别如下:

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

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

接下来看一下GPT 定时器几个重要的寄存器,第一个就是GPT 的配置寄存器GPTx_CR,
此寄存器的结构如图20.1.1.3 所示:

在这里插入图片描述
寄存器 GPTx_CR 我们用到的重要位如下:

描述
SWR(bit15)软件复位 ,向此位写 1 就可以复位 GPT 定时器,当 GPT 复位完成以后此位会自动清零。
FRR(bit9)运行模式选择,当此位为 0 的时候比较通道 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(0b001)。
ENMOD(bit1)GPT 使能模式,此位为 0 的时候如果关闭 GPT 定时器,计数器寄存器保存定时器关闭时候的计数值(记忆功能)。此位为 1 的时候如果关闭 GPT 定时器,计数器寄存器就会清零
EN(bit0)GPT 使能位,为 1 的时候使能 GPT 定时器,为 0 的时候关闭 GPT 定时器。

接下来看一下GPT 定时器的分频寄存器GPTx_PR,此寄存器结构如图20.1.1.4 所示:

在这里插入图片描述
图20.1.1.4 寄存器GPTx_PR 寄存器

寄存器GPTx_PR 我们用到的重要位就一个:(低12位):

描述
PRESCALER(bit11:0)这就是 12 位分频值,可设置 0~ 4095,分别对应 1~4096 分频。

接下来看一下GPT 定时器的状态寄存器GPTx_SR,此寄存器结构如图20.1.1.5 所示:

在这里插入图片描述
图20.1.1.5 GPTx_SR 寄存器结构

寄存器 GPTx_SR 重要的位如下:

描述
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 的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL 参
考手册》第1432 页的30.6 小节。

定时器实现高精度延时原理

高精度延时函数的实现肯定是要借助硬件定时器,使用 GPT 定时器来实现高精度延时。如果设置 GPT 定时器的时钟源为 ipg_clk=66MHz,设置 66 分频,那么进入 GPT定时器的最终时钟频率就是 66/66=1MHz,周期为 1us。 GPT 的计数器每计一个数就表示“过去”了 1us。如果计 10 个数就表示“过去”了 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。设置定时器GPT1的工作模式,

2、设置GPT1 的分频值
设置寄存器GPT1_PR 寄存器的PRESCALAR(bit111:0)位,设置分频值。

3、设置GPT1 的比较值
如果要使用GPT1 的输出比较中断,那么GPT1 的输出比较寄存器GPT1_OCR1 的值可以根据所需的中断时间来设置。本章例程不使用比较输出中断,所以将GPT1_OCR1 设置为最大值,即:0XFFFFFFFF。

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

5、编写延时函数
GPT1 定时器已经开始运行了,可以根据前面介绍的高精度延时函数原理来编写延时函数,针对us 和ms 延时分别编写两个延时函数。

硬件原理分析

本试验用到的资源如下:

①、一个LED 灯:LED0。
②、定时器GPT1。

本实验通过高精度延时函数来控制LED0 的闪烁,可以通过示波器来观察LED0 的控制IO输出波形,通过波形的频率或者周期来判断延时函数精度是否正常。

实验程序编写

本实验对应的例程路径为:开发板光盘-> 1、裸机例程-> 12_highpreci_delay。
本章实验在上一章例程的基础上完成,更改工程名字为“delay”,直接修改bsp_delay.c 和bsp_delay.h 这两个文件,将bsp_delay.h 文件改为如下所示内容:

bsp_delay.h

#ifndef __BSP_DELAY_H
#define __BSP_DELAY_H
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名	: 	 bsp_delay.h
作者	   : 左忠凯
版本	   : V1.0
描述	   : 延时头文件。
其他	   : 无
论坛 	   : www.wtmembed.com
日志	   : 初版V1.0 2019/1/4 左忠凯创建
		
		 V2.0 2019/1/15 左忠凯修改
		 添加了一些函数声明。
***************************************************************/
#include "imx6ul.h"


/* 函数声明 */
void delay_init(void);
void delayus(unsigned    int usdelay);
void delayms(unsigned	 int msdelay);
void delay(volatile unsigned int n);
void gpt1_irqhandler(void);

#endif


bsp_delay.h 文件就是一些函数声明,很简单。在文件bsp_delay.c 中输入如下内容:

bsp_delay.c

#include "bsp_delay.h"

/*
 * @description	: 延时有关硬件初始化,主要是GPT定时器
				  GPT定时器时钟源选择ipg_clk=66Mhz
 * @param		: 无
 * @return 		: 无
 */
void delay_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的时候就产生中断
     * 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 */

	 /*
      * GPT的OCR1寄存器,GPT的输出比较通道1比较计数值,
      *	GPT的时钟为1Mz,那么计数器每计一个值就是就是1us。
      * 为了实现较大的计数,我们将比较值设置为最大的0XFFFFFFFF,
      * 这样一次计满就是:0XFFFFFFFFus = 4294967296us = 4295s = 71.5min
      * 也就是说一次计满最多71.5分钟,存在溢出
	  */
	GPT1->OCR[0] = 0XFFFFFFFF;

	GPT1->CR |= 1<<0;			//使能GPT1

	/* 一下屏蔽的代码是GPT定时器中断代码,
	 * 如果想学习GPT定时器的话可以参考一下代码。
	 */
#if 0
	/*
     * GPT的PR寄存器,GPT的分频设置
     * bit11:0  设置分频值,设置为0表示1分频,
     *          以此类推,最大可以设置为0XFFF,也就是最大4096分频
	 */
	GPT1->PR = 65;	//设置为1,即65+1=66分频,因此GPT1时钟为66M/66=1MHz


	 /*
      * GPT的OCR1寄存器,GPT的输出比较1比较计数值,
      * 当GPT的计数值等于OCR1里面值时候,输出比较1就会发生中断
      * 这里定时500ms产生中断,因此就应该为1000000/2=500000;
	  */
	GPT1->OCR[0] = 500000;//500ms产生中断

	/*
     * GPT的IR寄存器,使能通道1的比较中断
     * bit0: 0 使能输出比较中断
	 */
	GPT1->IR |= 1 << 0;

	/*
     * 使能GIC里面相应的中断,并且注册中断处理函数
	 */       //宏定义里中断ID是87
	GIC_EnableIRQ(GPT1_IRQn);	//使能GIC中对应的中断
	system_register_irqhandler(GPT1_IRQn, (system_irq_handler_t)gpt1_irqhandler, NULL);	//注册中断服务函数	
#endif
	
}

#if 0
/* 中断处理函数 */
void gpt1_irqhandler(void)
{ 
	static unsigned char state = 0;

	state = !state;

	/*
     * GPT的SR寄存器,状态寄存器
     * bit2: 1 输出比较1发生中断
	 */
	if(GPT1->SR & (1<<0)) 
	{
		led_switch(LED2, state);
	}
	
	GPT1->SR |= 1<<0; /* 清除中断标志位 */
}
#endif
 
/*
 * @description		: 微秒(us)级延时
 * @param - value	: 需要延时的us数,最大延时0XFFFFFFFFus
 * @return 			: 无
 */
void delayus(unsigned    int usdelay)
{
	unsigned long oldcnt,newcnt;
	unsigned long tcntvalue = 0;	/* 走过的总时间  */

	oldcnt = GPT1->CNT;
	while(1)
	{
		newcnt = GPT1->CNT;
		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 delayms(unsigned	 int msdelay)
{
	int i = 0;
	for(i=0; i<msdelay; i++)
	{
		delayus(1000);
	}
}

/*
 * @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);
	}
}

文件bsp_delay.c 中一共有5 个函数,分别为:delay_init、delayus、delayms 、delay_short和delay。除了delay_short 和delay 以外,其他三个都是新增加的。函数delay_init 是延时初始化函数,主要用于初始化GPT1 定时器,设置其时钟源、分频值和输出比较寄存器值。第43 到68 行被屏蔽掉的程序是GPT1 的中断初始化代码,如果要使用GPT1 的中断功能的话可以参考此部分代码。第73 到89 行被屏蔽掉的程序是GPT1 的中断处理函数gpt1_irqhandler,同样的,如果需要使用GPT1 中断功能的话可以参考此部分代码。

函数delayus 和delayms 就是us 级和ms 级的高精度延时函数,函数delayus 就是按照我们在20.1.2 小节讲解的高精度延时原理编写的,delayus 函数处理GPT1 计数器溢出的情况。函数delayus 只有一个参数usdelay,这个参数就是要延时的us 数。delayms 函数很简单,就是对delayus(1000)的多次叠加,此函数也只有一个参数msdelay,也就是要延时的ms 数。

最后修改main.c 文件,内容如下:

#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_keyfilter.h"

/*
 * @description	: main函数
 * @param 		: 无
 * @return 		: 无
 */
int main(void)
{
	unsigned char state = OFF;

	int_init(); 				/* 初始化中断(一定要最先调用!) */
	imx6u_clkinit();			/* 初始化系统时钟 			*/
	delay_init();				/* 初始化延时 			*/
	clk_enable();				/* 使能所有的时钟 			*/
	led_init();					/* 初始化led 			*/
	beep_init();				/* 初始化beep	 		*/

	while(1)			
	{	
		state = !state;
		led_switch(LED0, state);
		delayms(500);
	}

	return 0;
}

main.c 函数很简单,在第20 行调用delay_init 函数进行延时初始化,最后在while 循环中周期性的点亮和熄灭LED0,调用函数delayms 来实现延时。

编译下载及示波器验证

编写Makefile 和链接脚本

因为本章例程并没有新建任何文件,所以只需要修改Makefile 中的TARGET 为delay 即可,链接脚本保持不变。

编译下载

使用Make 命令编译代码,编译成功以后使用软件imxdownload 将编译完成的delay.bin 文件下载到SD 卡中,命令如下:

chmod 777 imxdownload //给予imxdownload 可执行权限,一次即可
./imxdownload delay.bin /dev/sdd //烧写到SD 卡中,不能烧写到/dev/sda 或sda1 设备里面!

烧写成功以后将SD 卡插到开发板的SD 卡槽中,然后复位开发板。程序运行正常的话LED0会以500ms 为周期不断的亮、灭闪烁。可以通过肉眼观察LED 亮灭的时间是否为500ms。但是肉眼观察肯定不准确,既然本章号称高精度延时实验,那么就得经得住专业仪器的测试。我们将“示例代码20.3.3”中第29 行,也就是main 函数while 循环中的延时改为“delayus(20)”,也就是LED0 亮灭的时间各为20us,那么一个完整的周期就是20+20=40us,LED0 对应的IO 频率就应该是1/0.00004=25000Hz=25KHz。使用示波器测试LED0 对应的IO 频率,结果如图20.4.2.1 所示:

在这里插入图片描述
图20.4.2.1 20us 延时波形

从图20.4.2.1 可以看出,LED0 对应的IO 波形频率为22.3KHz周期是44.9us,那么main函数中while 循环执行一次的时间就是44.9/2=22.45us,大于我们设置的20us,看起来好像是延时不准确。但是我们要知道这22.45us 是main 函数里面while 循环总执行时间,也就是下面代码的总执行时间:

while(1)
{
	state = !state;
	led_switch(LED0, state);
	delayus(20);
}

在上面代码中不止有delayus(20)延时函数,还有控制LED 灯亮灭的函数,这些代码的执行也需要时间的,即使是delayus 函数,其内部也是要消耗一些时间的。假如我们将while 循环里面的代码改为如下形式:

while(1)
{
	GPIO1->DR &= ~(1<<3);
	delayus(20);
	GPIO1->DR |= (1<<3);
	delayus(20);
}

上述代码我们通过直接操作寄存器的方式来控制IO 输出高低电平,理论上while 循环执行时间会更小,并且while 循环里面使用了两个delayus(20),因此执行一次while 循环的理论时间应该是40us,和上面做的实验一样。重新使用示波器测量一下,结果如图20.4.2.2 所示:
在这里插入图片描述
图20.4.2.2 修改while 循环后的波形

从图20.4.2.2 可以看出,此时while 循环执行一次的时间是41.8us,那么一次delayus(20)的时间就是41.8/2=20.9us,很接近我们的20us 理论值。但是还是因为有其他程序开销存在,在加上示波器测量误差,所以不可能测量出绝对的20us。但是其已经非常接近了,基本可以证明我们的高精度延时函数是成功的、可以用的。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值