3. GPT(通用定时器)模块配置与驱动

各位同学,今天我们来聊聊GPT模块。说实话,定时器在嵌入式系统里就像人的心跳一样重要。我做过不少项目,几乎每个项目都离不开定时器——PWM生成、输入捕获、周期中断、延时……你想想看,没有定时器,MCU就像个没节奏的乐手,乱套了。

GPT,全称General Purpose Timer,通用定时器。在AUTOSAR架构里,它属于MCAL层,负责提供基础的定时服务。嗯,这里要注意,不同芯片厂商的GPT实现细节可能不同,但核心逻辑是相通的。

3.1 GPT功能概述

GPT能干哪些事?我简单列一下:

  • 周期中断:每隔固定时间触发一次中断,比如1ms一次
  • 单次定时:计时到指定值后触发一次中断,然后停止
  • 硬件触发:输出比较、输入捕获等高级功能
  • 时间基准:为系统提供时间戳或延时基准

我个人习惯把GPT分成两类:一类是自由运行的,一直跑不停;另一类是单次触发的,跑完就停。你在配置时得想清楚,你的应用场景需要哪种。

核心要点:GPT的本质就是一个计数器,在时钟驱动下不断累加,当计数值与预设值匹配时,产生事件或中断。

3.2 寄存器详解

说到寄存器,很多初学者一看手册就头大。其实没那么复杂,GPT相关的寄存器就那么几个关键角色:

寄存器名称 功能描述 我踩过的坑
GPT_CR(控制寄存器) 使能定时器、选择模式、配置时钟源 记得先配好其他寄存器,最后再使能
GPT_SR(状态寄存器) 标志位,指示溢出、比较匹配等事件 中断服务函数里一定要清标志位!
GPT_CNT(计数值寄存器) 当前计数值,可读可写 读的时候可能正在更新,建议连续读两次校验
GPT_CMP(比较寄存器) 设定比较值,决定中断触发时机 比较值不能超过定时器的最大计数值
GPT_PSC(预分频寄存器) 对时钟源进行分频,降低计数频率 分频值一般要加1,比如写0就是不分频

我曾经在一个项目里,因为没仔细看数据手册,把预分频寄存器写成了期望的分频系数,结果定时器跑得飞快,中断疯狂触发,整个系统直接卡死。嗯,从那以后我每次配定时器都会先算一遍分频值。

3.3 EB配置步骤(时钟源、分频、周期)

在EB Tresos里配置GPT,说白了就是填几个参数。我建议你按这个顺序来:

  1. 选择时钟源:一般有系统时钟、外设时钟、外部时钟等选项。我个人习惯用系统时钟,省事。
  2. 设置预分频:根据你的目标频率来算。比如系统时钟100MHz,你想要1MHz的计数频率,那就分频100倍。
  3. 配置周期:也就是比较值。比如计数频率1MHz,想要1ms中断一次,比较值就设1000。
  4. 选择模式:连续模式还是单次模式,根据需求来。
  5. 使能中断:勾上中断使能,配好中断优先级。

小技巧:在EB里配置时,可以先在Excel里算好参数,再填进去。我习惯写个小脚本自动生成配置值,避免手算出错。

你想想看,如果时钟源选错了,后面所有计算都是白费。所以第一步一定要确认时钟树,看看GPT模块的时钟到底是从哪来的。

3.4 驱动API实现

驱动API是GPT模块的灵魂。AUTOSAR标准定义了一套接口,我们来实现最核心的几个:

初始化(Gpt_Init)

void Gpt_Init(const Gpt_ConfigType* ConfigPtr)
{
    // 1. 关闭定时器
    GPT_CR &= ~(1 << GPT_CR_ENABLE);
    
    // 2. 配置预分频
    GPT_PSC = ConfigPtr->Prescaler;
    
    // 3. 配置比较值
    GPT_CMP = ConfigPtr->CompareValue;
    
    // 4. 配置模式
    if(ConfigPtr->Mode == GPT_MODE_CONTINUOUS)
    {
        GPT_CR |= (1 << GPT_CR_CONT);
    }
    
    // 5. 使能中断(如果需要)
    if(ConfigPtr->InterruptEnabled)
    {
        GPT_CR |= (1 << GPT_CR_IE);
    }
    
    // 6. 清空计数值
    GPT_CNT = 0;
}

启动(Gpt_StartTimer)

void Gpt_StartTimer(uint32_t Channel, uint32_t Value)
{
    // 设置计数值
    GPT_CNT = 0;
    
    // 设置比较值
    GPT_CMP = Value;
    
    // 启动定时器
    GPT_CR |= (1 << GPT_CR_ENABLE);
}

停止(Gpt_StopTimer)

void Gpt_StopTimer(uint32_t Channel)
{
    // 关闭使能位
    GPT_CR &= ~(1 << GPT_CR_ENABLE);
    
    // 清空计数值
    GPT_CNT = 0;
}

获取计数值(Gpt_GetTimeElapsed)

uint32_t Gpt_GetTimeElapsed(uint32_t Channel)
{
    uint32_t cnt1, cnt2;
    
    // 连续读两次,防止读取时计数值更新
    cnt1 = GPT_CNT;
    cnt2 = GPT_CNT;
    
    if(cnt1 == cnt2)
    {
        return cnt1;
    }
    else
    {
        // 再读一次
        return GPT_CNT;
    }
}

注意:获取计数值时,一定要考虑原子性问题。我曾经在中断里读计数值,结果读到一半被更高优先级中断打断,拿到的数据是错的。后来加了临界区保护才解决。

3.5 中断处理

中断处理是GPT模块的重头戏。我见过不少新手,中断服务函数写得又臭又长,结果系统响应越来越慢。记住一句话:中断服务函数里只做最必要的事

标准的中断处理流程是这样的:

void Gpt_IrqHandler(void)
{
    // 1. 检查中断标志位
    if(GPT_SR & (1 << GPT_SR_OVF))
    {
        // 2. 清标志位(必须!)
        GPT_SR |= (1 << GPT_SR_OVF);
        
        // 3. 调用回调函数
        if(Gpt_Callback != NULL)
        {
            Gpt_Callback();
        }
    }
}

这里有个细节:清标志位的顺序。我建议先清标志位,再执行回调。为什么?因为如果在回调里又触发了中断,标志位没清的话,可能会丢失中断。嗯,这个坑我踩过,后来改成了先清后执行。

经验之谈:中断优先级要合理设置。GPT中断一般用于时间基准,优先级不宜太低,否则时间精度会受影响。但也不能太高,否则会饿死其他中断。我一般设在中等级别。

最后说一句,调试GPT的时候,最好先用逻辑分析仪或者示波器看波形。我曾经靠肉眼盯着调试信息看定时精度,看了半天也没看出问题,后来一上示波器,发现时钟源配错了,分频系数差了10倍。所以说,工具要用对,别光靠猜。

好了,GPT模块的核心内容就这些。下一节我们讲PWM,到时候会用到GPT的知识,记得复习一下。