硬汉嵌入式论坛

 找回密码
 立即注册
查看: 242|回复: 3
收起左侧

[FreeRTOS] tickless模式tick补偿时期的疑惑?

[复制链接]

13

主题

36

回帖

75

积分

初级会员

积分
75
发表于 2025-4-15 17:56:59 | 显示全部楼层 |阅读模式
大家下午好,最近在研究Freertos的低功耗功能,在学习tickless功能时我遇到了一个小问题,想问问大佬们。
下面是一个freertos的官方实现代码,我发现他进入wfi时是关中断状态,然后在被中断唤醒之后,立刻打开了中断开关。之后再进行关中断对tick进行补偿,然后再第二次打开中断开关。
这样的话我就有问题了,如果是一个释放信号量的中断唤醒了cpu,那么在中断开关第一次被打开之后,会立刻进入中断处理函数,释放信号量,pengsv标志位置位,
在退出中断处理函数之后就会进入pendsv异常处理,进行上下文切换。在进行上下文切换之后,退出pendsv处理函数之后就不是进入vPortSuppressTicksAndSleep函数了,而是进入高优先级的任务。
这样的话,高优先级任务不就是运行在异常环境吗?现在的tick还没有补偿啊,如果任务进行一个delay(10),不就异常状态了吗?我不理解官方是如何防止这种情况的,所以请教一下诸位大佬。



[C] 纯文本查看 复制代码
	__weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
	{
	uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
	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 );

			/* Re-enable interrupts to allow the interrupt that brought the MCU
			out of sleep mode to execute immediately.  see comments above
			__disable_interrupt() call above. */
			__enable_irq();
			__dsb( portSY_FULL_READ_WRITE );
			__isb( portSY_FULL_READ_WRITE );

			/* Disable interrupts again because the clock is about to be stopped
			and interrupts that execute while the clock is stopped will increase
			any slippage between the time maintained by the RTOS and calendar
			time. */
			__disable_irq();
			__dsb( portSY_FULL_READ_WRITE );
			__isb( portSY_FULL_READ_WRITE );

			/* Disable the SysTick clock without reading the
			portNVIC_SYSTICK_CTRL_REG register to ensure the
			portNVIC_SYSTICK_COUNT_FLAG_BIT is not cleared if it is set.  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*/
			portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );

			/* Determine if the SysTick clock has already counted to zero and
			been set back to the current reload value (the reload back being
			correct for the entire expected idle time) or if the SysTick is yet
			to count to zero (in which case an interrupt other than the SysTick
			must have brought the system out of sleep mode). */
			if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
			{
				uint32_t ulCalculatedLoadValue;

				/* The tick interrupt is already pending, 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;

				/* 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. */
			portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
			portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
			vTaskStepTick( ulCompleteTickPeriods );
			portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;

			/* Exit with interrupts enabled. */
			__enable_irq();
		}
	}


回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
115434
QQ
发表于 2025-4-16 12:29:59 | 显示全部楼层
他这个是处理完毕才打开的中断
回复

使用道具 举报

13

主题

36

回帖

75

积分

初级会员

积分
75
 楼主| 发表于 2025-4-17 13:56:44 | 显示全部楼层
eric2013 发表于 2025-4-16 12:29
他这个是处理完毕才打开的中断

它在82行的时候就打开了中断,那个时候还没有做tick补偿
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
115434
QQ
发表于 2025-4-18 11:14:09 | 显示全部楼层
云安 发表于 2025-4-17 13:56
它在82行的时候就打开了中断,那个时候还没有做tick补偿

这个是在空闲任务里面执行的,之前挂起所有任务了。

[C] 纯文本查看 复制代码
/*
 * -----------------------------------------------------------
 * The Idle task.
 * ----------------------------------------------------------
 *
 * The portTASK_FUNCTION() macro is used to allow port/compiler specific
 * language extensions.  The equivalent prototype for this function is:
 *
 * void prvIdleTask( void *pvParameters );
 *
 */
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
        /* Stop warnings. */
        ( void ) pvParameters;

        /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
        SCHEDULER IS STARTED. **/

        /* In case a task that has a secure context deletes itself, in which case
        the idle task is responsible for deleting the task's secure context, if
        any. */
        portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );

        for( ;; )
        {
                /* See if any tasks have deleted themselves - if so then the idle task
                is responsible for freeing the deleted task's TCB and stack. */
                prvCheckTasksWaitingTermination();

                #if ( configUSE_PREEMPTION == 0 )
                {
                        /* If we are not using preemption we keep forcing a task switch to
                        see if any other task has become available.  If we are using
                        preemption we don't need to do this as any task becoming available
                        will automatically get the processor anyway. */
                        taskYIELD();
                }
                #endif /* configUSE_PREEMPTION */

                #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
                {
                        /* When using preemption tasks of equal priority will be
                        timesliced.  If a task that is sharing the idle priority is ready
                        to run then the idle task should yield before the end of the
                        timeslice.

                        A critical region is not required here as we are just reading from
                        the list, and an occasional incorrect value will not matter.  If
                        the ready list at the idle priority contains more than one task
                        then a task other than the idle task is ready to execute. */
                        if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
                        {
                                taskYIELD();
                        }
                        else
                        {
                                mtCOVERAGE_TEST_MARKER();
                        }
                }
                #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */

                #if ( configUSE_IDLE_HOOK == 1 )
                {
                        extern void vApplicationIdleHook( void );

                        /* Call the user defined function from within the idle task.  This
                        allows the application designer to add background functionality
                        without the overhead of a separate task.
                        NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
                        CALL A FUNCTION THAT MIGHT BLOCK. */
                        vApplicationIdleHook();
                }
                #endif /* configUSE_IDLE_HOOK */

                /* This conditional compilation should use inequality to 0, not equality
                to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
                user defined low power mode        implementations require
                configUSE_TICKLESS_IDLE to be set to a value other than 1. */
                #if ( configUSE_TICKLESS_IDLE != 0 )
                {
                TickType_t xExpectedIdleTime;

                        /* It is not desirable to suspend then resume the scheduler on
                        each iteration of the idle task.  Therefore, a preliminary
                        test of the expected idle time is performed without the
                        scheduler suspended.  The result here is not necessarily
                        valid. */
                        xExpectedIdleTime = prvGetExpectedIdleTime();

                        if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
                        {
                                vTaskSuspendAll();
                                {
                                        /* Now the scheduler is suspended, the expected idle
                                        time can be sampled again, and this time its value can
                                        be used. */
                                        configASSERT( xNextTaskUnblockTime >= xTickCount );
                                        xExpectedIdleTime = prvGetExpectedIdleTime();

                                        /* Define the following macro to set xExpectedIdleTime to 0
                                        if the application does not want
                                        portSUPPRESS_TICKS_AND_SLEEP() to be called. */
                                        configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );

                                        if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
                                        {
                                                traceLOW_POWER_IDLE_BEGIN();
                                                portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
                                                traceLOW_POWER_IDLE_END();
                                        }
                                        else
                                        {
                                                mtCOVERAGE_TEST_MARKER();
                                        }
                                }
                                ( void ) xTaskResumeAll();
                        }
                        else
                        {
                                mtCOVERAGE_TEST_MARKER();
                        }
                }
                #endif /* configUSE_TICKLESS_IDLE */
        }
}
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-4-26 00:51 , Processed in 0.217872 second(s), 24 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表