|

楼主 |
发表于 2016-8-17 16:02:03
|
显示全部楼层
8.3 STM32F407实现串口打印调试
为了获取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一次中断 */
- #define timerINTERRUPT_FREQUENCY 20000
-
- /* 中断优先级 */
- #define timerHIGHEST_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_TimeBaseInitTypeDef TIM_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[500];
-
- 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字节。 |
|