延时最简单粗暴的方式就是使用空循环来延时,依赖的是时钟主频(默认是396M)来计数,一旦修改了 6ull 的时钟主频,延时效果就会存在偏差。
因此我们可以使用 EPIT 或者 GPT 的计数功能实现高精度延时,EPIT 是向下计数(自减),GPT 是向上计数(自增)。
目录
一、GPT 初始化
二、延时函数delayus(us 级)
1、原理解析
2、具体实现
三、延时函数delayms(ms级)
一、GPT 初始化
和前面定时中断不一样,如果只是要实现延时功能的话,无需初始化中断,因为延时只用到了 GPT 的计数功能。
- GPT 复位,并等待复位完成
- 配置GPT定时器(设置计数器初始值、选择时钟源、选择工作模式)
- 设置分频数(66 分频)
- 启动定时器
/*
* @description : gpt 定时初始化(不会产生中断,只是单纯的计数)
* @param : 定时器时间间隔
* @return : 无
*/
void delay_init()
{
// 禁用GPT定时器
GPT1_CR = 0;
GPT1_CR |= (1 << 15);
while((GPT1_CR >> 15) & 0x01); // 等待复位完成
/*
* 配置GPT定时器
* bit 1: 1 设置计数器初始值为0
* bit 8-6: 001 选择时钟源ipg_clk
* bit 9: 1 选择工作模式free-run
*/
GPT1_CR |= ((1 << 1) | (1 << 6) | (1 << 9));
// 分频
GPT1_PR &= ~(0xFFF << 0); // 低 12 bit 清零
GPT1_PR |= (65 << 0); // 66 分频
// 启动定时器
GPT1_CR |= (1 << 0);
}
二、延时函数delayus(us 级)
比如我们要延时 500ms,66MHz时钟源,66分频的条件下,500ms 转换成计数值就是 500000,因此当 delta >= 500000 时,说明延时完毕,所以问题的关键就是求从开始到现在经过了多少计数值。
1、原理解析
Free-Run 模式下,当计数器到达 0xFFFF FFFF 时就会溢出,重新从 0 开始计数。如果计数器没有溢出,从开始到现在,计数的差值如下。(计数的差值是和时间关联的)
delta = newcount - oldcount;
如果计数器溢出了,从开始到现在,计数的差值如下
delta = delta1 + delta2 = 0xFFFFFFFF - oldcount + newcount;
注意:这里的 period 与时钟周期无关
2、具体实现
判断是否移除,需要用到 GPT1_SR 的 ROV 位(bit 5)。
/*
* @description : 延时函数(us级)
* @param - n : 要延时的us数
* @return : 无
*/
void delayus(unsigned int val)
{
// 获取到当前计数器的值
unsigned int oldcount = GPT1_CNT;
unsigned int delta = 0;
while (1)
{
unsigned int newcount = GPT1_CNT;
// 判断是否溢出
if ((GPT1_SR >> 5) & 0x01) // 说明溢出了
{
delta = 0xFFFFFFFF - oldcount + newcount;
}
else
{
delta = newcount - oldcount;
}
if (delta >= val) break; // 延时完毕,跳出循环
}
GPT1_SR &= ~(1 << 5); // 清除溢出标志位
}
解析:if (delta >= val)
66 MHz 的时钟源,66分频 =》一个时钟周期的间隔就是 1/1M = 1/10^6
val 的单位是 us:
- 转换成 s,结果 = val / 10^6
- 转换成对应的计数值 = 10^6 * val / 10^6 = val
因此,val (us)经过转换以后,最终的计数值就是 val
三、延时函数delayms(ms级)
/*
* @description : 延时函数(ms级)
* @param - n : 要延时的ms数
* @return : 无
*/
void delayms(unsigned int val)
{
delayus(val * 1000);
}