深入解析TMS320DM355定时器:32位非链式模式与看门狗配置实战
1. 项目概述:为什么需要深入理解DM355的定时器?
在嵌入式系统开发,尤其是涉及音视频处理、电机控制或实时数据采集的项目里,时间就是一切。一个精准、可靠且灵活的定时器,往往是整个系统稳定运行的“心跳”。我接触过不少基于TI Davinci系列芯片的项目,TMS320DM355是其中非常经典的一款,它集成了ARM926EJ-S核心和丰富的多媒体外设。很多工程师在驱动摄像头、编码视频流或者实现精确的延时控制时,都会用到它的定时器模块,但往往只停留在“配个中断,数个数”的层面,一旦遇到更复杂的需求,比如需要两个独立且不同精度的定时器,或者想用好看门狗功能,就容易卡壳。
这正是我们今天要拆解的核心:TMS320DM355的64位定时器/看门狗模块,特别是其强大的“32位非链式模式”。这个模式允许你将一个64位定时器资源拆分成两个完全独立的32位定时器来使用,其中一个还能带预分频器,相当于花一份钱买了两样东西,极大地提高了外设利用率和设计灵活性。无论是你需要一个高精度的微秒级定时器来触发DMA搬运数据,还是需要一个秒级的定时器来做系统心跳,都可以通过配置这个模式来实现。而看门狗功能,则是系统抗干扰、防死锁的最后一道保险,其配置逻辑和普通定时器有显著区别,一旦理解不透,很可能导致系统无法正常启动或者误复位。
2. 核心架构与模式解析:从64位到双32位的蜕变
要玩转这个定时器,首先得从芯片手册里那张核心框图(对应原文Figure 6)说起。虽然手册画得详细,但第一次看容易眼花。我用更直白的方式帮你梳理一下。
2.1 全局控制寄存器(TGCR)的核心作用
你可以把TGCR寄存器想象成这个定时器模块的“模式开关总控”。它最重要的一个位就是TIMMODE。这个位决定了整个定时器模块的“人格”:
- TIMMODE = 0:64位定时器模式。TIM12和TIM34寄存器串联成一个64位大计数器,PRD12和PRD34串联成一个64位周期值。这是最宽位宽的模式,适合需要超长定时的场景。
- TIMMODE = 1:32位非链式模式。这就是本文的重点。此时,TIM12和PRD12组成一个独立的32位定时器(我们称为Timer 1:2);TIM34和PRD34组成另一个独立的32位定时器(称为Timer 3:4)。两者互不干扰,可以独立配置、独立运行。
- TIMMODE = 2:看门狗模式。此时定时器2被强制配置为64位看门狗定时器,这是它的专属模式,我们后面会详述。
所以,进入32位非链式模式的第一步,就是在TGCR中设置TIMMODE = 1。
2.2 双32位定时器的分工与差异
设置好模式后,我们就得到了两个定时器,但它们的能力并不完全相同:
Timer 1:2(无预分频器):
- 架构:这是一个“纯净”的32位定时器。它的时钟源(通过TCR寄存器的CLKSRC12选择)直接驱动32位计数器(TIM12)。
- 特点:计时精度最高,直接反映输入时钟的周期。例如,如果输入时钟是24MHz,那么每个计数就代表大约41.67纳秒。适合需要高精度时间基准的场景,比如产生精确的PWM波边沿。
Timer 3:4(带4位预分频器):
- 架构:这是一个“32位定时器+4位预分频器”的组合。时钟源(通过TCR寄存器的CLKSRC34选择)先经过一个4位预分频器。
- 预分频器原理:预分频器自己有一个计数器(PSC34)和一个周期寄存器(TDDR34,在TGCR中)。输入时钟每来一个脉冲,PSC34就加1。当PSC34计数到等于TDDR34设定的值时,再过一个输入时钟周期,预分频器才会输出一个脉冲给后面的32位主计数器(TIM34)。所以,主计数器的时钟频率 = 输入时钟频率 / (TDDR34 + 1)。
- 特点:通过预分频,可以大幅降低定时器中断的频率,扩展定时范围,减少CPU处理中断的负担。例如,输入时钟24MHz,设置TDDR34 = 239,那么主计数器时钟就是100kHz,更适合产生10ms、100ms这种系统心跳节拍。
关键细节与避坑点: 手册中Figure 7的示例图非常关键,它展示了预分频器“慢一拍”的特性:当PSC34等于TDDR34时,并不立刻输出时钟,而是等到下一个输入时钟沿才输出并复位PSC34。这意味着,如果你设置TDDR34=0,预分频器是1分频(即不分频),但输出时钟相对输入会有1个周期的延迟。在计算极其精确的时序时,这个延迟需要考虑进去。
2.3 时钟源选择与同步
两个定时器的时钟源都可以独立选择(通过TCR的CLKSRC12和CLKSRC34):
- 0 = 内部时钟:通常来自芯片的系统时钟分频。这是最常用的模式,稳定且易于控制。
- 1 = 外部时钟/事件:可以来自特定的GPIO引脚(Timer 1:2用GPIO1或GPIO2;Timer 3:4用GPIO3或GPIO2)。这个模式可用于外部脉冲计数或测量信号频率。
重要注意事项: 当使用外部时钟时,手册特别强调:一旦定时器停止,必须保持至少一个外部时钟周期的禁用状态,才能再次启动。否则定时器可能无法正常开始计数。这是因为内部逻辑需要时间与外部异步时钟进行同步。在软件设计时,如果需要频繁启停外部时钟模式的定时器,务必在
STOP操作后插入一个短暂延时(大于一个外部时钟周期)再START。
3. 32位非链式模式的详细配置流程
理解了架构,我们来一步步看如何配置。配置的核心围绕着几个关键寄存器:TGCR(全局控制)、TCR(定时器控制)、PRDn(周期)、RELn(重载周期,仅用于连续重载模式)。
3.1 配置Timer 1:2(无预分频器)
假设我们需要Timer 1:2在内部时钟(假设为24MHz)下,产生一个1ms的中断。
选择模式并释放复位:首先,确保TGCR的TIMMODE=1(32位非链式模式)。然后,将TIM12RS位设为1,这将Timer 1:2的逻辑电路从复位状态释放,准备接受配置。
// 伪代码示例 TGCR |= (1 << 1); // 设置TIMMODE=1, 假设TIMMODE在bit1 TGCR |= (1 << 3); // 设置TIM12RS=1, 假设TIM12RS在bit3设置定时周期:计算周期值。周期 = 定时时间 * 时钟频率。1ms * 24MHz = 24000。将这个值写入PRD12寄存器。
PRD12 = 24000 - 1; // 注意:计数器从0开始计数到PRD值,所以通常写入(目标计数值-1)选择时钟源并设置工作模式:在TCR寄存器中,设置CLKSRC12=0选择内部时钟。然后设置ENAMODE12字段。这个字段决定了定时器如何运行:
- 00b:禁用。定时器不计数。
- 01b:单次模式。计数器从0开始,计数到等于PRD12后产生中断,然后停止。
- 10b:连续模式。计数到PRD12后产生中断,计数器清零,然后重新开始计数。这是我们最常用的周期性中断模式。
- 11b:连续重载模式。计数到PRD12后产生中断,计数器清零,并且自动将REL12寄存器的值加载到PRD12,然后以新的周期开始下一轮计数。这允许在运行时动态改变下一个周期的时长。
TCR &= ~(0x3 << 4); // 清除CLKSRC12位域,假设在bit[5:4] // CLKSRC12保持0(内部时钟) TCR |= (0x2 << 8); // 设置ENAMODE12=10b(连续模式),假设在bit[9:8](仅限ENAMODE=11b时)设置重载周期:如果你使用连续重载模式,需要在当前定时周期结束前,将下一个周期值写入REL12寄存器。这个写入操作可以在中断服务程序中进行。
3.2 配置Timer 3:4(带预分频器)
假设我们需要Timer 3:4产生一个10ms的中断,同样使用24MHz内部时钟。
选择模式并释放复位:同样,确保TIMMODE=1。然后将TIM34RS位设为1。
TGCR |= (1 << 1); // TIMMODE=1 TGCR |= (1 << 4); // TIM34RS=1,假设在bit4设置预分频器:为了得到10ms中断,直接计数会需要24MHz * 0.01s = 240,000次计数,仍在32位范围内,但中断频率为100Hz,对CPU有一定负担。我们可以用预分频器来降低中断频率。比如,我们想让主计数器每100us中断一次(10kHz),然后计数100次得到10ms。
- 预分频器输出频率 = 输入时钟频率 / (TDDR34 + 1)
- 目标:主计数器时钟 = 10kHz = 24MHz / (N + 1)
- 计算得 N = (24MHz / 10kHz) - 1 = 2399。但注意,TDDR34是4位寄存器,最大值是15!所以这里计算错误。
- 正确思路:4位预分频器最大分频比是16。我们应该用它来扩展定时范围,而不是精细分频。例如,设置TDDR34 = 15(即16分频),则主计数器时钟 = 24MHz / 16 = 1.5MHz。
- 此时,10ms需要的主计数器计数 = 1.5MHz * 0.01s = 15000。将15000-1写入PRD34。
- 同时,将TDDR34 = 15写入TGCR的相应位域(PSC34在运行时会自动与之比较,我们只需配置TDDR34)。
设置周期并启用:将计算好的PRD34值写入寄存器。在TCR中,设置CLKSRC34=0,ENAMODE34=10b(连续模式)。
// 设置预分频器分频比 16分频 TGCR &= ~(0xF << 8); // 清除TDDR34位域,假设在bit[11:8] TGCR |= (15 << 8); // 设置TDDR34 = 15 // 设置周期 PRD34 = 15000 - 1; // 设置控制寄存器 TCR &= ~(0x3 << 6); // 清除CLKSRC34,假设在bit[7:6] TCR |= (0x2 << 10); // 设置ENAMODE34=10b,假设在bit[11:10]
3.3 关键寄存器位详解与配置表
为了让配置更清晰,我将关键控制位整理成下表,方便查阅:
| 寄存器 | 位域 | 名称 | 功能描述 | 常用设置 |
|---|---|---|---|---|
| TGCR | TIMMODE | 定时器模式 | 0=64位, 1=32位非链式, 2=看门狗 | 1 |
| TIM12RS | Timer 1:2 复位 | 0=复位(保持计数值), 1=释放(可操作) | 1(使能时) | |
| TIM34RS | Timer 3:4 复位 | 0=复位, 1=释放 | 1(使能时) | |
| TDDR34 | Timer 3:4 预分频周期 | 4位,设置预分频值(0-15) | 根据所需分频比设定 | |
| TCR | CLKSRC12 | Timer 1:2 时钟源 | 0=内部时钟, 1=外部时钟/事件 | 0 |
| CLKSRC34 | Timer 3:4 时钟源 | 0=内部时钟, 1=外部时钟/事件 | 0 | |
| ENAMODE12 | Timer 1:2 使能模式 | 00=禁用, 01=单次, 10=连续, 11=连续重载 | 10(连续) | |
| ENAMODE34 | Timer 3:4 使能模式 | 同上 | 10(连续) | |
| PRD12 | 31:0 | Timer 1:2 周期值 | 32位周期寄存器 | 根据定时时间和时钟计算 |
| PRD34 | 31:0 | Timer 3:4 周期值 | 32位周期寄存器 | 根据定时时间、时钟和预分频计算 |
| REL12 | 31:0 | Timer 1:2 重载周期 | 仅在ENAMODE12=11b时使用 | 动态更新的下一个周期值 |
| REL34 | 31:0 | Timer 3:4 重载周期 | 仅在ENAMODE34=11b时使用 | 动态更新的下一个周期值 |
4. 高级功能与特殊模式(Timer 3专属)
DM355的Timer 3在32位非链式模式下,还支持两个非常实用的高级功能,需要通过设置TGCR中的BW_COMPATIBLE(向后兼容)位来启用。这体现了芯片设计的灵活性。
4.1 事件捕获模式
这是什么?普通的定时器是“时间到了就触发”。而事件捕获模式是“外部信号来了,就记录下当前的时间戳,并立刻触发中断”。这对于测量脉冲宽度、频率,或者同步外部事件极其有用。
如何工作?
- 使能BW_COMPATIBLE位。
- 在TCR中设置CAPMODE12或CAPMODE34为1,使能对应定时器的事件捕获。
- 通过CAPEVTMODE选择捕获触发边沿(上升沿、下降沿或双边沿)。
- 当指定的GPIO引脚(Timer 1:2用GPIO1/2, Timer 3:4用GPIO3/4)上出现设定的边沿事件时:
- 定时器立即停止计数。
- 产生中断(TINT)、EDMA事件(TEVT)和RTO事件。
- 将当前的计数器值(TIM12/TIM34)自动拷贝到对应的捕获寄存器(CAP12/CAP34)中。这样,你在中断服务程序里读CAP寄存器,就能知道事件发生的精确时刻。
- 如果定时器工作在连续重载模式(ENAMODE=11b),还会自动重载周期寄存器。
- 完成后,定时器自动重启,继续从0开始计数。
实操心得: 这个功能用来测频非常方便。比如,让定时器在连续模式下自由运行,捕获模式设置在上升沿。两个上升沿事件之间CAP寄存器的差值,就是信号的周期。比起用输入捕获中断再软件读取计数器,这种方式硬件自动完成,精度更高,几乎无软件延迟。
4.2 读复位模式
这是什么?这是一个调试利器。通常,读取运行中的定时器计数器值是有风险的,因为可能在你读取高16位和低16位的瞬间,计数器进位了,导致读到一个错误的值(比如0x0000FFFF 瞬间变成 0x00010000,你可能读到0x0000FFFF或0x00010000,但正确的应该是0x0000FFFF)。读复位模式就是为了安全地读取计数器值而设计的。
如何工作?
- 使能BW_COMPATIBLE位。
- 在TCR中设置READRSTMODE位为1。
- 当你读取TIM12或TIM34寄存器时,硬件自动执行以下操作:
- 定时器立即停止计数。
- 将当前的计数器值自动拷贝到对应的捕获寄存器(CAP12/CAP34)中。
- 不产生中断或EDMA事件(这是与事件捕获的关键区别)。
- 如果处于连续重载模式,会重载周期寄存器。
- 完成后,定时器自动重启。
- 此时,你读到的TIM寄存器值可能是未定义的(因为触发了复位流程),但捕获寄存器CAP里的值就是你读取瞬间的准确计数值。你后续应该去读CAP寄存器。
避坑指南: 在常规应用中,如果不需要精确读取运行中的计数器,建议不要开启读复位模式。因为每次读取操作都会导致定时器暂停和重启,引入不可控的时间抖动。这个功能仅在调试、单次精确测量时使用。使用后,记得关闭此模式。
5. 看门狗定时器的独立世界
看门狗是系统的“救命稻草”,其配置逻辑与通用定时器截然不同,一旦理解错误,系统可能无法启动或频繁复位。
5.1 看门狗模式的核心逻辑
- 独占性:只有Timer 2可以配置为看门狗模式(通过设置TGCR的TIMMODE=2)。其他定时器不可用此模式。
- 强制性64位:在看门狗模式下,Timer 2强制使用64位模式(TIM34:TIM12组成计数器,PRD34:PRD12组成周期值)。
- 无外部时钟,无单次模式:看门狗只能使用内部时钟,且只能工作在连续模式(实际上是一种特殊的使能模式)。
- 终极目的:计数器从0开始递增,当达到设定的周期值时,触发系统级硬件复位(而不仅仅是中断)。为了防止复位,必须在计数器溢出前,通过软件执行一个特定的“喂狗”序列。
5.2 状态机与“喂狗”序列
这是看门狗最精妙也最容易出错的地方。其操作是一个严格的状态机(对应原文Figure 10):
- 初始状态:硬件复位后,看门狗禁用(WDEN=0)。你可以配置PRD寄存器设置超时时间。
- 预激活状态:设置WDEN=1,并第一次向WDKEY寄存器写入
0xA5C6。此时进入预激活状态,计数器仍不清零。 - 激活状态:在预激活状态下,第二次向WDKEY寄存器写入
0xDA7E。这个操作会清零计数器,并真正启动看门狗计数。此时进入激活状态。 - 服务状态:看门狗开始运行。必须在超时前,重复执行正确的喂狗序列:先写
0xA5C6,再写0xDA7E到WDKEY。每次成功的序列都会将计数器清零,重新开始计时。 - 超时状态:如果超时前未收到正确喂狗序列,系统复位。复位后,看门狗回到初始禁用状态。
致命的陷阱:
- 写入WDKEY的序列必须是
A5C6->DA7E。先写DA7E再写A5C6是无效的。 - 在激活状态,任何非
A5C6或DA7E的值写入WDKEY,或者错误的写入顺序,都会立即触发超时复位! - 一旦超时发生,只有硬件复位才能重新启用看门狗。
5.3 看门狗配置代码示例
// 假设寄存器地址已映射 // 1. 配置为看门狗模式 TGCR = (2 << 1); // 设置TIMMODE=2 (看门狗模式),并释放TIM12RS/TIM34RS(通常硬件会设置) // 2. 设置超时周期(例如,设定在24MHz时钟下约2秒超时) // 64位周期值 = 时间 * 频率 = 2s * 24,000,000 Hz = 48,000,000 // 将48,000,000分配到两个32位寄存器 PRD34 = (48000000ULL >> 32) & 0xFFFFFFFF; // 高32位,本例中为0 PRD12 = 48000000ULL & 0xFFFFFFFF; // 低32位 // 3. 启动看门狗状态机 WDTCR |= (1 << 0); // 设置WDEN=1,使能看门狗(但还未启动计数) WDTCR = (WDTCR & ~0xFF00) | (0xA5C6 << 8); // 第一次写密钥 A5C6 到 WDKEY 位域(假设在bit[15:8]) // 现在处于预激活状态 WDTCR = (WDTCR & ~0xFF00) | (0xDA7E << 8); // 第二次写密钥 DA7E,启动看门狗计数 // 现在看门狗开始运行! // 4. 在系统主循环或空闲任务中定期“喂狗” void FeedWatchdog(void) { // 必须严格按照序列! WDTCR = (WDTCR & ~0xFF00) | (0xA5C6 << 8); WDTCR = (WDTCR & ~0xFF00) | (0xDA7E << 8); }6. 中断、EDMA事件与RTO事件联动
定时器不仅仅是计时的,更是触发后续动作的“发令枪”。
- 中断(TINT):计数器匹配周期时,会向ARM内核产生中断。你需要在内核中配置好中断控制器,并编写中断服务程序来处理。这是最常用的方式,用于执行周期性的软件任务。
- EDMA事件(TEVT):计数器匹配周期时,会触发一个EDMA(增强型直接内存访问)事件。这可以用于不占用CPU的周期性数据搬运。例如,定时器触发EDMA,将ADC采集的数据从外设FIFO直接搬到内存的指定缓冲区。配置好EDMA通道的源/目标地址和传输量后,整个过程由硬件完成,效率极高。
- RTO事件(仅Timer 3):这是DM355特有的实时输出事件,可以连接到芯片内部的其他模块(如视频前端等),用于硬件同步。Timer 3的1:2侧和3:4侧分别对应RTO输入0和1。
配置要点: 中断和EDMA事件的使能通常在芯片的系统级中断控制器和EDMA控制器中配置,而不是在定时器模块本身。定时器只负责产生事件脉冲。你需要查阅ARM子系统和EDMA控制器的文档来正确配置映射关系。
7. 常见问题排查与调试技巧
在实际开发中,定时器不工作或者行为异常是家常便饭。以下是我总结的几个排查思路:
定时器完全不计数:
- 检查时钟:首先确认定时器模块的PSC(电源与睡眠控制器)时钟是否使能。DM355的外设时钟默认可能是关闭的,需要在PSC模块中使能对应定时器的时钟域。
- 检查复位位:确认TGCR中的TIM12RS/TIM34RS是否已设置为1(释放复位)。这是最容易被忽略的一步。
- 检查模式:确认TIMMODE是否设置正确。
- 检查ENAMODE:确认TCR中的ENAMODE是否已设置为非0的有效模式(01,10,11)。
中断不产生:
- 检查PRD值:确保PRD寄存器已写入非0值。如果PRD为0,计数器永远匹配不上(因为计数器从0开始),不会产生事件。
- 检查计数器:读取TIM寄存器,看它是否在递增。如果不递增,回到上一步排查。如果递增但不到PRD值,说明周期设得太大了。
- 检查中断屏蔽:定时器本地产生事件后,需要确保ARM中断控制器(AINTC)中对应的中断线未被屏蔽,且优先级等配置正确。
- 检查中断标志:有些架构需要在中断服务程序中手动清除外设的中断标志位。查阅手册确认是否需要清除TCR或INTCTL_STAT寄存器中的某个状态位。
定时精度偏差大:
- 计算错误:仔细核对时钟源频率、预分频值、周期值的计算。特别注意预分频器是(N+1)分频。
- 中断延迟:软件中断服务程序如果执行时间过长,会影响下一次定时的绝对精度。对于高精度定时,考虑使用EDMA事件代替中断,或者使用Timer 3的捕获模式测量实际间隔。
- 时钟源问题:确认使用的内部时钟频率是否与预期一致。有时系统时钟可能被PLL或分频器配置改变。
看门狗误复位:
- 喂狗序列错误:这是最常见的原因。检查代码中喂狗的序列是否严格是
A5C6->DA7E,并且中间不能被其他操作打断或插入其他写WDKEY的操作。 - 喂狗间隔过长:计算看门狗超时周期,确保喂狗函数执行的间隔远小于超时时间。考虑在系统主循环以及可能阻塞的关键任务中都加入喂狗操作。
- 初始化顺序错误:确保是先设置WDEN=1,再依次写入密钥
A5C6和DA7E来启动。如果在WDEN=0时写密钥是无效的。
- 喂狗序列错误:这是最常见的原因。检查代码中喂狗的序列是否严格是
使用仿真器调试时的注意事项:
- 注意EMUMGT寄存器中的FREE和SOFT位。当仿真器暂停CPU时,这两个位决定了定时器是继续运行还是停止。默认情况下(FREE=0, SOFT=0),定时器会立即停止,这可能影响依赖于定时计数的超时检测逻辑。在调试与时间相关的功能时,可能需要调整这些设置。
通过以上这些步骤和要点,你应该能够驾驭TMS320DM355这颗芯片的定时器模块了。从基本的周期性中断,到复杂的双定时器协同,再到看门狗保护和高级捕获功能,这套硬件提供了相当强大的时间管理能力。关键在于理解各个模式下的状态机和数据流,仔细计算时钟和周期,并在调试时耐心地按照从电源时钟、复位、配置到中断事件的路径进行排查。