TMS320F28335定时器中断实战:从寄存器配置到LED闪烁(附完整代码)

在嵌入式系统开发中,定时器中断是最基础也最核心的功能之一。对于DSP初学者来说,掌握TMS320F28335的定时器中断机制,不仅能快速实现LED闪烁这样的基础功能,更是后续开发PWM、ADC采样等复杂应用的基础。本文将带你从零开始,通过Timer0中断实现LED控制,并深入解析每个配置步骤背后的原理。

1. 环境搭建与工程创建

在开始编码前,我们需要准备好开发环境。推荐使用Code Composer Studio(CCS)作为开发工具,这是TI官方提供的集成开发环境,对TMS320系列DSP有完善的支持。

创建新工程的步骤:

  1. 打开CCS,选择File → New → CCS Project
  2. 在Project Type中选择"C2000"
  3. 选择TMS320F28335作为目标器件
  4. 设置工程名称和存储路径
  5. 选择空项目模板(Empty Project)

工程创建完成后,需要添加必要的库文件。F28335的开发通常需要以下文件:

  • DSP2833x_Headers文件夹中的头文件
  • DSP2833x_Common文件夹中的源文件
  • 项目特定的源文件(我们将创建的main.c等)

提示:TI提供了完善的例程库,建议从TI官网下载并安装controlSUITE,其中包含大量F28335的参考代码。

2. 定时器工作原理深度解析

TMS320F28335内部有三个32位CPU定时器(Timer0/1/2),其中Timer1和Timer2通常为DSP/BIOS操作系统保留。当不使用操作系统时,这三个定时器都可作为通用定时器使用。

2.1 定时器内部结构

定时器的核心由两部分组成:

  1. 预分频模块

    • 16位分频寄存器(TDDRH:TDDR)
    • 16位预分频计数器(PSCH:PSC)
  2. 定时/计数模块

    • 32位周期寄存器(PRDH:PRD)
    • 32位计数寄存器(TIMH:TIM)

定时器的工作流程如下:

  1. 系统时钟(SYSCLKOUT)每个脉冲使预分频计数器减1
  2. 当预分频计数器减到0时,向主计数器借位(TIMH:TIM减1)
  3. 主计数器减到0时,产生中断信号并重载周期值

2.2 定时器中断路径

TMS320F28335的中断系统采用PIE(外设中断扩展)架构,Timer0中断信号路径为:

Timer0 → PIE组1第7小中断 → CPU INT1

这种分级结构大大扩展了中断源数量,但也增加了配置复杂度。下面是Timer0相关的重要寄存器:

寄存器名称功能描述关键位
TCR定时器控制寄存器TSS(停止位)、TRB(重载位)、TIE(中断使能)
PRD周期寄存器决定定时器中断频率
TPR预分频寄存器扩展定时范围

3. 定时器配置实战

现在,我们开始实际配置Timer0。整个过程分为硬件初始化和软件配置两部分。

3.1 硬件初始化

首先需要配置系统时钟和外设时钟。F28335默认使用内部振荡器,但实际项目中常使用外部晶振。

void InitSysCtrl(void)
{
    // 禁用看门狗
    DisableDog();
    
    // 初始化PLL,假设使用10MHz外部晶振,配置为150MHz
    InitPll(0x0E, 0x03);
    
    // 初始化外设时钟
    InitPeripheralClocks();
}

3.2 Timer0详细配置步骤

完整的Timer0配置流程如下:

  1. 使能定时器时钟
EALLOW;
SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1;  // 使能Timer0时钟
EDIS;
  1. 初始化定时器参数
// 指向Timer0寄存器
CpuTimer0.RegsAddr = &CpuTimer0Regs;

// 设置周期值(假设150MHz时钟,500ms中断)
CpuTimer0Regs.PRD.all = 0xFFFFFFFF;
CpuTimer0Regs.TPR.all = 0;
CpuTimer0Regs.TPRH.all = 0;

// 停止定时器并重载
CpuTimer0Regs.TCR.bit.TSS = 1;
CpuTimer0Regs.TCR.bit.TRB = 1;
  1. 使用TI提供的配置函数 TI提供了便捷的配置函数ConfigCpuTimer(),可以简化设置过程:
void ConfigCpuTimer(struct CPUTIMER_VARS *Timer, float Freq, float Period)
{
    Uint32 temp;
    
    // 计算周期值
    temp = (long)(Freq * Period);
    Timer->RegsAddr->PRD.all = temp;
    
    // 预分频设为1
    Timer->RegsAddr->TPR.all = 0;
    Timer->RegsAddr->TPRH.all = 0;
    
    // 配置控制寄存器
    Timer->RegsAddr->TCR.bit.TSS = 1;  // 先停止定时器
    Timer->RegsAddr->TCR.bit.TRB = 1;  // 重载
    Timer->RegsAddr->TCR.bit.TIE = 1;  // 使能中断
}
  1. 中断向量表配置
EALLOW;
PieVectTable.TINT0 = &TIM0_IRQn;  // 设置中断服务函数入口
EDIS;
  1. 使能中断
// 启动定时器
CpuTimer0Regs.TCR.bit.TSS = 0;

// 使能PIE和CPU级中断
IER |= M_INT1;  // 使能CPU INT1
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;  // 使能PIE组1第7中断

// 使能全局中断
EINT;
ERTM;

4. 中断服务函数与LED控制

中断服务函数(ISR)是定时器中断的核心,这里我们实现LED翻转功能。

4.1 编写中断服务函数

interrupt void TIM0_IRQn(void)
{
    // 翻转LED2状态
    LED2_TOGGLE;
    
    // 清除中断标志
    PieCtrlRegs.PIEACK.bit.ACK1 = 1;
}

4.2 LED初始化

