FreeRTOS的OS时钟与基准时钟

问题背景:

        FreeRTOS在使用OS之后,将OS的基础时钟与其他基础时钟都放在Systick_Handler中,其他基准时钟会不准,OS时钟却依旧很准。

        博主只是一个在读研究生,在公司的项目中遇到的问题,期间在CSDN上搜了很多相关资料,但是没有对于这个问题的详细描述与解答,在另外几篇blog中,有人讲述了问题的解决方法,有人已经知道了原因,但是没有人对这个问题的原因给出了详细的解答,组长本着锻炼我的想法让我试试,花了大概10天终于将这个问题琢磨透了。

        本文不具体细讲OS相关参数如何配置

具体现象: 

        项目2是基于项目1的衍生产品,仅比项目1多一个FOC功能,一个FOC占用了70%的资源,因此开发的时候屏蔽了大部分的周期任务,仅保留了10ms的

        开发的时候断开debug(debug的时候看门狗不会触发复位),配置好运行环境后,上电输出PWM,电机不转,示波器观察波形发现一直在复位,秒复位。

 ROOT CAUSE:

        问题就处在最开始没有将OS的基准时钟单独放在Systick_Handler中

void SysTick_Handler(void)
{	
    WD_Counter;
	/* 如何调度器已经开启,运行或者挂起都算开启 */
	if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
	{
		xPortSysTickHandler();	
	}
}

        那么重点来了:为什么将几个基准时钟放一起就不准了呢?明明只是添加了一个“WD_Counter++”就不准了?而且只有OS基准时钟是准的,显得很诡异。(当然,比较了解FreeRTOS的应该已经猜到答案了)

         回到最开始,发现WD_Counter所在的Systick_Handler()从本来定义的1ms触发变成了10ms触发(下左图),而且运行电机的时候规律的10ms波形会穿插的1ms与2ms 的波形(下右图),但是通过调全局变量发现 :OS嘀嗒计数值和基于嘀嗒计数值的10ms周期任务都是准的!!!

 

        我们再看看代码,xPortSysTickHandler()就是嘀嗒中断的主要内容,里面调用了xTaskIncrementTick()便是整个嘀嗒中断的精髓了,任务的调度、调度器的挂起与使能、OS基准时钟都在里面了。当然执行这步前还有最关键的一步:vPortRaiseBASEPRI(),关闭优先级不高于5(在FreeRTOS中,中断优先级不高于5就是数字小于5,中断优先级高于一切任务的优先级)。

        发现什么诡异的地方了吗?没错,现在目前看来Systick_Handler()像是被约束住了,只有在周期任务和中断来的时候,才会触发。

void xPortSysTickHandler( void )
{
	vPortRaiseBASEPRI();	/* 关闭中断 */
	{
		/* 如果返回值标记了任务切换,即有优先级高的任务 */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* 设置PendSV中断位 */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();	/* 打开中断 */
}

         在花了几天排查了项目模块因素后,发现这个问题就是OS的问题,怎么控制变量去测我们自己开发的代码都找不到原因。期间还怀疑是被资源锁给锁住了,后来在debug中打断点发现,确实每次进入嘀嗒中断的时间就是10ms,并不是被锁住了无法正常赋值。

         刚接触FreeRTOS的我已然不知如何下手了,优先级最低的嘀嗒中断被约束了,从1ms变到了10ms

         没错,就是我最前面加粗没标红的10ms周期任务啊。经过测试,发现没有其他中断的情况下,Systick_Handler()的进入时间取决于当前周期任务中周期最短的那个任务。

        于是接着排查,用GPT5定时器新建一个1ms中断(这个不依赖于OS是准的),通过如下的方式观察嘀嗒计数器晶振次数变量                  (CPU->SYSTICK_CUR.reg)发现,每次向下计数溢出的reload值被改了。晶振40MHZ,1ms就是计数40000=0x9C40次,所以按理来说if语句中判断条件为 cpu_tick_current<0x9C40 应该才是正确的计数,但是只有将值改为当前最小周期整数倍才是准的(这时候测试开的是5ms周期任务,40000*5 = 0x30D40)。

         激动的心,颤抖的手,接着往下查。查找了所有可能修改嘀嗒定时器reload值的变量,发现唯一能修改的地方便是

 __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ),这是FreeRTOS的低功耗功能。简单的说就是
