微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 手机设计讨论 > MTK手机平台交流 > MTK 多个层次的timer的使用

MTK 多个层次的timer的使用

时间:10-02 整理:3721RD 点击:

小结一:

mtk timer 有很多种,从最低层的KAL(kernel adpat layer)到MMI,都有timer的身影。

一、KAL的timer

KAL timer是非常底层的timer, 它的实现是直接封装MTK的RTOS(nuleus)的timer,实现方式是由HISR,从而这种timer具有很高的优先级,也就是说,当这个timer 超时时,就会触发一个HISR(高级中断 High Level ISR (Interrupt Service Routine)),这个中断会回调注册的回调函数。所以这种timer 使用时,要比较小心,它的优先级很高,在回调函数运行时,其他事件是得不到处理的。


//创建一个timer, 参数是timer的名字

kal_timerid kal_create_timer(kal_char* timer_name_ptr); void


//设置timer 超时时间, timer_id 是 create 返回值,handler_func_ptr是回调函数,handler_param_ptr 是回调函数返回的参数(一般回调函数都是这么设置,这样很灵活),delay,是超时时间,注意这个参数的单位是ticks 不是ms。reschedule_time 表示是否循环使用timer,0 表示 timer 超时一次就停止,1 表示自动循环启动timer。

kal_set_timer(kal_timerid timer_id, kal_timer_func_ptr handler_func_ptr, void* handler_param_ptr, kal_uint32 delay, kal_uint32 reschedule_time);   


二、stack timer

这种timer 与 KAL timer 最大的区别是:stack timer 超时后,发送一个超时消息到相应task的消息队列,由task的消息队列来处理这个消息,而不像KAL timer,直接在中断状态回调注册函数。从时间的精确性来说,stack timer 可能没有KAL timer 精确(发送消息,task 切换等都要花费时间),但是 stack 更加的安全(在 task 里处理这个消息),提高并发性(stack timer 到期后只是发送了一个消息,具体并不处理)。当然 stack timer 底层具体实现还是依靠KAL timer。有一点需要注意的是,当stack timer 超时后,向消息队列发送了消息,但是这个消息还没有来的处理,这个时候取消了这个timer,需要特别处理,虽然这种情况发生的概率很小。


// stack timer 初始化 module_id 是要接受消息的 mdoule,也就是当stack timer 超时,会向该module 发送消息。

void stack_init_timer(stack_timer_struct *stack_timer, kal_char *timer_name, module_type module_id);


//判断这个消息是否继续有效,也就是是否需要处理,这个函数就是用于方式上面提到那种情况的发生。

kal_bool stack_is_time_out_valid(stack_timer_struct *stack_timer);


//这个函数与上面的函数成对使用,具体看例子。

void stack_process_time_out(stack_timer_struct *stack_timer); void


//启动定时期,timer_index  timer 索引,超时时间。

stack_start_timer(stack_timer_struct *stack_timer, kal_uint16 timer_indx, kal_uint32 init_time);

小结二:

KAL timer 和 stack timer, 这两种timer 在平时用的比较少,在驱动开发,或者时间要求特别精确的情况下,使用 KAL timer, 一般在task要管理一组timer,用 stack timer 加上 event scheduler,后者就是今天要介绍的第三种timer。

event scheduler 处理的timer 时间精确性上来说,相对不是那么精确,对于上层app应用来说,是必不可少的。MMI 层的timer(StartTimer 系列函数) 就是用event scheduler + stack timer 来实现的。


extern event_scheduler *new_evshed(void *timer_id, void(*start_timer)(void *, unsigned int),

void (*stop_timer)(void *),
kal_uint32 fuzz,

malloc_fp_t alloc_fn_p,

free_fp_t free_fn_p,

kal_uint8 max_delay_ticks);

//创建一个 event scheduler ,  timer_id 是stack timer 创建的timer id,一般称为base timer,start_timer 启动这个base timer, stop_timer 停止这个base timer,fuzz 校正timer 时间,alloc_fn_p 内存分配函数,free_fn_p 内存释放函数,max_delay_ticks timer 最大可延迟时间,这个表示timer的准确度,这个参数的作用主要是用于节省电池。


//设置一个timer,es 用 new_evshed 创建的,event_hf, timer 超时后的回调函数,event_hf_param 回调函数传入的参数,elapse_time timer的时间。

extern eventid evshed_set_event(event_scheduler *es,
                                kal_timer_func_ptr event_hf,
                                void *event_hf_param,
                                kal_uint32 elapse_time);


