席萌0209 发表于 2016-8-17 15:48:34

第8章 FreeRTOS调试方法(打印任务执行情况)

第8章       FreeRTOS调试方法(打印任务执行情况)

    本章节为大家介绍FreeRTOS的调试方法,这里的调试方法主要是教会大家如何获取任务的执行情况,通过获取的任务信息,可以进一步的配置和优化工程,这种方法非常实用,建议初学者必须掌握。
    本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407以及F429。
8.1 串口打印调试说明
8.2 STM32F103实现串口打印调试
8.3 STM32F407实现串口打印调试
8.4 STM32F429实现串口打印调试
8.5总结

席萌0209 发表于 2016-8-17 15:54:24

8.1串口打印调试说明


       很多时候,我们需要了解任务的执行状态,任务栈的使用情况以及各个任务的CPU使用率,这时就需要用到官方提供的两个函数vTaskList和vTaskGetRunTimeStats。用户就可以通过这两个函数获得任务的执行情况。
      获取了任务执行情况后,可以通过串口将其打印出来,当然,也可以通过任何其它方式将其显示出来。本教程配套的例子统一采用串口打印的方式显示任务的执行情况。另外有一点要特别注意,这种调试方式仅限测试目的,实际项目中不要使用,这种测试方式比较影响系统实时性。

席萌0209 发表于 2016-8-17 15:57:50

8.2 STM32F103实现串口打印调试


    为了获取FreeRTOS的任务信息,需要创建一个定时器,这个定时器的时间基准精度要高于系统时钟节拍,这样得到的任务信息才准确。这里提供的函数仅用于测试目的,切不可将其用于实际项目,原因有两点:
u FreeRTOS的系统内核没有对总的计数时间做溢出保护。
u 定时器中断是50us进入一次,比较影响系统性能。
    这里使用的是32位变量来保存50us一次的计数值,最大支持计数时间:2^32 * 50us / 3600s =59.6分钟。运行时间超过了59.6分钟将不准确。
    具体在FreeRTOS的工程中如何做才可以实现任务信息获取呢?下面分三步进行说明。

8.2.1   使能相关宏定义


   需要在FreeRTOSConfig.h文件中使能如下宏定义:
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern volatile uint32_t ulHighFrequencyTimerTicks;
#endif

/* Run time and task stats gathering related definitions. */
#define configUSE_TRACE_FACILITY                  1
#define configGENERATE_RUN_TIME_STATS               1
#define configUSE_STATS_FORMATTING_FUNCTIONS      1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()    (ulHighFrequencyTimerTicks = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE()            ulHighFrequencyTimerTicks
其中变量ulHighFrequencyTimerTicks是需要用户去定义的,我们这里是将其定义在了定时器初始化文件SysInfoTest.c里面。

8.2.2   精度高于滴答定时器的时钟初始化


    这里采用STM32F103内部的TIM6实现50us一次的中断,在中断函数里面对变量ulHighFrequencyTimerTicks进行计数操作,以供FreeRTOS系统使用,具体实现的代码如下:
#include "bsp.h"


/* 定时器频率,50us一次中断 */
#definetimerINTERRUPT_FREQUENCY    20000

/* 中断优先级 */
#definetimerHIGHEST_PRIORITY       1

/* 被系统调用 */
volatile uint32_t ulHighFrequencyTimerTicks = 0UL;

