1. tick工作原理简介
其实这应该说“定时器工作原理简介”更合适些,1个系统tick就是一个定时器硬件中断,定时器的工作原理很简单,就是内部有一个递减的计数器,当减到0时产生一个中断:
假设定时器模块的输入频率是1MHz,系统定义的1S内tick数是100,也就是100Hz,那么可以计算出递减计数器要设置的值为1MHz/100Hz=10000。可以看出递减计数器相当于一个分频器,输入端每来一个脉冲,其值就减去1,当减到0时产生一个中断,同时其值自动重载成10000,如此循环下去。
2. 系统获取时间操作
系统获取时间相关接口是基于tick来工作的。但是这是有误差的:
虚线表示一个tick中断还未产生,如果此时来获取时间,获取到的时间只是之前tick累计的时间。假设tick中断产生时刻和获取时间那一时刻之间的跨度是4ms,那么获取的时间就有4ms的误差,高精度时钟就是为了消除这种误差而诞生的。
3. 高精度时钟原理
3.1 基本原理
我们知道,上述误差产生的根本原因是没有将tick中断产生时刻和获取时间那一时刻之间的跨度更新到时间里去,如果我们能计算出这段时间并加到时间里去不就行了吗?看到没有,so easy!
结合第1小节和第2小节中的图,分析如下:
- 1个tick时间其实等价于递减计数器的初始值,假设是10000,也就是说递减10000次相当于过了一个tick时间
- 我们用1000 * 1000 * 1000 / 10000得到递减一次的时间,单位是ns
- 我们用计数器初始值减去获取时间那一刻计数器中的值,就得到了获取时间时刻计数器已经递减的次数
- 拿递减一次的时间 * 递减的次数 = tick中断产生时刻和获取时间那一时刻之间的时间跨度
3.2 特殊情况
当系统是多核时,系统产生了一个由cpu0来处理的tick中断,当cpu0还没有更新整个系统的tick数时,这时cpu1来获取时间,按照3.1的方法计算后还要加上一个tick的时间才是正确的。
4. 代码展示
/********************************************************************************************************* ** 函数名称: bspTickHighResolution ** 功能描述: 修正从最近一次 tick 到当前的精确时间. ** 输 入 : ptv 需要修正的时间 ** 输 出 : NONE ** 全局变量: ** 调用模块: *********************************************************************************************************/ VOID bspTickHighResolution (struct timespec *ptv) { REGISTER UINT32 uiCntCur, uiDone; uiCntCur = (UINT32)timerGetCnt(4); uiDone = GuiFullCnt - uiCntCur; /* * 检查是否有 TICK 中断请求 */ if (rSRCPND & BIT_TIMER4) { /* * 这里由于 TICK 没有及时更新, 所以需要重新获取并且加上一个 TICK 的时间 */ uiCntCur = (UINT32)timerGetCnt(4); uiDone = GuiFullCnt - uiCntCur; if (uiCntCur != 0) { uiDone += GuiFullCnt; } } ptv->tv_nsec += (LONG)((Gui64NSecPerCnt7 * uiDone) >> 7); if (ptv->tv_nsec >= 1000000000) { ptv->tv_nsec -= 1000000000; ptv->tv_sec++; } }
- GuiFullCnt表示递减计数器的初始值,也就是产生1个tick时间的计数值;Gui64NSecPerCnt7表示递减一次的时间,但是这个时间被扩大了128倍,目的是为了提高计算精度。
- uiDone表示获取时间时刻计数器已经递减的次数,uiCntCur表示获取时间时刻计数器当前值
- if (rSRCPND & BIT_TIMER4)用于3.2小节特殊情况的判断,条件成立表示系统的tick数还没来得及更新,不成立表示系统的tick数已经被更新
- 当uiCntCur=0时,表示tick中断刚刚产生,这时uiDone就已经代表一个tick的计数值,所以就无需再加上一个tick的计数值了
- 最后将修正后的时间赋值给tv_nsec成员
评论