//时间超时后,统一处理超时回调函数。也就是说,当stack timer 向相应的mod 发送 MSG_ID_TIMER_EXPIRY后,需要调用该函数,该函数会处理相应的回调函数。

extern void evshed_timer_handler(event_scheduler *es);


MTK 的这套机制让人感觉很别扭,像 evshed_timer_handler 这样的函数都要手动去调用,仔细想想,也只能这么来用,event scheduler 依赖于 stack timer, 而stack timer 又只能往相应的mod 里发送消息,而这个mod的消息处理又是自己手动写的,如果不开放 evshed_timer_handler 这个函数,那么超时了,event scheduler 也不知道。

下面举了个例子,就上面这么一说,估计也是晕晕的,我当时看了N遍代码,才慢慢明白些,也没有个资料可以参考。

就看MMI timer的实现,平时开发应用的时候,这个是用的最多的。


源文件:MMITimer.c

//初始化函数:

void L4InitTimer(void)

{


    TIMERTABLE *p;

    TIMERTABLE *pp;


    // 1 释放timer table 内存

    p = g_timer_table.next;

    pp = NULL;

    do

    {

        if (p != NULL)

        {

            pp = p->next;

            OslMfree(p);

        }

        p = pp;

    } while (p != NULL);

    /* reset g_timer_talbe */

    mEMSet(&g_timer_table, 0, sizeof(TIMERTABLE));

    g_timer_table_size = SIMULTANEOUS_TIMER_NUM;

    g_timer_table_used = 0;


    /* Initiate the clock time callback function. */

    get_clocktime_callback_func = NULL;

    set_clocktime_callback_func = NULL;


    //2 初始化 stack timer 1

    /* Initate the no alignment stack timer */

    stack_init_timer(&base_timer1, "MMI_Base_Timer1", MOD_MMI);


    //3 根据 stack timer 1 ,创建 event scheduler 1

    /* Create a no alignment timer schedule */

    event_scheduler1_ptr = new_evshed(

                            &base_timer1,

                            L4StartBaseTimer,

                            L4StopBaseTimer,

                            0,

                            kal_evshed_get_mem,

                            kal_evshed_free_mem,

                            0);

    //4 初始化 stack timer 2

    /* Initate the alignment stack timer */

    stack_init_timer(&base_timer2, "MMI_Base_Timer2", MOD_MMI);


    //5 根据 stack timer 2 创建 event scheduler 2

    /* Create an alignment timer schedule */

    event_scheduler2_ptr = new_evshed(

                            &base_timer2,

                            L4StartBaseTimer,

                            L4StopBaseTimer,

                            0,

                            kal_evshed_get_mem,

                            kal_evshed_free_mem,

                            254);

}


说明:MMI 共有两种timer,一种是 no alignment timer ,一种叫 alignment timer。

两者的区别有两点:

1,前置不会延迟,也就是说相对于后来来说,精确很多,当然后者有可能延迟,也就是用后者创建了一个100ms timer  ,也许过了 150ms 才被回调 ,甚至 300ms。

2,前置在手机休眠时不会被关起,而后会被挂起,也就是如果用后者创建了一个timer,还没有到期,这个时候手机休眠了,那么这个timer就不会被触发了,直到手机再次唤醒。

在MMI timer 里面,这两种timer 分别对应  event_scheduler1_ptr 和  event_scheduler2_ptr。

在 MMI 实现timer 里面用了一种 table,用来存放每一个timer的信息,这种table 是一种链表 加上 数组的实现。这样实现 可以省去一些内存的频繁申请和释放。

小结三:

上面是最常用的MMI timer 实现机制的初始化过程。下面要说的是创建一个timer。MMI 层,启动一个timer,最终都会调用到 L4StartTimer 这个函数。具体来分析一下这个函数

// 参数  nTimerId 是要自己定义一个timer id,在 timerEvents.h 里的 MMI_TIMER_IDS 定义,用来区分timer

// TimerExpiry 超时的回调函数

// funcArg 回调函数回传的参数

// nTimeDuration 超时时间,单位ms

// alignment alignment或者non-alignment,在 MTK timer 小结 2 说明过

static void L4StartTimer(

        unsigned short nTimerId,

        oslTimerFuncPtr TimerExpiry,

        void *funcArg,

        unsigned long nTimeDuration,

        unsigned char alignment)