/*
*********************************************************************************************************
*    函 数 名: vSetupTimerTest
*    功能说明: 创建定时器
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void vSetupSysInfoTest(void)
{
   bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);
}

/*
*********************************************************************************************************
*    函 数 名: TIM6_IRQHandler
*    功能说明: TIM6中断服务程序。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void TIM6_IRQHandler( void )
{
   if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
   {
         ulHighFrequencyTimerTicks++;
         TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
   }
}

/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/
其中函数bsp_SetTIMforInt在文件bsp_tim_pwm.c里面进行了实现,源代码如下:
/*
*********************************************************************************************************
*    函 数 名: bsp_SetTIMforInt
*    功能说明: 配置TIM和NVIC,用于简单的定时中断. 开启定时中断。 中断服务程序由应用程序实现。
*    形    参: TIMx : 定时器
*               _ulFreq : 定时频率 (Hz)。 0 表示关闭。
*               _PreemptionPriority : 中断优先级分组
*               _SubPriority : 子优先级
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetTIMforInt(TIM_TypeDef* TIMx, uint32_t _ulFreq, uint8_t _PreemptionPriority, uint8_t _SubPriority)
{
   TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
   uint16_t usPeriod;
   uint16_t usPrescaler;
   uint32_t uiTIMxCLK;

   /* 使能TIM时钟 */
   if ((TIMx == TIM1) || (TIMx == TIM8))
   {
         RCC_APB2PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
   }
   else
   {
         RCC_APB1PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
   }

   if (_ulFreq == 0)
   {
         TIM_Cmd(TIMx, DISABLE);   /* 关闭定时输出 */

         /* 关闭TIM定时更新中断 (Update) */
         {
            NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
            uint8_t irq = 0;   /* 中断号, 定义在 stm32f4xx.h */

            if (TIMx == TIM1)
                   irq = TIM1_UP_IRQn;
            else if (TIMx == TIM2)
                   irq = TIM2_IRQn;
            else if (TIMx == TIM3)
                   irq = TIM3_IRQn;
            else if (TIMx == TIM4)
                   irq = TIM4_IRQn;
            else if (TIMx == TIM5)
                   irq = TIM5_IRQn;
            else if (TIMx == TIM6)
                   irq = TIM6_IRQn;
            else if (TIMx == TIM7)
                   irq = TIM7_IRQn;
            else if (TIMx == TIM8)
                   irq = TIM8_UP_IRQn;

            NVIC_InitStructure.NVIC_IRQChannel = irq;
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
            NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
            NVIC_Init(&NVIC_InitStructure);
         }      
         return;
   }

    /*-----------------------------------------------------------------------
         system_stm32f4xx.c 文件中 static void SetSysClockToHSE(void) 函数对时钟的配置如下:

            //HCLK = SYSCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
               
            //PCLK2 = HCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
            
            //PCLK1 = HCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

         APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13,TIM14
         APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11

   ----------------------------------------------------------------------- */
   if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM9) || (TIMx == TIM10) || (TIMx == TIM11))
   {
         /* APB2 定时器 */
         uiTIMxCLK = SystemCoreClock;
   }
   else /* APB1 定时器 .*/
   {
         uiTIMxCLK = SystemCoreClock;   // SystemCoreClock / 2;
   }

   if (_ulFreq < 100)
   {
         usPrescaler = 10000 - 1;                     /* 分频比 = 1000 */
         usPeriod =(uiTIMxCLK / 10000) / _ulFreq- 1; /* 自动重装的值 */
   }
   else if (_ulFreq < 3000)
   {
         usPrescaler = 100 - 1;                        /* 分频比 = 100 */
         usPeriod =(uiTIMxCLK / 100) / _ulFreq- 1;/* 自动重装的值 */
   }
   else /* 大于4K的频率,无需分频 */
   {
         usPrescaler = 0;                     /* 分频比 = 1 */
         usPeriod = uiTIMxCLK / _ulFreq - 1;/* 自动重装的值 */
   }

   /* Time base configuration */
   TIM_TimeBaseStructure.TIM_Period = usPeriod;
   TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
   TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

   TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);

   TIM_ARRPreloadConfig(TIMx, ENABLE);

   /* TIM Interrupts enable */
   TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);

   /* TIMx enable counter */
   TIM_Cmd(TIMx, ENABLE);

   /* 配置TIM定时更新中断 (Update) */
   {
         NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
         uint8_t irq = 0;   /* 中断号, 定义在 stm32f4xx.h */

         if (TIMx == TIM1)
            irq = TIM1_UP_IRQn;
         else if (TIMx == TIM2)
            irq = TIM2_IRQn;
         else if (TIMx == TIM3)
            irq = TIM3_IRQn;
         else if (TIMx == TIM4)
            irq = TIM4_IRQn;
         else if (TIMx == TIM5)
            irq = TIM5_IRQn;
         else if (TIMx == TIM6)
            irq = TIM6_IRQn;
         else if (TIMx == TIM7)
            irq = TIM7_IRQn;
         else if (TIMx == TIM8)
            irq = TIM8_UP_IRQn;

         NVIC_InitStructure.NVIC_IRQChannel = irq;
         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
         NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
         NVIC_Init(&NVIC_InitStructure);
   }
}

8.2.3   获取任务执行情况


    通过前面8.2.1小节和8.2.2小节的设置后,大家就可以在工程中通过FreeRTOS的两个函数vTaskList和vTaskGetRunTimeStats获取任务的执行情况。这里我们分成简单的两步进行说明:
