FreeRTOS系列|任务堆栈

本文介绍了如何在基于Cortex-M3内核的STM32F103 MCU上确定FreeRTOS任务栈大小,包括函数嵌套、任务切换和中断时的栈需求。通过分析MDK的html文件来估算栈大小,并提供了两种栈溢出检测方案。此外,还展示了如何使用vTaskList()函数打印任务状态信息,以及在STM32CubeMX中配置FreeRTOS和任务的实践步骤。最后,通过实例演示了如何利用按键触发任务状态打印,以实现实时的堆栈检测。

任务堆栈

运行freertos系统的大部分都是资源有限的MCU,所以对于RAM我们都要考虑尽量的节省,避免资源浪费。下面将会基于Cortex-M3内核的STM32F103型MCU来介绍FreeRTOS任务栈大小的确定方法以及栈溢出检测方法

1. 任务堆栈大小

需要用到堆栈的地方:

  • 函数嵌套:函数局部变量、函数形参、函数返回地址、函数内部状态值
  • 任务切换:任务切换时所有的寄存器都需要入栈
  • 中断:M3内核MCU有8个寄存器是自动入栈的(任务栈),进入中断以后其余寄存器入栈以及可能发生的中断嵌套都是用的系统栈
2. 任务堆栈大小确定方法
2.1 MDK html文件分析

通过查看工程源码中“MDK-ARM”里的工程名文件夹下的html文件可以知道每个被调用函数的最大栈需求以及各个函数之间的调用关系
在这里插入图片描述

2.2 栈溢出检测

栈溢出有两种检测方案

  • 方案一:在任务切换时检测任务栈指针是否过界;
  • 方案二:任务创建的时候将任务栈所有数据初始化为0xa5,任务切换并进行任务栈检测的时候检查末尾的16个字节是否都是0xa5;
/*******************栈溢出检测宏的配置********************/
#define configCHECK_FOR_STACK_OVERFLOW
0, 配置为0,表示不启动栈溢出检测
1, 配置为1,表示启用栈溢出检测方案一
2, 配置为2,表示启用栈溢出检测方案二
/*********************栈溢出回调函数**********************/
函数原型:void vApplicationStackOverflowHook(TaskHandle_t *pxTask,signed char *pcTaskName)
传 入 值:pxTask 堆栈溢出任务的句柄
		 pcTaskName 堆栈溢出任务的名称

2.3 任务状态打印

通过调用vTaskList()函数打印每个任务的详细信息(栈名、栈状态、优先级、栈的剩余空间、任务序号)

/*******************任务状态信息打印宏的配置*******************/
#define configUSE_TRACE_FACILITY  //必须置1
#define configUSE_STATS_FORMATTING_FUNCTIONS  //必须置1
/*********************任务状态信息打函数**********************/
函数原型:void vTaskList(char *pcWriteBuffer)
传 入 值:pcWriteBuffer 缓冲区地址

根据传入的缓冲区(缓冲区要足够大,以容纳生成的报告,每个任务大约需要40个字节)生成字符串,这个字符串包含所有任务信息
在这里插入图片描述

3. 任务堆栈检测应用

以较常用的任务状态打印的堆栈检测方法为例:使用STM32CubeMX配置FreeRTOS,打开任务状态配置,创建如下三个任务:

  • Led_Task:D2指示灯闪烁
  • Usart_Task:每隔1s向串口输出字符串
  • Key_Task:按下K_UP,打印任务状态信息
3.1 STM32CubeMX设置
  • RCC设置外接HSE,时钟设置为72M
  • PC1设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • PA0设置为GPIO输入模式、下拉模式;PE2/PE3/PE4设置为GPIO输入模式、上拉模式
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • 激活FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数
    在这里插入图片描述在这里插入图片描述
  • 配置宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS为1
    在这里插入图片描述
  • 使用FreeRTOS操作系统,一定要将HAL库的Timebase Source从SysTick改为其他定时器,选好定时器后,系统会自动配置TIM
    在这里插入图片描述
  • 输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM软件编程
  • 创建按键驱动文件key.c和key.h,参考按键输入例程
  • 添加Led_Task、Usart_Task和Key_Task任务函数代码
