freertos提供了vTaskGetRunTimeStats这一方法来测量cpu使用率,具体参见 《安富莱_STM32-V6开发板_FreeRTOS教程》或者网上其教程,不再赘述。
这一方法有几个问题:
1、freerots需要一个频率较高的定时器,在vTaskSwitchContext()函数里(在tasks.c里)
记录一下当前运行的任务所使用的时间
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE(); //获取高分辨率定时器计数值
#endif
/* Add the amount of time the task has been running to the
accumulated time so far. The time the task started running was
stored in ulTaskSwitchedInTime. Note that there is no overflow
protection here so count values are only valid until the timer
overflows. The guard against negative values is to protect
against suspect run time stat counter implementations - which
are provided by the application, not the kernel. */
if( ulTotalRunTime > ulTaskSwitchedInTime ) //给当前任务的计时变量加上这一段的运行时间
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime; //作为下一个任务运行时间的起点
}
#endif /* configGENERATE_RUN_TIME_STATS */
portGET_RUN_TIME_COUNTER_VALUE的原型是
#define portGET_RUN_TIME_COUNTER_VALUE getRunTimeCounterValue
getRunTimeCounterValue这个函数需要用户自己定义,之前能搜到的教程是在定时器中断里整一个自加变量实现的
volatile uint32_t ulHighFrequencyTimerTicks = 0UL;
uint32_t getRunTimeCounterValue(void)
{
return ulHighFrequencyTimerTicks;
}
void TIM6_IRQHandler( void )
{
if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
ulHighFrequencyTimerTicks++;
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
} }
其实定时器是需要的,但中断完全没必要。我改成了下面的样子
uint32_t getRunTimeCounterValue(void) //TIM5不需要开中断
{
return TIM5->CNT;
}
stm32的TIM5是32位的,我可以把频率设的很高比如1M,从而使时间统计精度更高,也不用担心频繁的中断浪费性能。
2、freertos自带的功能无法处理定时器溢出问题
3、freertos自带的功能其实是统计的从rtos开始运行开始,各任务运行时间的累积。如果我想获得一个时间片内,各任务的cpu占用情况呢?毕竟很多任务的执行需要触发条件。
我是这样做的:
首先使用TIM5,频率1M,无中断。(用16位定时器也没问题,频率稍微低一点,或者统计时间稍微短一点)
void configureTimerForRunTimeStats(void)
{
HAL_TIM_Base_Start(&htim5);
}
__inline uint32_t getRunTimeCounterValue(void)
{
return TIM5->CNT;
}
在一个1s为周期的任务里,统计cpu占用。计时周期长短看需求,反正统计的是占用率。
uint8_t u8TaskListBuff[200];
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
osDelay(1000);
HAL_TIM_Base_Stop(&htim5);
memset(u8TaskListBuff, 0, 200);
vTaskGetRunTimeStats((char*)u8TaskListBuff);
HAL_UART_Transmit_IT(phComDbg,u8TaskListBuff,strlen((char*)u8TaskListBuff));
TIM5->CNT = 0;
HAL_TIM_Base_Start(&htim5);
}
/* USER CODE END StartDefaultTask */
}
如果只是改成这样的话,程序就sb了。。。必须手动给每个任务的ulRunTimeCounter清零。
一开始尝试在vTaskSwitchContext做清零,那些一直挂起的任务不能被正确的清零。
后来我想vTaskGetRunTimeStats执行过程中清零
vTaskGetRunTimeStats调用了uxTaskGetSystemState获取任务信息列表,继续调用prvListTasksWithinSingleList获取各个状态下的任务信息列表。所以在rvListTasksWithinSingleList 或被它调用的vTaskGetInfo里对ulRunTimeCounter清零就好。
注意:对pxTaskStatusArray里的任务的ulRunTimeCounter清零没用,它只是被复制出来的任务信息,不是任务本身的指针。
我在vTaskGetInfo下添加了清零代码(不管任务处于什么状态,都会被清零)
void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState )
{
/******省略****/
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
pxTaskStatus->ulRunTimeCounter = pxTCB->ulRunTimeCounter;
pxTCB->ulRunTimeCounter=0; //清零
}
#else
...
现在好使了
注意cubemx重新生成代码的话,上面那个清零动作记得重新加。测量cpu占用一般也都是调试时使用。