#if configUSE_TICKLESS_IDLE == 1

    __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
    {
    uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickCTRL;
    TickType_t xModifiableIdleTime;

        /* Make sure the SysTick reload value does not overflow the counter. */
        if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
        {
            xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
        }

        /* Stop the SysTick momentarily.  The time the SysTick is stopped for
        is accounted for as best it can be, but using the tickless mode will
        inevitably result in some tiny drift of the time maintained by the
        kernel with respect to calendar time. */
        portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;

        /* Calculate the reload value required to wait xExpectedIdleTime
        tick periods.  -1 is used because this code will execute part way
        through one of the tick periods. */
        ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
        if( ulReloadValue > ulStoppedTimerCompensation )
        {
            ulReloadValue -= ulStoppedTimerCompensation;
        }

        /* Enter a critical section but don't use the taskENTER_CRITICAL()
        method as that will mask interrupts that should exit sleep mode. */
        __disable_irq();
        __dsb( portSY_FULL_READ_WRITE );
        __isb( portSY_FULL_READ_WRITE );

        /* If a context switch is pending or a task is waiting for the scheduler
        to be unsuspended then abandon the low power entry. */
        if( eTaskConfirmSleepModeStatus() == eAbortSleep )
        {
            /* Restart from whatever is left in the count register to complete
            this tick period. */
            portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;

            /* Restart SysTick. */
            portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;

            /* Reset the reload register to the value required for normal tick
            periods. */
            portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;

            /* Re-enable interrupts - see comments above __disable_irq() call
            above. */
            __enable_irq();
        }
        else
        {
            /* Set the new reload value. */
            portNVIC_SYSTICK_LOAD_REG = ulReloadValue;

            /* Clear the SysTick count flag and set the count value back to
            zero. */
            portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

            /* Restart SysTick. */
            portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;

            /* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
            set its parameter to 0 to indicate that its implementation contains
            its own wait for interrupt or wait for event instruction, and so wfi
            should not be executed again.  However, the original expected idle
            time variable must remain unmodified, so a copy is taken. */
            xModifiableIdleTime = xExpectedIdleTime;
            configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
            if( xModifiableIdleTime > 0 )
            {
                __dsb( portSY_FULL_READ_WRITE );
                __wfi();
                __isb( portSY_FULL_READ_WRITE );
            }
            configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

            /* Stop SysTick.  Again, the time the SysTick is stopped for is
            accounted for as best it can be, but using the tickless mode will
            inevitably result in some tiny drift of the time maintained by the
            kernel with respect to calendar time. */
            ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG;
            portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL & ~portNVIC_SYSTICK_ENABLE_BIT );

            /* Re-enable interrupts - see comments above __disable_irq() call
            above. */
            __enable_irq();

            if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
            {
                uint32_t ulCalculatedLoadValue;

                /* The tick interrupt has already executed, and the SysTick
                count reloaded with ulReloadValue.  Reset the
                portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
                period. */
                ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );

                /* Don't allow a tiny value, or values that have somehow
                underflowed because the post sleep hook did something
                that took too long. */
                if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
                {
                    ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
                }

                portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;

                /* The tick interrupt handler will already have pended the tick
                processing in the kernel.  As the pending tick will be
                processed as soon as this function exits, the tick value
                maintained by the tick is stepped forward by one less than the
                time spent waiting. */
                ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
            }
            else
            {
                /* Something other than the tick interrupt ended the sleep.
                Work out how long the sleep lasted rounded to complete tick
                periods (not the ulReload value which accounted for part
                ticks). */
                ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;

                /* How many complete tick periods passed while the processor
                was waiting? */
                ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;

                /* The reload value is set to whatever fraction of a single tick
                period remains. */
                portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
            }

            /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
            again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
            value.  The critical section is used to ensure the tick interrupt
            can only execute once in the case that the reload register is near
            zero. */
            portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
            portENTER_CRITICAL();
            {
                portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
                vTaskStepTick( ulCompleteTickPeriods );
                portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
            }
            portEXIT_CRITICAL();
        }
    }

#endif /* #if configUSE_TICKLESS_IDLE */

*  上面的代码及其调用的函数可以简要概括如下:

(1) 处于空闲任务其他任务处于阻塞或者挂起才可以进入,且会判断是否满足进入条件(当系统运行于低功耗模式 的时钟节拍数大于宏定义 configEXPECTED_IDLE_TIME_BEFORE_SLEEP (系统默认是2个系统时钟节拍,用户定义必须大于2))。

(2)在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态。

(3)进入即关闭滴答定时器(OS_PreSleepProcessing()中可以选择更多操作),可以记录下系统节拍中断的关闭时间,当系统节拍中断再次开启运行的时候补上这段时间。

(4)当一个中断或唤醒事件导致退出停止模式时, HSI RC振荡器被选为系统时钟。

(5)退出低功耗的停机模式后,需要重新配置使用HSE(OS_PostSleepProcessing()中完成)。

(6)在进入低功耗模式之前获取还有多长时间进行下一个任务。

         好,好,好,到这里就真相大白了。是因为在空闲任务进入了低功耗模式,紧接着关闭了滴答定时器,所以里面的功能全部都失效了,这也就是WC_Counter不准的原因,然后在退出低功耗模式的时候,通过前面获取下一次任务或者中断来临的时间点,补上嘀嗒定时器的计数值,所以在我们看来,这个数值和周期任务(周期任务直接取决于嘀嗒计数值)都是准的,也所以在我们看来,嘀嗒中断被周期任务于中断约束了。

第一篇BLOG,尽量缩减了内容,可能字还是有点多红火火恍恍惚惚,我觉得这样能讲的更透。

 

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值