【物联网】GPT延时

发布于:2025-04-10 ⋅ 阅读:(30) ⋅ 点赞:(0)


前言

使用 GPT 实现延时控制以及基于 PWM 实现蜂鸣器发声与频率调节这两个场景,分析它们在嵌入式系统中的使用方法、底层原理与调试技巧。无论是你正在开发 STM32、ESP32,还是 RISC-V 架构下的 IoT 模块

实例一:GPT 精确延时控制按钮去抖
场景:物联网按钮频繁被用户点击,出现抖动误触,需要消抖处理。
解决方案:使用 GPT 实现精确的延时判断,忽略高频波动。

实例二:PWM 控制蜂鸣器发出“滴滴”提示音
场景:用户打开设备或操作成功时,需要蜂鸣器提示音。
解决方案:用 PWM 输出固定频率(如 2KHz)控制无源蜂鸣器工作一小段时间。

实例三:实现不同频率蜂鸣提示代表不同事件
场景:警报、完成提示、错误提示等不同事件用不同频率蜂鸣音区分。
解决方案:定义不同的 PWM 频率与播放时长组合,对应不同事件。

实例四:蜂鸣器“滴滴滴”重复发声
场景:某些错误或找设备功能需要连续蜂鸣。
解决方案:循环开启关闭 PWM,同时使用 GPT 控制节奏。

实例五:低功耗设计中使用 GPT 进入延时唤醒模式
场景:IoT 设备在某些操作后进入低功耗状态,需要延时唤醒继续任务。
解决方案:GPT 进入 sleep 模式前设置延时中断,作为轻量唤醒机制。

一、GPT实现延时

1. 定时器介绍

在软件中我们也经常使用定时器,如:Qt中的定时器,当定时时间到达的时候,会发送一个timeOut()信号通知外界定时时间到达。软件中使用的定时器最终是由硬件来实现的,接下来我们来看看定时器在硬件上是如何工作的。

这个图描述的是一个定时器(Timer)模块的基本工作原理,结合了分频器、计数器(递增/递减)和中断机制,常用于嵌入式开发中实现精确延时或定时触发事件。

图中路径:100Hz 输入信号 → 分频器 DIV:10 → 输出 10Hz(每0.1秒)→ 计数器 ±1 → 满足条件触发中断 → ARM核心响应
举个例子:你是一个定时喝水的程序员
你办公室的秒表(频率 100Hz,相当于 100次/秒)在疯狂滴滴滴叫。
你觉得太吵了,就设置了一个“分频器”,把 100Hz 降到 10Hz(相当于 0.1秒一个滴答)。
你拿出一个计数器(设置成 100),每听到一次“滴”,你就把计数器减 1。
当计数器减到 0,你就触发一个动作:“起身喝水”。

在这里插入图片描述

2. I.MX6ull GPT定时器介绍

GPT有一个32位向上计数器。定时器计数器值可以使用外部引脚上的事件捕获到寄存器中。捕获触发器可以被编程为上升沿或/和下降沿。GPT还可以在输出比较引脚上生成事件,并在定时器达到编程值时产生中断。GPT有一个12位预分频器,它提供从多个时钟源获得的可编程时钟频率

有时钟源的选择、2路的输入捕获、3路的输出比较
在这里插入图片描述
Features
• One 32-bit up-counter with clock source selection, including external clock.
一个带时钟源选择的32位向上计数器,包括外部时钟

• Two input capture channels with a programmable trigger edge.
具有可编程触发边缘的两个输入捕获通道

• Three output compare channels with a programmable output mode. A “forced compare” feature is also available.
三个输出比较通道,具有可编程输出模式。还提供“强制比较”功能

• Can be programmed to be active in low power and debug modes.
可编程为在低功耗和调试模式下处于活动状态。

• Interrupt generation at capture, compare, and rollover(计数器的值计数到最大值,然后从0开始) events.
在捕获、比较和翻转事件时中断生成

• Restart or free-run modes for counter operations.
为计数器操作重新启动或自由运行模式。

1)GPT定时器工作原理

在这里插入图片描述

输入到预定标器的时钟可以从4个时钟源中选择。下表描述了GPT的时钟源。有关时钟设置、配置和门控信息,请参阅时钟控制器模块(CCM)

在这里插入图片描述

在这里插入图片描述

可以从 4 个时钟源中选择输入到预分频器的时钟,分别为:
• 高频参考时钟(ipg_clk_highfreq),
• 低频参考时钟(ipg_clk_32k),
外围时钟(ipg_clk)
• 外部时钟(GPT_CLK)或者晶体振荡器时钟(ipg_clk_24M)

外部时钟(GPT_CLK)或者晶体振荡器时钟(ipg_clk_24M)只能选择一个。

这里我们选择ipg_clk=66MHZ作为GPT定时器的输入时钟源,芯片在启动的时候,芯片内部的程序已经做了系统时钟初始化,初始化的时钟频率如下:

把板子启动一下 复位 输入clocks 可以查看时钟信号频率 也可以在u_boot中输入clocks
IPG的频率是66mhz
在这里插入图片描述
具体怎么选择 需要看寄存器 GPTx_CR
在这里插入图片描述
把寄存器CR的第6-8位设置1就是外围时钟(ipg_clk)66mhz

