Tick信号对于任何一款操作系统而言,就类似于人的心脏脉搏,关键性不言而寓,其本质上就是操作系统的激励源,各种调度算法,时间片等概念,包括具体的任务,可以理解为一个巨大的状态机,在激励源的激励下,按部就班执行,一切都是可预测的,只不过复杂度比较高而已。
同样,对于裸机而言,虽没有多任务执行的能力,但是实现了tick机制,可以编写出比较复杂的裸机软件,而且结构清新,可读性强,扩展简单。当然,如果我们真的需要在裸机下实现多种任务的执行,比如在后台下载http网页的时候,同时显示下载进度,并可以接收用户的任何点击操作,比如停止按钮等。我们可以通过协程就可以实现多任务了,协程的本质就是由程序员来控制各种任务的轮换,而不是由操作系统本身按照一定的算法进行调度。当然也可以用传统的MCU编程方法,前后台系统来实现。方法很多,但都异曲同工,条条大路通罗马。
Tick其实很简单,就是一周期性执行的一段代码,不管cpu当前在执行什么任务,时间到了,这段代码必须运行。在这个周期性的代码里,我们可以将一个全局变量加一,用于记录流逝的时间,在具体的应用软件里,我们可以依据此变量实现精确延时。
了解到这里,我想大家已经明白该怎么做了,就是申请一个周期性触发中断的定时器,然后在中断处理函数里,添加相应的代码而已。
Exynos4412裸机开发系列教程中使用了一个100HZ的定时器,也就最大精度为10ms,小于10ms的延时都是不太准确的,当然我们可以提高到1000HZ,这样精度就可以到1ms了。
好了,来看具体的实现。首先,我们需要初始化定时器,设置相应的寄存器,并注册中断处理函数:
void exynos4412_tick_initial(void)
{
u64_t pclk;
if(!clk_get_rate("pclk", &pclk))
return;
if(!request_irq("TIMER4", timer_interrupt, 0))
return;
/* Using pwm timer 4, prescaler for timer 4 is 16 */
writel(EXYNOS4412_TCFG0, (readl(EXYNOS4412_TCFG0) & ~(0xff<<8)) | (0x0f<<8));
/* Select mux input for pwm timer4 is 1/2 */
writel(EXYNOS4412_TCFG1, (readl(EXYNOS4412_TCFG1) & ~(0xf<<16)) | (0x01<<16));
/* Load value for 10 ms timeout */
writel(EXYNOS4412_TCNTB4, (u32_t)(pclk / (2 * 16 * 100)));
/* Auto load, manaual update of timer 4 and stop timer4 */
writel(EXYNOS4412_TCON, (readl(EXYNOS4412_TCON) & ~(0x7<<20)) | (0x06<<20));
/* Enable timer4 interrupt and clear interrupt status bit */
writel(EXYNOS4412_TINT_CSTAT, (readl(EXYNOS4412_TINT_CSTAT) & ~(0x1<<4)) | (0x01<<4) | (0x01<<9));
/* Start timer4 */
writel(EXYNOS4412_TCON, (readl(EXYNOS4412_TCON) & ~(0x7<<20)) | (0x05<<20));
/* initial system tick */
tick_hz = 100;
jiffies = 0;
}
中断处理函数里,只让一个全局变量jiffies简单的加一,并清中断标志:
static void timer_interrupt(void * data)
{
/* tick count */
jiffies++;
/* Clear interrupt status bit */
writel(EXYNOS4412_TINT_CSTAT, (readl(EXYNOS4412_TINT_CSTAT) & ~(0x1f<<5)) | (0x01<<9));
}
当然这里还有些辅助函数及变量,这里一遍贴出
volatile u32_t jiffies = 0;
static u32_t tick_hz = 0;
u32_t get_system_hz(void)
{
return tick_hz;
}
u64_t clock_gettime(void)
{
if(get_system_hz() > 0)
return (u64_t)jiffies * 1000000 / get_system_hz();
return 0;
}
有了以上的tick机制,我们就可以实现一些比较使用的功能了,比如延时函数,具体实现如下。
static volatile u32_t loops_per_jiffy = 0;
void __attribute__ ((noinline)) __delay(volatile u32_t loop)
{
for(; loop > 0; loop--);
}
void udelay(u32_t us)
{
u32_t hz = get_system_hz();
if(hz)
__delay(us * loops_per_jiffy / (1000000 / hz));
else
__delay(us);
}
void mdelay(u32_t ms)
{
u32_t hz = get_system_hz();
if(hz)
__delay(ms * loops_per_jiffy / (1000 / hz));
else
__delay(ms * 1000);
}
static void calibrate_delay(void)
{
u32_t ticks, loopbit;
s32_t lps_precision = 8;
u32_t hz = get_system_hz();
if(hz > 0)
{
loops_per_jiffy = (1<<12);
while((loops_per_jiffy <<= 1) != 0)
{
/* wait for "start of" clock tick */
ticks = jiffies;
while (ticks == jiffies);
/* go ... */
ticks = jiffies;
__delay(loops_per_jiffy);
ticks = jiffies - ticks;
if(ticks)
break;
}
loops_per_jiffy >>= 1;
loopbit = loops_per_jiffy;
while(lps_precision-- && (loopbit >>= 1))
{
loops_per_jiffy |= loopbit;
ticks = jiffies;
while(ticks == jiffies);
ticks = jiffies;
__delay(loops_per_jiffy);
/* longer than 1 tick */
if(jiffies != ticks)
loops_per_jiffy &= ~loopbit;
}
}
else
{
loops_per_jiffy = 0;
}
}
void exynos4412_tick_delay_initial(void)
{
calibrate_delay();
}
这里有个关键的函数calibrate_delay,这个是用于计算loops_per_jiffy,计算方法为逐次逼近,这个是linux内核里计算bogomips的方法,即每秒种运行多少条指令,是一个相对量,同平台,同环境比较才有意义。
计算出此变量后,我们就可以实现精确延时了,比如mdelay及udelay函数,大家可以自行做实验,看是否准确。
恩,需要源码的朋友,可以直接在本博客寻找链接,或者直接加QQ8192542