1. 从寄存器手册到实战:GPTM定时器控制、同步与中断的深度解析
搞嵌入式开发,尤其是基于ARM Cortex-M内核的MCU,定时器(Timer)绝对是你绕不开的核心外设。它就像系统里的“心跳”和“节拍器”,负责精确计时、生成PWM波、触发ADC采样,甚至是协调多个外设同步工作。很多新手朋友一看到数据手册里那几十页的寄存器描述就头大,特别是GPTM(General-Purpose Timer Module)这种功能强大的定时器模块,光是控制、同步和中断相关的寄存器就有好几个,每个寄存器里还有一堆位域,确实容易让人眼花缭乱。
今天,我就以TI的Tiva™ C系列TM4C123GH6ZRB这颗经典的MCU为例,带大家把GPTM模块里最核心的几个寄存器——GPTMCTL(控制寄存器)、GPTMSYNC(同步寄存器)、GPTMIMR(中断屏蔽寄存器)、GPTMRIS(原始中断状态寄存器)和GPTMMIS(屏蔽的中断状态寄存器)——彻底掰开揉碎了讲清楚。我们不只停留在“这个位是干嘛的”的层面,更要深入到“为什么这么设计”以及“实际编程中怎么用、有哪些坑”,让你下次再配置定时器时,心里有底,手上有谱。
2. GPTM控制寄存器(GPTMCTL):定时器的“总开关”与“精细调谐”
如果把一个GPTM定时器模块比作一台精密的机床,那么GPTM配置寄存器(GPTMCFG)和定时器模式寄存器(GPTMTnMR)决定了它是车床还是铣床(工作模式),而GPTMCTL寄存器就是这台机床的操作面板。上面不仅有电源开关(定时器使能),还有急停按钮(调试暂停)、运行模式切换(事件边沿选择)以及联动其他设备的触发接口(ADC触发使能)。
2.1 寄存器概览与访问须知
GPTMCTL寄存器的偏移地址是0x00C。对于TM4C123GH6ZRB,它有多个定时器实例(Timer 0-5),每个都有独立的基地址。例如,16/32位的Timer 0基址是0x4003.0000,那么它的GPTMCTL寄存器地址就是0x4003.000C。
在动手写代码之前,有一个黄金法则必须牢记:修改GPTMCTL寄存器中任何与特定定时器(Timer A或Timer B)相关的位之前,必须确保对应的TnEN位(Timer n Enable)是清零(禁用)状态。这是数据手册里加粗强调的“Important”。为什么?因为定时器在运行时,其内部状态机可能正处于某个敏感状态(比如正在加载新值、产生边沿)。此时如果突然改变控制信号(比如使能ADC触发或改变事件边沿),可能导致不可预测的行为,比如误触发、计数错乱。所以,安全的操作顺序永远是:先停止定时器(清零TAEN/TBEN),再修改配置,最后重新使能。
2.2 核心位域详解与实战配置
GPTMCTL寄存器主要控制Timer A和Timer B两个子定时器,它们的位域定义是对称的。我们以Timer A为例进行拆解,Timer B的位域(TBEN, TBEVENT等)逻辑完全一致。
TAEN (Bit 0): Timer A使能位这是最基础的位。写0,定时器停止,所有相关逻辑(计数、比较、输出)冻结。写1,定时器根据GPTMCFG和GPTMTAMR寄存器配置的模式开始运行。
- 实战代码片段(使用TI的TivaWare库):
直接操作寄存器的话,就是// 假设我们使用Timer0的Timer A // 1. 先禁用定时器(安全操作的第一步) TimerDisable(TIMER0_BASE, TIMER_A); // 2. 进行一系列其他配置(设置模式、装载值、分频等)... // 3. 最后使能定时器 TimerEnable(TIMER0_BASE, TIMER_A);HWREG(TIMER0_BASE + TIMER_O_CTL) &= ~TIMER_CTL_TAEN;和HWREG(TIMER0_BASE + TIMER_O_CTL) |= TIMER_CTL_TAEN;。
TASTALL (Bit 1): Timer A调试暂停使能这是一个非常实用的调试功能。当你的代码在调试器中遇到断点,或者单步执行时,处理器核心会被“挂起”。如果TASTALL=0,定时器会无视核心挂起,继续自顾自地计数。这在调试与时间严格相关的逻辑(比如PWM输出、精确延时)时很麻烦,因为你一暂停,时间还在走,现象就变了。设置TASTALL=1后,一旦调试器挂起核心,定时器也自动暂停,整个世界都“静止”了,方便你观察瞬间状态。
- 注意事项:这个位只在调试器介入时生效。程序正常运行时,无论
TASTALL为何值,定时器都正常运行。
TAEVENT (Bits 3:2): Timer A事件模式这个位域仅在定时器配置为输入边沿计数(Edge-Count)模式或输入边沿时间(Edge-Time)模式(即捕获模式)时才有意义。它决定了定时器捕获外部引脚信号的哪种边沿。
0x0: 上升沿触发。0x1: 下降沿触发。0x3: 双边沿(上升和下降)都触发。0x2: 保留,不要使用。- 一个关键的坑:数据手册的Note里特别指出,如果使能了PWM输出反相(通过
TAPWML位),会翻转边沿检测的中断行为。举个例子,你设置了TAEVENT=0x0(上升沿中断),同时也设置了TAPWML=1(输出反相)。那么,实际PWM信号产生的上升沿,在内部逻辑看来可能是下降沿,从而导致你期望的中断无法产生。在设计捕获电路和中断服务程序时,一定要把输出极性这个因素考虑进去。
RTCEN (Bit 4): RTC(实时时钟)停止使能这个位是针对芯片内部RTC模块的。当RTCEN=1时,即使处理器被调试器停止,RTC也会继续运行。这对于需要保持绝对时间戳的应用(如数据记录仪)至关重要。它的优先级高于TASTALL。也就是说,如果RTCEN=1,即使TASTALL=1,RTC也不会停止。
TAOTE (Bit 5): Timer A输出触发使能这是实现定时器与ADC模块硬件级联动的关键!设置TAOTE=1,当Timer A发生超时事件(在单次触发或周期模式下计数到0)时,GPTM模块会自动产生一个脉冲信号输出到ADC的触发源选择器。
- 重要限制(坑点):这个触发只由超时事件产生。匹配事件(Match)、比较事件(Compare)或PWM事件都不会产生ADC触发信号。这意味着你不能直接用PWM的占空比切换点来触发ADC,必须用定时器的周期结束点。
- 完整链路:要使ADC真的被触发,还需要在ADC模块中,通过
ADCEMUX寄存器选择对应的定时器作为触发源。例如,Timer0A的超时触发,可能对应ADCEMUX中的某个EMn位选择TRIGGER_TIMER0A。
TAPWML (Bit 6): Timer A PWM输出电平控制当定时器工作在PWM模式时,这个位控制PWM输出引脚的默认有效电平(即输出比较匹配前的电平)。
0: 输出不受影响。通常意味着匹配发生前输出低电平,匹配后输出高电平(具体取决于PWM模式)。1: 输出反向。将上述逻辑反转。- 应用场景:驱动某些外设可能需要高电平有效,有些则需要低电平有效。通过此位可以灵活适配,无需更改硬件电路。
3. GPTM同步寄存器(GPTMSYNC):让多个定时器“齐步走”
在复杂的控制系统中,经常需要多个定时器同时启动、同步运行,或者让一个定时器的事件去触发另一个定时器。例如,用Timer0产生一个主时钟,Timer1和Timer2基于这个主时钟的节拍分别执行不同的PWM或捕获任务。硬着头皮用软件在中断里先后启动多个定时器,总会引入微小的不同步误差。GPTMSYNC寄存器就是为了消除这种误差而生的硬件同步机制。
3.1 同步机制的工作原理
GPTMSYNC寄存器仅在GPTM模块0(即Timer0/1/2/3/4/5所在的模块)上有效。它包含两组控制位:
SYNCT0~SYNCT5: 用于同步16/32位定时器(Timer 0-5)。SYNCWT0~SYNCWT5: 用于同步32/64位宽定时器(Wide Timer 0-5,由两个16/32位定时器级联形成)。
每个控制位域有2个比特,可以编码4种状态:
0x0: 无操作,该定时器不受同步操作影响。0x1: 由Timer A的超时事件来触发目标定时器的同步操作。0x2: 由Timer B的超时事件来触发目标定时器的同步操作。0x3: 由Timer A或Timer B的超时事件来触发目标定时器的同步操作。
这里的“触发同步操作”具体指什么?当你在GPTMSYNC寄存器中为某个目标定时器(比如Timer2)设置了SYNCT2=0x1,然后对GPTMSYNC寄存器执行一次写操作(即使你只是写入相同的值),硬件并不会立即同步。它会“记住”这个设置。之后,当源定时器(Timer0A)发生一次超时事件时,硬件会自动将目标定时器(Timer2)的计数器重新加载其初始值(从GPTMTAILR/TBILR),从而实现与源定时器事件的严格同步启动或复位。
3.2 实战:配置Timer0A同步Timer1和Timer2
假设我们需要Timer0A作为一个1ms周期的主定时器,Timer1和Timer2需要与Timer0A同时开始它们的周期计数。
- 配置各个定时器:首先,独立配置好Timer0, Timer1, Timer2为周期模式,设置好各自的装载值(决定各自的周期)。但先不要使能Timer1和Timer2。
- 设置同步寄存器:
使用TivaWare库函数可能更清晰:// 设置Timer0A的超时事件同步Timer1和Timer2 // SYNCT1对应Timer1, SYNCT2对应Timer2 uint32_t syncValue = 0; syncValue |= (0x1 << 0); // SYNCT0 = 0x1 (理论上也可同步自己,但通常不需要) syncValue |= (0x1 << 2); // SYNCT1 = 0x1 (位[3:2], 0x1左移2位) syncValue |= (0x1 << 4); // SYNCT2 = 0x1 (位[5:4], 0x1左移4位) HWREG(TIMER0_BASE + TIMER_O_SYNC) = syncValue; // TIMER0_BASE是GPTM模块0的基址TimerSynchronize(TIMER0_BASE, TIMER_0A_SYNC | TIMER_1A_SYNC | TIMER_2A_SYNC);(注意库函数对参数的定义可能不同,需查手册)。 - 触发同步:完成上述配置后,第一次使能Timer0A。当Timer0A第一次计数到0并超时时,硬件会自动将Timer1和Timer2的计数器重置为初始值,并使它们开始计数。注意,目标定时器需要事先配置好并处于“就绪”状态。
- 注意事项:
- 同步操作是事件驱动的,只有源定时器超时事件发生,才会同步目标定时器。
- 同步操作会重置目标定时器的计数器,但不会改变其使能状态。目标定时器必须在同步事件发生前已被使能(或配置为在加载后自动开始)。
- 这个功能对于需要严格相位关系的多路PWM生成,或者多通道ADC的定时触发采样,是必不可少的。
4. GPTM中断管理三剑客:IMR, RIS, MIS
中断是定时器响应事件、通知CPU的核心方式。GPTM模块提供了三个紧密相关的寄存器来管理中断:GPTMIMR(中断屏蔽寄存器)、GPTMRIS(原始中断状态寄存器)和GPTMMIS(屏蔽的中断状态寄存器)。理解它们的关系,是写出稳定可靠中断服务程序(ISR)的关键。
4.1 中断信号产生与处理的流水线
我们可以把中断的产生到CPU响应看作一条流水线:
- 事件发生:定时器超时、匹配、捕获到边沿等。
- 置位RIS:无论你是否关心这个事件,硬件都会在GPTMRIS(原始中断状态寄存器)的对应位上置1。
RIS是“Raw Interrupt Status”的缩写,它是最原始、未经任何过滤的中断标志。 - 通过IMR过滤:GPTMIMR(中断屏蔽寄存器)就像一个开关。对应位为1,表示允许该中断信号通过;为0,则阻断。只有那些在
IMR中被使能(位为1)的中断源,其信号才能继续向下传递。 - 生成MIS:通过
IMR过滤后的中断信号,会体现在GPTMMIS(屏蔽的中断状态寄存器)中。MIS是“Masked Interrupt Status”的缩写。只有MIS寄存器中的位被置1,才会真正向NVIC(嵌套向量中断控制器)发出中断请求,进而可能触发CPU跳转到ISR。 - CPU响应与清除:CPU进入ISR后,需要读取
MIS或RIS来判断是哪个中断源触发的(通常读MIS更直接),并在处理完成后,通过向GPTMICR(中断清除寄存器)的对应位写1来清除中断标志。清除操作会同时将RIS、MIS中的对应位清零。
4.2 关键位域解析与中断使能流程
中断类型很多,我们挑几个典型的讲,逻辑都是相通的。
TATOIM (Bit 0): Timer A超时中断屏蔽
0: 禁止Timer A超时中断。1: 使能Timer A超时中断。- 何时触发:当Timer A在单次触发(One-Shot)或周期(Periodic)模式下,计数器达到其终止值(0或GPTMTAILR的值)时,如果此位为1,则中断信号可以通过。
TAMIM (Bit 4): Timer A匹配中断屏蔽
0: 禁止Timer A匹配中断。1: 使能Timer A匹配中断。- 何时触发:在单次触发或周期模式下,当Timer A的计数器值(GPTMTAR)与匹配寄存器值(GPTMTAMATCHR)相等时触发。注意:要使能匹配中断,除了设置
TAMIM,还需要在模式寄存器GPTMTAMR中设置TAMIE位。
CAEIM (Bit 2) / CAMIM (Bit 1): Timer A捕获事件/匹配中断屏蔽
- 这两个用于输入捕获模式。
CAEIM:使能后,每当在捕获引脚上检测到指定的边沿(由TAEVENT决定),就会产生中断。适用于测量脉冲频率。CAMIM:使能后,当捕获到的计数值(GPTMTAR)与预设的匹配值(GPTMTAMATCHR)相等时产生中断。适用于测量脉冲宽度是否超过特定阈值。
RTCIM (Bit 3): 实时时钟中断屏蔽
- 使能RTC模块产生的中断。
WUEIM (Bit 16): 32/64位定时器写操作更新错误中断屏蔽
- 这是一个错误状态中断。当软件错误地连续两次写入Timer A的寄存器,或者在写入Timer B寄存器之前先写了Timer A寄存器(对于级联的32/64位模式有严格的写入顺序要求),该位置1。使能此中断可以帮助在调试时快速发现配置顺序的错误。
4.3 完整的中断配置与处理实战
假设我们要配置Timer0A在周期模式下,每次超时产生一个中断,并在中断服务程序中翻转一个LED。
- 初始化与配置定时器:
#include <stdint.h> #include <stdbool.h> #include “inc/hw_memmap.h” #include “inc/hw_types.h” #include “driverlib/sysctl.h” #include “driverlib/timer.h” #include “driverlib/interrupt.h” // 使能Timer0外设时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0); // 等待外设就绪(好习惯) while(!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0)); // 配置Timer0A为32位周期模式 TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER); // 设置定时器装载值,假设系统时钟80MHz,实现1ms中断 TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet() / 1000 - 1); // 关键步骤:先清除可能存在的旧中断标志 TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT); - 使能中断(配置IMR):
这行代码背后,就是设置// 使能Timer0A的超时中断 TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);GPTMIMR寄存器的TATOIM位为1。 - 在NVIC级别使能中断:
// 在处理器NVIC中使能Timer0A的中断向量 IntEnable(INT_TIMER0A); // 设置中断优先级(可选) IntPrioritySet(INT_TIMER0A, 0x00); - 使能定时器:
TimerEnable(TIMER0_BASE, TIMER_A); - 编写中断服务程序(ISR):
void Timer0A_Handler(void) { // 1. 读取中断状态(通常读MIS寄存器),判断中断源 uint32_t intStatus = TimerIntStatus(TIMER0_BASE, true); // true表示读取MIS // 2. 清除已处理的中断标志(向GPTMICR对应位写1) TimerIntClear(TIMER0_BASE, intStatus); // 清除触发本次中断的标志位 // 3. 执行中断任务,例如翻转LED LED_Toggle(); // 注意:TimerIntClear函数内部通常会同时清除RIS和MIS标志。 // 但有一个大坑:如果你在清除标志后,但在退出ISR前,同一个中断事件又发生了, // RIS会再次被置位。但由于你刚刚清除了MIS,且IMR仍为使能,这个新事件会再次 // 让MIS置位,导致CPU刚退出ISR又立刻进入,形成“中断风暴”。 // 解决方法:确保中断处理足够快,或者考虑在ISR开始时暂时禁用该中断(谨慎使用)。 } - 关于GPTMRIS和GPTMMIS的读取选择:
- 在ISR中,通常使用
TimerIntStatus(TIMER0_BASE, true)来读取GPTMMIS。因为它直接告诉你,是哪个被使能的中断源最终导致了本次CPU中断。代码清晰,逻辑直接。 - 在某些高级诊断或监控场景,你可能会想读取
GPTMRIS(TimerIntStatus(TIMER0_BASE, false)),看看有没有任何事件发生,即使它没有被使能。例如,用于调试或事件计数。
- 在ISR中,通常使用
4.4 一个极易踩坑的细节:定时器禁用与中断标志
数据手册在GPTMRIS寄存器的描述里用“Note”给出了一个非常重要的警告:通过清除GPTMCTL.TnEN位来禁用定时器,并不会自动清除GPTMRIS寄存器中已有的中断标志位。
这意味着什么?假设你的程序流程是这样的:使能定时器中断 -> 中断发生 -> 在ISR中清除标志 -> 出于某种原因(如切换模式),你禁用了定时器(TnEN=0)-> 稍后重新使能定时器(TnEN=1)。如果在禁用期间,GPTMRIS中的标志位由于某种原因(可能是软件误操作或未及时清除)仍然为1,那么在你重新使能定时器的瞬间,如果对应的GPTMIMR中断屏蔽位也是1,这个“陈旧”的中断标志会立刻通过GPTMMIS向CPU发出中断请求,导致一个“虚假”的中断。
避坑指南:在每次准备重新使能一个定时器之前,如果程序逻辑不能保证其GPTMRIS标志是干净的,一个安全的做法是手动清除可能相关的所有中断标志。
// 在禁用定时器后,重新使能前 TimerDisable(TIMER0_BASE, TIMER_A); // ... 进行其他配置 ... // 安全操作:清除该定时器所有可能的中断标志 TimerIntClear(TIMER0_BASE, TIMER_TIM_A_TIMEOUT | TIMER_TIM_A_MATCH | TIMER_CAP_A_EVENT | TIMER_CAP_A_MATCH); // 现在可以安全地重新使能定时器了 TimerEnable(TIMER0_BASE, TIMER_A);5. 寄存器操作实战:从理论到代码
理解了每个位的含义,最终要落到代码上。虽然TI提供的TivaWare库函数封装得很好,但了解其背后的寄存器操作,能让你在调试和优化时更有底气。
5.1 直接寄存器操作示例
假设我们不使用库函数,要直接配置Timer1的Timer B为边沿计数模式,下降沿触发,并使能中断。
// 定义寄存器地址(以Timer1为例,16/32位模式基址为0x4003.1000) #define TIMER1_CTL (*(volatile uint32_t *)(0x40031000 + 0x00C)) #define TIMER1_CFG (*(volatile uint32_t *)(0x40031000 + 0x000)) #define TIMER1_TBMR (*(volatile uint32_t *)(0x40031000 + 0x008)) #define TIMER1_IMR (*(volatile uint32_t *)(0x40031000 + 0x018)) #define TIMER1_ICR (*(volatile uint32_t *)(0x40031000 + 0x024)) // 1. 禁用Timer B (遵循先禁用再修改的原则) TIMER1_CTL &= ~(1 << 8); // 清除TBEN位 (Bit 8) // 2. 配置为16位边沿计数模式 (假设GPTMCFG=0x04) TIMER1_CFG = 0x04; // 16位定时器模式 TIMER1_TBMR = (0x3 << 0); // TnMR字段设置为0x3,捕获模式,边沿计数 // 3. 配置控制寄存器:下降沿触发,使能定时器 // TBEVENT = 0x1 (下降沿), TBEN = 1 uint32_t ctlValue = (0x1 << 10) | (0x1 << 8); // TBEVENT在bit[11:10],设为0x1。TBEN在bit8。 TIMER1_CTL = ctlValue; // 4. 使能中断(假设为捕获事件中断) TIMER1_IMR |= (1 << 10); // 设置CBEIM位 (Bit 10) // 5. 清除可能存在的旧中断标志 TIMER1_ICR |= (1 << 10); // 向CBECINT位 (Bit 10) 写1清除 // 6. 在NVIC中使能Timer1B中断(此处略,需操作NVIC寄存器)5.2 使用TivaWare DriverLib的最佳实践
对于大多数应用,使用TI官方的DriverLib是更高效、更不易出错的方式。上面的例子用DriverLib实现:
#include “driverlib/timer.h” // 初始化外设时钟等... SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1); // 配置为边沿计数模式,下降沿 TimerConfigure(TIMER1_BASE, TIMER_CFG_16_BIT_PAIR | TIMER_CFG_B_CAP_COUNT); TimerControlEvent(TIMER1_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE); // 设置事件计数目标值(可选,若只需计数次数) TimerMatchSet(TIMER1_BASE, TIMER_B, 100); // 计数100个下降沿后触发匹配中断 // 使能捕获事件中断和匹配中断 TimerIntEnable(TIMER1_BASE, TIMER_CAPB_EVENT | TIMER_CAPB_MATCH); // 清除旧中断标志 TimerIntClear(TIMER1_BASE, TIMER_CAPB_EVENT | TIMER_CAPB_MATCH); // 使能Timer B TimerEnable(TIMER1_BASE, TIMER_B); // 配置NVIC... IntEnable(INT_TIMER1B);DriverLib函数内部已经帮你处理了寄存器操作的顺序和位域组合,代码可读性和可维护性大大提升。
6. 常见问题排查与调试技巧
在实际项目中,定时器配置不出问题几乎是不可能的。下面是一些常见症状和排查思路。
问题1:定时器根本不开中断,或者中断只进一次。
- 检查顺序:确认是否遵循了“配置->清标志->使能中断->使能定时器”的顺序。错误的顺序可能导致首次中断丢失。
- 检查IMR和NVIC:用调试器查看
GPTMIMR寄存器对应位是否已置1。再检查NVIC的ISER寄存器,确认CPU级别中断是否已使能。 - 检查ISR中的清标志操作:确保中断服务程序里正确清除了对应的中断标志。忘记清除标志会导致中断只触发一次。
- 检查中断优先级:如果其他高优先级中断长时间阻塞,你的定时器中断可能无法得到响应。
问题2:中断频繁触发,近乎“死循环”(中断风暴)。
- 检查清除时机:最常见的原因是在ISR中先处理任务,最后才清除标志。如果任务处理时间较长,在此期间定时器事件再次发生,
RIS置位,由于标志未清,MIS会持续有效,导致CPU不断响应中断。务必在ISR入口处或尽早清除标志。 - 检查事件频率与处理能力:定时器中断频率是否超过了ISR处理能力的上限?例如,1MHz的中断频率,ISR执行需要2微秒,必然导致灾难。
- 检查“虚假中断”:回顾4.4节的坑,检查是否有在定时器禁用-重新使能过程中未清除旧标志的情况。
问题3:ADC触发不工作。
- 检查TAOTE/TBOTE:确认
GPTMCTL中的输出触发使能位已置1。 - 检查定时器模式:确认定时器是否工作在单次触发(One-Shot)或周期(Periodic)模式。PWM和输入捕获模式不会产生ADC触发信号。
- 检查ADC配置:确认ADC模块的触发源选择寄存器(如
ADCEMUX)已正确选择对应的定时器作为触发源。 - 使用示波器或逻辑分析仪:测量定时器对应的ADC触发输出引脚(具体引脚需查数据手册引脚复用表),看超时事件时是否有脉冲产生。这是最直接的硬件验证方法。
问题4:多定时器同步不准。
- 检查GPTMSYNC配置:确认同步源(
SYNCTn/SYNCWTn)设置是否正确,是Timer A还是Timer B事件。 - 检查同步时机:同步是在源定时器超时事件发生时执行的。确保源定时器已经运行并产生了超时事件。
- 检查目标定时器状态:目标定时器在同步事件发生时必须是已使能状态。如果目标是禁用状态,同步事件会被忽略。
- 软件同步作为补充:对于极其苛刻的同步要求,可以考虑在硬件同步的基础上,在同一个高优先级中断里用软件微调多个定时器的计数器值。
调试技巧:善用调试器观察寄存器
- 在IDE(如CCS, Keil)的调试模式下,定期查看
GPTMCTL,GPTMRIS,GPTMMIS这几个关键寄存器的值。 GPTMRIS告诉你“发生了什么事件”,GPTMMIS告诉你“哪些事件最终导致了中断请求”。对比两者,可以快速判断是中断未使能,还是标志未清除。- 在怀疑中断风暴时,在ISR入口设置断点,观察是否被连续调用。同时观察
GPTMICR的写入操作是否成功清除了标志。