新闻  |   论坛  |   博客  |   在线研讨会
RTEMS系统时钟原理
xiajiashan | 2012-08-29 10:35:14    阅读:1726   发布文章

作者,下家山(转载请注明作者及出处,请尊重原创)

By 下家山 Q群 75303301 上海松江文汇路928号258室 松江大学城

上海索漫科技 http://www.xiajiashan.com 专注嵌入式(ARM7,Cortex-M0,Cortex-M3,ARM9,linux)培训 一:从应用看原理

每个系统都有一种时钟来支持,常常听到的某某系统支持“时分复用”(亦或叫“轮转调度”),TCP/IP中超时重传,我们每天用的手机能准确计算年,月,日,时,分,秒,还有Linux中常看到的Jifferies。。。。,这些都是通过系统时钟来实现的。

二:从RTEMS4.6.99.3-GP32说起

当然,这种系统时钟是需要硬件支持,象rtems46.99.3中的GP32 bsp就是通过2400的timer4来实现的。(注意,既然系统把timer4用来做系统时钟,timer4就不能用来做其他的定时操作了)

那么,rtems中是如何实现这种系统时钟的呢?下面,我以rtems4.6.99.3中的gp32为例来阐述这个问题:(注意,gp32是针对sumsung的2400,我这里以sumsung的2410讲解)

RTEMS中如果要用到时钟,

首先,需要在应用程序中定义系统配置文件。如,在GP32自带的例子ticker中,(../testsuites/samples/ticker/system.h)就有如下语句:

#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER

系统就根据这条语句启动系统时钟。那么系统是怎么根据这条语句启动系统时钟的呢?请看其脉络:

三:RTEMS4.6.99.3-GP32启动系统时钟代码流程
 
2.jpg

通过一层层的调用,系统时钟已布置好了。但,至此,仅仅是理清了整个rtems代码的组织方式。那么,rtems是怎样用2400的timer4实现系统时钟的呢?

四:关于TICK

这就涉及到定时器了的原理了。

定时器是通过中断来实现的,在函数Clock_driver_support_initialize_hardware()中,通过设置2400的timer4的相关寄存器。(因为,我在2410上调试的,所以函数Clock_driver_support_initialize_hardware()中我加了rTCFG0 = rTCFG0 & ~(0xffffff) | 0x000f00 ;)即可定义系统多长时间tick一次,也即rtems的时间精度(粒度)。其中,BSP_Configuration.microseconds_per_tick就是在应用程序中配置的值,单位是微秒。如果,希望系统的精度达到1毫秒,则应定义BSP_Configuration.microseconds_per_tick = 1000。在pppd例程中定义的是BSP_Configuration.microseconds_per_tick = 10000,即10ms。因此,可知系统每10ms发生一次tick。而在ticker例程中,并没有定义CONFIGURE_MICROSECONDS_PER_TICK,此时系统采用默认配置,其值为10ms。(参考confdefs.h)。

By 下家山 Q群 75303301 上海松江文汇路928号258室 松江大学城

上海索漫科技 http://www.xiajiashan.com 专注嵌入式(ARM7,Cortex-M0,Cortex-M3,ARM9,linux)培训五:怎么实现年,月,日,时,分,秒

我们每天用电脑,但想没想过电脑右下角的时钟是这么实现计时的呢?还有,我们每天用的手机其时间是这么实现的呢?下面,通过rtems中的ticker例程来剖析其中奥秘。

在ticker例程的init.c中,定义了一个rtems_time_of_day类型的结构体time,并初始化为 time.year = 1988;

time.month = 12;

time.day = 31;

time.hour = 9;

time.minute = 0;

time.second = 0;

time.ticks = 0;

接着,通过调用rtems_clock_set函数把time结构体传给内核,作为系统时钟的时间基准。内核中,根据先前定义的CONFIGURE_MICROSECONDS_PER_TICK换算成“每秒产生的tick数”进行计数。以CONFIGURE_MICROSECONDS_PER_TICK = 10000为例,则系统时钟“每秒产生的tick数”为100。因此,系统时钟每tick一次,time.ticks加1,当达到100时向秒进位,即time.second加1(同时,time.ticks复位到0)。Time.second计数到60时又向前进一,即time.minute 加1(同时,time.second复位到0)。以此类推,直到time.year,这个就不用我多说了。

而,系统通过调用rtems_clock_get函数即可获得当前时间值。这样一个系统时钟就运行起来了。

集以上所说,其实还有一个根本性的问题,不知有人产生过疑问没有?

追本逐末,时钟的根源是tick,那么tick是这么产生的呢?

六:TICK的发生

Tick是通过中断实现的。

回到上面的脉络图,在函数Install_clock中调用的Clock_driver_support_install_isr这个函数,就是注册时钟中断号。Clock_driver_support_install_isr这个函数调用了系统函数BSP_install_rtems_irq_handler,而BSP_install_rtems_irq_handler传给内核一个很重要的参数clock_isr_data,它是个结构体,内容如下:

rtems_irq_connect_data clock_isr_data = {BSP_INT_TIMER4,

(rtems_irq_hdl)Clock_isr,clock_isr_on, clock_isr_off,clock_isr_is_on,

3, /* unused for ARM cpus */

0 }; /* unused for ARM cpus */