u 第1步:先做精度高于滴答定时器的时钟初始化
/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
   /*
       在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。
       这样做的好处是:
       1. 防止执行的中断服务程序中有FreeRTOS的API函数。
       2. 保证系统正常启动,不受别的中断影响。
       3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。
       在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1)
       和cpsie i是等效的。
   */
   __set_PRIMASK(1);
   
   /* 硬件初始化 */
   bsp_Init();
   
   /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项
         目中不要使用,因为这个功能比较影响系统实时性。
      2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
   */
   vSetupSysInfoTest();
   
   /* 创建任务 */
   AppTaskCreate();
   
    /* 启动调度,开始执行任务 */
    vTaskStartScheduler();

   /*
       如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
       heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小:
       #define configTOTAL_HEAP_SIZE      ( ( size_t ) ( 17 * 1024 ) )
   */
   while(1);
}
u 第2步:初始化好以后就可以在FreeRTOS的任务中使用了。这里将任务信息获取功能放在了任务vTaskTaskUserIF里面去实现:
/*
*********************************************************************************************************
*    函 数 名: vTaskTaskUserIF
*    功能说明: 接口消息处理。
*    形    参: pvParameters 是在创建该任务时传递的形参
*    返 回 值: 无
*   优 先 级: 1(数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
static void vTaskTaskUserIF(void *pvParameters)
{
   uint8_t ucKeyCode;
   uint8_t pcWriteBuffer;

    while(1)
    {
         ucKeyCode = bsp_GetKey();
      
         if (ucKeyCode != KEY_NONE)
         {
            switch (ucKeyCode)
            {
                   /* K1键按下 打印任务执行情况 */
                   case KEY_DOWN_K1:         
                     printf("=================================================\\r\\n");
                     printf("任务名      任务状态 优先级   剩余栈 任务序号\\r\\n");
                     vTaskList((char *)&pcWriteBuffer);
                     printf("%s\\r\\n", pcWriteBuffer);
                  
                     printf("\\r\\n任务名       运行计数         使用率\\r\\n");
                     vTaskGetRunTimeStats((char *)&pcWriteBuffer);
                     printf("%s\\r\\n", pcWriteBuffer);
                     break;
                  
                   /* 其他的键值不处理 */
                   default:                  
                     break;
            }
         }
      
         vTaskDelay(20);
   }
}

8.2.4   串口打印效果


    STM32F103配套的完整工程是:V4-301_FreeRTOS实验_串口调试方法(打印任务执行情况),串口打印的效果如下(波特率115200,数据位8,奇偶校验位无,停止位1):

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
    #definetskBLOCKED_CHAR          ( 'B' )任务阻塞
    #definetskREADY_CHAR         ( 'R' ) 任务就绪
    #definetskDELETED_CHAR         ( 'D' )任务删除
    #definetskSUSPENDED_CHAR   ( 'S' ) 任务挂起
另外要注意剩余栈的单位是word,即4字节。比如vTaskUserIF任务的剩余栈是321,代表321*4 = 1284字节。

席萌0209 发表于 2016-8-17 16:02:03

8.3STM32F407实现串口打印调试


    为了获取FreeRTOS的任务信息,需要创建一个定时器,这个定时器的时间基准精度要高于系统时钟节拍,这样得到的任务信息才准确。这里提供的函数仅用于测试目的,切不可将其用于实际项目,原因有两点:
u FreeRTOS的系统内核没有对总的计数时间做溢出保护。
u 定时器中断是50us进入一次,比较影响系统性能。
    这里使用的是32位变量来保存50us一次的计数值,最大支持计数时间:2^32 * 50us / 3600s =59.6分钟。运行时间超过了59.6分钟将不准确。
    具体在FreeRTOS的工程中如何做才可以实现任务信息获取呢?下面分三步进行说明。

8.3.1   使能相关宏定义


    需要在FreeRTOSConfig.h文件中使能如下宏定义:
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern volatile uint32_t ulHighFrequencyTimerTicks;
#endif

/* Run time and task stats gathering related definitions. */
#define configUSE_TRACE_FACILITY                  1
#define configGENERATE_RUN_TIME_STATS               1
#define configUSE_STATS_FORMATTING_FUNCTIONS      1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()    (ulHighFrequencyTimerTicks = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE()            ulHighFrequencyTimerTicks

其中变量ulHighFrequencyTimerTicks是需要用户去定义的,我们这里是将其定义在了定时器初始化文件SysInfoTest.c里面。

