Linux学习笔记16---高精度延时实验

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

1、 高精度延时简介

        1.1 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 位向上计数器。
  • ②、两个输入捕获通道,可以设置触发方式。
  • ③、三个输出比较通道,可以设置输出模式。
  • ④、可以生成捕获中断、比较中断和溢出中断。
  • ⑤、计数器可以运行在重新启动(restart)(自由运行)free-run 模式。
  • GPT 定时器的可选时钟源如图 20.1.1.1 所示:

        从图 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 定时器的时钟源,前面已经说过了,本章例程选择 ipg_clk 作为 GPT 定时器时钟源。
  • ②、此部分为 12 位分频器,对时钟源进行分频处理,可设置 0~4095,分别对应 1~4096 分频。
  • ③、经过分频的时钟源进入到 GPT 定时器内部 32 位计数器。
  • ④和⑤、这两部分是 GPT 的两路输入捕获通道,本章不讲解 GPT 定时器的输入捕获。
  • ⑥、此部分为输出比较寄存器,一共有三路输出比较,因此有三个输出比较寄存器,输出比较寄存器是 32 位的。
  • ⑦、此部分位输出比较中断,三路输出比较中断,当计数器里面的值和输出比较寄存器里面的比较值相等就会触发输出比较中断。
        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 定时器几个重要的寄存器,第一个就是 GPT 的配置寄存器 GPTx_CR , 此寄存器的结构如图 20.1.1.3 所示:
寄存器 GPTx_CR 我们用到的重要位如下:
        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 为时钟源。本章例程选择 ipg_clk 作为 GPT 定时器的时钟源,因此此位设置位 1(0b001)
        ENMOD(bit1): GPT 使能模式,此位为 0 的时候如果关闭 GPT 定时器,计数器寄存器保存定时器关闭时候的计数值。此位为 1 的时候如果关闭 GPT 定时器,计数器寄存器就会清零。
        EN(bit): GPT 使能位,为 1 的时候使能 GPT 定时器,为 0 的时候关闭 GPT 定时器。
接下来看一下 GPT 定时器的分频寄存器 GPTx_PR ,此寄存器结构如图 20.1.1.4 所示:
寄存器 GPTx_PR 我们用到的重要位就一个: PRESCALER(bit11:0) ,这就是 12 位分频值,可设置 0~4095 ,分别对应 1~4096 分频。
接下来看一下 GPT 定时器的状态寄存器 GPTx_SR ,此寄存器结构如图 20.1.1.5 所示:
        寄存器 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 小节。

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

        高精度延时函数的实现肯定是要借助硬件定时器,前面说了本章实验使用 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 延时分别编写两个延时函数。

2、 硬件原理分析

本试验用到的资源如下:
①、一个 LED 灯: LED0
②、定时器 GPT1
本实验通过高精度延时函数来控制 LED0 的闪烁,可以通过示波器来观察 LED0 的控制 IO
输出波形,通过波形的频率或者周期来判断延时函数精度是否正常。

3、 实验程序编写

本章实验在上一章例程的基础上完成,更改工程名字为“ delay ”,直接修改 bsp_delay.c
bsp_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
bsp_delay.h 文件就是一些函数声明,很简单。在文件 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;/** 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}#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 来实现延时。

4、 编写 Makefile 和链接脚本

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

5、编译下载

使用 Make 命令编译代码,编译成功以后使用软件 imxdownload2 将编译完成的 bsp.bin 文件生成可执行的img文件,命令如下:

make
./imxdownload2 delay.bin

如果  imxdownload2无权限,可用以下命令添加权限

chmod 777 imxdownload2

 

利用Win32DiskImager软件将load.img执行文件写入SD卡,SD卡插入开发板上即可正常运行。如果代码运行正常的话 LED0 会以以 500ms 为周期不断的亮、灭闪烁。可以通过肉眼观察 LED 亮灭的时间是否为 500ms。

但是肉眼观察肯定不准确,既然本章号称高精度延时实验,那么就得经得住专业仪器的测试。我们
率就应该是 1/0.00004=25000Hz=25KHz 。使用示波器测试 LED0 对应的 IO 频率,结果如图
20.4.2.1 所示:

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

例程

【免费】Linux学习笔记17-高精度延时实验例程资源-CSDN文库


http://www.ppmy.cn/embedded/160353.html

相关文章

CLK敏感源和完整GND平面

SPI Flash芯片辐射发射&#xff08;RE&#xff09;问题: 某款产品在3米法电波暗室进行辐射&#xff08;RE&#xff09;发射测试时,发现多个频点余量不满足6dB管控要求. 通过频谱分析仪近场探头分析定位到干扰频点来自于SPI Flash时钟信号的高次谐波干扰&#xff0c;深入分析发…

【deepseek实战】绿色好用,不断网

前言 最佳deepseek火热网络&#xff0c;我也开发一款windows的电脑端&#xff0c;接入了deepseek&#xff0c;基本是复刻了网页端&#xff0c;还加入一些特色功能。 助力国内AI&#xff0c;发出自己的热量 说一下开发过程和内容的使用吧。 目录 一、介绍 二、具体工作 1.1、引…

vulnhub DC-3

报错解决 这个靶机有一个报错,更改一下磁盘就可以了 开机就可以了 先获取一下靶机IP arp-scan -l 信息收集 nmap -A -sS -p- 192.168.47.143 就开了一个80端口 扫目录 dirb http://192.168.47.143/ 在WebRbot里面扫出来两个txt文件 在这个网页http://192.168.47.143/READ…

iOS 音频录制、播放与格式转换

iOS 音频录制、播放与格式转换:基于 AVFoundation 和 FFmpegKit 的实现 在 iOS 开发中,音频处理是一个非常常见的需求,比如录音、播放音频、音频格式转换等。本文将详细解读一段基于 AVFoundation 和 FFmpegKit 的代码,展示如何实现音频录制、播放以及 PCM 和 AAC 格式之间…

54【ip+端口+根目录通信】

上节课讲到&#xff0c;根目录起到定位作用&#xff0c;比如我们搭建一个php网站后&#xff0c;注册系统是由根目录的register.php文件执行&#xff0c;那么我们给这个根目录绑定域名https://127.0.0.1&#xff0c;当我们浏览器访问https://127.0.0.1/register.php时&#xff0…

5 个开源且免费的提示词管理系统,按照 从优到劣 排序

1. PromptSource 研发背景: 国家: 国际协作&#xff08;主要由美国和欧洲团队主导&#xff09;。 团队: BigScience Workshop&#xff0c;一个由 Hugging Face 和多个研究机构共同支持的开源社区。 简介: 专注于创建、管理和共享提示词模板。 特点: 提供 Web 界面&#xff…

【前端】【面试】【经典一道题】前端 Vue、React 采用单向数据流的原因

前端Vue、React采用单向数据流的原因 一、可预测性 1. 数据流向清晰 在单向数据流架构里&#xff0c;数据从父组件流向子组件的路径是明确且可预期的。 React示例&#xff1a;父组件通过 props 传递数据给子组件&#xff0c;子组件只能读取 props 中的数据&#xff0c;没有直…

Linux详细讲解

学习目标 那什么是Linux&#xff1f;那为什么要学习Linux&#xff1f;怎么在Linux网络配置Linux常用命令有哪些&#xff1f; 什么是Linux&#xff0c;为什么要学习Linux&#xff0c;怎么在Linux网络配置&#xff0c;Linux常用命令有哪些&#xff1f; 那什么是Linux&#xff1f;…