在main函数中,我们需要初始化LED GPIO:

void LED_Init(void)
{
    EALLOW;
    GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;  // 配置GPIO0为普通IO
    GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;   // 设置为输出
    GpioDataRegs.GPASET.bit.GPIO0 = 1;   // 初始化为高电平(灯灭)
    EDIS;
}

4.3 主函数实现

完整的main函数如下:

void main(void)
{
    // 初始化系统控制
    InitSysCtrl();
    
    // 初始化PIE和中断向量表
    InitPieCtrl();
    IER = 0x0000;
    IFR = 0x0000;
    InitPieVectTable();
    
    // 初始化LED和定时器
    LED_Init();
    TIM0_Init(150, 500000);  // 150MHz, 500ms中断
    
    // 主循环
    while(1)
    {
        // 可以在这里添加其他任务
        DELAY_US(100);
    }
}

5. 调试技巧与常见问题

在实际开发中,定时器中断可能会遇到各种问题。下面是一些常见问题及解决方案:

5.1 中断不触发

可能原因

  1. 全局中断未使能(缺少EINT)
  2. PIEACK未清除
  3. 定时器未启动(TSS=1)
  4. 中断向量表配置错误

排查步骤

  1. 检查IER和PIEIER寄存器值
  2. 在中断服务函数起始处设置断点
  3. 查看TCR寄存器的TIF位是否置1

5.2 中断频率不正确

定时器中断频率由以下公式决定:

中断周期 = (PRD + 1) × (TDDR + 1) / CPU时钟频率

调试建议

  1. 使用CCS的寄存器查看功能,确认PRD和TDDR值
  2. 检查系统时钟配置是否正确

5.3 使用CCS调试技巧

  1. 实时变量监控:在Expressions窗口添加关键变量
  2. 寄存器查看:View → Registers → CPU Timer Registers
  3. 周期计数器:利用定时器的InterruptCount统计中断次数

注意:调试中断时,建议先禁用优化(Project Properties → Build → C2000 Compiler → Optimization Level → None)

6. 进阶应用与扩展

掌握了基础定时器中断后,可以进一步探索以下应用:

6.1 多定时器协同工作

通过配置Timer0和Timer1,可以实现更复杂的时间管理:

// 初始化两个定时器
TIM0_Init(150, 100000);  // 100ms
TIM1_Init(150, 500000);  // 500ms

// 在各自的中断服务函数中执行不同任务
interrupt void TIM0_IRQn(void) {
    // 快速任务
    PieCtrlRegs.PIEACK.bit.ACK1 = 1;
}

interrupt void TIM1_IRQn(void) {
    // 慢速任务
    PieCtrlRegs.PIEACK.bit.ACK2 = 1;
}

6.2 定时器与PWM结合

TMS320F28335的ePWM模块可以基于定时器实现精确的PWM输出:

// 配置ePWM1使用Timer0作为时钟基准
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;  // 递增计数模式
EPwm1Regs.TBPRD = 15000;                    // PWM周期
EPwm1Regs.CMPA.half.CMPA = 5000;            // 占空比

6.3 低功耗定时器应用

在电池供电设备中,可以配置定时器唤醒休眠的DSP:

// 进入休眠前配置
CpuTimer0Regs.TCR.bit.TSS = 0;  // 确保定时器运行

// 休眠指令
asm(" IDLE");

7. 完整代码示例

以下是整合后的完整代码,包含所有必要部分:

#include "DSP2833x_Device.h"
#include "DSP2833x_Examples.h"

// 定义LED操作宏
#define LED1_TOGGLE GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1
#define LED2_TOGGLE GpioDataRegs.GPATOGGLE.bit.GPIO1 = 1

// 函数声明
void InitSysCtrl(void);
void LED_Init(void);
void TIM0_Init(float Freq, float Period);

interrupt void TIM0_IRQn(void);

void main(void)
{
    // 初始化系统控制
    InitSysCtrl();
    
    // 初始化PIE和中断向量表
    InitPieCtrl();
    IER = 0x0000;
    IFR = 0x0000;
    InitPieVectTable();
    
    // 配置Timer0中断
    EALLOW;
    PieVectTable.TINT0 = &TIM0_IRQn;
    EDIS;
    
    // 初始化硬件
    LED_Init();
    TIM0_Init(150, 500000);  // 150MHz, 500ms
    
    // 主循环
    while(1)
    {
        __asm(" NOP");
    }
}

// Timer0初始化
void TIM0_Init(float Freq, float Period)
{
    EALLOW;
    SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1;
    EDIS;
    
    // 指向Timer0寄存器
    CpuTimer0.RegsAddr = &CpuTimer0Regs;
    
    // 配置定时器
    ConfigCpuTimer(&CpuTimer0, Freq, Period);
    
    // 启动定时器
    CpuTimer0Regs.TCR.bit.TSS = 0;
    
    // 使能中断
    IER |= M_INT1;
    PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
    EINT;
    ERTM;
}

// Timer0中断服务函数
interrupt void TIM0_IRQn(void)
{
    LED2_TOGGLE;
    PieCtrlRegs.PIEACK.bit.ACK1 = 1;
}

// LED初始化
void LED_Init(void)
{
    EALLOW;
    // GPIO0和GPIO1配置为输出
    GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;
    GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0;
    GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
    GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
    // 初始状态:LED1亮,LED2灭
    GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;
    GpioDataRegs.GPASET.bit.GPIO1 = 1;
    EDIS;
}

在实际项目中,定时器中断的稳定性和精确性直接影响整个系统的性能。通过本文的实践,不仅能实现LED闪烁这样的基础功能,更能深入理解TMS320F28335的中断机制,为后续开发更复杂的DSP应用奠定坚实基础。

更多推荐