1、问题描述
使用rtthread框架自带的hwtimer实现硬件定时时,会出现如下问题:
- 用hwtimer默认的频率rt_hwtimer_device.freq(即1000000)时,发现设置的超时时间会减半。比如设置10秒超时,实际是5秒就超时。
- 为了减少硬件定时器的中断次数,将rt_hwtimer_device.freq设置为1000,超时时间变得很诡异。比如设置5秒超时,实际是860毫秒就超时,而且启动定时器后会立马进入一次ISR。
2、查找问题的原因
先说结论:
- rtthread设置硬件定时器的分频时,使用的定时器时钟不准确,导致最终的超时时间不是预想的值。
- 定时器的分频寄存器是16位,当设置的rt_hwtimer_device.freq太小时,会导致分频值超过65535,从而实际设置到分配寄存器的值被截断。
查看drv_hwtimer.c源码的__set_timerx_freq函数的实现,如下:
static void __set_timerx_freq(uint32_t timerx, uint32_t freq)
{
uint32_t ap1freq, ap2freq;
uint16_t prescaler;
uint32_t temp;
if (timerx == TIMER0 || timerx == TIMER7 || timerx == TIMER8 \
|| timerx == TIMER9 || timerx == TIMER10)
{
ap2freq = rcu_clock_freq_get(CK_APB2);
temp = RCU_CFG0 & RCU_CFG0_APB2PSC;
temp >>= 11; // 此处错误,应该改为 temp >>= 13。因为APB2 Prescaler的分频在RCU_CFG0寄存器的[13:15]位
/* whether should frequency doubling */
temp = (temp < 4) ? 0 : 1;
prescaler = (ap2freq << temp) / freq - 1;
}
else
{
ap1freq = rcu_clock_freq_get(CK_APB1);
temp = RCU_CFG0 & RCU_CFG0_APB1PSC;
temp >>= 8; // 此处错误,应该改为 temp >>= 10。因为APB1 Prescaler的分频在RCU_CFG0寄存器的[10:12]位
/* whether should frequency doubling */
temp = (temp < 4) ? 0 : 1;
prescaler = (ap1freq << temp) / freq - 1;
}
timer_prescaler_config(timerx, prescaler, TIMER_PSC_RELOAD_NOW);
}
此函数的作用是根据freq设置定时器的分频值prescaler。
定时器的时钟设置如下图所示
TIMER1/2/3/4/5/6/11/12/13属于APB1;TIMER0/7/8/9/10属于APB2。以TIMER4为例,它的时钟由CK_AHB、APB1 Prescaler的分频(由RCU_CFG0寄存器的[10:12]位(APB1PSC[2:0])设置)和CK_APB1的倍频(由RCU_CFG1寄存器的[24]位(TIMERSEL))实现。
而__set_timerx_freq函数只考虑了APB1的分频值。且获取分频值的寄存器移位错误,上面的函数中已标注。
rcu_clock_freq_get(CK_APB1)函数获取CK_APB1的值,即CK_AHB经过APB1 Prescaler分频后的值,由它决定prescaler的值。而实际上还需考虑CK_APB1的倍频值。并不能简单的用表达式temp = (temp < 4) ? 0 : 1;获取定时器的时钟。
当rtthread使用硬件PWM时,会调用 rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);将定时器频率重新设置为与CK_AHB一样的值(APB1分频值为4,此函数倍频为4)。