8.3.2   精度高于滴答定时器的时钟初始化


    这里采用STM32F407内部的TIM6实现50us一次的中断,在中断函数里面对变量ulHighFrequencyTimerTicks进行计数操作,以供FreeRTOS系统使用,具体实现的代码如下:
#include "bsp.h"


/* 定时器频率,50us一次中断 */
#definetimerINTERRUPT_FREQUENCY    20000

/* 中断优先级 */
#definetimerHIGHEST_PRIORITY       1

/* 被系统调用 */
volatile uint32_t ulHighFrequencyTimerTicks = 0UL;

/*
*********************************************************************************************************
*    函 数 名: vSetupTimerTest
*    功能说明: 创建定时器
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void vSetupSysInfoTest(void)
{
   bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);
}

/*
*********************************************************************************************************
*    函 数 名: TIM6_DAC_IRQHandler
*    功能说明: TIM6中断服务程序。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void TIM6_DAC_IRQHandler ( void )
{
   if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
   {
         ulHighFrequencyTimerTicks++;
         TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
   }
}

/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/

其中函数bsp_SetTIMforInt在文件bsp_tim_pwm.c里面进行了实现,源代码如下:
/*
*********************************************************************************************************
*    函 数 名: bsp_SetTIMforInt
*    功能说明: 配置TIM和NVIC,用于简单的定时中断. 开启定时中断。 中断服务程序由应用程序实现。
*    形    参: TIMx : 定时器
*               _ulFreq : 定时频率 (Hz)。 0 表示关闭。
*               _PreemptionPriority : 中断优先级分组
*               _SubPriority : 子优先级
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetTIMforInt(TIM_TypeDef* TIMx, uint32_t _ulFreq, uint8_t _PreemptionPriority, uint8_t _SubPriority)
{
   TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
   uint16_t usPeriod;
   uint16_t usPrescaler;
   uint32_t uiTIMxCLK;

   /* 使能TIM时钟 */
   if ((TIMx == TIM1) || (TIMx == TIM8))
   {
         RCC_APB2PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
   }
   else
   {
         RCC_APB1PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
   }

   if (_ulFreq == 0)
   {
         TIM_Cmd(TIMx, DISABLE);   /* 关闭定时输出 */

         /* 关闭TIM定时更新中断 (Update) */
         {
            NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
            uint8_t irq = 0;   /* 中断号, 定义在 stm32f4xx.h */

            if (TIMx == TIM1)
                   irq = TIM1_UP_IRQn;
            else if (TIMx == TIM2)
                   irq = TIM2_IRQn;
            else if (TIMx == TIM3)
                   irq = TIM3_IRQn;
            else if (TIMx == TIM4)
                   irq = TIM4_IRQn;
            else if (TIMx == TIM5)
                   irq = TIM5_IRQn;
            else if (TIMx == TIM6)
                   irq = TIM6_IRQn;
            else if (TIMx == TIM7)
                   irq = TIM7_IRQn;
            else if (TIMx == TIM8)
                   irq = TIM8_UP_IRQn;

            NVIC_InitStructure.NVIC_IRQChannel = irq;
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
            NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
            NVIC_Init(&NVIC_InitStructure);
         }      
         return;
   }

    /*-----------------------------------------------------------------------
         system_stm32f4xx.c 文件中 static void SetSysClockToHSE(void) 函数对时钟的配置如下:

            //HCLK = SYSCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
               
            //PCLK2 = HCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
            
            //PCLK1 = HCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

         APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13,TIM14
         APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11

   ----------------------------------------------------------------------- */
   if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM9) || (TIMx == TIM10) || (TIMx == TIM11))
   {
         /* APB2 定时器 */
         uiTIMxCLK = SystemCoreClock;
   }
   else /* APB1 定时器 .*/
   {
         uiTIMxCLK = SystemCoreClock;   // SystemCoreClock / 2;
   }

   if (_ulFreq < 100)
   {
         usPrescaler = 10000 - 1;                     /* 分频比 = 1000 */
         usPeriod =(uiTIMxCLK / 10000) / _ulFreq- 1; /* 自动重装的值 */
   }
   else if (_ulFreq < 3000)
   {
         usPrescaler = 100 - 1;                        /* 分频比 = 100 */
         usPeriod =(uiTIMxCLK / 100) / _ulFreq- 1;/* 自动重装的值 */
   }
   else /* 大于4K的频率,无需分频 */
   {
         usPrescaler = 0;                     /* 分频比 = 1 */
         usPeriod = uiTIMxCLK / _ulFreq - 1;/* 自动重装的值 */
   }

   /* Time base configuration */
   TIM_TimeBaseStructure.TIM_Period = usPeriod;
   TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
   TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

   TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);

   TIM_ARRPreloadConfig(TIMx, ENABLE);

   /* TIM Interrupts enable */
   TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);

   /* TIMx enable counter */
   TIM_Cmd(TIMx, ENABLE);

   /* 配置TIM定时更新中断 (Update) */
   {
         NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
         uint8_t irq = 0;   /* 中断号, 定义在 stm32f4xx.h */

         if (TIMx == TIM1)
            irq = TIM1_UP_IRQn;
         else if (TIMx == TIM2)
            irq = TIM2_IRQn;
         else if (TIMx == TIM3)
            irq = TIM3_IRQn;
         else if (TIMx == TIM4)
            irq = TIM4_IRQn;
         else if (TIMx == TIM5)
            irq = TIM5_IRQn;
         else if (TIMx == TIM6)
            irq = TIM6_IRQn;
         else if (TIMx == TIM7)
            irq = TIM7_IRQn;
         else if (TIMx == TIM8)
            irq = TIM8_UP_IRQn;

         NVIC_InitStructure.NVIC_IRQChannel = irq;
         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
         NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
         NVIC_Init(&NVIC_InitStructure);
   }
}


