使用定时器计数,向FreeRTOS添加CPU使用率功能

8 篇文章 1 订阅

最近向一个项目中移植了FreeRTOS,发现这个os没有很好地支持CPU统计功能,既然它没有,我自己就加一个。

原理

我要实现的是仅是获取整个CPU的使用率,各个线程的使用率就不统计了。一半系统上运行的肯定不止一个线程,但不需要每个线程都要计算占用率,只需要计算空闲线程的占用率就可以。

这个方法需要一个寄存器,能读出实时的时间,我在STM32F411上用TIM5->CNT来作为这个寄存器。为什么使用TIM5? 我是看重它是一个32位的寄存器,自增频率可以提到很高。

自增频率是有限制的,因为寄存器字长有限,每隔一段时间寄存器会溢出,两次读取之间最多只能容许一次溢出,如果超过一次,计算的时间就会偏小(真实值小了(溢出次数-1)*溢出周期),所以溢出频率不能太高,要频率小于读取频率。

然后构造一个函数,负责读取这个寄存器,对于我这个例子就是TIM5->CNT了。每次调用这个函数,这个函数就会返回一个值,代表上次调用距本次调用过了多少个时间间隔。

然后在线程调度的地方加入一个函数调用,调用的函数里要获取当前线程句柄,然后判断是否为空闲线程,并且调用前面那个获取时间的函数,就可以得到这个线程运行了多长时间,然后进行一些处理,得到占用率。

实现

TIM5的计数器是32位,我设置它自增频率为1MHz,每一个多小时溢出一次,肯定是满足要求的。(没有什么线程会连续运行1小时不调度吧)

定时器初始化代码,使用MXCube生成,CPU主频96MHz
不要忘记在启动线程调度之前使用 HAL_TIM_Base_Start(&htim5);


void MX_TIM5_Init(void)
{

  /* USER CODE BEGIN TIM5_Init 0 */

  /* USER CODE END TIM5_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM5_Init 1 */

  /* USER CODE END TIM5_Init 1 */
  htim5.Instance = TIM5;
  htim5.Init.Prescaler = 95;
  htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim5.Init.Period = 0xffffffff;
  htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM5_Init 2 */

  /* USER CODE END TIM5_Init 2 */

}

这个函数就是前面说的那个函数,用于获取经过的时间

//获取距上次调用这个函数过了多少个时间间隔
TickType_t H_TS_GetDT(){
  static TickType_t lastT=0;
  TickType_t nowT;
  TickType_t r;

  nowT=(TickType_t)TIM5->CNT;

  r=nowT-lastT;
  lastT=nowT;

  return r;
}

下面的代码提供了获取占用率的方法,
还有供FreeRTOS调用的void H_TS_GetCPULoadCall(),在这个方法里完成时间统计

#define vH_TS_CPULoad_T             400000
extern int RTOS_IsIdleThread(void);
static volatile int H_TS_CPU_Load=-1;
static volatile TickType_t H_TS_RunTickCnt=0;
static volatile TickType_t H_TS_IdleTickCnt=0;

void H_TS_GetCPULoadCall(){

  TickType_t dt;
  TickType_t RunTickCnt;
  TickType_t IdleTickCnt;

  dt=H_TS_GetDT();
  RunTickCnt=H_TS_RunTickCnt;
  IdleTickCnt=H_TS_IdleTickCnt;

  RunTickCnt+=dt;
  if(RTOS_IsIdleThread()!=0){
    IdleTickCnt+=dt;
  }
  if(RunTickCnt>vH_TS_CPULoad_T){
    H_TS_CPU_Load=1000-(1000*IdleTickCnt/RunTickCnt);
    H_TS_RunTickCnt=0;
    H_TS_IdleTickCnt=0;
  }else{
    H_TS_RunTickCnt=RunTickCnt;
    H_TS_IdleTickCnt=IdleTickCnt;
  }
}

/**
 * @brief 获取CPU使用率
 * @return CPU使用率 单位0.1%
 */
int H_TS_GetCPULoad(void){

  return H_TS_CPU_Load;
}

还有一个函数,用于判断当前线程是否为空闲线程,
这个函数被实现在FreeRTOS源码的tasks.c文件的最末尾。
(xIdleTaskHandle被定义为了static, 只能把这个函数定义在这个文件里)

int RTOS_IsIdleThread(void){

	if(pxCurrentTCB==xIdleTaskHandle){
		return -1;
	}

	return 0;
}