选择完时钟信号之后 再经过预分频器 设置寄存器GPTx_PR:

在这里插入图片描述

A.ipg_clk : 66000kHZ
B.Prescaler: 65
最终GPT定时器的输入时钟源: 66000kHZ/(65 + 1) = 1000KHZ,周期为 1us。
也就是说每隔1us,GPT内部的计数器值+1,GPTx_CNT 是个32位寄存器,
GPTx_CNT最大的值是0XFFFFFFFF,也就是0XFFFFFFFFus=4294967295us≈4294s≈72min。
也就是说72分钟以后GPTx_CNT寄存器就会回滚到0X00000000

GPT计数器可以编程为以两种模式之一工作:重新启动模式或自由运行模式

在重新启动模式下(可通过GPT控制寄存器GPT_CR选择),当计数器达到比较值时,计数器复位并从0x00000000再次启动。
重新启动功能仅与比较通道1相关联。对通道1的比较寄存器的任何写访问都将重置GPT计数器。这样做是为了避免在计数进行时比较值从较高值更改为较低值时可能丢失比较事件。对于其他两个比较通道,当比较事件发生时,计数器不会重置。

在自由运行模式下,当所有3个通道发生比较事件时,计数器不会重置;相反,计数器继续计数,直到0xffffffff,然后滚动(到0x00000000)

用人话讲:重新启动模式就是 使用当前值和比较值(通道1)作比较 如果一样就复位 如果被强制改动也复位
自由模式就是 不会因为比较值命中而复位,它是溢出(到 0xFFFFFFFF)才回绕,与比较值无关

如何操作定时器:
直接总结就是 设置1工作 设置0不工作
这里有一个CNT寄存器 是用来读取当前计数器的值是多少

通用计时器(GPT)有一个计数器(GPT_CNT),它是一个32位自由运行的向上计数器,它在软件启用后开始计数(EN=1)。
在这里插入图片描述
如果禁用GPT计时器(EN=0),则主计数器和预分频计数器将冻结其当前计数值。ENMOD位确定设置EN位并再次启用计数器时GPT计数器的值。

如果设置了ENMOD位(=1),则启用GPT时,主计数器和预分频器计数器值将重置为0(EN=1)。
在这里插入图片描述
如果想全部复位 就在GPT_CR的第15位写个1

硬件重置将所有GPT寄存器重置为各自的重置值。除了输出比较寄存器(OCR1、OCR2、OCR3)之外的所有寄存器都获得0x0的值。比较寄存器重置为0xFFFF_FFFF。

软件复位(GPT_CR控制寄存器中的SWR位)复位除EN、ENMOD、STOPEN、WAITEN和DBGEN位之外的所有寄存器位。这些位的状态不受软件复位的影响。请注意,*在禁用GPT时,*可以进行软件复位。
在这里插入图片描述

2)GPT的输入捕获

(1)输入捕获的使用场景

在嵌入式开发中,我们常需要捕获传感器的高电平(或低电平)信号的持续时间,如红外解码信号、编码器输入信号等。
在这里插入图片描述
从直观上理解,就是要不断的检测这个信号,当信号从0变到1时,记录一个时间,再从1变到0时,记录另一个时间,两个时间差就是高电平的持续时间了。

(2)输入捕获工作原理
在这里插入图片描述
• 启动定时器,让CNT计数器在不停的计数
• 首先配置定时器的输入通道为上升沿捕获,这样当检测到从0到1的跳变时,ICR就会先保存当前的CNT值
• 然后将定时器的输入通道为下降沿捕获,当检测从1到0的跳变时,ICR就会先保存当前的CNT值
• 最终我们根据两次捕获的值,就可以计算出高电平持续的时间

(3)GPT的输入捕获操作
当使能打开 捕获产生 这时候就会发生中断
可以在上升沿或下降沿捕获

在这里插入图片描述

有两个输入捕获通道,每个输入捕获通道都有一个专用的捕获引脚、捕获寄存器和输入边缘检测/选择逻辑。每个输入捕获功能都有一个关联的状态标志,可以导致处理器发出中断服务请求。

在这里插入图片描述
当输入捕获引脚上发生选定的边缘转换时,GPT_CNT的内容将在相应的捕获寄存器上捕获,并设置适当的中断状态标志。如果设置了相应的使能位(在中断寄存器中),则可以在检测到转换时生成中断请求。捕获可以编程为发生在输入引脚的上升沿、下降沿、上升沿和下降沿上,或者可以禁用捕获。事件与选择运行计数器的时钟同步。只有在先前记录的转换之后至少一个时钟周期(选择运行计数器的时钟)发生的转换才能保证触发捕获事件。输入转换的锁定中最多可以有一个时钟周期的不确定性。输入捕获寄存器可以在任何时候读取,而不会影响它们的值。
总结:捕获事件产生的时候 有一些状态标志会被设置。捕获的时机可以在下降沿、上升沿和下降沿上的时候。捕获的值可以在任何时间读取
在这里插入图片描述
设置下降沿、上升沿和下降沿上
在这里插入图片描述
设置捕获事件有没有产生
在这里插入图片描述
捕获的值是多少
在这里插入图片描述

