Exynos4412裸机开发系列教程--TICK机制

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


你可能感兴趣的:(Exynos4412)