8.3.3   获取任务执行情况


   通过前面8.2.1小节和8.2.2小节的设置后,大家就可以在工程中通过FreeRTOS的两个函数vTaskList和vTaskGetRunTimeStats获取任务的执行情况。这里我们分成简单的两步进行说明:
u 第1步:先做精度高于滴答定时器的时钟初始化
/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
   /*
       在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。
       这样做的好处是:
       1. 防止执行的中断服务程序中有FreeRTOS的API函数。
       2. 保证系统正常启动,不受别的中断影响。
       3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。
       在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1)
       和cpsie i是等效的。
   */
   __set_PRIMASK(1);
   
   /* 硬件初始化 */
   bsp_Init();
   
   /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项
         目中不要使用,因为这个功能比较影响系统实时性。
      2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
   */
   vSetupSysInfoTest();
   
   /* 创建任务 */
   AppTaskCreate();
   
    /* 启动调度,开始执行任务 */
    vTaskStartScheduler();

   /*
       如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
       heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小:
       #define configTOTAL_HEAP_SIZE      ( ( size_t ) ( 17 * 1024 ) )
   */
   while(1);
}

u 第2步:初始化好以后就可以在FreeRTOS的任务中使用了。这里将任务信息获取功能放在了任务vTaskTaskUserIF里面去实现:
/*
*********************************************************************************************************
*    函 数 名: vTaskTaskUserIF
*    功能说明: 接口消息处理。
*    形    参: pvParameters 是在创建该任务时传递的形参
*    返 回 值: 无
*   优 先 级: 1(数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
static void vTaskTaskUserIF(void *pvParameters)
{
   uint8_t ucKeyCode;
   uint8_t pcWriteBuffer;

    while(1)
    {
         ucKeyCode = bsp_GetKey();
      
         if (ucKeyCode != KEY_NONE)
         {
            switch (ucKeyCode)
            {
                   /* K1键按下 打印任务执行情况 */
                   case KEY_DOWN_K1:         
                     printf("=================================================\\r\\n");
                     printf("任务名      任务状态 优先级   剩余栈 任务序号\\r\\n");
                     vTaskList((char *)&pcWriteBuffer);
                     printf("%s\\r\\n", pcWriteBuffer);
                  
                     printf("\\r\\n任务名       运行计数         使用率\\r\\n");
                     vTaskGetRunTimeStats((char *)&pcWriteBuffer);
                     printf("%s\\r\\n", pcWriteBuffer);
                     break;
                  
                   /* 其他的键值不处理 */
                   default:                  
                     break;
            }
         }
      
         vTaskDelay(20);
   }
}


8.3.4   串口打印效果


    STM32F407配套的完整工程是:V5-301_FreeRTOS实验_串口调试方法(打印任务执行情况),串口打印的效果如下(波特率115200,数据位8,奇偶校验位无,停止位1):

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
    #definetskBLOCKED_CHAR          ( 'B' )任务阻塞
    #definetskREADY_CHAR         ( 'R' ) 任务就绪
    #definetskDELETED_CHAR         ( 'D' )任务删除
    #definetskSUSPENDED_CHAR   ( 'S' ) 任务挂起
