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,说白了就是填几个参数。我建议你按这个顺序来:
- 选择时钟源:一般有系统时钟、外设时钟、外部时钟等选项。我个人习惯用系统时钟,省事。
- 设置预分频:根据你的目标频率来算。比如系统时钟100MHz,你想要1MHz的计数频率,那就分频100倍。
- 配置周期:也就是比较值。比如计数频率1MHz,想要1ms中断一次,比较值就设1000。
- 选择模式:连续模式还是单次模式,根据需求来。
- 使能中断:勾上中断使能,配好中断优先级。
小技巧:在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的知识,记得复习一下。