3)GPT的输出比较

在这里插入图片描述
设置3个比较值 如果计数器和比较值相等 则会产生输出比较事件
三个输出比较通道使用相同的计数器(GPT_CNT)作为输入捕获通道。当输出比较寄存器的编程内容与GPT_CNT中的值匹配时,设置输出比较状态标志并生成中断(如果在中断寄存器中设置了相应的位)。因此,输出比较定时器引脚将被设置、清除、切换、完全不受影响或输出一个低脉冲(脉冲持续时间为定时器的时钟源的周期)

每一个GPT计时器管3个通道
在这里插入图片描述
当产生事件后 可以通过高低电平的切换、脉冲信号等等来通知
在这里插入图片描述
这里可以设置需要产生什么样的动作
在这里插入图片描述
状态标志
在这里插入图片描述
还有一个“强制比较(forced-compare)”功能,一旦设置,就会马上产生比较事件;不管当前计数器值是 否等于比较值。强制比较的产生的事件,跟正常的输出事件相同,只是它不会设置状态标记位并且不会产生中断。 一旦设置 force-compare 位,该事件会即刻产生,这个位是自动清除的,读的话一直零。
通过软件 强制产生一个比较事件(中断标志和状态标志不会改变)
在这里写个1 产生强制比较
在这里插入图片描述
在这里插入图片描述

3. 高精度延时实现

1)实现思路

• 软件复位GPT定时器
• 确定GPT定时器的时钟源
• 确定比较寄存器的值( 根据延时时间来确定 )
• 开启GPT定时器
等待输出比较事件产生
• 关闭GPT定时器

#include "imx6ull.h"

// 初始化GPT定时器 时钟源
void gpt_init(void)
{
    /* 重置GPT定时器 */
    GPT1->CR |= (1 << 15); // 设置控制寄存器的第15位为1,开始重置

    // 等待重置完成,检查第15位是否变回0
    while(GPT1->CR & (1 << 15))
    {
        // 啥也不干,就等着
    }

    /*
     选择时钟源:用IPG时钟(66MHz)
     把第6到8位清零,然后设第6位为1,选择IPG时钟
    */
    GPT1->CR &= ~(0x7 << 6); // 先清零第6-8位
    GPT1->CR |= (0x1 << 6);  // 再把第6位置1

    /*
     设置GPT预分频器为65
     GPT时钟 = 66MHz / (65+1) = 1MHz,也就是1微秒(1us)一个tick
    */
    GPT1->PR = 65; // 预分频器设为65

    return; // 初始化完成,返回
}

// 等待比较事件发生
void wait_cmp_event(void)
{
    int flag; // 定义一个标志变量

    // 循环检查状态寄存器的第0位,直到它变成1,表示比较事件发生了
    do{
        flag = GPT1->SR & (1 << 0); // 读取状态寄存器的第0位
    }while(!flag); // 如果flag是0,就继续等

    return; // 事件发生后返回
}

// 启动GPT定时器
void gpt_start(void)
{
    // 设置控制寄存器:
    // 第9位设1:使能比较输出1
    // 第1位设1:使能自由运行模式
    // 第0位设1:启动定时器
    GPT1->CR |= (1 << 9) | (1 << 1) | (1 << 0);
    return; // 启动完成,返回
}

// 停止GPT定时器
void gpt_stop(void)
{
    // 把控制寄存器的第0位清零,停止定时器
    GPT1->CR &= ~(1 << 0);
    return; // 停止完成,返回
}

// 延时指定微秒(us)
void gpt_delay_us(uint32_t us)
{
    gpt_init();          // 先初始化GPT定时器
    GPT1->OCR1 = us;     // 设置比较寄存器的值为延时的微秒数

    gpt_start();         // 启动定时器
    wait_cmp_event();    // 等待延时时间到达
    gpt_stop();          // 停下定时器

    return; // 延时完成,返回
}

// 延时指定毫秒(ms)
void gpt_delay_ms(uint32_t ms)
{
    gpt_init();          // 初始化GPT定时器
    GPT1->OCR1 = 1000 * ms; // 设置比较值,1ms=1000us,所以乘以1000
    gpt_start();         // 启动定时器
    wait_cmp_event();    // 等待延时时间到达
    gpt_stop();          // 停下定时器

    return; // 延时完成,返回
}

// 延时指定秒(sec)
void gpt_delay_sec(uint32_t sec)
{
    gpt_delay_ms(sec * 1000); // 1秒=1000毫秒,直接调用毫秒延时函数
    return; // 延时完成,返回
}

// GPT测试函数:让LED灯每隔500毫秒亮灭一次
void gpt_test(void)
{
    led_init(); // 初始化LED

    // 死循环,让LED不停闪烁
    while(1){
        led_on();          // 点亮LED
        gpt_delay_ms(500); // 延时500毫秒
        led_off();         // 熄灭LED
        gpt_delay_ms(500); // 再延时500毫秒
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到