3. 时基单元(TBU)详解

各位同学,欢迎来到第三章。这一章我们聊聊TBU——时基单元。说实话,很多工程师在学GTM时,最容易忽略的就是TBU。大家总觉得它就是个计数器,没什么好研究的。但我告诉你,TBU是整个GTM的“心跳”。没有它,PWM生成、定时捕捉、中断触发,全都玩不转。

我个人习惯把TBU比作一个城市的“标准时钟”。你想想看,城市里每个人、每辆车、每个红绿灯,如果各自用各自的时间,那不乱套了吗?TBU就是那个给所有人对表的“北京时间”。

3.1 TBU的计数器结构(TBU_TS0/1/2)

TBU内部有三个独立的计数器,分别叫TBU_TS0、TBU_TS1和TBU_TS2。这三个家伙各有各的脾气。

计数器 位宽 典型时钟源 用途
TBU_TS0 24位 CMU_CLK0 全局时间基准,最常用
TBU_TS1 24位 CMU_CLK1 辅助时间基准,可用于低速任务
TBU_TS2 24位 CMU_CLK2 特殊用途,比如外部事件同步

每个计数器都是24位的,这意味着什么?如果时钟是100MHz,那么一个计数器大概能跑167毫秒才会溢出。嗯,这里要注意——24位其实不算大,所以很多应用里我们需要配合软件来做溢出处理。

我在项目中遇到过一个问题:有个同事直接用TBU_TS0的原始值做时间戳,结果跑了几百毫秒后数值突然变小了,他以为是bug。其实就是溢出了,没处理。所以大家记住,TBU的计数器是循环计数的,不是无限大的。

核心要点:TBU_TS0/1/2是三个独立的24位递增计数器。它们各自有自己的时钟源,互不干扰。但你可以通过配置让它们同步启动。

3.2 TBU与CMU的时钟关联

TBU自己不产生时钟,它需要CMU(时钟管理单元)给它喂时钟。说白了,CMU是“厨师”,TBU是“吃饭的人”。

CMU可以输出三路时钟:CMU_CLK0、CMU_CLK1、CMU_CLK2。这三路时钟的频率可以独立配置。比如你可以让CMU_CLK0跑100MHz,CMU_CLK1跑10MHz,CMU_CLK2跑1MHz。然后分别喂给TBU_TS0、TBU_TS1、TBU_TS2。

为什么会这样设计?我猜英飞凌的工程师考虑的是灵活性。有些模块需要高精度的时间基准,比如PWM的边沿对齐;有些模块只需要一个粗略的定时,比如看门狗。你想想看,如果所有模块都用同一个100MHz时钟,那低速模块的计数器会很快溢出,软件处理起来很麻烦。

我的建议:在实际项目中,我通常把TBU_TS0配成最高频率,用于PWM和捕捉;TBU_TS1配成中等频率,用于定时中断;TBU_TS2配成低频,用于系统心跳。这样各司其职,互不干扰。

3.3 TBU的同步与触发机制

TBU有一个很实用的功能:同步启动。你可以让三个计数器在同一个时刻开始计数。这在多通道PWM应用中特别重要。

举个例子,你有一个三相电机控制项目,需要三路PWM。如果三路PWM的时基不是同时开始的,那相位关系就乱了。电机可能会抖动,甚至烧坏。我曾经就见过一个案例,工程师没做TBU同步,结果电机启动时电流冲击特别大,直接把MOSFET炸了。

TBU的同步机制是这样的:

  1. 先配置好每个计数器的时钟源和初始值
  2. 然后写一个全局触发寄存器(TBU_CTRL)
  3. 所有计数器在下一个时钟沿同时开始计数

除了同步启动,TBU还支持外部触发。你可以用一个GPIO引脚或者一个定时器事件来触发TBU复位或启动。这个功能在需要与外部设备对齐时间时非常有用。

避坑指南:我曾经犯过一个错误——在配置TBU同步时,忘记先停止所有计数器。结果同步触发后,有的计数器从0开始,有的从中间值开始,完全乱套了。正确的做法是:先停止所有TBU计数器,配置好参数,再统一触发同步启动。

3.4 实战:配置TBU作为全局时间基准

好了,理论讲完了,我们来动手。下面是一个实际的配置流程,我以英飞凌TC3xx系列为例。

第一步:配置CMU时钟

先让CMU输出我们需要的时钟频率。假设我们想让TBU_TS0跑100MHz。

// 配置CMU_CLK0为100MHz
CMU->CLKEN0.B.CLKEN = 1;      // 使能CMU时钟
CMU->CLKCFG0.B.CLKDIV = 0;    // 不分频,直接用系统时钟
// 假设系统时钟就是100MHz

第二步:配置TBU计数器

把TBU_TS0的时钟源设为CMU_CLK0,并设置初始值。

// 配置TBU_TS0
TBU->TS0_SRC.B.CLK_SEL = 0;   // 选择CMU_CLK0
TBU->TS0_VAL.B.VALUE = 0;     // 初始值设为0
TBU->TS0_CTRL.B.EN = 1;       // 使能计数器

第三步:同步启动

如果你只需要一个计数器,那直接使能就行。但如果你需要多个计数器同步,就要用下面的方法。

// 先停止所有计数器
TBU->CTRL.B.STOP = 1;

// 配置每个计数器的时钟源和初始值
TBU->TS0_SRC.B.CLK_SEL = 0;
TBU->TS0_VAL.B.VALUE = 0;
TBU->TS1_SRC.B.CLK_SEL = 1;
TBU->TS1_VAL.B.VALUE = 0;

// 清除停止标志,所有计数器同时开始
TBU->CTRL.B.STOP = 0;

第四步:读取当前时间

配置好之后,你就可以随时读取TBU_TS0的值,作为全局时间戳。

uint32_t get_timestamp(void)
{
    uint32_t ts;
    ts = TBU->TS0_VAL.B.VALUE;
    return ts;
}

实战经验:在实际项目中,我通常会在系统初始化时调用一次TBU同步配置,然后整个系统的所有定时任务都基于这个全局时间基准。比如,一个任务需要延时10微秒,我就读取当前TBU_TS0的值,加上1000(假设100MHz时钟,10微秒就是1000个时钟周期),然后轮询等待TBU_TS0达到这个值。这样做的好处是:所有任务的定时都是精确的,不会因为中断或任务调度而漂移。

嗯,到这里,TBU的核心内容就讲完了。总结一下:TBU是GTM的“心跳”,三个计数器可以独立配置时钟,也可以同步启动。配置时要注意先停止再同步,避免乱序。下一章我们会讲CMU的详细配置,到时候你会更清楚如何给TBU“喂”合适的时钟。