前言

继GPIO和SPI实验之后,本次实验深入DSP的核心机制——中断管理CPU定时器。通过配置PIE(外设中断扩展)和CPU定时器0,我们可以实现精确的时间控制。实验分为两个任务:任务1实现LED以1:5的比例周期性闪烁;任务2则是一个“作弊秒表”,能够通过按键调节计时速度、暂停/继续、清零,并在四位数码管上显示时间。

本文将详细讲解TMS320F2812的中断架构、CPU定时器工作原理,并给出完整的代码解析和实验现象。


一、实验概述

1.1 实验目的

  • 理解TMS320F2812的PIE扩展中断结构(PIEIER、PIEIFR、PIEACK等)

  • 掌握CPU定时器0的配置方法(周期寄存器、预定标寄存器、控制寄存器)

  • 学习在中断服务函数中实现精确的定时任务

  • 结合GPIO按键输入、SPI数码管显示,完成一个完整的嵌入式小项目

1.2 实验环境

  • 硬件:SEED-DPS2812M开发板、4位数码管模块、按键SW3/SW6/SW9、XDS510仿真器、PC

  • 软件:CCS(Code Composer Studio)

1.3 实验任务

任务功能描述
任务1利用CPU定时器实现LED点亮1个时间周期、熄灭5个时间周期(一个周期=定时器中断周期)
任务2四位作弊秒表:数码管显示0~9999,每秒递增1(正常速度);SW3加速(周期减少0.2s),SW9减速(周期增加0.2s),SW6暂停/继续;同时按下SW3+SW6清零

二、实验原理

2.1 TMS320F2812的中断系统

F2812的中断分为三个级别:外设级 → PIE级 → CPU级

  • 外设级:每个外设(如CPU定时器、SPI、ADC)都有自己的中断标志和使能位。

  • PIE级:将96个外设中断复用为12个CPU中断(INT1~INT12)。每个INTx下有8个中断源(INTx.1~INTx.8)。PIE模块通过PIEIERx(使能)、PIEIFRx(标志)、PIEACK(应答)来管理。

  • CPU级:CPU有16个可屏蔽中断(INT1~INT14,以及RTOSINT、DLOGINT),通过IER(中断使能寄存器)和全局中断屏蔽INTM控制。

