目录
- 前言
- 整体文件结构
- 源码分析(保姆级讲解)
- 初始化延时函数部分:
- 微秒(us)级延时部分
- 毫秒(ms)级延时部分
- 主函数main部分
- 编译下载
- 结束语
前言
在上一讲过程中我们使用了EPIT定时器
来实现了定时器中断,从而解决了按键消抖的问题。但是我们会发现一个问题就是延时时间不准,具体延时函数如下所示:
/*
* @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);
}
}
由上述代码可知,使用循环来实现的延时函数不准确,误差会很大,虽然使用到延时函数的地方精度要求都不会很严格(要求严格的话就使用硬件定时器了),但是延时函数肯定是越精确越好。这样延时函数就可以使用在某些对时序要求严格的场合。
所以本篇文章就去讨论下如何使用GPT定时器
来实现高精度延时。
整体文件结构
源码分析(保姆级讲解)
初始化延时函数部分:
void delay_init(void)
{
GPT1->CR = 0;
GPT1->CR = 1 << 15;
while((GPT1->CR >> 15) & 0x01);
GPT1->CR = (1<<6);
GPT1->PR = 65;
GPT1->OCR[0] = 0XFFFFFFFF;
GPT1->CR |= 1<<0;
}
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
GPT1->CR = 0;
//该寄存器具体配置如下:
清零,bit0
也为0
,即关闭GPT定时器
。
GPT1->CR = 1 << 15;
while((GPT1->CR >> 15) & 0x01);
由上可知,该寄存器的第15
位为复位位,当该位为1
时代表可以进行软复位
。
因为当GPT复位完成
后,会自动将该位清零,即复位成功后会退出该while循环
。
GPT1->CR = (1<<6);
我们在研究该寄存器配置之前先看一下GPT定时器的可选时钟源有哪些。
从上图所示,一共有5个时钟源,分别为ipg_clk_24M、GPT_CLK(外部时钟)、ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq。在本文章中使用ipg_clk作为GPT的时钟源,那我们看一下该时钟源的时钟频率有多大?
该时钟源的时钟频率为66MHz。
具体配置讲解在之前的文章中有讲解:看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动GPIO中断《包括时钟讲解》) 2023.5.9
并且GPT定时器结构如图所示:
①、此部分为 GPT 定时器的时钟源,前面已经说过了,选择 ipg_clk 作为 GPT 定时器时钟源。
②、此部分为 12 位分频器,对时钟源进行分频处理,可设置 0~ 4095,分别对应 1~4096 分
频。
③、经过分频的时钟源进入到 GPT 定时器内部 32 位计数器。
④和⑤、这两部分是 GPT 的两路输入捕获通道,在本文章中不讲解 GPT 定时器的输入捕获。
⑥、此部分为输出比较寄存器,一共有三路输出比较,因此有三个输出比较寄存器,输出比较寄存器是 32 位的。
⑦、此部分位输出比较中断,三路输出比较中断,当计数器里面的值和输出比较寄存器里面的比较值相等就会触发输出比较中断。
GPT1->PR = 65;
该寄存器如下所示:
设置分频值,设置为0即代表1分频,以此类推,最大可以设置为 0XFFF,也就是最大 4096 分频。
所以我们设置该寄存器为65,即代表为65+1 = 66分频,即最终GPT时钟为1MHz。
GPT1->OCR[0] = 0XFFFFFFFF;
该寄存器的具体配置如下:
使用了第一个比较寄存器。
即使用GPT的输出比较1比较计数值。由于之前配置GPT时钟为1MHz,所以相当于计数器每计一个值就是1us。那么为了实现较大的计数,我们将比较值设置为最大的 0XFFFFFFFF,
这样一次计满就是:0XFFFFFFFFus = 4294967296us = 4295s = 71.5min
也就是说一次计满最多 71.5 分钟,存在溢出。
GPT1->CR |= 1<<0;
该寄存器具体配置如下:
即使能GPT定时器。
微秒(us)级延时部分
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)
tcntvalue += newcnt - oldcnt;
else
tcntvalue += 0XFFFFFFFF-oldcnt + newcnt;
oldcnt = newcnt;
if(tcntvalue >= usdelay)
break;
}
}
}
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
void delayus(unsigned int usdelay)
//该函数的参数usdelay主要是表示需要延时的us数,最大延时0XFFFFFFFFus。
unsigned long oldcnt,newcnt;
unsigned long tcntvalue = 0;
oldcnt代表上一时刻中GPT定时器计数器寄存器中的值。
newcnt代表此时该时刻GPT定时器计数器寄存器中的值。
tcntvalue 代表走过的总时间,用于判断是否达到了传入函数的参数usdelay值。
oldcnt = GPT1->CNT;
在进入计时之前,先记录该时刻GPT定时器计数器寄存器中的值,并且将该值赋值给oldcnt。
newcnt = GPT1->CNT;
进入while循环后,经历了一段时间,用newcnt重新记录过了一段时间GPT定时器计数器寄存器中的值。
if(newcnt > oldcnt)
tcntvalue += newcnt - oldcnt;
else
tcntvalue += 0XFFFFFFFF-oldcnt + newcnt;
如果这两个值不一样,则代表已经经过一段时间,则需要更新tcntvalue。
如果newcnt > oldcnt,则代表GPT是向上计数器,并且没有溢出。
如果不符合newcnt > oldcnt,则代表发生了溢出,即需要更新tcntvalue。
oldcnt = newcnt;
在更新完tcntvalue之后,将newcnt赋值给oldcnt ,为了下一次计算总时间做准备。
if(tcntvalue >= usdelay)
break;
如果tcntvalue 大于了我们所设置的usdelay值,则提前退出该函数即可。
毫秒(ms)级延时部分
void delayms(unsigned int msdelay)
{
int i = 0;
for(i=0; i<msdelay; i++)
{
delayus(1000);
}
}
1ms = 1000us。
主函数main部分
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;
}
编译下载
程序运行正常的话 LED0会以 500ms 为周期不断的亮、灭闪烁。
结束语
如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!