打印

高分讨论:软件检测 stm32 的 CPU 占用率

[复制链接]
8837|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
acgean|  楼主 | 2011-3-28 09:06 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本人在 stm32 应用程序中, 想通过软件检测出 CPU 的占用率. 就象 Windows 系统里的任务管理器能够告知 CPU 的使用率一样. 当然在 UCOS 里也具有这个功能.
    但是, 我现在是在裸奔系统里. 这个系统有多个不同优先级且发生频率很高的中断服务, 大部分的程序功能都是在中断服务里完成的. 现要求尽量不使用复杂的计算和软件变换. 也不希望因为要检测 CPU 的占用率而在代码中增加 "cli" 这样的中断禁止功能. 并尽可能少用硬件(比如定时器)资源.  
总结起来一句话, 要尽可能简单地完成检测 CPU 占用率的功能, 并要求计算结果能够相当地准确反映实践情况(当然也并非要求到几个周期都不能误差)
    顺便提一下, 以前在 luminary, 倒是有个功能, 设定某个定时器在 CPU 关闭时钟的 Idle 模式, 也同时关闭时钟, 这样通过这个定时器的计数值, 就可以算出 CPU 占用率. 但 STM32 没有提供这个功能.

下面我做了个程序模块, 使用的是 Systick 定时器里的计数器值来检测.
思路是: 在 main() 中执行 Idle (其实是__wfi 函数) 之前. 先调用 Idle_SaveCount() 记录下当前的 Systick 计数值. 并保存在全局变量 g_ulCPUUsageIdle 中. 然后就进入 Idle 模式.
    当发生中断唤醒时, CPU 首先会执行该中断服务程序, 于是我在所有中断服务开始处首先调用 CPUUsageINTsvr() 函数.
本函数读取 Systick 计数值, 取 g_ulCPUUsageIdle 变量值并清零(清零的目的是避免迟来的中断重复计算).
    另外, 有个特别的中断服务 --Systick 中断. 当这个中断发生时, Systick 当前计数值已经是最大值了, 所以不能调用 CPUUsageINTsvr(), 好在 Systick 中断发生时计数值肯定为零. 于是专门写了个 Systick 中断里记录用的函数 CPUUsageSystick().
    最后, 在 main() 里或者 Systick 里, 定期(比如1S) 调用 CPUIdleClocks() 取得 IDLE 所用的时钟数, 并清除累计值. 用 (1-累计值/时钟频率) 来计算 CPU 占用率.

#define GetSTCurrent() SysTick->VAL  
u32 g_ulCPUUsageIdle;  // 记录进入 Idle 的时刻, 0 代表非唤醒中断
u32 g_ulCPUIdleCount;  // 累计到的 Idle 的时间
__inline void Idle_SaveCount(void) {  
    g_ulCPUUsageIdle = GetSTCurrent(); // 保存进入 Idle 的时刻
}
// 判断是否为中断唤醒, 保存唤醒时刻的 SYSTICK 当前计数值.
void CPUUsageINTsvr(void) {
    unsigned int stcount = GetSTCurrent(), idly = g_ulCPUUsageIdle;
    g_ulCPUUsageIdle = 0;   // 置0, 禁止迟来中断进行重复计算
    if (idly > stcount) {   // 保证有效的休眠时间
        g_ulCPUIdleCount += idly - stcount;  // 累计 IDLE 的时间
    }
}
__inline void CPUUsageSystick(void) {
    unsigned int inc = g_ulCPUUsageIdle;
    g_ulCPUUsageIdle = 0;
    g_ulCPUIdleCount += inc;
}
unsigned long CPUIdleClocks(void) {
    unsigned long stclks;
    stclks = g_ulCPUIdleCount;
    g_ulCPUIdleCount = 0;
    return (stclks);
}

从原理上讲, 忽略的中断响应时间, 应该不会导致太大的误差. 但调试后发现的结果是. 每次 CPUIdleClocks 返回的时钟数居然超过时钟频率, 而且超过了将近一半! 问题在哪里, 是否有更好的办法来计算呢?
沙发
airwill| | 2011-3-30 12:31 | 只看该作者
感觉考虑得已经比较详细了, 再分析一下, 哪里有遗漏呢

使用特权

评论回复
板凳
IJK| | 2011-3-30 14:46 | 只看该作者
我觉得这么做更简单:
1.用1个计数器 u32 count;   让它在主循环中每次加1