另外要注意剩余栈的单位是word,即4字节。比如vTaskUserIF任务的剩余栈是321,代表321*4 = 1284字节。

席萌0209 发表于 2016-8-17 16:05:54

8.4STM32F429实现串口打印调试


    为了获取FreeRTOS的任务信息,需要创建一个定时器,这个定时器的时间基准精度要高于系统时钟节拍,这样得到的任务信息才准确。这里提供的函数仅用于测试目的,切不可将其用于实际项目,原因有两点:
u FreeRTOS的系统内核没有对总的计数时间做溢出保护。
u 定时器中断是50us进入一次,比较影响系统性能。
    这里使用的是32位变量来保存50us一次的计数值,最大支持计数时间:2^32 * 50us / 3600s =59.6分钟。运行时间超过了59.6分钟将不准确。
    具体在FreeRTOS的工程中如何做才可以实现任务信息获取呢?下面分三步进行说明。

8.4.1   使能相关宏定义


    需要在FreeRTOSConfig.h文件中使能如下宏定义:
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern volatile uint32_t ulHighFrequencyTimerTicks;
#endif

/* Run time and task stats gathering related definitions. */
#define configUSE_TRACE_FACILITY                  1
#define configGENERATE_RUN_TIME_STATS               1
#define configUSE_STATS_FORMATTING_FUNCTIONS      1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()    (ulHighFrequencyTimerTicks = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE()            ulHighFrequencyTimerTicks
其中变量ulHighFrequencyTimerTicks是需要用户去定义的,我们这里是将其定义在了定时器初始化文件SysInfoTest.c里面。

8.4.2   精度高于滴答定时器的时钟初始化


    这里采用STM32F429内部的TIM6实现50us一次的中断,在中断函数里面对变量ulHighFrequencyTimerTicks进行计数操作,以供FreeRTOS系统使用,具体实现的代码如下:
#include "bsp.h"


/* 定时器频率,50us一次中断 */
#definetimerINTERRUPT_FREQUENCY    20000

/* 中断优先级 */
#definetimerHIGHEST_PRIORITY       1

/* 被系统调用 */
volatile uint32_t ulHighFrequencyTimerTicks = 0UL;

