STM32-FreeRTOS移植与调试

使用STM32CubeMX生成FreeRTOS中间件并创建两个任务,一个任务循环闪灯,另一个任务循环打印串口消息。进阶使用XRTOS调试,监控FreeRTOS任务堆栈大小以及分析任务的CPU使用率。

1.初始化工程

1.1 新建工程

STM32CubeMX创建工程,系统时钟、时钟树配置和调试口自行配置。PC13设置输出,启用USART1(提前用USB转串口模块接入PA9和PA10),工程名称为freertos_demo。有问题则参考之前的文章。

新建工程

1.2 配置FreeRTOS

  • 展开Middleware and Software Packs选项卡
  • 选择FreeRTOS,Interface选择CMSIS_V1,因为STM32F103C8T6就是M3内核的芯片,选择CMSIS_V1会更节省空间。

选择FreeRTOS配置

1.3 创建任务

  • 选择Tasks and Queues标签,点击Add按钮
  • Task Name修改为LedTask
  • Entry Function均修改为led_task
  • 点击OK按钮确认
  • 同理再创建一个串口任务uart_task

创建闪灯任务

创建串口任务

1.4 编译导入

1.4.1 Keil编译

点击GENRATE CODE生成工程代码,点击Open Project,尝试用Keil编译,没有报错则可以将工程导入EIDE

生成工程代码

如果STM32CubeMX生成工程之后,KEIL没有编译,则EIDE尝试导入MDK工程时,可能会出现编译失败的情况,比如丢失RAM/ROM布局导致的编译失败

1.4.2 EIDE编译

打开VSCode,EIDE插件选择Import Project,选择刚刚创建的工程, 注意EIDE的工作区不要保存在与MDK同一目录下,虽然不影响EIDE编译调试,但是会导致VSCode工作区无法跟踪工程根目录的文件。EIDE工作区需要保存在工程根目录下

EIDE导入

EIDE编译烧录

2.编辑任务

2.1 闪灯任务

  • main.c文件中的MX_FREERTOS_Init();函数执行初始化任务,跳转进入该函数
  • 往下滑找到led_task任务,跳转进入该任务
  • 将原本任务里的for循环中的osDelay(1);修改为以下代码
    1
    2
    osDelay(500);
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);

闪灯任务

2.2 串口任务

根据STM32-串口USART的第八点重定向printf,成功配置串口重定向之后尝试在串口任务每隔500ms打印数据

串口任务

3.调试FreeRTOS

在启动任何调试之前都必须确保工程的优化等级为0,否则各种情况都有可能发生,比如:创建了断点,但是程序无法暂停在断点处;或者创建断点的时候断点发生偏移。注意,如果EIDE再次导入STM32CubeMX生成的MDK工程,则需要再次检查优化等级。

确保优化等级为0

F5尝试启动调试,如果可以正常启动调试则在程序运行后几秒后创建一个断点,点击底部栏的XRTOS面板,查看任务状态

XRTOS查看任务状态

XRTOS的面板必须在程序暂停后才能更新,观察XRTOS的任务状态列表,TaskName表示任务名称,黄色高亮的行代表当前断点暂停在哪个任务,任务优先级,程序地址等。但是其中有一些问题,比如Stack End、Stack Used以及RunTime等都是以问号显示。需要配置更多宏以及定时器资源才能正确监控任务状态。

3.1 启动任务监控

FreeRTOSConfig.h文件中,增加configUSE_TRACE_FACILITY

1
2
3
4
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
#define configUSE_TRACE_FACILITY 1
/* USER CODE END Defines */

编译后启动调试,应该就能看到插件给出的提示, 单击展开提示

监控提示

3.2 开启栈尾监控

FreeRTOSConfig.h文件中,增加configRECORD_STACK_HIGH_ADDRESS,尽可能确保FreeRTOS的版本在V10.0.0以上

1
2
3
4
5
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
#define configUSE_TRACE_FACILITY 1
#define configRECORD_STACK_HIGH_ADDRESS 1
/* USER CODE END Defines */

编译后启动调试,等任务运行几秒后创建断点暂停程序,查看任务状态,应该可以看到栈尾状态,也就能查看完整的任务深度状态

任务深度

3.3 统计CPU使用率

CPU使用率,这里称为Runtime,用于统计每个任务的CPU使用率,能够直观地判断各个任务对CPU的占用情况。但是获取该Runtime属性需要消耗一个高速定时器。

根据监控提示,用于统计CPU使用率的高速定时器必须10倍与FreeRTOS的系统中断时间(默认是1ms)。因此定时器的中断时间至少在100us以内,等价于10kHZ频率。STM32的定时器TIM1能够符合要求。

Also, add the following two macros to provide a high speed counter – something at least 10x faster than your RTOS scheduler tick. One strategy could be to use a HW counter and sample its current value when needed

STM32CubeMX配置STM32的TIM1频率为1Mhz,溢出计数为100-1,实现弱函数HAL_TIM_PeriodElapsedCallback并在该函数里递增计数,并将该计数值设置为全局变量以供FreeRTOS调用,就能够实现统计CPU使用率的效果。

创建定时器TIM1

启用定时器中断

main.c中添加变量CPU_Runtime并初始化为0

1
2
3
/* USER CODE BEGIN PV */
volatile uint32_t CPU_RunTime = 0;
/* USER CODE END PV */

main.c中启用定时器TIM1中断

1
2
3
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);
/* USER CODE END 2 */

main.c中实现TIM1中断,累加CPU_RunTime

1
2
3
4
5
6
7
8
9
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim1)
{
CPU_RunTime++; // 更新计数值
}
}
/* USER CODE END 4 */

FreeRTOSConfig.h中添加外部变量CPU_Runtime以及采样时间的宏, 完整FreeRTOS调试配置如下

1
2
3
4
5
6
7
8
9
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
#define configUSE_TRACE_FACILITY 1
#define configRECORD_STACK_HIGH_ADDRESS 1
extern volatile uint32_t CPU_RunTime;
#define configGENERATE_RUN_TIME_STATS 1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime
/* USER CODE END Defines */

编译启动调试后,能够正常看到各个任务的CPU使用率

CPU使用率

4.监控分析

4.1 任务深度

以闪灯任务LedTask为例,在未申请的临时变量时,其内存使用是96Byte,而在申请了临时变量后,内存使用是352Byte,相差刚好是256Byte

未申请临时变量

已申请临时变量

4.2 CPU使用率

  • LedTask闪灯任务中增加阻塞延时500msHAL_Delay(500)
  • UartTask串口任务注释printf语句,修改延迟时间为1000ms,并增加切换LED状态代码

此时两个任务执行的内容基本一致:都是延迟1s切换LED灯状态。唯一的区别就是闪灯任务的延迟是HAL库进行阻塞延时。进入调试状态,能够看到闪灯任务的CPU占用率是45.77%,接近50%;而串口任务占用率是0%。

阻塞延迟对CPU占用率的影响

删除闪灯任务中的延时,循环中仅切换LED灯,然后再加入osDelay(1);,调试查看前后任务的CPU占用率时间区别,明显能够看出加入了1ms延时的任务的CPU使用率有了明显的降低。

循环中没有延时

循环中包含1ms延时

修改串口任务延时1ms再打印数据,对比闪灯任务能够明显看到printf函数执行是相当耗时的。

串口对比闪灯任务的CPU使用率

参考


STM32-FreeRTOS移植与调试
https://blog.gogo.uno/2024/03/23/stm32-freertos/
作者
Orionxer
发布于
2024年3月24日
更新于
2024年8月3日
许可协议