文章

FreeRTOS任务堆栈

任务堆栈

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

1. 任务堆栈大小

需要用到堆栈的地方:

  • 函数嵌套:函数局部变量、函数形参、函数返回地址、函数内部状态值
  • 任务切换:任务切换时所有的寄存器都需要入栈
  • 中断:M3 内核 MCU 有 8 个寄存器是自动入栈的 (任务栈),进入中断以后其余寄存器入栈以及可能发生的中断嵌套都是用的系统栈

2. 任务堆栈大小确定方法

2.1 MDK html 文件分析

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Static Call Graph for image Test\Test.axf

#<CALLGRAPH># ARM Linker, 5060750: Last Updated: Mon Jul 31 11:19:52 2023

Maximum Stack Usage = 232 bytes + Unknown(Cycles, Untraceable Function Pointers)
Call chain for Maximum Stack Depth:
main ⇒ MX_FREERTOS_Init ⇒ osThreadCreate ⇒ xTaskCreate ⇒ pvPortMalloc ⇒ xTaskResumeAll ⇒ xTaskIncrementTick
Mutually Recursive functions
ADC1_2_IRQHandler   ⇒   ADC1_2_IRQHandler
BusFault_Handler   ⇒   BusFault_Handler
HardFault_Handler   ⇒   HardFault_Handler
MemManage_Handler   ⇒   MemManage_Handler
StartDefaultTask   ⇒   StartDefaultTask
UsageFault_Handler   ⇒   UsageFault_Handler
2.2 栈溢出检测

栈溢出有两种检测方案:

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

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

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

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

img

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,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数

    img

    img

  • 配置宏 configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS 为 1

    img

  • 使用 FreeRTOS 操作系统,一定要将 HAL 库的 Timebase Source 从 SysTick 改为其他定时器,选好定时器后,系统会自动配置 TIM

    img

  • 输入工程名,选择路径(不要有中文),选择 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 任务函数代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 按键,串口打印出每个任务的详细信息

img

以上转载自博主“安迪西嵌入式”的FreeRTOS专栏,仅作学习记录,如有侵权,请联系删除。

本文由作者按照 CC BY 4.0 进行授权