/*
*********************************************************************************************************
*    函 数 名: vSetupTimerTest
*    功能说明: 创建定时器
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void vSetupSysInfoTest(void)
{
   bsp_SetTIMforInt(TIM6, timerINTERRUPT_FREQUENCY, timerHIGHEST_PRIORITY, 0);
}

/*
*********************************************************************************************************
*    函 数 名: TIM6_DAC_IRQHandler
*    功能说明: TIM6中断服务程序。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void TIM6_DAC_IRQHandler ( void )
{
   if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
   {
         ulHighFrequencyTimerTicks++;
         TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
   }
}

/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/
其中函数bsp_SetTIMforInt在文件bsp_tim_pwm.c里面进行了实现,源代码如下:
/*
*********************************************************************************************************
*    函 数 名: bsp_SetTIMforInt
*    功能说明: 配置TIM和NVIC,用于简单的定时中断. 开启定时中断。 中断服务程序由应用程序实现。
*    形    参: TIMx : 定时器
*               _ulFreq : 定时频率 (Hz)。 0 表示关闭。
*               _PreemptionPriority : 中断优先级分组
*               _SubPriority : 子优先级
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetTIMforInt(TIM_TypeDef* TIMx, uint32_t _ulFreq, uint8_t _PreemptionPriority, uint8_t _SubPriority)
{
   TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
   uint16_t usPeriod;
   uint16_t usPrescaler;
   uint32_t uiTIMxCLK;

   /* 使能TIM时钟 */
   if ((TIMx == TIM1) || (TIMx == TIM8))
   {
         RCC_APB2PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
   }
   else
   {
         RCC_APB1PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
   }

   if (_ulFreq == 0)
   {
         TIM_Cmd(TIMx, DISABLE);   /* 关闭定时输出 */

         /* 关闭TIM定时更新中断 (Update) */
         {
            NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
            uint8_t irq = 0;   /* 中断号, 定义在 stm32f4xx.h */

            if (TIMx == TIM1)
                   irq = TIM1_UP_IRQn;
            else if (TIMx == TIM2)
                   irq = TIM2_IRQn;
            else if (TIMx == TIM3)
                   irq = TIM3_IRQn;
            else if (TIMx == TIM4)
                   irq = TIM4_IRQn;
            else if (TIMx == TIM5)
                   irq = TIM5_IRQn;
            else if (TIMx == TIM6)
                   irq = TIM6_IRQn;
            else if (TIMx == TIM7)
                   irq = TIM7_IRQn;
            else if (TIMx == TIM8)
                   irq = TIM8_UP_IRQn;

            NVIC_InitStructure.NVIC_IRQChannel = irq;
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
            NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
            NVIC_Init(&NVIC_InitStructure);
         }      
         return;
   }

    /*-----------------------------------------------------------------------
         system_stm32f4xx.c 文件中 static void SetSysClockToHSE(void) 函数对时钟的配置如下:

            //HCLK = SYSCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
               
            //PCLK2 = HCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
            
            //PCLK1 = HCLK
            RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

         APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13,TIM14
         APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11

   ----------------------------------------------------------------------- */
   if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM9) || (TIMx == TIM10) || (TIMx == TIM11))
   {
         /* APB2 定时器 */
         uiTIMxCLK = SystemCoreClock;
   }
   else /* APB1 定时器 .*/
   {
         uiTIMxCLK = SystemCoreClock;   // SystemCoreClock / 2;
   }

   if (_ulFreq < 100)
   {
         usPrescaler = 10000 - 1;                     /* 分频比 = 1000 */
         usPeriod =(uiTIMxCLK / 10000) / _ulFreq- 1; /* 自动重装的值 */
   }
   else if (_ulFreq < 3000)
   {
         usPrescaler = 100 - 1;                        /* 分频比 = 100 */
         usPeriod =(uiTIMxCLK / 100) / _ulFreq- 1;/* 自动重装的值 */
   }
   else /* 大于4K的频率,无需分频 */
   {
         usPrescaler = 0;                     /* 分频比 = 1 */
         usPeriod = uiTIMxCLK / _ulFreq - 1;/* 自动重装的值 */
   }

   /* Time base configuration */
   TIM_TimeBaseStructure.TIM_Period = usPeriod;
   TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;
   TIM_TimeBaseStructure.TIM_ClockDivision = 0;
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
   TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

   TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);

   TIM_ARRPreloadConfig(TIMx, ENABLE);

   /* TIM Interrupts enable */
   TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);

   /* TIMx enable counter */
   TIM_Cmd(TIMx, ENABLE);

   /* 配置TIM定时更新中断 (Update) */
   {
         NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
         uint8_t irq = 0;   /* 中断号, 定义在 stm32f4xx.h */

         if (TIMx == TIM1)
            irq = TIM1_UP_IRQn;
         else if (TIMx == TIM2)
            irq = TIM2_IRQn;
         else if (TIMx == TIM3)
            irq = TIM3_IRQn;
         else if (TIMx == TIM4)
            irq = TIM4_IRQn;
         else if (TIMx == TIM5)
            irq = TIM5_IRQn;
          else if (TIMx == TIM6)
            irq = TIM6_IRQn;
         else if (TIMx == TIM7)
            irq = TIM7_IRQn;
         else if (TIMx == TIM8)
            irq = TIM8_UP_IRQn;

         NVIC_InitStructure.NVIC_IRQChannel = irq;
         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
         NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
         NVIC_Init(&NVIC_InitStructure);
   }
}

8.4.3   获取任务执行情况


    通过前面8.2.1小节和8.2.2小节的设置后,大家就可以在工程中通过FreeRTOS的两个函数vTaskList和vTaskGetRunTimeStats获取任务的执行情况。这里我们分成简单的两步进行说明:
