高精度时间修正
1. TICK工作原理
其实这应该说“定时器工作原理”更合适些,1个系统tick就是一个定时器硬件中断,全志R16定时器的工作原理很简单,就是内部有一个递减的计数器,当减到0时产生一个中断:
假设定时器模块的输入频率是1MHz,系统定义的tick数是100,也就是100Hz,那么可以计算出递减计数器要设置的值为1MHz/100Hz=10000。可以看出递减计数器相当于一个分频器,输入端每来一个脉冲,其值就减去1,当减到0时产生一个中断,同时其值自动重载成10000,如此循环下去。
2. 系统获取时间操作
系统获取时间相关接口是基于tick来工作的,这其实有精度的:
虚线表示一个tick中断还未产生,如果此时来获取时间,获取到的时间只是之前tick累计的时间。假设tick中断产生时刻和获取时间那一时刻之间的跨度是4ms,那么获取的时间就有4ms的误差,高精度时间修正就是为了消除这种误差而诞生的。
3. 时间修正
我们知道,上述误差产生的根本原因是没有将tick中断产生时刻和获取时间那一时刻之间的跨度更新到时间里去,如果我们能计算出这段时间并加到时间里去不就行了吗?看到没有,so easy!
根据以上的信息,我们可以很容易的想出以下修正算法:
- 根据输入频率我们可以得到计数器递减一次所需要的时间。
- 计数器的初始值是定时器初始化时就确定的,用其值减去获取时间那一刻计数器当前值就可以得到计数器已经递减了多少次。
- 用计数器递减的次数 x 递减一次所需时间 = 需要修正的时间。
- 此外还需要考虑一种特殊情况:当系统是多核时,系统产生了一个由cpu0来处理的tick中断,当cpu0还没有更新整个系统的tick数时,这时cpu1来获取时间,按照上述方法计算后还要加上一个tick的时间才是正确的。
4. 编写代码
我们首先需要在初始化时将定时器的递减初始值和一次递减的时间记录下来:
static UINT32 u32FullCnt; static UINT64 u64NsecPerCnt7; VOID timerStart (INT32 iNum, UINT32 uiHZ) { UINT32 uiCount; UINT32 uiIntvOffset; UINT32 uiCtrlOffset; if (0 == iNum) { uiIntvOffset = TIMER0_INTV; uiCtrlOffset = TIMER0_CTRL; } else if (1 == iNum) { uiIntvOffset = TIMER1_INTV; uiCtrlOffset = TIMER1_CTRL; } else { return ; } uiCount = TIMER_FREQ; uiCount /= uiHZ; if (0 == iNum) { u32FullCnt = uiCount; u64NsecPerCnt7 = ((1000 * 1000 * 1000 / LW_TICK_HZ) << 7) / u32FullCnt; } writel(uiCount, TIMER_BASE + uiIntvOffset); writel(BIT(1) | BIT(2) | BIT(4), TIMER_BASE + uiCtrlOffset); while ((readl(TIMER_BASE + uiCtrlOffset) >> 1) & 0x01); writel(readl(TIMER_BASE + uiCtrlOffset) | BIT(0), TIMER_BASE + uiCtrlOffset); writel(readl(TIMER_BASE + TIMER_IRQ_EN) | BIT(iNum), TIMER_BASE + TIMER_IRQ_EN); }
注意这里记录的递减一次的时间单位的ns,因为SylixOS高精度时间的精度就是1ns,但是如果定时器的时钟频率超过1MHz的话,一次脉冲时间就会小于1ns,所以u64NsecPerCnt7 在计算过程中将结果左移7位扩大以提高计算精度,最终得到的修正时间会再右移7位恢复到ns精度。
修正算法中需要获取当前计数器的值,所以需要额外定义一个这个接口:
static UINT32 timerCurGet (INT32 iNum) { if ((0 != iNum) && (1 != iNum)) return 0; return readl(TIMER_BASE + (TIMER0_CUR + iNum * 0x10)); }
一切准备好之后,接下来就是实现修正算法了:
VOID timerTickHighResolution (struct timespec *ptv) { register UINT32 u32DoCnt; /* * work out how many counts have gone since last timer interrupt */ u32DoCnt = u32FullCnt - timerCurGet(0); /* * check to see if there is an interrupt pending */ if (timerIsIntPending(0)) { /* * re-read the timer, and try and fix up for the missed * interrupt. Note, the interrupt may go off before the * timer has re-loaded from wrapping. */ u32DoCnt = u32FullCnt - timerCurGet(0); if (u32DoCnt != u32FullCnt) { u32DoCnt += u32FullCnt; } } ptv->tv_nsec += (LONG)((u64NsecPerCnt7 * u32DoCnt) >> 7); if (ptv->tv_nsec >= 1000000000) { ptv->tv_nsec -= 1000000000; ptv->tv_sec++; } }
- u32DoCnt就表示获取时间时刻计数器递减的次数,乘上u64NsecPerCnt7就表示需要修正的时间,但是这个得到的时间的扩大后的,所以需要再右移7位恢复到ns精度。
- 上述计算出的ns时间如果超过1s的话,需要将struct timespec结构中的秒成员加1。
- 中间的if判断就是用来处理本章第3小节所说的那种特殊情况的。
在bspLib.c中的bspTickHighResolution 接口中调用我们上面实现的修正函数即可:
VOID bspTickHighResolution (struct timespec *ptv) { /* * TODO: 修改为你的处理代码 */ timerTickHighResolution(ptv); }
评论