中断响应流程(以CPU定时器0为例):

  1. 定时器0中断事件发生 → 外设标志位(TIF)置1。

  2. PIE模块识别INT1.7(定时器0挂在INT1.7),设置PIEIFR1.7 = 1

  3. PIEIER1.7 = 1PIEACK1 = 0,则向CPU发出INT1请求。

  4. CPU设置IFR1 = 1,若IER1 = 1INTM = 0,则响应该中断。

  5. CPU从PIE向量表中取出预置的中断服务函数地址并执行。

  6. 中断服务函数中必须:

    • 清除外设中断标志(CpuTimer0Regs.TCR.bit.TIF = 1

    • 清除PIEACK(PieCtrlRegs.PIEACK.all = PIEACK_GROUP1

2.2 CPU定时器结构

F2812有三个32位定时器:Timer0、Timer1、Timer2。Timer0可供用户自由使用,Timer1/Timer2通常预留给DSP/BIOS。

核心寄存器

  • TIMH:TIM:32位计数寄存器(递减)

  • PRDH:PRD:32位周期寄存器(重装载值)

  • TDDRH:TDDR:16位分频寄存器

  • PSCH:PSC:16位预定标计数器

  • TCR:控制寄存器(包含TSS启动/停止位、TIE中断使能位、TIF中断标志位等)

定时器中断频率公式

fTINT=SYSCLKOUT(PRDH:PRD+1)×(TDDRH:TDDR+1)fTINT​=(PRDH:PRD+1)×(TDDRH:TDDR+1)SYSCLKOUT​

通常我们配置TDDRH:TDDR = 0,则中断周期 = (PRDH:PRD + 1) / SYSCLKOUT

例如:SYSCLKOUT = 150 MHz(时钟周期 ≈ 6.67 ns),若需要10ms中断,则 PRD = 150e6 × 0.01 - 1 = 1,499,999

2.3 硬件连接回顾

  • 数码管:SPI(GPIOF0~F3)驱动74HC164,段码发送;位选由GPIOB8~B13控制(四位数码管)

  • 按键:SW3(GPIOB5)、SW6(GPIOB4)、SW9(GPIOB3),公共端GPIOB0输出低电平(线性键盘接法)

  • LED(任务1):GPIOA0


三、任务1:LED周期性闪烁(1:5)

3.1 设计思路

  • 配置CPU定时器0产生1秒中断(周期可调,本实验用1秒作为“一个时间周期”)。

  • 在中断服务函数中,使用状态机:

    • 状态0:LED点亮,计数器加1,当计数器达到1时切换到状态1。

    • 状态1:LED熄灭,计数器加1,当计数器达到5时切换回状态0。

  • 这样LED点亮1秒、熄灭5秒,循环。

3.2 关键代码解析

// 全局变量
Uint32 TimerState = 0;   // 0:点亮阶段, 1:熄灭阶段
Uint32 BlinkCounter = 0; // 记录当前阶段经过的中断次数

interrupt void cpu_timer0_isr(void)
{
    if(TimerState == 0)  // 点亮阶段
    {
        GpioDataRegs.GPADAT.bit.GPIOA0 = 1;  // LED亮
        BlinkCounter++;
        if(BlinkCounter >= 1)                // 点亮1个周期
        {
            TimerState = 1;
            BlinkCounter = 0;
        }
    }
    else  // 熄灭阶段
    {
        GpioDataRegs.GPADAT.bit.GPIOA0 = 0;  // LED灭
        BlinkCounter++;
        if(BlinkCounter >= 5)                // 熄灭5个周期
        {
            TimerState = 0;
            BlinkCounter = 0;
        }
    }
    // 清除中断标志和PIE应答
    CpuTimer0Regs.TCR.bit.TIF = 1;
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

3.3 定时器初始化

InitCpuTimers();                              // 必须调用,关联地址
ConfigCpuTimer(&CpuTimer0, 150, 1000000);    // 150MHz, 1秒周期(单位微秒)
CpuTimer0Regs.TCR.bit.TIE = 1;               // 使能定时器中断
IER |= M_INT1;                               // 使能CPU INT1
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;           // 使能PIE INT1.7(定时器0)
StartCpuTimer0();                            // TSS=0启动
EINT;                                        // 开全局中断

3.4 实验现象

  • 下载程序后,LED(GPIOA0)以点亮1秒、熄灭5秒的规律循环闪烁。

  • 变化分明,符合预期。


四、任务2:作弊秒表(四位数码管 + 按键调速)

4.1 系统功能

  • 正常计时:每秒递增1,数码管显示0000~9999,循环。

  • SW3(加速):每按一次,计时周期减少0.2秒(即每秒递增更快),最小周期0.2秒。

  • SW9(减速):每按一次,计时周期增加0.2秒,最大周期3秒。

  • SW6(暂停/继续):单按暂停计时,再按恢复。

  • SW3 + SW6同时按下:秒表清零(且自动暂停)。

4.2 设计要点

(1)定时器中断周期选择

由于需要支持0.2秒、0.4秒、…、3.0秒的可变周期,如果直接修改定时器的PRD值会比较麻烦(需要重新配置定时器)。更简单的做法是:定时器固定产生10ms中断,在中断服务函数中使用一个计数器Timer10msCount累加,当累加值达到 Interval / 10(Interval单位毫秒)时,才更新秒表数值。这样只需修改Interval变量即可调速,无需重新配置硬件定时器。

(2)按键扫描与消抖
  • 按键采用线性连接(GPIOB0输出低,GPIOB3~B5为输入)。

  • KeyScan()函数中实现:

    • 独立按键去抖(计数3次,约30ms)。

    • 组合键检测(SW3和SW6同时按下为清零)。

    • 防止重复触发(按下标志keyPressFlag)。

(3)数码管动态扫描
  • 主循环中持续扫描四位,每位点亮约500μs,利用人眼视觉暂留显示完整数字。

  • SPI发送段码时,注意spi_xmit函数将数据放到高8位(因为代码中SPI配置为16位模式,但只用了高8位)。

(4)中断服务函数中的注意点
interrupt void cpu_timer0_isr(void)
{
    Timer10msCount++;
    KeyScanCounter++;      // 通知主循环进行按键扫描(每10ms一次)
    
    if(!PauseFlag)
    {
        if(Timer10msCount >= (Interval / 10))
        {
            Timer10msCount = 0;
            DisData++;
            if(DisData > 9999) DisData = 0;
        }
    }
    // 清除标志...
}

4.3 关键代码片段

调速逻辑(按键处理)
// SW3加速
if(sw3_state == 0 && combPressFlag == 0) {
    // 消抖后
    if(Interval < 3000) Interval += 200;
    if(Interval > 3000) Interval = 3000;
}
// SW9减速
if(sw9_state == 0) {
    if(Interval > 200) Interval -= 200;
    if(Interval < 200) Interval = 200;
}
组合键清零
if(sw3_state == 0 && sw6_state == 0) {  // 同时按下
    if(comboDebounce >= 2 && comboPressed == 0) {
        comboPressed = 1;
        DisData = 0;
        Timer10msCount = 0;
        PauseFlag = 1;   // 清零后自动暂停,防止立即增加
    }
}
SPI发送时注意数据位置
void spi_xmit(unsigned char a)
{
    SpiaRegs.SPITXBUF = (Uint16)a << 8;  // 高8位有效
}

这是因为SPICCR配置为16位字符长度,但实际段码只有8位,放在高字节,低字节补0。如果希望直接使用8位,可将SPICCR的CHAR位域配置为7(8位字符模式)。

4.4 实验现象

  • 正常情况:数码管从0000开始,每1秒加1。

  • 按SW3:数字跳动速度加快,每按一次,速度提升一档(周期减少0.2s),最快约每秒跳5次(0.2s周期)。

  • 按SW9:速度减慢,最慢约每3秒加1。

  • 按SW6:数字停止变化;再按SW6,继续变化。

  • 同时按SW3+SW6:数字立即归零并暂停。

  • 数码管显示稳定,无闪烁,位选切换正确。


五、实验总结与心得体会

5.1 中断配置要点

  • EALLOW保护:修改PIE向量表时需要EALLOW

  • 清除标志顺序:先清除外设中断标志,再清除PIEACK,否则可能丢失中断。

  • PIEACK机制:同一组的某个中断被响应后,PIEACKx会被硬件置位,该组其他中断暂时无法向CPU请求。在ISR结束时必须写1清除PIEACKx

  • 全局中断DINT关闭,EINT开启,注意ERTM(实时仿真模式下的中断使能)一般也调用。

5.2 定时器配置经验

  • ConfigCpuTimer()的第三个参数单位是微秒,最大值受32位限制(约4295秒)。

  • 如果定时器中断周期较短(如10ms),中断服务函数中应避免复杂的延时或大量计算,以免影响系统响应。

  • 调速方案采用软件计数器(Timer10msCount比较Interval)比动态修改硬件周期寄存器更简单可靠。

5.3 按键处理心得

  • 机械按键必须消抖,本实验采用30ms延时去抖(中断10ms一次,计数3次)。

  • 组合键检测需要独立的状态变量,避免和单键功能冲突。

  • 清零后自动暂停,符合用户预期(否则清零后计数器立即又递增,看不到清零效果)。

5.4 系统集成体会

本实验综合了:

  • CPU定时器(精确计时)

  • PIE中断管理

  • GPIO按键输入

  • SPI驱动数码管

  • 动态扫描显示

通过这个“作弊秒表”项目,真正理解了嵌入式系统中如何将多个外设协同工作,以及中断驱动模型的重要性。


六、完整代码与工程

任务1和任务2的完整代码如附件所示(或参见文中代码块)。在CCS中新建工程,将代码放入main.c,编译下载即可运行。

注意事项

  • 确保开发板上的数码管位选GPIOB8~B13与代码中的宏定义一致(可能与具体外设板有关,必要时调整)。

  • 按键连接方式:SW3→GPIOB5,SW6→GPIOB4,SW9→GPIOB3,公共端GPIOB0接地。

  • 任务1的LED连接GPIOA0,也可以改成其他引脚。


七、常见问题排查

现象可能原因解决方法
定时器不中断未使能PIE或CPU中断,或未清除PIEACK检查IERPIEIER1.7PIEACK清除代码
LED一直亮或一直灭状态机逻辑错误或中断未进入在ISR中设置断点,观察TimerState变化
按键无响应GPIO方向配置错误,或未设置公共输出低电平检查Init_Key_Gpio是否正确将GPIOB0置0并保持
数码管显示乱码SPI数据位宽或相位不匹配确认SPICCR的CHAR位域,尝试改为0x47(8位)或调整时钟极性
调速后时间不准确Interval单位理解错误确保定时器中断周期为10ms,Interval单位是毫秒,比较值为Interval/10

八、拓展思考

  • 如果要求秒表精确到0.01秒,应该如何修改?(提高定时器中断频率,例如1ms中断)

  • 能否将变速改为连续可调(如按住SW3持续加速)?

  • 如何增加一个复位按键,使间隔恢复默认1秒?

这些问题留给读者自行尝试。


本文为TMS320F2812中断与定时器实验的完整记录,希望对学习DSP的小伙伴有帮助!

如果觉得写得好,欢迎点赞、收藏、转发!如有疑问,评论区交流讨论。

更多推荐