• (二)基于STM32L431的Liteos低功耗Runstop模式的实现优化(退出stop2模式后任务相关Tick补偿优化)


    前言:

    使用Liteos的develop版本,Runstop模式由于没有相关代码,如果想要实现uA级功耗的话,Runstop模式需要自己实现,实现的大概过程在我的另一篇(一)基于STM32L431的Liteos低功耗Runstop模式的实现有体现。

    上一篇文章实现Runstop模式存在的问题:

    ①进入stop模式的时间必须大于至少50个tick,否则唤醒之后系统的任务调度会有问题。
    ②即使进入stop模式的时间大于50个tick,唤醒之后需要执行的任务并没有立刻执行,而是会有时间间隔,这个时间最长达到了312ms,那么系统的实时性就没有了,而且会增加功耗。
    在这里插入图片描述

    测试下来发现,如果是因为软件定时器唤醒的,软件定时器的回调函数能正常执行,软件定时器因为只需要修改头部节点的值就可以,所以不会有问题;如果是因为任务唤醒的,那么任务的执行会有时间间隔,并且这个间隔时间是不一定的。那么就说明是唤醒之后的任务tick补偿有问题。

    正常运行时每次systick中断服务函数中会调用osTaskScan()函数,来扫描有没有要执行的任务,如果有就进行任务切换,关键代码:

        g_stTskSortLink.usCursor = (g_stTskSortLink.usCursor + 1) % OS_TSK_SORTLINK_LEN;
        pstListObject = g_stTskSortLink.pstSortLink + g_stTskSortLink.usCursor;
        if (pstListObject->pstNext == pstListObject)
        {
            return;
        }
    
        for (pstTaskCB = LOS_DL_LIST_ENTRY((pstListObject)->pstNext, LOS_TASK_CB, stTimerList);&pstTaskCB->stTimerList != (pstListObject);) /*lint !e413*/
        {
            usTempStatus = pstTaskCB->usTaskStatus;
            if (UWROLLNUM(pstTaskCB->uwIdxRollNum) > 0)
            {
                UWROLLNUMDEC(pstTaskCB->uwIdxRollNum);
                break;
            }
    
            LOS_ListDelete(&pstTaskCB->stTimerList);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到里面有一个关键的变量 g_stTskSortLink.usCursor每次中断都会加1,OS_TSK_SORTLINK_LEN值为32,而且使用osTaskNextSwitchTimeGet()函数来获取最近一个任务延时到来时间时,也用到了g_stTskSortLink.usCursor的值,但是我在之前任务任务的tick补偿的时候并没有关注这个值。在调用LOS_TaskDelay()函数进行任务延时的时候会调用osTaskAdd2TimerList()函数来把任务添加到延时链表中,在这个函数中也用到了g_stTskSortLink.usCursor的值,那么g_stTskSortLink.usCursor的值应该是导致唤醒后任务执行不及时的原因。

    分析源码后发现,Liteos是通过一个环状的队列来判断任务延时是否到来的。具体实现如下图(以OS_TSK_SORTLINK_LEN等于10为例),假设当前g_stTskSortLink.usCursor的值为2,一个任务需要延时120ms,每个tick是5ms,也就是一共延时24个tick。每次systick中断g_stTskSortLink.usCursor值加1,那么24个tick过去之后,g_stTskSortLink.usCursor应该在6的位置。因此任务添加到时间链表中的位置应该是6。

    g_stTskSortLink.usCursor当前的值为2,执行一轮再到2的时候过去了10个tick,也就是50ms。而对于任务来说,插入位置6之后任务控制块中UWROLLNUM(pstTaskCB->uwIdxRollNum)的值应该是2,因为每一轮是50ms,过两轮就是100ms,g_stTskSortLink.usCursor的值从2变为6需要经过4个tick,就是20ms,加起来就是任务需要延时的总时间120ms。
    在这里插入图片描述
    理解了这个之后就能对任务进行正确的Tick补偿。osIdxRollNumDec()函数要变为:

    LITE_OS_SEC_TEXT_MINOR VOID osIdxRollNumDec(UINT32 Tick)
    {
        LOS_TASK_CB *pstTaskCB;
        LOS_DL_LIST *pstListObject;
        UINT32 uwIndex =0;
    		UINT16 uwRollTicks = Tick%OS_TSK_SORTLINK_LEN;
    
        for (uwIndex = 0; uwIndex < OS_TSK_SORTLINK_LEN; uwIndex++)
        {
    				UINT32 uwTaskTick = Tick;
            pstListObject = g_stTskSortLink.pstSortLink + (g_stTskSortLink.usCursor + uwIndex)%OS_TSK_SORTLINK_LEN;
            if (pstListObject->pstNext != pstListObject)
            {
                pstTaskCB = LOS_DL_LIST_ENTRY((pstListObject)->pstNext, LOS_TASK_CB, stTimerList);
    						uwTaskTick /= OS_TSK_SORTLINK_LEN;
    					
    						if(uwTaskTick > UWROLLNUM(pstTaskCB->uwIdxRollNum))
    						{
    							uwTaskTick = UWROLLNUM(pstTaskCB->uwIdxRollNum);
    						}
    						UWROLLNUMDECMULT(pstTaskCB->uwIdxRollNum,uwTaskTick);
            }
        }
    		g_stTskSortLink.usCursor = (g_stTskSortLink.usCursor + uwRollTicks)%OS_TSK_SORTLINK_LEN;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    任务延时补偿的函数没有问题之后还需要注意的是,通过osStopTicksGet()函数获取到的进入stop模式的tick数需要减去1。之所以这么做是因为进入stop模式前会把systick关掉,唤醒之后打开,然后再产生一次systick中断的时候,在中断服务函数中会执行osTaskScan()和osSwtmrScan()函数,这样才能进行正常的任务切换,否则就错过了要执行的位置,那么就需要等下一轮才能执行,系统也就出了问题。还拿上面的图片举例,g_stTskSortLink.usCursor还是2,任务延时插入后位置是6,如果直接补偿24个tick后g_stTskSortLink.usCursor变为6,唤醒之后的下一次systick中断后,g_stTskSortLink.usCursor的值变为7,任务也就得不到执行,需要下一轮了,这中间就需要耽误45ms。

    我移植Liteos的时候LOSCFG_BASE_CORE_TICK_PER_SECOND配置的是200,也就是每个tick5ms。在前一篇文章中提到过唤醒之后初始化时钟和外设等需要的时间是1.91ms,如果项目需求对tick的误差不敏感的话,这部分的误差就可以不算进去,那么就不需要获取lptim定时器的计数值,lptim只用来在需要的时间唤醒MCU。如果在乎这段时间的话,就还可以使用前一篇文章中提到的获取lptim计数值后进行补偿,不过就要注意tick补偿之后g_stTskSortLink.usCursor的位置。

    测试:
    系统因为软件定时器超时需要唤醒:

    进入stop之前获取到tick数0x578(1400*5ms = 7s),那么唤醒时间(补偿tick)为0x577(1399),g_stTskSortLink.usCursor值为1。系统中OS_TSK_SORTLINK_LEN值为32。
    在这里插入图片描述
    唤醒之后补偿tick后,g_stTskSortLink.usCursor值为0x18。(1399%32 = 23 23+1 = 24(0x18))
    在这里插入图片描述
    唤醒之后的下一个systick中断,g_stTskSortLink.usCursor在osTaskScan()函数中已经加1(0x19),此时软件定时器的头部节点计数值uwCount为1,减1后为0,执行osSwTmrTimeoutHandle函数。
    在这里插入图片描述

    系统因为任务延时到达需要唤醒:

    下一个要执行的任务为test_task1,进入stop之前获取到tick数0xC8(200*5ms = 1s),那么唤醒时间(补偿tick)为0xC7(199),g_stTskSortLink.usCursor值为0x09。
    在这里插入图片描述
    唤醒之后补偿tick后,g_stTskSortLink.usCursor值为0x10。(199%32 = 7 7+9 = 16(0x10))
    在这里插入图片描述
    唤醒之后的下一个systick中断,g_stTskSortLink.usCursor在osTaskScan()函数中加1(0x11),此时可以看到要操作的任务控制块就是test_task1,任务的状态为0x20(OS_TASK_STATUS_DELAY),任务的延时值UWROLLNUM(pstTaskCB->uwIdxRollNum)为0。对于OS_TSK_SORTLINK_LEN值为32来说,pstTaskCB->uwIdxRollNum的值每一个表示32个tick,每个tick5ms就是160ms。
    在这里插入图片描述
    任务的tick补偿修改好后,通过osStopTicksGet()函数获取到的tick数只要大于2个tick就可以再次进入stop模式,之所以有限制是因为任务调度和唤醒后的初始化都需要时间开销。

    唤醒后任务执行情况:
    在这里插入图片描述
    从唤醒到任务得到执行的时间基本都不到2ms:
    在这里插入图片描述

  • 相关阅读:
    【问题思考总结】截得两部分质量相等的点是否就是质心?
    MCU(单片机)datasheet(规格说明书)
    Leetcode1191. K-Concatenation Maximum Sum
    JUL 学习
    我与随机红包算法的故事
    旧物回收小程序开发,开启绿色生活新篇章
    MySQL高级SQL语句
    关于前端的文件下载问题,通过超链接a无法自定义文件名
    【每日一书】Python+selenium如何实现自动化测试?
    Android ViewPager2 + Fragment 联动
  • 原文地址:https://blog.csdn.net/sinat_42731525/article/details/127441913