LPC17XX定时器配置异常排查:计数速率低于预期
LPC17XX定时器初始化与计数不准问题排查
我在实现LPC17XX定时器的初始化与运行功能,为验证定时器效果,设置了一个每1微秒递增的计数器,但实际运行后发现计数速率远低于预期——例如系统运行529000微秒时,计数器值仅为123832。请帮忙检查定时器配置。
相关寄存器说明:
- MR0I、MR0R、MR0S:定时器匹配控制寄存器位
- CE、CR:定时器控制寄存器位
原始代码
#define PRESCALER_3 2 #define MR0I 0x00000001 #define MR0R 0x00000002 #define MR0S 0x00000004 #define CE 0x00000001 #define CR 0x00000002 void TI_InitTimer(uint8_t u8_NumTimer, uint32_t u32_TempsUs, uint8_t u8_AutoRestart) { uint32_t u32_Horloge; uint32_t u32_Frequence; u32_Horloge = SY_PeriphClock(); // 读取外设时钟频率 u32_Frequence = 1000000 / u32_TempsUs; switch(u8_NumTimer) { case TIMER3: { PCONP |= PCTIM3; // 启用定时器3模块时钟 T3PR = PRESCALER_3 - 1; // 预分频值=1,时钟分频为2 T3CTCR = 0; // 定时器模式(内部频率计数) T3MR0 = (u32_Horloge / PRESCALER_3) / u32_Frequence; T3MCR = 0; // 未使用比较器功能 T3EMR = 0; // 未使用输出翻转功能 T3MCR |= MR0R; T3MCR &= ~MR0S; T3MCR |= MR0I; T3TCR &= ~CE; T3TCR |= CR; T3TCR &= ~CR; T3TCR |= CE; IT_InitIrq(INT_TIMER3,PRIO_INT_TIMER3); g_u8_CompteurTimer3 = 0; g_u8_AutoRestartTimer3 = u8_AutoRestart; break; } default: break; } } void TIMER3_IRQHandler(void) { uint8_t u8_Source; u8_Source = (uint8_t)T3IR; if((u8_Source & 0x01) != 0) // 是否为比较器0触发? { if(g_u8_AutoRestartTimer3 == OFF) { TI_ActiveTimer(TIMER3, OFF); } g_u8_CompteurTimer3++; } T3IR = u8_Source; }
问题分析与修复方案
1. 计数器类型溢出
g_u8_CompteurTimer3是uint8_t类型,最大值仅为255,计数超过后会自动归零,直接导致计数结果严重失真。
- 修复:将计数器改为
uint32_t类型,避免溢出。
2. 匹配值计算逻辑误差
原代码中u32_Frequence = 1000000 / u32_TempsUs,当u32_TempsUs=1时u32_Frequence=1000000,后续计算T3MR0 = (u32_Horloge / PRESCALER_3) / u32_Frequence,若外设时钟不是1000000的整数倍,整数除法会产生截断误差,导致匹配值偏小或偏大,影响定时精度。
- 修复:直接计算微秒对应的定时器计数值:
u32_TimerTicks = (u32_Horloge / 1000000) * u32_TempsUs,再除以预分频值得到T3MR0,逻辑更直观且误差更小。
3. 定时器启动流程冗余
原代码中先关闭定时器,再复位、取消复位,最后启动,步骤冗余且可能引入时序问题。
- 修复:简化启动流程:先置位CR位复位定时器,再直接设置为CE位启动定时器,操作更简洁可靠。
4. 中断标志位清除错误
原代码中T3IR = u8_Source会将所有读取到的中断位都清除,若存在其他未处理的中断,会被误清除,导致后续中断异常。
- 修复:仅清除MR0对应的中断标志位,即
T3IR = 0x01。
5. 外设时钟配置确认
需要确保SY_PeriphClock()返回的是定时器3的实际工作时钟。LPC17XX中定时器外设时钟由PCLKSEL0/PCLKSEL1寄存器控制,若该寄存器设置了分频(比如4分频),会导致定时器实际工作时钟远低于预期,进而减慢计数速率。
- 修复:检查并设置
PCLKSEL0中TIMER3对应的位(通常是第24、25位),确保外设时钟分频符合预期(比如设置为00,即PCLK=CCLK/1)。
修改后的代码示例
#define PRESCALER_3 2 #define MR0I 0x00000001 #define MR0R 0x00000002 #define MR0S 0x00000004 #define CE 0x00000001 #define CR 0x00000002 // 更换为32位计数器,避免溢出 uint32_t g_u32_CompteurTimer3 = 0; uint8_t g_u8_AutoRestartTimer3 = 0; void TI_InitTimer(uint8_t u8_NumTimer, uint32_t u32_TempsUs, uint8_t u8_AutoRestart) { uint32_t u32_Horloge; uint32_t u32_TimerTicks; u32_Horloge = SY_PeriphClock(); // 读取外设时钟频率 // 直接计算目标微秒对应的定时器计数值 u32_TimerTicks = (u32_Horloge / 1000000) * u32_TempsUs; switch(u8_NumTimer) { case TIMER3: { PCONP |= PCTIM3; // 启用定时器3模块时钟 // 确认TIMER3的外设时钟分频,示例设置为PCLK=CCLK/1 PCLKSEL0 &= ~(0x03 << 24); PCLKSEL0 |= (0x00 << 24); T3PR = PRESCALER_3 - 1; // 预分频值=1,时钟分频为2 T3CTCR = 0; // 定时器模式(内部频率计数) T3MR0 = u32_TimerTicks / PRESCALER_3; // 计算预分频后的匹配值 T3MCR = MR0R | MR0I; // 合并设置匹配复位+中断 T3EMR = 0; // 未使用输出翻转功能 // 简化启动:先复位定时器,再启动 T3TCR = CR; // 复位计数器 T3TCR = CE; // 启动定时器 IT_InitIrq(INT_TIMER3, PRIO_INT_TIMER3); g_u32_CompteurTimer3 = 0; g_u8_AutoRestartTimer3 = u8_AutoRestart; break; } default: break; } } void TIMER3_IRQHandler(void) { if(T3IR & 0x01) // 检查MR0中断标志 { if(g_u8_AutoRestartTimer3 == OFF) { TI_ActiveTimer(TIMER3, OFF); } g_u32_CompteurTimer3++; T3IR = 0x01; // 仅清除MR0中断标志 } }
内容的提问来源于stack exchange,提问作者younes anouar
相关产品推荐
相关产品推荐

