第3章:系统时钟与定时器——时钟树解析与精准延时实战

各位同学,咱们今天来啃一块硬骨头——TC275的时钟系统。说实话,我刚接触这颗芯片时,看着时钟树框图也懵了好一阵。但搞嵌入式嘛,时钟就是心脏,跳错了整个系统都得瘫痪。今天我就把这块掰开了揉碎了讲给你听。

3.1 时钟树解析:PLL/OSC/CCU

TC275的时钟源主要有三个:外部晶振(OSC)、内部振荡器、还有PLL倍频。我个人习惯把时钟树想象成一条水管系统——源头是晶振,PLL就是增压泵,CCU则是分水闸。

OSC(振荡器):通常接8MHz或20MHz晶振。我遇到过新手直接焊了个40MHz晶振上去,结果芯片死活不工作——嗯,超规格了。

PLL(锁相环):这才是重头戏。TC275的PLL可以把输入时钟倍频到最高200MHz。公式很简单:

fPLL = fOSC * (N / P) / K2

其中N是倍频系数,P是预分频,K2是后分频。举个例子:8MHz晶振,N=40,P=2,K2=1,得到160MHz。

CCU(时钟控制单元):它负责把PLL输出的时钟分配给各个外设。比如CPU跑200MHz,CAN模块只能跑40MHz,那就得靠CCU分频。

核心要点:TC275的时钟树有三个主要分支——系统时钟(fSYS)、外设时钟(fPER)、以及备份时钟(fBACK)。调试时务必确认每个分支的时钟源和分频系数。

3.2 系统时钟配置实战

配置时钟,说白了就是操作几个寄存器。我最常用的步骤是:

  1. 先切到备份时钟,防止配置过程中系统死掉
  2. 配置PLL的N/P/K2参数
  3. 等待PLL锁定(检查LOCK位)
  4. 切回PLL时钟

来看一段实际代码:

void SystemClock_Init(void)
{
    // 1. 切换到备份时钟(fBACK = fOSC/2)
    SCU_CCUCON0.B.SYSCLKSEL = 0;  // 选择备份时钟
    
    // 2. 配置PLL
    SCU_CCUCON1.B.PLLN = 40;      // 倍频系数N=40
    SCU_CCUCON1.B.PLLP = 2;       // 预分频P=2
    SCU_CCUCON1.B.PLLK2 = 1;      // 后分频K2=1
    
    // 3. 使能PLL并等待锁定
    SCU_CCUCON0.B.PLLEN = 1;
    while(SCU_CCUSTAT.B.PLLLOCK == 0);  // 等待锁定
    
    // 4. 切回PLL时钟
    SCU_CCUCON0.B.SYSCLKSEL = 1;
}

避坑指南:我曾经在等待PLL锁定时忘了加超时处理,结果晶振坏了,程序死循环在那。建议加个超时计数,比如等待10000次还没锁定就报错。

3.3 STM(系统定时器)使用

STM是TC275内置的一个64位定时器,精度高、中断快。我一般用它做系统心跳——比如每1ms触发一次中断,用来做任务调度。

配置STM其实就三步:

  • 设置比较值(决定定时周期)
  • 使能比较中断
  • 启动定时器

举个例子,实现1ms定时:

void STM_Init(void)
{
    // 假设fSYS=160MHz,1ms = 160000个时钟周期
    STM0_CMP0 = 160000;           // 设置比较值
    STM0_ICR.B.CMP0EN = 1;       // 使能比较中断
    STM0_CR.B.STEN = 1;          // 启动定时器
}

// 中断服务函数
void STM_IRQHandler(void)
{
    STM0_ISCR.B.CMP0IRR = 1;     // 清除中断标志
    // 这里放你的周期性任务
}

注意:STM是64位计数器,但比较寄存器只有32位。所以最大定时周期是2^32 / fSYS。160MHz下约26.8秒。超过这个时间需要软件扩展。

3.4 GPT12(通用定时器)使用

GPT12是TC275的通用定时器模块,有5个通道。和STM不同,GPT12更灵活——可以捕获、比较、PWM输出。我曾在电机控制项目里用它做编码器脉冲计数,效果很好。

GPT12的配置稍微复杂点:

void GPT12_Init(void)
{
    // 使能GPT12模块时钟
    SCU_GPT12CLR.B.GPT12EN = 1;
    
    // 配置T2为定时器模式,预分频64
    GPT12_T2CON.B.T2M = 1;       // 定时器模式
    GPT12_T2CON.B.T2I = 4;       // 预分频64(160MHz/64=2.5MHz)
    
    // 设置定时值(1秒 = 2.5M ticks)
    GPT12_T2 = 2500000;
    
    // 使能中断
    GPT12_T2IC.B.T2INT = 1;
    
    // 启动
    GPT12_T2CON.B.T2R = 1;
}

你想想看,GPT12和STM最大的区别在哪?STM是系统级定时器,精度高但功能单一;GPT12是外设定时器,功能丰富但配置繁琐。选哪个?看需求。

3.5 实战:精准延时与周期性任务

好了,理论说完了,咱们来点实际的。精准延时在嵌入式里太常见了——驱动LED闪烁、控制步进电机、采样传感器数据,哪个都离不开。

方案一:使用STM实现微秒级延时

void delay_us(uint32_t us)
{
    uint64_t start = STM0_TIM0;  // 读取当前计数值
    uint64_t ticks = (uint64_t)us * 160;  // 160MHz下,1us=160 ticks
    
    while((STM0_TIM0 - start) < ticks);
}

这个方案精度高,但会占用CPU。我一般在初始化阶段用,运行时尽量不用。

方案二:使用GPT12实现周期性任务

假设我们要每10ms翻转一次LED:

void GPT12_T2_IRQHandler(void)
{
    // 清除中断标志
    GPT12_T2CON.B.T2R = 0;
    GPT12_T2 = 25000;  // 10ms (2.5MHz * 0.01s)
    GPT12_T2CON.B.T2R = 1;
    
    // 翻转LED
    P00_OMR.B.PS0 = 1;  // 假设LED接在P0.0
}

实战经验:我在做工业控制器时,用GPT12的T3通道做PWM输出,T4通道做输入捕获测频率。两个通道共用同一个定时器基准,同步性特别好。你想想看,如果分别用两个独立定时器,相位差就很难控制。

最后说个坑:中断优先级。STM和GPT12的中断优先级要合理分配。我习惯把STM设成最高优先级(系统心跳不能丢),GPT12次之。曾经有一次我把GPT12优先级设得比STM还高,结果系统心跳偶尔丢失,整个任务调度乱套了——排查了两天才找到原因。

好了,时钟和定时器这块就讲这么多。下节课咱们聊PWM和输入捕获,到时候会用到今天讲的GPT12知识。记得动手写代码,光看是学不会的。