论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
本章节为大家讲解ThreadX原装任务统计分析功能的实现,支持MDK,IAR和GCC。
16.1 ThreadX的任务统计分析实现原理
16.2 ThreadX的任务统计分析功能移植
16.3 IAR的ThreadX插件实现
16.4 实验例程说明
16.5 总结
对于Cortex-M内核,带有DWT时钟周期计数器,比如芯片主频是100MHz,那么DWT统计的时钟周期分辨率就是10ns。
ThreadX就是借助此功能实现任务统计分析,使用简单,仅需使能就可以使用。M3,M4和M7内核都带这个功能,而M0内核不带,使用中要注意。
这里以移植到MDK为例进行说明,其它IAR,GCC的移植方法是一样的。
从ThreadX 内核V6.1.7版本开始,加入了任务统计分析功能,位于源码软件包的如下路径:
ThreadX\utility\execution_profile_kit 。
按照前面章节的移植方法,升级ThreadX内核的版本到V6.1.7后,添加此文件即可。
别忘了添加相应路径:
C文件要添加宏定义:TX_EXECUTION_PROFILE_ENABLE, TX_CORTEX_M_EPK
汇编文件要添加宏定义:TX_EXECUTION_PROFILE_ENABLE,TX_ENABLE_EXECUTION_CHANGE_NOTIFY
对于MDK AC5,Misc Controls加上 –cpreproc
默认情况下,ThreadX的移植文件tx_initialize_low_level.s带了DWT时钟周期计数器的使能:
/* Enable the cycle count register. */
LDR r0, =0xE0001000 ; Build address of DWT register
LDR r1, [r0] ; Pickup the current value
ORR r1, r1, #1 ; Set the CYCCNTENA bit
STR r1, [r0] ; Enable the cycle count register
保险起见,在bsp.c文件也调用了函数bsp_InitDWT()做初始化。
Threadx提供了三个64bit的全局变量统计时间信息,单位是系统时钟计数器,比如主频是100MHz,那么单位就是10ns。
统计从上电开始,所有任务总的运行时间。
统计从上电开始,总的空闲时间。
统计从上电开始,中断服务程序总的执行实现。
为了方便统计CPU利用率,可以采用:
_tx_execution_thread_time_total/(_tx_execution_thread_time_total + _tx_execution_idle_time_total + _tx_execution_isr_time_total)
但这种统计不能反应CPU利用率瞬时变化,所以做了一个瞬时的统计方法,每200ms统计一次:
/*
*********************************************************************************************************
* 函 数 名: AppTaskStart
* 功能说明: 启动任务。
* 形 参: thread_input 是在创建该任务时传递的形参
* 返 回 值: 无
优 先 级: 2
*********************************************************************************************************
*/
static void AppTaskStart (ULONG thread_input)
{
EXECUTION_TIME TolTime, IdleTime, deltaTolTime, deltaIdleTime;
uint32_t uiCount = 0;
(void)thread_input;
/* 内核开启后,恢复HAL里的时间基准 */
HAL_ResumeTick();
/* 外设初始化 */
bsp_Init();
/* 创建任务 */
AppTaskCreate();
/* 创建任务间通信机制 */
AppObjCreate();
/* 计算CPU利用率 */
IdleTime = _tx_execution_idle_time_total;
TolTime = _tx_execution_thread_time_total + _tx_execution_isr_time_total + _tx_execution_idle_time_total;
while (1)
{
/* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
bsp_ProPer1ms();
/* CPU利用率统计 */
uiCount++;
if(uiCount == 200)
{
uiCount = 0;
deltaIdleTime = _tx_execution_idle_time_total - IdleTime;
deltaTolTime = _tx_execution_thread_time_total + _tx_execution_isr_time_total +
_tx_execution_idle_time_total - TolTime;
OSCPUUsage = (double)deltaIdleTime/deltaTolTime;
OSCPUUsage = 100- OSCPUUsage*100;
IdleTime = _tx_execution_idle_time_total;
TolTime = _tx_execution_thread_time_total + _tx_execution_isr_time_total +
_tx_execution_idle_time_total;
}
tx_thread_sleep(1);
}
}
任务信息统一通过函数DispTaskInfo进行打印:
/*
*********************************************************************************************************
* 函 数 名: DispTaskInfo
* 功能说明: 将ThreadX任务信息通过串口打印出来
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void DispTaskInfo(void)
{
TX_THREAD *p_tcb; /* 定义一个任务控制块指针 */
p_tcb = &AppTaskStartTCB;
/* 打印标题 */
App_Printf("===============================================================\r\n");
App_Printf("CPU利用率 = %5.2f%%\r\n", OSCPUUsage);
App_Printf("任务执行时间 = %.9fs\r\n", (double)_tx_execution_thread_time_total/SystemCoreClock);
App_Printf("空闲执行时间 = %.9fs\r\n", (double)_tx_execution_idle_time_total/SystemCoreClock);
App_Printf("中断执行时间 = %.9fs\r\n", (double)_tx_execution_isr_time_total/SystemCoreClock);
App_Printf("系统总执行时间 = %.9fs\r\n", (double)(_tx_execution_thread_time_total + \
_tx_execution_idle_time_total + \
_tx_execution_isr_time_total)/SystemCoreClock);
App_Printf("===============================================================\r\n");
App_Printf(" 任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名\r\n");
App_Printf(" Prio StackSize CurStack MaxStack Taskname\r\n");
/* 遍历任务控制列表TCB list),打印所有的任务的优先级和名称 */
while (p_tcb != (TX_THREAD *)0)
{
App_Printf(" %2d %5d %5d %5d %s\r\n",
p_tcb->tx_thread_priority,
p_tcb->tx_thread_stack_size,
(int)p_tcb->tx_thread_stack_end - (int)p_tcb->tx_thread_stack_ptr,
(int)p_tcb->tx_thread_stack_end - (int)p_tcb->tx_thread_stack_highest_ptr,
p_tcb->tx_thread_name);
p_tcb = p_tcb->tx_thread_created_next;
if(p_tcb == &AppTaskStartTCB) break;
}
}
效果:
IAR和MDK的实现一样,移植了V6.1.7或者以上版本后,添加统计分析文件即可,剩下就是使能IAR的ThreadX插件:
配套例子:
V7-3011_ThreadX Task Statistics
实验目的:
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
========================================================
CPU利用率 = 0.89%
任务执行时间 = 0.586484645s
空闲执行时间 = 85.504470575s
中断执行时间 = 0.173225395s
系统总执行时间 = 86.264180615s
=======================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
5 4092 167 167 App Msp Pro
4 4092 167 167 App Task UserIF
5 4092 167 167 App Task COM
0 1020 191 191 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理,这里用作LED闪烁。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
System Timer Thread任务:系统定时器任务
2、 (1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。
3、默认上电是通过串口打印信息,如果使用RTT打印信息
(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可
#define Enable_RTTViewer 1
(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
RTT打印信息方式(AC5,AC6和IAR):
Embedded Studio仅支持调试状态RTT打印:
由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
本章节主要为大家讲解了ThreadX原装的任务统计分析功能的实现,比较实用。