2.用定时器或者systick每100ms(或其它定时时间)中断1次,中断里计算count的增量,用另1个变量u32 MaxCount记录下count增量的最大值

3.CPU 占用率 = 100% - 当前count增量 / MaxCount

使用特权

评论回复
评论
大道至简 2019-2-10 20:12 回复TA
你这个方法,如果是RTOS的程序,就不能用了 
地板
IJK| | 2011-3-30 14:48 | 只看该作者
LZ的问题,我估计在于:“用另1个变量u32 MaxCount记录下count增量的最大值”这个动作没有做。

使用特权

评论回复
5
acgean|  楼主 | 2011-3-30 17:10 | 只看该作者
楼上是不是没有弄明白我的意思? 我要计算非 Idle 状态占总运行状态的比例呀
你都没有谈到进入 Idle 状态啊. 你能做到的是 每秒进入了多少次 Idle 状态.
但因为中断的原因. 每次 Idle 状态的时间不可能相同的

使用特权

评论回复
6
airwill| | 2011-4-1 09:20 | 只看该作者
仔细看了一下你的代码, 虽然逻辑上没有问题. 但是有机构缺陷(bug).
1. 不能完全避免一次 IDLE, 两次中断重复累计.
// 判断是否为中断唤醒, 保存唤醒时刻的 SYSTICK 当前计数值.

void CPUUsageINTsvr(void) {
    unsigned int stcount = GetSTCurrent(), idly = g_ulCPUUsageIdle;
    g_ulCPUUsageIdle = 0;   // 置0, 禁止迟来中断进行重复计算
    if (idly > stcount) {   // 保证有效的休眠时间
        g_ulCPUIdleCount += idly - stcount;  // 累计 IDLE 的时间
    }
}
先读取了 idly = g_ulCPUUsageIdle; 然后再清零. 如果在读取完就发生中断嵌套. 那么嵌套上来的新中断会再次累计计数值. 估计这是最大的漏洞.

2. 漏记录的 BUG
看第一个函数:
__inline void Idle_SaveCount(void) {  
    g_ulCPUUsageIdle = GetSTCurrent(); // 保存进入 Idle 的时刻
}
虽然使用了 __inline 函数, 可能在设置  g_ulCPUUsageIdle 后下一条指令就进入 Idle 状态了. 但如果在执行 Idle 指令前就发生中断的话, 则中断服务清除了这个标志, 本次 Idle 真正的值就被漏掉了.

因此: 和中断共享的变量, 处理要非常细心才可.

使用特权

评论回复
7
IJK| | 2011-4-1 11:01 | 只看该作者
楼上是不是没有弄明白我的意思? 我要计算非 Idle 状态占总运行状态的比例呀
你都没有谈到进入 Idle 状态啊. 你能做到的是 每秒进入了多少次 Idle 状态.
但因为中断的原因. 每次 Idle 状态的时间不可能相同的 ...
acgean 发表于 2011-3-30 17:10


3L的描述确实有点跟LZ的问题无关。但实际上3L的方法同样适合于LZ。

使用特权

评论回复
8
IJK| | 2011-4-1 11:11 | 只看该作者
参照3L,加上下面的内容:

主循环的形式如下:
while(1)
{
  count++;
  if(满足进入idle的条件)
  {
    进入idle...
  }
  //...
}

Idle 状态占的百分比 = 进入Idle 状态时_CPU占用率 - 未进入Idle 状态时_CPU占用率
注:未进入Idle 状态时_CPU占用率并不是恒定的值,所以这种方法得到的 Idle 状态占的百分比 可能不是很准。

使用特权

评论回复
9
acgean|  楼主 | 2011-4-3 19:42 | 只看该作者
楼上倒也不失是个办法. 在以事件驱动方式做的, 用 main() 做总调度的系统了, 是可以粗略的测试一下空闲情况.
但是, 我的系统里, 大量的任务直接在中断服务里完成. main() 里基本不做什么事情. 所以楼上的办法基本不可用啊.

使用特权

评论回复
10
airwill| | 2011-4-6 12:27 | 只看该作者
可否考虑在最高优先级中断里执行 IDLE 指令, 因为这样可以减少中断开销, 又可以避免 6 楼提到的两个缺陷.

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

33

主题

446

帖子

1

粉丝