这个结构体有两个很重要的成员,即BSP_INT_TIMER4和Clock_isr。前者告诉BSP_install_rtems_irq_handler函数,要注册的中断源是timer4;后者是一个中断句柄(句柄就是函数的入口地址),用中断术语描述就叫ISR(interrupt service routine中断服务例程),当timer4中断发生时,即跳到Clock_isr执行。我们再来看Clock_isr里面的内容。

rtems_isr Clock_isr(rtems_vector_number vector)

{

Clock_driver_ticks += 1;

Clock_driver_support_at_tick();

rtems_clock_tick();

}/*为了简洁起见,我去掉了注释和非执行语句*/

函数Clock_driver_support_at_tick();主要完成中断寄存器的复位清零动作,不然第二次中断就不能发生了。

全局变量Clock_driver_ticks就是我们要找的答案,看到没有,每发生一次中断,它的值加1。

By 下家山 Q群 75303301 上海松江文汇路928号258室 松江大学城

上海索漫科技 http://www.xiajiashan.com 专注嵌入式(ARM7,Cortex-M0,Cortex-M3,ARM9,linux)培训 再来看函数rtems_clock_tick();其实这个函数才是真正的为系统时钟服务。跟进去发现它调用了_TOD_Tickle_ticks();函数,这个函数是个内敛函数位于cpukit\rtems\src\clocktick.c。它做了两件事情#define _TOD_Tickle_ticks() \

_TOD_Current.ticks++; \

_Watchdog_Ticks_since_boot++

注意了,这里也有个全局变量加1的动作,其实,这才是我们要找的真正的答案。系统时钟实现计时就是通过取当前tick值_TOD_Current.ticks。

这里又会产生一个疑惑,在发生中断时两个全局变量都做了加1的动作,那么time.ticks计数到100后向前进1而自己清零的动作在哪里呢?这个问题说来又话长了,我这里只告诉大家其代码在哪些位置,具体实现你们去琢磨。

函数rtems_initialize_executive_early(cpukit\sapi\src\exinit.c)-à _TOD_Handler_initialization(cpukit\score\src\coretod.c)。

 

七:定时器实现TICK计数

RTEMS中时间的准确性依赖于tick的实现,而tick是依赖于定时器4(timer4)的实现,timer4每发生一次中断tick实现加1的计数。Timer4是怎么实现定时器的中断的呢?

回到上面的脉络图

#define Clock_driver_support_initialize_hardware() \

do { \

uint32_t cr; \

uint32_t freq,m,p,s; \

/*set prescale to 15*/ \

rTCFG0 = rTCFG0 & ~(0xffffff) | 0x000f00 ; \

/* set MUX for Timer4 to 1/16 */ \

cr=rTCFG1 & 0xFFF0FFFF; \

rTCFG1=(cr | (3<<16)); \

freq = get_PCLK(); \

/* set TIMER4 counter, input freq=PLCK/16/16Mhz*/ \

freq = (freq /16)/16; \

rTCNTB4 = ((freq / 1000) * BSP_Configuration.microseconds_per_tick) / 1000; \

/*unmask TIMER4 irq*/ \

rINTMSK&=~BIT_TIMER4; \

/* start TIMER4 with autoreload */ \

cr=rTCON & 0xFF8FFFFF; \

rTCON=(cr|(0x6<<20)); \

rTCON=(cr|(0x5<<20)); \

} while (0)

要弄懂定时器的工作原理,需要搞清楚三个量

定时器的输入频率 freq

定时器的缓冲计数器TCNTB(内部有一个同名的递减计数器TCNT)

定时器的计数比较器TCMPB(内部有一个同名的递减计数器TCMP,Timer4中没有这个寄存器)

定时器有个输入频率,这个输入频率的来源是PCLK(PCLK的频率来自与板子上的晶振),通过rTCFG0,rTCFG1两个寄存器即可设置PCLK的分频参数(针对s3c2410来说,PCLK有50.7M,不能把这么高的频率输入给定时器,而要进行分频),之后即可得到定时器4的输入频率freq = PCLK/16/16。以PCLK= 50.7M记,freq = 198046HZ,即1秒发生198046次脉冲。

定时器的计数器TCNT根据脉冲频率计数,每发生一次脉冲减1;

TCNT递减到0,则触发中断(TCNT从TCNTB中装载初始值,开始下一轮计数)。

定时公式:

T = { 1 / freq} * TCNTB

如果BSP_Configuration.microseconds_per_tick = 10000,则刚好能实现每10ms发生一次中断(tick)。

注意:TCMPB在调制脉冲(PWM)时才有用。

By 下家山 Q群 75303301 上海松江文汇路928号258室 松江大学城

上海索漫科技 http://www.xiajiashan.com 专注嵌入式(ARM7,Cortex-M0,Cortex-M3,ARM9,linux)培训转载:请注明,作者,下家山 请尊重原创!

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
02年接触ARM和ucos,开发过有线和无线图像报警器,IPCamera,人脸识别系统,OCR识别系统,指纹识别系统,05年开始从事Linux及Rtems下WiFi,camera,Ethernet等驱动开发工作,专做嵌入式linux培训,致力于把我十年来的研发经验传授给每一个学员,招人的可以找我,ximenpiaoxue4016@sina.com
推荐文章
最近访客