最后,需要FreeRTOS去调用void H_TS_GetCPULoadCall()
port.c中找到__asm void xPortPendSVHandler( void ),修改为下面的代码。
主要修改两个地方

  1. 开头的声明中添加extern H_TS_GetCPULoadCall;
  2. 插入bl H_TS_GetCPULoadCallbl vTaskSwitchContext之前
    这样FreeRTOS调度时就可以调用到统计相关的方法了
__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;
	extern H_TS_GetCPULoadCall;

	PRESERVE8

	mrs r0, psp
	//isb
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB
	ldr	r2, [r3]

	/* Is the task using the FPU context?  If so, push high vfp registers. */
	tst r14, #0x10
	it eq
	vstmdbeq r0!, {s16-s31}

	/* Save the core registers. */
	stmdb r0!, {r4-r11, r14}

	/* Save the new top of stack into the first member of the TCB. */
	str r0, [r2]

	stmdb sp!, {r0, r3}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	//dsb
	//isb
	bl H_TS_GetCPULoadCall
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r0, r3}

	/* The first item in pxCurrentTCB is the task top of stack. */
	ldr r1, [r3]
	ldr r0, [r1]

	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}

	/* Is the task using the FPU context?  If so, pop the high vfp registers
	too. */
	tst r14, #0x10
	it eq
	vldmiaeq r0!, {s16-s31}

	msr psp, r0
	//isb
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
		#if WORKAROUND_PMU_CM001 == 1
			push { r14 }
			pop { pc }
			nop
		#endif
	#endif

	bx r14
}

这样,就可以在其他地方调用int H_TS_GetCPULoad(void)来获取CPU使用率。

附加测试

代码添加完了,得测试一下。我的项目正好可以测试。

最大优化开启
o3

f静态

f动态
屏幕的左上角为CPU占用率,右上角第一行为OLED显存刷新频率(底层SPI发送的帧速率),第二行为FFT渲染的帧频率
之前写代码都是畏首畏尾,生怕一不小心写出单片机跑不动的代码,但用了CPU统计功能之后,我发现我的担心是多余的,即使OLED有好几个帧缓存相互复制,显示算法不字节对齐(OLED 一个字节表示8个像素,造成了如果y不为8的倍数的情况下显示算法变得复杂),仅刷屏CPU的占用率为8%,发现这个单片机的性能比我想象得还要强大。在处理数据时,CPU的占用率为30%多,这个单片机还能干更多事情啊(邪魅一笑)。
不过帧率似乎不太理想,帧率我是人为限制帧率的,第一行的F:应该在166上下浮动才对,但实际上才130~140,而且居然还会波动,最低时与FFT帧速率差不多,这很明显有问题。

刷屏相关代码
由于这个工程原本使用的是我自己写的os ,替换为FreeRTOS后为了方便把相关API用宏定义定向到了FreeRTOS中

相关宏定义
宏定义
渲染线程
渲染线程
刷屏线程
刷屏线程
刷屏线程优先级比渲染线程高

消息队列Screen_Display_Param.RecvQueue用来传递帧缓存指针
信号量Screen_Display_Param.RecvDone用于刷屏线程向渲染线程报告自己已经接收到帧缓存

渲染线程应该是每隔6ms生成一个帧缓存发送到刷屏线程中,然后刷屏线程通过底层SPI刷新屏幕,这个循环与刷屏线程为两个不同的线程。通过消息队列与信号量进行同步。刷屏线程在等待DMA传输的同时渲染线程渲染帧缓存。
按道理应该为166帧,但实际上并没有。并且在我原来的工程中(使用我自己写的os),是能达到166帧的,并且很稳定,即使有没有处理数据都没有大的变化(165~167之间变化),但在FreeRTOS中,仿佛延时时间变长了。感觉cpu被FreeRTOS占用了,但计算的CPU占用率与我的系统的占用率可以说是几乎一致,最后我干脆把这个循环的延时去掉,把6ms执行一次的限制将到2ms,但结果得到的情况居然是没有变化!!,按道理应该是帧率变高,但实际上并没有变化。这个情况非常奇怪。

刚开始猜测是FreeRTOS线程与线程之间消息传递有延迟,但我感觉不太可能,这个延迟是很致命的问题,对于我这个项目来说还没什么,但是其他项目就不一定了。或许是FreeRTOS配置头文件某些配置有问题,当是配置项有很多,一时半会找不到哪里出问题了。

后来又想到是不是cpu统计的问题,但去掉相关代码后问题依旧。(实际上这个统计压根占不了多少性能)。

2021年11月20日18点02分->这个帧速率的问题已得到解决:
https://blog.csdn.net/qq_42907191/article/details/121439789.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值