void Led_Task(void const * argument){
  /* USER CODE BEGIN Led_Task */
  /* Infinite loop */
  for(;;){
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_RESET);
    osDelay(500);  //1ms时基
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_SET);
    osDelay(500);  //1ms时基
  }
  /* USER CODE END Led_Task */
}

void Usart_Task(void const * argument){
  /* USER CODE BEGIN Usart_Task */
  /* Infinite loop */
  for(;;){
	printf("UsartTask is Runing!\r\n");
    osDelay(1000);
  }
  /* USER CODE END Usart_Task */
}

uint8_t u8TaskListBuff[400];
void KeyTask(void const * argument){
  /* USER CODE BEGIN KeyTask */
	uint8_t key = 0;	
  /* Infinite loop */
  for(;;){
    key = KEY_Scan(0);	
	switch(key){
		case KEY_UP_PRES:
			memset(u8TaskListBuff, 0, 400);
			vTaskList((char*)u8TaskListBuff);
			printf("Name      State    Priority    Stack   Num\r\n");
			printf("******************************************************\r\n");
			printf("%s",u8TaskListBuff);
			printf("******************************************************\r\n");
			key = 0;
			break;
		case KEY_DOWN_PRES:
			//....
			key = 0;
			break;
	}
	osDelay(10);
  }
  /* USER CODE END KeyTask */
}

3.3 下载验证

编译无误下载到开发板后,D2指示灯闪烁表示程序正常运行。打开串口调试助手,可以看到串口每隔1s输出相应字符;按下K_UP按键,串口打印出每个任务的详细信息
在这里插入图片描述

### 如何在 FreeRTOS 中打印任务堆栈信息 为了获取并显示 FreeRTOS任务堆栈信息,可以利用 `uxTaskGetStackHighWaterMark` 函数来查询每个任务自创建以来最低空闲栈空间的最大值。这有助于评估任务所需的实际栈大小,并优化分配给该任务的栈空间。 ```c void PrintTaskStackInfo(void) { TaskStatus_t *pxTaskStatusArray; UBaseType_t uxArraySize, x; /* 获取当前系统中的活动任务数量 */ uxArraySize = uxTaskGetNumberOfTasks(); /* 分配内存用于存储所有任务的状态 */ pxTaskStatusArray = pvPortMalloc( sizeof( TaskStatus_t ) * uxArraySize ); if( pxTaskStatusArray != NULL ) { /* 将所有任务的状态写入数组 */ uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, NULL ); for( x = 0; x < uxArraySize; x++ ) { char cTaskName[ configMAX_TASK_NAME_LEN ]; strncpy(cTaskName, (char*)pcTaskGetName(pxTaskStatusArray[x].xHandle), configMAX_TASK_NAME_LEN); cTaskName[configMAX_TASK_NAME_LEN - 1] = '\0'; printf("Task Name: %s\n", cTaskName); // 输出任务名称 /* 打印剩余最高水位线(即最小可用栈深度),单位为字 */ printf("Highest Water Mark: %u words remaining\n", pxTaskStatusArray[x].usStackHighWaterMark); /* 计算已使用的栈百分比 */ uint8_t ucCurrentPriority; size_t stackDepthWords = pxTaskStatusArray[x].usStackHighWaterMark; size_t usedStackSizeBytes = ((stackDepthWords + 1) * portGET_STACK_TYPE_SIZE()) - pxTaskStatusArray[x].usStackHighWaterMark * portGET_STACK_TYPE_SIZE(); float usagePercentage = (float)(usedStackSizeBytes / (portGET_STACK_TYPE_SIZE() * pxTaskStatusArray[x].usStackDepth)) * 100.0f; printf("Used Stack Size Percentage: %.2f%%\n", usagePercentage); } vPortFree(pxTaskStatusArray); } } ``` 上述代码片段展示了如何遍历所有正在运行的任务,并收集有关它们栈使用情况的数据[^2]。通过调用 `uxTaskGetSystemState()` 可以获得一系列关于各个任务的具体细节,其中包括高水位标记——这是指自从任务启动之后从未被占用过的最大连续栈区域长度;换句话说也就是最接近满载时仍保持未使用的那一部分栈区间的尺寸。此函数返回的结果可以帮助开发者了解程序执行期间各任务实际消耗了多少栈空间,从而做出合理的调整以防止潜在的溢出风险。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值