{

    /*----------------------------------------------------------------*/

    /* Local Variables                                                    /*----------------------------------------------------------------*/

    TIMERTABLE *pTable = NULL;

    U16 i = 0;

    U32 temp;


    /*----------------------------------------------------------------*/

    /* Code Body                                                      

/*----------------------------------------------------------------*/

    if (TimerExpiry == NULL)

    {   /* If TimerExpiry is NULL, we don't start the timer */

        MMI_ASSERT(0);

        return;

    }


    MMI_ASSERT(nTimerId < MAX_TIMERS);


    if (L4TimerUsePreciseTick(nTimerId))

    {

        alignment = TIMER_IS_NO_ALIGNMENT;

    }


     // 把 ticks 转化为ms

    // mtk 底层的timer 的单位都是 ticks

    if (nTimeDuration == 1000)

    {

        temp = KAL_TICKS_1_SEC - 4;

    }

    else

    {

        temp = (U32)((nTimeDuration / 5) * MMI_TICKS_5_MSEC);

    }


    if (temp == 0)

    {

        /* Cause by rounding. If expire immediately, MODIS boot-up failure because MMI keeps running and block NVRAM task */

        temp = (U32)MMI_TICKS_5_MSEC;

    }


    //取得存放所有MMI timer 的table

    // 在 MTK timer 小结 2 中提到,这个table 是 list + array 组合

    // 如果只用存数组的话,初始化大小不好确定,而且要 array relloc. 如果考虑内存回收还容易引起数组震荡

    // 如果只用list的话,一个timer如果很频繁的话,会不停的malloc 和 free

    pTable = &g_timer_table;


    //判断timer 是否已经 满了

    // 如果满了,那么需要在timer table 后面增加一个timer node

    if (g_timer_table_used >= g_timer_table_size)

    {

        do

        {

            if (pTable->next == NULL)

            {

                pTable->next = OslMalloc(sizeof(TIMERTABLE));

                memset(pTable->next, 0, sizeof(TIMERTABLE));

                g_timer_table_size += SIMULTANEOUS_TIMER_NUM;

                pTable = pTable->next;

                i = 0;

                break;

            }

            pTable = pTable->next;

        } while (pTable != NULL);

    }

    else

    {

        //寻找空的 timer node

        i = 0;

        do

        {

            if (pTable->tm.event_id == NULL)

            {   /* find the empty space */

                break;

            }

            i++;

            if (i >= SIMULTANEOUS_TIMER_NUM)

            {

                pTable = pTable->next;

                i = 0;

            }

        } while (pTable != NULL);


        if (pTable == NULL)

        {

       /* Can't find the empty space in TIMERTABLE list, assert! */

            MMI_ASSERT(0);

        }

    }   /* if (g_timer_table_used >= g_timer_table_size) */


    // 根据 algigment 属性,分别创建一个 event scheduler timer

    // 把 timer 的信息保存到 timer node 里面

    if (alignment == TIMER_IS_NO_ALIGNMENT)

    {

        /* MSB(Most Significant Bit) is align_timer_mask */

        pTable->tm.timer_info = nTimerId | NO_ALIGNMENT_TIMER_MASK;

        pTable->tm.event_id = evshed_set_event(

                                    event_scheduler1_ptr,

                                    (kal_timer_func_ptr) L4CallBackTimer,

                                    (void*)&(pTable->tm),

                                    temp);

        pTable->tm.arg = funcArg;

        pTable->tm.callback_func = TimerExpiry;

        g_timer_table_used++;

    }

    else if (alignment == TIMER_IS_ALIGNMENT)

    {

        /* MSB(Most Significant Bit) is align_timer_mask */

        pTable->tm.timer_info = nTimerId | ALIGNMENT_TIMER_MASK;

        pTable->tm.event_id = evshed_set_event(

                                    event_scheduler2_ptr,

                                    (kal_timer_func_ptr) L4CallBackTimer,

                                    (void*)&(pTable->tm),

                                    temp);

        pTable->tm.arg = funcArg;

        pTable->tm.callback_func = TimerExpiry;

        g_timer_table_used++;

    }

}

再说一句 timer table,其实也可以用 timer pool 来实现,把超时或者stop的timer node,放入到free pool 中,需要时再拿出来,也可以避免重复的malloc 和 free。

        在 设置 event scheduler timer 中,设置了一个回调函数 L4CallBackTimer,这个回调函数就是在 stack timer 超时需要回调的函数。

那具体再哪里回调?在 MTK timer 小结 2 提到, event scheduler 还是依赖于 stack timer,在 MTK timer 小结 1 提到 stack timer 超时后是向相应的mod 发送消息。

        在初始化L4InitTimer函数中,stack timer 初始化(stack_init_timer(&base_timer1, "MMI_Base_Timer1", MOD_MMI))时mod 是 MOD_MMI,那么超时后,就会像 mmi task 发送超时消息。

        MMI 层 在 MMI_task 函数中处理所有的消息,while 消息处理中,可以看到