u 第1步:先做精度高于滴答定时器的时钟初始化
/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
   /*
       在启动调度前,为了防止初始化STM32外设时有中断服务程序执行,这里禁止全局中断(除了NMI和HardFault)。
       这样做的好处是:
       1. 防止执行的中断服务程序中有FreeRTOS的API函数。
       2. 保证系统正常启动,不受别的中断影响。
       3. 关于是否关闭全局中断,大家根据自己的实际情况设置即可。
       在移植文件port.c中的函数prvStartFirstTask中会重新开启全局中断。通过指令cpsie i开启,__set_PRIMASK(1)
       和cpsie i是等效的。
   */
   __set_PRIMASK(1);
   
   /* 硬件初始化 */
   bsp_Init();
   
   /* 1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项
         目中不要使用,因为这个功能比较影响系统实时性。
      2. 为了正确获取FreeRTOS的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
   */
   vSetupSysInfoTest();
   
   /* 创建任务 */
   AppTaskCreate();
   
    /* 启动调度,开始执行任务 */
    vTaskStartScheduler();

   /*
       如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
       heap空间不足造成创建失败,此要加大FreeRTOSConfig.h文件中定义的heap大小:
       #define configTOTAL_HEAP_SIZE      ( ( size_t ) ( 17 * 1024 ) )
   */
   while(1);
}
u 第2步:初始化好以后就可以在FreeRTOS的任务中使用了。这里将任务信息获取功能放在了任务vTaskTaskUserIF里面去实现:
/*
*********************************************************************************************************
*    函 数 名: vTaskTaskUserIF
*    功能说明: 接口消息处理。
*    形    参: pvParameters 是在创建该任务时传递的形参
*    返 回 值: 无
*   优 先 级: 1(数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
static void vTaskTaskUserIF(void *pvParameters)
{
   uint8_t ucKeyCode;
   uint8_t pcWriteBuffer;

    while(1)
    {
         ucKeyCode = bsp_GetKey();
      
         if (ucKeyCode != KEY_NONE)
         {
            switch (ucKeyCode)
            {
                   /* K1键按下 打印任务执行情况 */
                   case KEY_DOWN_K1:         
                     printf("=================================================\\r\\n");
                     printf("任务名      任务状态 优先级   剩余栈 任务序号\\r\\n");
                     vTaskList((char *)&pcWriteBuffer);
                     printf("%s\\r\\n", pcWriteBuffer);
                  
                     printf("\\r\\n任务名       运行计数         使用率\\r\\n");
                     vTaskGetRunTimeStats((char *)&pcWriteBuffer);
                     printf("%s\\r\\n", pcWriteBuffer);
                     break;
                  
                   /* 其他的键值不处理 */
                   default:                  
                     break;
            }
         }
      
         vTaskDelay(20);
   }
}

8.4.4   串口打印效果


    STM32F429配套的完整工程是:V6-301_FreeRTOS实验_串口调试方法(打印任务执行情况),串口打印的效果如下(波特率115200,数据位8,奇偶校验位无,停止位1):

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
    #definetskBLOCKED_CHAR          ( 'B' )任务阻塞
    #definetskREADY_CHAR         ( 'R' ) 任务就绪
    #definetskDELETED_CHAR         ( 'D' )任务删除
    #definetskSUSPENDED_CHAR   ( 'S' ) 任务挂起
另外要注意剩余栈的单位是word,即4字节。比如vTaskUserIF任务的剩余栈是321,代表321*4 = 1284字节。

席萌0209 发表于 2016-8-17 16:06:23

8.5总结

    本章节主要是指导大家如何获取任务的执行情况,非常的实用,建议初学者务必掌握。

lkmtdkjplg 发表于 2018-8-21 14:12:19

不错。。。。。。。。。。。。

bian 发表于 2018-12-25 13:52:19

多谢。。。。。。。。。

EmbeddedOsprey 发表于 2022-7-13 13:35:08

50 us 的中断非常影响性能,所以我自己重新设计了一个组件,利用系统切换钩子函数功能,实现对任务的执行时间的累计,1 s 计算一次,10 s 中找一次最大值。有以下两个优点:
1、计算过程及其简单,基本不占用 CPU
2、不怕溢出,可直接嵌入项目中长时间运行。 使用 DWT 外设(也可使用其它定时器)。
目前已经在 RT-thread 中实现了(以提交至官方仓库),并且在项目中长时间稳定运行,后续计划在 FreeRTOS 中实现。
链接:线程CPU使用率到底该如何计算?

eric2013 发表于 2022-7-13 15:41:14

EmbeddedOsprey 发表于 2022-7-13 13:35
50 us 的中断非常影响性能,所以我自己重新设计了一个组件,利用系统切换钩子函数功能,实现对任务的执行时 ...

楼主位的是官方的玩法,改成用ThreadX或者uCOS的基于DWT的统计玩法就行。在任务切换的钩子函数统计各个任务即可。

很好实现。

北海之风 发表于 2024-3-27 09:24:09

很好 很强大
页: [1]
查看完整版本: 第8章 FreeRTOS调试方法(打印任务执行情况)