imx6ull-裸机学习实验11——高精度延时实验
目录
前言
高精度延时
GPT定时器
GPT特性
GPT时钟源
GPT结构
GPT工作模式
重新启动(restart)模式:
自由运行(free-run)模式:
GPT寄存器
配置寄存器 GPTx_CR
分频寄存器 GPTx_PR
状态寄存器 GPTx_SR
计数寄存器 GPTx_CNT
输出比较寄存器 GPTx_OCR
定时器实现高精度延时原理
高精度延时的实现步骤
实验程序编写
delay.h
delay.c
函数delay_init
函数delayus
函数delayms
main.c
前言
在前面的实验中我们使用循环来实现延时函数,但是使用循环来实现的延时函数不准确,误差会很大。
本讲实验我们使用正点原子imx6ull开发板,跟着官方例程来学习一下如何使用硬件定时器来实现高精度延时。
高精度延时
学过 STM32 的同学应该知道,可以使用 SYSTICK 来实现高精度延时。 I.MX6U 没有 SYSTICK 定时器,但是 I.MX6U 有其它定时器,比如EPIT定时器、GPT 定时器,本讲实验我们就是用GPT 定时器来实现高精度延时。
GPT定时器
GPT特性
GPT 定时器全称为 General Purpose Timer。
GPT 定时器是一个 32 位向上定时器(也就是从 0X00000000 开始向上递增计数), GPT 定时器也可以跟一个值进行比较,当计数器值和这个值相等的话就发生比较事件,产生比较中断。
GPT 定时器有一个 12 位的分频器,可以对 GPT 定时器的时钟源进行分频。
GPT 定时器特性如下:
- 一个可选时钟源的 32 位向上计数器。
- 两个输入捕获通道,可以设置触发方式。
- 三个输出比较通道,可以设置输出模式。
- 可以生成捕获中断、比较中断和溢出中断。
- 计数器可以运行在重新启动(restart)或(自由运行)free-run 模式。
GPT时钟源
GPT 定时器的可选时钟源如图 :
一共有五个时钟源,分别为:
- ipg_clk_24M、
- GPT_CLK(外部时钟)、
- ipg_clk、
- ipg_clk_32k、
- ipg_clk_highfreq。
本例程选择 ipg_clk 为 GPT 的时钟源, ipg_clk=66MHz。
GPT结构
GPT 定时器结构如图:
①、此部分为 GPT 定时器的时钟源,本章例程选择 ipg_clk 作为 GPT 定时器时钟源。
②、此部分为 12 位分频器,对时钟源进行分频处理,可设置 0~4095,分别对应 1~4096 分频。
③、经过分频的时钟源进入到 GPT 定时器内部 32 位计数器。
④和⑤、这两部分是 GPT 的两路输入捕获通道。
⑥、此部分为输出比较寄存器,一共有三路输出比较,因此有三个输出比较寄存器,输出比较寄存器是 32 位的。
⑦、此部分位输出比较中断,三路输出比较中断,当计数器里面的值和输出比较寄存器里面的比较值相等就会触发输出比较中断。
GPT工作模式
GPT 定时器有两种工作模式:重新启动(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寄存器
本讲实验我们用到的寄存器如下:
- 配置寄存器 GPTx_CR
- 分频寄存器 GPTx_PR
- 状态寄存器 GPTx_SR
- 计数寄存器 GPTx_CNT
- 输出比较寄存器 GPTx_OCR
配置寄存器 GPTx_CR
GPT控制寄存器(GPT_CR)用于对GPT操作进行编程和配置。对GPT控制寄存器的IP总线写入发生在等待状态的一个周期之后,而IP总线读取发生在0个等待状态之后。
SWR(bit15):复位 GPT 定时器,向此位写 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 为时钟源。
ENMOD(bit1): GPT 使能模式。
- 此位为 0 的时候如果关闭 GPT 定时器,计数器寄存器保存定时器关闭时候的计数值。
- 此位为 1 的时候如果关闭 GPT 定时器,计数器寄存器就会清零。
EN(bit0): GPT 使能位,为 1 的时候使能 GPT 定时器,为 0 的时候关闭 GPT 定时器。
分频寄存器 GPTx_PR
GPT预分频器寄存器(GPT_PR)包含确定运行计数器的时钟分频值的位。
寄存器 GPTx_PR 我们用到的重要位就一个: PRESCALER(bit11:0),这就是 12 位分频值,可设置 0~4095,分别对应 1~4096 分频。
状态寄存器 GPTx_SR
GPT状态寄存器(GPT_SR)包含指示计数器已翻转的位,以及输入捕获和输出比较通道上是否发生了任何事件。通过向这些位写入1来清除它们。
ROV(bit5): 回滚标志位,当计数值从 0XFFFFFFFF 回滚到 0X00000000 的时候此位置 1。
IF2~IF1(bit4:3): 输入捕获标志位,当输入捕获事件发生以后此位置 1,一共有两路输入捕获通道。如果使用输入捕获中断的话需要在中断处理函数中清除此位。
OF3~OF1(bit2:0):输出比较中断标志位,当输出比较事件发生以后此位置 1,一共有三路输出比较通道。如果使用输出比较中断的话需要在中断处理函数中清除此位。
计数寄存器 GPTx_CNT
GPT计数器寄存器(GPT_CNT)是主计数器的寄存器。GPT_CNT是一个只读寄存器,可以在不影响GPT计数过程的情况下读取。
输出比较寄存器 GPTx_OCR
GPT 定时器的输出比较寄存器 GPTx_OCR,每个输出比较通道对应一个输出比较寄存器,因此一个 GPT 定时器有三个 OCR 寄存器,它们的作都是相同的。
以输出比较通道 1 为例,其输出比较寄存器为 GPTx_OCR1,这是一个 32 位寄存器,用于存放 32 位的比较值。当计数器值和寄存器 GPTx_OCR1 中的值相等就会产生比较事件,如果使能了比较中断的话就会触发相应的中断。
定时器实现高精度延时原理
如果设置 GPT 定时器的时钟源为 ipg_clk=66MHz,设置 66 分频,那么进入 GPT定时器的最终时钟频率就是 66/66=1MHz,周期为 1us。
通过读取寄存器 GPTx_CNT 中的值就知道计了多少个数。
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、编写延时函数
对 us 和 ms 延时分别编写两个延时函数。
实验程序编写
本试验用到的资源如下:一个 LED 灯: LED0。定时器 GPT1。
通过高精度延时函数来控制 LED0 的闪烁,可以通过示波器来观察 LED0 的控制 IO输出波形,通过波形的频率或者周期来判断延时函数精度是否正常。
需要修改 bsp_delay.c 和bsp_delay.h 这两个文件。
delay.h
bsp_delay.h 里面,一些函数声明:
#ifndef __BSP_DELAY_H
#define __BSP_DELAY_H#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
delay.c
文件 bsp_delay.c 中一共有 5 个函数,分别为: delay_init、 delayus、 delayms 、 delay_short和 delay。
其中,delay_init、 delayus、 delayms三个函数是新增的,让我们来分析一下。
函数delay_init
函数 delay_init 是延时初始化函数,主要用于初始化 GPT1 定时器,设置其时钟源、分频值和输出比较寄存器值。
/** @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}
下面是函数 delay_init里面被屏蔽的GPT1 的中断初始化代码,如果要使用 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;/** GPT的IR寄存器,使能通道1的比较中断* bit0: 0 使能输出比较中断*/GPT1->IR |= 1 << 0;/** 使能GIC里面相应的中断,并且注册中断处理函数*/GIC_EnableIRQ(GPT1_IRQn); //使能GIC中对应的中断system_register_irqhandler(GPT1_IRQn, (system_irq_handler_t)gpt1_irqhandler, NULL); //注册中断服务函数
#endif
GPT1 的中断处理函数 gpt1_irqhandler,同样的,如果需要使用 GPT1 中断功能的话可以参考此部分代码。
/* 中断处理函数 */
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; /* 清除中断标志位 */
}
函数delayus
delayus 函数处理 GPT1 计数器溢出的情况。函数delayus 只有一个参数 usdelay,这个参数就是要延时的 us 数。
/** @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; /* 跳出 */}}
}
函数delayms
delayms 函数,就是对delayus(1000)的多次叠加,此函数也只有一个参数 msdelay,也就是要延时的 ms 数。
/** @description : 毫秒(ms)级延时* @param - msdelay : 需要延时的ms数* @return : 无*/
void delayms(unsigned int msdelay)
{int i = 0;for(i=0; i<msdelay; i++){delayus(1000);}
}
main.c
main.c 函数很简单,调用 delay_init 函数进行延时初始化,最后在 while 循环中周期性的点亮和熄灭 LED0,调用函数 delayms 来实现延时。
实验代码的修改重点大致如上,编译成功后烧写到我们的开发板上验证。
程序运行正常的话 LED0会以 500ms 为周期不断的亮、灭闪烁。可以通过肉眼观察 LED 亮灭的时间是否为 500ms。
有条件的同学也可以使用示波器测试 LED0 对应的 IO 频率,主播没有条件,我们就不测试了。