case MSG_ID_TIMER_EXPIRY:

        {

        kal_uint16 msg_len;

        EvshedMMITimerHandler(get_local_para_ptr(Message.oslDataPtr, &msg_len));

        }

        break;

这个就是处理 stack timer 地方。也就是stack timer 超时后,会回调 EvshedMMITimerHandler 函数。

void EvshedMMITimerHandler(void *dataPtr)

{

    /*----------------------------------------------------------------*/

/* Local Variables                                                 /*----------------------------------------------------------------*/

    stack_timer_struct *stack_timer_ptr;

    stack_timer_ptr = (stack_timer_struct*) dataPtr;

    /*----------------------------------------------------------------*/

/* Code Body                                                   

/*----------------------------------------------------------------*/

    // 判断是哪个 stack timer

    if (stack_timer_ptr == &base_timer1)

    {

        // 这里需要判断 这个stack timer 是否还是有效,也就是否被stop

        // 这种情况出现环境在 MTK timer 小结 1 中介绍过

        // 如果无效,就不要触发这个timer

        if (stack_is_time_out_valid(&base_timer1))

        {

            //调用 这个函数,触发注册的 event scheduler timer

            evshed_timer_handler(event_scheduler1_ptr);

        }

        stack_process_time_out(&base_timer1);

    }

    else if (stack_timer_ptr == &base_timer2)

    {

        if (stack_is_time_out_valid(&base_timer2))

        {

            evshed_timer_handler(event_scheduler2_ptr);

        }

        stack_process_time_out(&base_timer2);

    }

}

这个函数在 调用 evshed_timer_handler 函数时,就会回调 由 evshed_set_event 注册的timer 回调函数 L4CallBackTimer。

小结四:

说到回调函数 L4CallBackTimer。

static void L4CallBackTimer(void *p)

{

    // 在evshed_set_event 第三个参数中传得 timer noder

    // 这里转换这个指针

    mmi_frm_timer_type *pTimer = (mmi_frm_timer_type *)p;

    // 得到timer id

    U32 nTimerId = pTimer->timer_info & (~NO_ALIGNMENT_TIMER_MASK);

    // 得到回调函数

    oslTimerFuncPtr pTimerExpiry = pTimer->callback_func;

    // 得到 回调 函数参数

    void * arg = pTimer->arg;

    // timer 个数减少

    g_timer_table_used--;

    // 清空这个 timer node

    memset( pTimer, 0, sizeof(mmi_frm_timer_type));

    // 回调 timer 的回调函数

    // 注意:现在这个回调函数是在 MMI task 环境下执行的

    if (pTimerExpiry)

    {

        pTimerExpiry((void *)arg);

    }

    // 这个的作用是 把 mmi task 外部循环队列的消息放入到内部消息循环

    // 具体在下次 分析 MMI task 时解释

    // 这里这么做主要是 让消息及时得到处理

    mmi_frm_fetch_msg_fROM_extQ_to_circularQ();

}

这样 整个 MMI timer 梳理通了,从初始化(小结二),到 set timer(小结三) 到最后的触发timer。

还有几个需要注意的地方,在 (小结二)说到, MMI timer 有两种,其中alignment timer 在手机 休眠时 会被挂起。

具体函数如下:

void mmi_frm_suspend_timers(U32 type)

{

    /*----------------------------------------------------------------*/

/* Local Variables                                                /*----------------------------------------------------------------*/

    event_scheduler *ev = NULL;

    // 判断是哪一种 timer,实际中,只使用 alignment

    switch(type)

    {

    case TIMER_IS_NO_ALIGNMENT:

        ev = event_scheduler1_ptr;

        break;

    case TIMER_IS_ALIGNMENT:

        ev = event_scheduler2_ptr;

        break;

    default:

        /* undefined type */

        MMI_ASSERT(0);

        break;

    }

    // 挂起所有 alignment timer

    evshed_suspend_all_events(ev);

}

    具体调用该函数的地方在 BacklightTimerHdlr 里面,这个函数具体实现以后分析,主要作用就是控制屏不背光灯,当屏幕背光灯关闭时,关闭 alignment timer.有挂起,就有恢复,函数 mmi_frm_resume_timers 调用地方在 TurnOnBacklight 等几个函数里,具体就不在分析。

不错呀不错呀

请问小编!
kal_set_timer中有一个参数是delay,这个参数的单位是ticks而不是ms,实验过后发现1 ticks大约在4ms以上,那么如何修改这个ticks,使得1ticks大约为1ms呢

你们这帮畜生。  完全看不明白

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top