专注嵌入式(ARM7,Cortex-M0,Cortex-M3,ARM9,linux)培训 一:从应用看原理
每个系统都有一种时钟来支持,常常听到的某某系统支持“时分复用”(亦或叫“轮转调度”),TCP/IP中超时重传,我们每天用的手机能准确计算年,月,日,时,分,秒,还有Linux中常看到的Jifferies。。。。,这些都是通过系统时钟来实现的。
当然,这种系统时钟是需要硬件支持,象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
系统就根据这条语句启动系统时钟。那么系统是怎么根据这条语句启动系统时钟的呢?请看其脉络:
通过一层层的调用,系统时钟已布置好了。但,至此,仅仅是理清了整个rtems代码的组织方式。那么,rtems是怎样用2400的timer4实现系统时钟的呢?
这就涉及到定时器了的原理了。
定时器是通过中断来实现的,在函数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是通过中断实现的。
回到上面的脉络图,在函数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)。
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)时才有用。
专注嵌入式(ARM7,Cortex-M0,Cortex-M3,ARM9,linux)培训转载:请注明,作者,下家山 请尊重原创!
roy1979.chou_250166599 2011-11-21 23:07